View Javadoc

1   /*
2    * Copyright 2006 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.oxm.xstream;
18  
19  import java.io.IOException;
20  import java.io.InputStream;
21  import java.io.InputStreamReader;
22  import java.io.OutputStream;
23  import java.io.OutputStreamWriter;
24  import java.io.Reader;
25  import java.io.Writer;
26  import java.util.Iterator;
27  import java.util.Map;
28  import javax.xml.stream.XMLEventReader;
29  import javax.xml.stream.XMLEventWriter;
30  import javax.xml.stream.XMLStreamException;
31  import javax.xml.stream.XMLStreamReader;
32  import javax.xml.stream.XMLStreamWriter;
33  
34  import com.thoughtworks.xstream.XStream;
35  import com.thoughtworks.xstream.converters.Converter;
36  import com.thoughtworks.xstream.converters.ConverterMatcher;
37  import com.thoughtworks.xstream.converters.SingleValueConverter;
38  import com.thoughtworks.xstream.io.HierarchicalStreamReader;
39  import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
40  import com.thoughtworks.xstream.io.xml.CompactWriter;
41  import com.thoughtworks.xstream.io.xml.DomReader;
42  import com.thoughtworks.xstream.io.xml.DomWriter;
43  import com.thoughtworks.xstream.io.xml.QNameMap;
44  import com.thoughtworks.xstream.io.xml.SaxWriter;
45  import com.thoughtworks.xstream.io.xml.StaxReader;
46  import com.thoughtworks.xstream.io.xml.StaxWriter;
47  import com.thoughtworks.xstream.io.xml.XppReader;
48  import org.springframework.beans.propertyeditors.ClassEditor;
49  import org.springframework.oxm.AbstractMarshaller;
50  import org.springframework.oxm.XmlMappingException;
51  import org.springframework.util.ObjectUtils;
52  import org.springframework.xml.stream.StaxEventContentHandler;
53  import org.springframework.xml.stream.XmlEventStreamReader;
54  import org.w3c.dom.Document;
55  import org.w3c.dom.Element;
56  import org.w3c.dom.Node;
57  import org.xml.sax.ContentHandler;
58  import org.xml.sax.InputSource;
59  import org.xml.sax.XMLReader;
60  import org.xml.sax.ext.LexicalHandler;
61  
62  /**
63   * Implementation of the <code>Marshaller</code> interface for XStream. By default, XStream does not require any further
64   * configuration, though class aliases can be used to have more control over the behavior of XStream.
65   * <p/>
66   * Due to XStream's API, it is required to set the encoding used for writing to outputstreams. It defaults to
67   * <code>UTF-8</code>.
68   * <p/>
69   * <b>Note</b> that XStream is an XML serialization library, not a data binding library. Therefore, it has limited
70   * namespace support. As such, it is rather unsuitable for usage within Web services.
71   *
72   * @author Peter Meijer
73   * @author Arjen Poutsma
74   * @see #setEncoding(String)
75   * @see #DEFAULT_ENCODING
76   * @see #setAliases(Map)
77   * @see #setConverters(ConverterMatcher[])
78   * @since 1.0.0
79   */
80  public class XStreamMarshaller extends AbstractMarshaller {
81  
82      /** The default encoding used for stream access. */
83      public static final String DEFAULT_ENCODING = "UTF-8";
84  
85      private XStream xstream = new XStream();
86  
87      private String encoding;
88  
89      private Class[] supportedClasses;
90  
91      /**
92       * Returns the encoding to be used for stream access. If this property is not set, the default encoding is used.
93       *
94       * @see #DEFAULT_ENCODING
95       */
96      public String getEncoding() {
97          return encoding != null ? encoding : DEFAULT_ENCODING;
98      }
99  
100     /**
101      * Sets the encoding to be used for stream access. If this property is not set, the default encoding is used.
102      *
103      * @see #DEFAULT_ENCODING
104      */
105     public void setEncoding(String encoding) {
106         this.encoding = encoding;
107     }
108 
109     /** Returns the XStream instance used by this marshaller. */
110     public XStream getXStream() {
111         return xstream;
112     }
113 
114     /**
115      * Sets the XStream mode.
116      *
117      * @see XStream#XPATH_REFERENCES
118      * @see XStream#ID_REFERENCES
119      * @see XStream#NO_REFERENCES
120      */
121     public void setMode(int mode) {
122         getXStream().setMode(mode);
123     }
124 
125     /**
126      * Sets the classes supported by this marshaller. If this property is empty (the default), all classes are
127      * supported.
128      *
129      * @see #supports(Class)
130      */
131     public void setSupportedClasses(Class[] supportedClasses) {
132         this.supportedClasses = supportedClasses;
133     }
134 
135     /**
136      * Sets the <code>Converters</code> or <code>SingleValueConverters</code> to be registered with the
137      * <code>XStream</code> instance.
138      *
139      * @see Converter
140      * @see SingleValueConverter
141      */
142     public void setConverters(ConverterMatcher[] converters) {
143         for (int i = 0; i < converters.length; i++) {
144             if (converters[i] instanceof Converter) {
145                 getXStream().registerConverter((Converter) converters[i], i);
146             }
147             else if (converters[i] instanceof SingleValueConverter) {
148                 getXStream().registerConverter((SingleValueConverter) converters[i], i);
149             }
150             else {
151                 throw new IllegalArgumentException("Invalid ConverterMatcher [" + converters[i] + "]");
152             }
153         }
154     }
155 
156     /**
157      * Set a alias/type map, consisting of string aliases mapped to <code>Class</code> instances (or Strings to be
158      * converted to <code>Class</code> instances).
159      *
160      * @see org.springframework.beans.propertyeditors.ClassEditor
161      */
162     public void setAliases(Map aliases) {
163         for (Iterator iterator = aliases.entrySet().iterator(); iterator.hasNext();) {
164             Map.Entry entry = (Map.Entry) iterator.next();
165             // Check whether we need to convert from String to Class.
166             Class type;
167             if (entry.getValue() instanceof Class) {
168                 type = (Class) entry.getValue();
169             }
170             else {
171                 ClassEditor editor = new ClassEditor();
172                 editor.setAsText(String.valueOf(entry.getValue()));
173                 type = (Class) editor.getValue();
174             }
175             addAlias((String) entry.getKey(), type);
176         }
177     }
178 
179     /**
180      * Adds an alias for the given type.
181      *
182      * @param name alias to be used for the type
183      * @param type the type to be aliased
184      */
185     public void addAlias(String name, Class type) {
186         getXStream().alias(name, type);
187     }
188 
189     /**
190      * Sets types to use XML attributes for.
191      *
192      * @see XStream#useAttributeFor(Class)
193      */
194     public void setUseAttributeForTypes(Class[] types) {
195         for (int i = 0; i < types.length; i++) {
196             getXStream().useAttributeFor(types[i]);
197         }
198     }
199 
200     /**
201      * Sets the types to use XML attributes for. The given map can contain either <code>&lt;String, Class&gt;</code>
202      * pairs, in which case {@link XStream#useAttributeFor(String,Class)} is called, or <code>&lt;Class,
203      * String&gt;</code> pairs, which results in {@link XStream#useAttributeFor(Class,String)}.
204      */
205     public void setUseAttributeFor(Map attributes) {
206         for (Iterator iterator = attributes.entrySet().iterator(); iterator.hasNext();) {
207             Map.Entry entry = (Map.Entry) iterator.next();
208             if (entry.getKey() instanceof String && entry.getValue() instanceof Class) {
209                 getXStream().useAttributeFor((String) entry.getKey(), (Class) entry.getValue());
210             }
211             else if (entry.getKey() instanceof Class && entry.getValue() instanceof String) {
212                 getXStream().useAttributeFor((Class) entry.getKey(), (String) entry.getValue());
213             }
214             else {
215                 throw new IllegalArgumentException("Invalid attribute key and value pair. " +
216                         "'useAttributesFor' property takes either a <String, Class> map or a <Class, String> map");
217             }
218         }
219     }
220 
221     public boolean supports(Class clazz) {
222         if (ObjectUtils.isEmpty(supportedClasses)) {
223             return true;
224         }
225         else {
226             for (int i = 0; i < supportedClasses.length; i++) {
227                 if (supportedClasses[i].isAssignableFrom(clazz)) {
228                     return true;
229                 }
230             }
231             return false;
232         }
233     }
234 
235     /**
236      * Convert the given XStream exception to an appropriate exception from the <code>org.springframework.oxm</code>
237      * hierarchy.
238      * <p/>
239      * The default implementation delegates to <code>XStreamUtils</code>. Can be overridden in subclasses.
240      *
241      * @param ex          exception that occured
242      * @param marshalling indicates whether the exception occurs during marshalling (<code>true</code>), or
243      *                    unmarshalling (<code>false</code>)
244      * @return the corresponding <code>XmlMappingException</code> instance
245      * @see XStreamUtils#convertXStreamException(Exception,boolean)
246      */
247     public XmlMappingException convertXStreamException(Exception ex, boolean marshalling) {
248         return XStreamUtils.convertXStreamException(ex, marshalling);
249     }
250 
251     /**
252      * Marshals the given graph to the given XStream HierarchicalStreamWriter. Converts exceptions using
253      * <code>convertXStreamException</code>.
254      */
255     private void marshal(Object graph, HierarchicalStreamWriter streamWriter) {
256         try {
257             getXStream().marshal(graph, streamWriter);
258         }
259         catch (Exception ex) {
260             throw convertXStreamException(ex, true);
261         }
262     }
263 
264     protected void marshalDomNode(Object graph, Node node) throws XmlMappingException {
265         HierarchicalStreamWriter streamWriter;
266         if (node instanceof Document) {
267             streamWriter = new DomWriter((Document) node);
268         }
269         else if (node instanceof Element) {
270             streamWriter = new DomWriter((Element) node);
271         }
272         else {
273             throw new IllegalArgumentException("DOMResult contains neither Document nor Element");
274         }
275         marshal(graph, streamWriter);
276     }
277 
278     protected void marshalXmlEventWriter(Object graph, XMLEventWriter eventWriter) throws XmlMappingException {
279         ContentHandler contentHandler = new StaxEventContentHandler(eventWriter);
280         marshalSaxHandlers(graph, contentHandler, null);
281     }
282 
283     protected void marshalXmlStreamWriter(Object graph, XMLStreamWriter streamWriter) throws XmlMappingException {
284         try {
285             marshal(graph, new StaxWriter(new QNameMap(), streamWriter));
286         }
287         catch (XMLStreamException ex) {
288             throw convertXStreamException(ex, true);
289         }
290     }
291 
292     protected void marshalOutputStream(Object graph, OutputStream outputStream)
293             throws XmlMappingException, IOException {
294         marshalWriter(graph, new OutputStreamWriter(outputStream, getEncoding()));
295     }
296 
297     protected void marshalSaxHandlers(Object graph, ContentHandler contentHandler, LexicalHandler lexicalHandler)
298             throws XmlMappingException {
299         SaxWriter saxWriter = new SaxWriter();
300         saxWriter.setContentHandler(contentHandler);
301         marshal(graph, saxWriter);
302     }
303 
304     protected void marshalWriter(Object graph, Writer writer) throws XmlMappingException, IOException {
305         marshal(graph, new CompactWriter(writer));
306     }
307 
308     private Object unmarshal(HierarchicalStreamReader streamReader) {
309         try {
310             return getXStream().unmarshal(streamReader);
311         }
312         catch (Exception ex) {
313             throw convertXStreamException(ex, false);
314         }
315     }
316 
317     protected Object unmarshalDomNode(Node node) throws XmlMappingException {
318         HierarchicalStreamReader streamReader;
319         if (node instanceof Document) {
320             streamReader = new DomReader((Document) node);
321         }
322         else if (node instanceof Element) {
323             streamReader = new DomReader((Element) node);
324         }
325         else {
326             throw new IllegalArgumentException("DOMSource contains neither Document nor Element");
327         }
328         return unmarshal(streamReader);
329     }
330 
331     protected Object unmarshalXmlEventReader(XMLEventReader eventReader) throws XmlMappingException {
332         try {
333             XMLStreamReader streamReader = new XmlEventStreamReader(eventReader);
334             return unmarshalXmlStreamReader(streamReader);
335         }
336         catch (XMLStreamException ex) {
337             throw convertXStreamException(ex, false);
338         }
339     }
340 
341     protected Object unmarshalXmlStreamReader(XMLStreamReader streamReader) throws XmlMappingException {
342         return unmarshal(new StaxReader(new QNameMap(), streamReader));
343     }
344 
345     protected Object unmarshalInputStream(InputStream inputStream) throws XmlMappingException, IOException {
346         return unmarshalReader(new InputStreamReader(inputStream, getEncoding()));
347     }
348 
349     protected Object unmarshalReader(Reader reader) throws XmlMappingException, IOException {
350         return unmarshal(new XppReader(reader));
351     }
352 
353     protected Object unmarshalSaxReader(XMLReader xmlReader, InputSource inputSource)
354             throws XmlMappingException, IOException {
355         throw new UnsupportedOperationException(
356                 "XStreamMarshaller does not support unmarshalling using SAX XMLReaders");
357     }
358 }