View Javadoc
1   /*
2    * Copyright 2002-2014 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.lang.reflect.Constructor;
27  import java.util.LinkedHashMap;
28  import java.util.List;
29  import java.util.Map;
30  import javax.xml.stream.XMLEventReader;
31  import javax.xml.stream.XMLEventWriter;
32  import javax.xml.stream.XMLStreamException;
33  import javax.xml.stream.XMLStreamReader;
34  import javax.xml.stream.XMLStreamWriter;
35  import javax.xml.transform.stream.StreamSource;
36  
37  import com.thoughtworks.xstream.MarshallingStrategy;
38  import com.thoughtworks.xstream.XStream;
39  import com.thoughtworks.xstream.converters.ConversionException;
40  import com.thoughtworks.xstream.converters.Converter;
41  import com.thoughtworks.xstream.converters.ConverterLookup;
42  import com.thoughtworks.xstream.converters.ConverterMatcher;
43  import com.thoughtworks.xstream.converters.ConverterRegistry;
44  import com.thoughtworks.xstream.converters.DataHolder;
45  import com.thoughtworks.xstream.converters.SingleValueConverter;
46  import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
47  import com.thoughtworks.xstream.core.DefaultConverterLookup;
48  import com.thoughtworks.xstream.core.util.CompositeClassLoader;
49  import com.thoughtworks.xstream.io.HierarchicalStreamDriver;
50  import com.thoughtworks.xstream.io.HierarchicalStreamReader;
51  import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
52  import com.thoughtworks.xstream.io.StreamException;
53  import com.thoughtworks.xstream.io.naming.NameCoder;
54  import com.thoughtworks.xstream.io.xml.CompactWriter;
55  import com.thoughtworks.xstream.io.xml.DomReader;
56  import com.thoughtworks.xstream.io.xml.DomWriter;
57  import com.thoughtworks.xstream.io.xml.QNameMap;
58  import com.thoughtworks.xstream.io.xml.SaxWriter;
59  import com.thoughtworks.xstream.io.xml.StaxReader;
60  import com.thoughtworks.xstream.io.xml.StaxWriter;
61  import com.thoughtworks.xstream.io.xml.XmlFriendlyNameCoder;
62  import com.thoughtworks.xstream.io.xml.XppDriver;
63  import com.thoughtworks.xstream.mapper.CannotResolveClassException;
64  import com.thoughtworks.xstream.mapper.Mapper;
65  import com.thoughtworks.xstream.mapper.MapperWrapper;
66  import org.w3c.dom.Document;
67  import org.w3c.dom.Element;
68  import org.w3c.dom.Node;
69  import org.xml.sax.ContentHandler;
70  import org.xml.sax.InputSource;
71  import org.xml.sax.XMLReader;
72  import org.xml.sax.ext.LexicalHandler;
73  
74  import org.springframework.beans.factory.BeanClassLoaderAware;
75  import org.springframework.beans.factory.InitializingBean;
76  import org.springframework.oxm.MarshallingFailureException;
77  import org.springframework.oxm.UncategorizedMappingException;
78  import org.springframework.oxm.UnmarshallingFailureException;
79  import org.springframework.oxm.XmlMappingException;
80  import org.springframework.oxm.support.AbstractMarshaller;
81  import org.springframework.util.Assert;
82  import org.springframework.util.ClassUtils;
83  import org.springframework.util.ObjectUtils;
84  import org.springframework.util.StringUtils;
85  import org.springframework.util.xml.StaxUtils;
86  
87  /**
88   * Implementation of the {@code Marshaller} interface for XStream.
89   *
90   * <p>By default, XStream does not require any further configuration and can (un)marshal
91   * any class on the classpath. As such, it is <b>not recommended to use the
92   * {@code XStreamMarshaller} to unmarshal XML from external sources</b> (i.e. the Web),
93   * as this can result in <b>security vulnerabilities</b>. If you do use the
94   * {@code XStreamMarshaller} to unmarshal external XML, set the
95   * {@link #setSupportedClasses(Class[]) supportedClasses} and
96   * {@link #setConverters(ConverterMatcher[]) converters} properties (possibly using
97   * a {@link CatchAllConverter}) or override the {@link #customizeXStream(XStream)}
98   * method to make sure it only accepts the classes you want it to support.
99   *
100  * <p>Due to XStream's API, it is required to set the encoding used for writing to
101  * OutputStreams. It defaults to {@code UTF-8}.
102  *
103  * <p><b>NOTE:</b> XStream is an XML serialization library, not a data binding library.
104  * Therefore, it has limited namespace support. As such, it is rather unsuitable for
105  * usage within Web Services.
106  *
107  * <p>This marshaller requires XStream 1.4 or higher, as of Spring 4.0.
108  * Note that {@link XStream} construction has been reworked in 4.0, with the
109  * stream driver and the class loader getting passed into XStream itself now.
110  *
111  * @author Peter Meijer
112  * @author Arjen Poutsma
113  * @author Juergen Hoeller
114  * @since 3.0
115  */
116 public class XStreamMarshaller extends AbstractMarshaller implements InitializingBean, BeanClassLoaderAware {
117 
118 	/**
119 	 * The default encoding used for stream access: UTF-8.
120 	 */
121 	public static final String DEFAULT_ENCODING = "UTF-8";
122 
123 
124 	private ReflectionProvider reflectionProvider;
125 
126 	private HierarchicalStreamDriver streamDriver;
127 
128 	private HierarchicalStreamDriver defaultDriver;
129 
130 	private Mapper mapper;
131 
132 	private Class<?>[] mapperWrappers;
133 
134 	private ConverterLookup converterLookup = new DefaultConverterLookup();
135 
136 	private ConverterRegistry converterRegistry = (ConverterRegistry) this.converterLookup;
137 
138 	private ConverterMatcher[] converters;
139 
140 	private MarshallingStrategy marshallingStrategy;
141 
142 	private Integer mode;
143 
144 	private Map<String, ?> aliases;
145 
146 	private Map<String, ?> aliasesByType;
147 
148 	private Map<String, String> fieldAliases;
149 
150 	private Class<?>[] useAttributeForTypes;
151 
152 	private Map<?, ?> useAttributeFor;
153 
154 	private Map<Class<?>, String> implicitCollections;
155 
156 	private Map<Class<?>, String> omittedFields;
157 
158 	private Class<?>[] annotatedClasses;
159 
160 	private boolean autodetectAnnotations;
161 
162 	private String encoding = DEFAULT_ENCODING;
163 
164 	private NameCoder nameCoder = new XmlFriendlyNameCoder();
165 
166 	private Class<?>[] supportedClasses;
167 
168 	private ClassLoader beanClassLoader = new CompositeClassLoader();
169 
170 	private XStream xstream;
171 
172 
173 	/**
174 	 * Set a custom XStream {@link ReflectionProvider} to use.
175 	 * @since 4.0
176 	 */
177 	public void setReflectionProvider(ReflectionProvider reflectionProvider) {
178 		this.reflectionProvider = reflectionProvider;
179 	}
180 
181 	/**
182 	 * Set a XStream {@link HierarchicalStreamDriver} to be used for readers and writers.
183 	 * <p>As of Spring 4.0, this stream driver will also be passed to the {@link XStream}
184 	 * constructor and therefore used by streaming-related native API methods themselves.
185 	 */
186 	public void setStreamDriver(HierarchicalStreamDriver streamDriver) {
187 		this.streamDriver = streamDriver;
188 		this.defaultDriver = streamDriver;
189 	}
190 
191 	private HierarchicalStreamDriver getDefaultDriver() {
192 		if (this.defaultDriver == null) {
193 			this.defaultDriver = new XppDriver();
194 		}
195 		return this.defaultDriver;
196 	}
197 
198 	/**
199 	 * Set a custom XStream {@link Mapper} to use.
200 	 * @since 4.0
201 	 */
202 	public void setMapper(Mapper mapper) {
203 		this.mapper = mapper;
204 	}
205 
206 	/**
207 	 * Set one or more custom XStream {@link MapperWrapper} classes.
208 	 * Each of those classes needs to have a constructor with a single argument
209 	 * of type {@link Mapper} or {@link MapperWrapper}.
210 	 * @since 4.0
211 	 */
212 	public void setMapperWrappers(Class<?>... mapperWrappers) {
213 		this.mapperWrappers = mapperWrappers;
214 	}
215 
216 	/**
217 	 * Set a custom XStream {@link ConverterLookup} to use.
218 	 * Also used as {@link ConverterRegistry} if the given reference implements it as well.
219 	 * @since 4.0
220 	 * @see DefaultConverterLookup
221 	 */
222 	public void setConverterLookup(ConverterLookup converterLookup) {
223 		this.converterLookup = converterLookup;
224 		if (converterLookup instanceof ConverterRegistry) {
225 			this.converterRegistry = (ConverterRegistry) converterLookup;
226 		}
227 	}
228 
229 	/**
230 	 * Set a custom XStream {@link ConverterRegistry} to use.
231 	 * @since 4.0
232 	 * @see #setConverterLookup
233 	 * @see DefaultConverterLookup
234 	 */
235 	public void setConverterRegistry(ConverterRegistry converterRegistry) {
236 		this.converterRegistry = converterRegistry;
237 	}
238 
239 	/**
240 	 * Set the {@code Converters} or {@code SingleValueConverters} to be registered
241 	 * with the {@code XStream} instance.
242 	 * @see Converter
243 	 * @see SingleValueConverter
244 	 */
245 	public void setConverters(ConverterMatcher... converters) {
246 		this.converters = converters;
247 	}
248 
249 	/**
250 	 * Set a custom XStream {@link MarshallingStrategy} to use.
251 	 * @since 4.0
252 	 */
253 	public void setMarshallingStrategy(MarshallingStrategy marshallingStrategy) {
254 		this.marshallingStrategy = marshallingStrategy;
255 	}
256 
257 	/**
258 	 * Set the XStream mode to use.
259 	 * @see XStream#ID_REFERENCES
260 	 * @see XStream#NO_REFERENCES
261 	 */
262 	public void setMode(int mode) {
263 		this.mode = mode;
264 	}
265 
266 	/**
267 	 * Set the alias/type map, consisting of string aliases mapped to classes.
268 	 * <p>Keys are aliases; values are either {@code Class} instances, or String class names.
269 	 * @see XStream#alias(String, Class)
270 	 */
271 	public void setAliases(Map<String, ?> aliases) {
272 		this.aliases = aliases;
273 	}
274 
275 	/**
276 	 * Set the <em>aliases by type</em> map, consisting of string aliases mapped to classes.
277 	 * <p>Any class that is assignable to this type will be aliased to the same name.
278 	 * Keys are aliases; values are either {@code Class} instances, or String class names.
279 	 * @see XStream#aliasType(String, Class)
280 	 */
281 	public void setAliasesByType(Map<String, ?> aliasesByType) {
282 		this.aliasesByType = aliasesByType;
283 	}
284 
285 	/**
286 	 * Set the field alias/type map, consisting of field names.
287 	 * @see XStream#aliasField(String, Class, String)
288 	 */
289 	public void setFieldAliases(Map<String, String> fieldAliases) {
290 		this.fieldAliases = fieldAliases;
291 	}
292 
293 	/**
294 	 * Set types to use XML attributes for.
295 	 * @see XStream#useAttributeFor(Class)
296 	 */
297 	public void setUseAttributeForTypes(Class<?>... useAttributeForTypes) {
298 		this.useAttributeForTypes = useAttributeForTypes;
299 	}
300 
301 	/**
302 	 * Set the types to use XML attributes for. The given map can contain
303 	 * either {@code &lt;String, Class&gt;} pairs, in which case
304 	 * {@link XStream#useAttributeFor(String, Class)} is called.
305 	 * Alternatively, the map can contain {@code &lt;Class, String&gt;}
306 	 * or {@code &lt;Class, List&lt;String&gt;&gt;} pairs, which results
307 	 * in {@link XStream#useAttributeFor(Class, String)} calls.
308 	 */
309 	public void setUseAttributeFor(Map<?, ?> useAttributeFor) {
310 		this.useAttributeFor = useAttributeFor;
311 	}
312 
313 	/**
314 	 * Specify implicit collection fields, as a Map consisting of {@code Class} instances
315 	 * mapped to comma separated collection field names.
316 	 * @see XStream#addImplicitCollection(Class, String)
317 	 */
318 	public void setImplicitCollections(Map<Class<?>, String> implicitCollections) {
319 		this.implicitCollections = implicitCollections;
320 	}
321 
322 	/**
323 	 * Specify omitted fields, as a Map consisting of {@code Class} instances
324 	 * mapped to comma separated field names.
325 	 * @see XStream#omitField(Class, String)
326 	 */
327 	public void setOmittedFields(Map<Class<?>, String> omittedFields) {
328 		this.omittedFields = omittedFields;
329 	}
330 
331 	/**
332 	 * Set annotated classes for which aliases will be read from class-level annotation metadata.
333 	 * @see XStream#processAnnotations(Class[])
334 	 */
335 	public void setAnnotatedClasses(Class<?>... annotatedClasses) {
336 		this.annotatedClasses = annotatedClasses;
337 	}
338 
339 	/**
340 	 * Activate XStream's autodetection mode.
341 	 * <p><b>Note</b>: Autodetection implies that the XStream instance is being configured while
342 	 * it is processing the XML streams, and thus introduces a potential concurrency problem.
343 	 * @see XStream#autodetectAnnotations(boolean)
344 	 */
345 	public void setAutodetectAnnotations(boolean autodetectAnnotations) {
346 		this.autodetectAnnotations = autodetectAnnotations;
347 	}
348 
349 	/**
350 	 * Set the encoding to be used for stream access.
351 	 * @see #DEFAULT_ENCODING
352 	 */
353 	public void setEncoding(String encoding) {
354 		this.encoding = encoding;
355 	}
356 
357 	@Override
358 	protected String getDefaultEncoding() {
359 		return this.encoding;
360 	}
361 
362 	/**
363 	 * Set a custom XStream {@link NameCoder} to use.
364 	 * The default is an {@link XmlFriendlyNameCoder}.
365 	 * @since 4.0.4
366 	 */
367 	public void setNameCoder(NameCoder nameCoder) {
368 		this.nameCoder = nameCoder;
369 	}
370 
371 	/**
372 	 * Set the classes supported by this marshaller.
373 	 * <p>If this property is empty (the default), all classes are supported.
374 	 * @see #supports(Class)
375 	 */
376 	public void setSupportedClasses(Class<?>... supportedClasses) {
377 		this.supportedClasses = supportedClasses;
378 	}
379 
380 	@Override
381 	public void setBeanClassLoader(ClassLoader classLoader) {
382 		this.beanClassLoader = classLoader;
383 	}
384 
385 
386 	@Override
387 	public void afterPropertiesSet() {
388 		this.xstream = buildXStream();
389 	}
390 
391 	/**
392 	 * Build the native XStream delegate to be used by this marshaller,
393 	 * delegating to {@link #constructXStream()}, {@link #configureXStream}
394 	 * and {@link #customizeXStream}.
395 	 */
396 	protected XStream buildXStream() {
397 		XStream xstream = constructXStream();
398 		configureXStream(xstream);
399 		customizeXStream(xstream);
400 		return xstream;
401 	}
402 
403 	/**
404 	 * Construct an XStream instance, either using one of the
405 	 * standard constructors or creating a custom subclass.
406 	 * @return the {@code XStream} instance
407 	 */
408 	@SuppressWarnings("deprecation")
409 	protected XStream constructXStream() {
410 		// The referenced XStream constructor has been deprecated as of 1.4.5.
411 		// We're preserving this call for broader XStream 1.4.x compatibility.
412 		return new XStream(this.reflectionProvider, getDefaultDriver(),
413 				this.beanClassLoader, this.mapper, this.converterLookup, this.converterRegistry) {
414 			@Override
415 			protected MapperWrapper wrapMapper(MapperWrapper next) {
416 				MapperWrapper mapperToWrap = next;
417 				if (mapperWrappers != null) {
418 					for (Class<?> mapperWrapper : mapperWrappers) {
419 						Assert.isAssignable(MapperWrapper.class, mapperWrapper);
420 						Constructor<?> ctor;
421 						try {
422 							ctor = mapperWrapper.getConstructor(Mapper.class);
423 						}
424 						catch (NoSuchMethodException ex) {
425 							try {
426 								ctor = mapperWrapper.getConstructor(MapperWrapper.class);
427 							}
428 							catch (NoSuchMethodException ex2) {
429 								throw new IllegalStateException("No appropriate MapperWrapper constructor found: " + mapperWrapper);
430 							}
431 						}
432 						try {
433 							mapperToWrap = (MapperWrapper) ctor.newInstance(mapperToWrap);
434 						}
435 						catch (Exception ex) {
436 							throw new IllegalStateException("Failed to construct MapperWrapper: " + mapperWrapper);
437 						}
438 					}
439 				}
440 				return mapperToWrap;
441 			}
442 		};
443 	}
444 
445 	/**
446 	 * Configure the XStream instance with this marshaller's bean properties.
447 	 * @param xstream the {@code XStream} instance
448 	 */
449 	protected void configureXStream(XStream xstream) {
450 		if (this.converters != null) {
451 			for (int i = 0; i < this.converters.length; i++) {
452 				if (this.converters[i] instanceof Converter) {
453 					xstream.registerConverter((Converter) this.converters[i], i);
454 				}
455 				else if (this.converters[i] instanceof SingleValueConverter) {
456 					xstream.registerConverter((SingleValueConverter) this.converters[i], i);
457 				}
458 				else {
459 					throw new IllegalArgumentException("Invalid ConverterMatcher [" + this.converters[i] + "]");
460 				}
461 			}
462 		}
463 
464 		if (this.marshallingStrategy != null) {
465 			xstream.setMarshallingStrategy(this.marshallingStrategy);
466 		}
467 		if (this.mode != null) {
468 			xstream.setMode(this.mode);
469 		}
470 
471 		try {
472 			if (this.aliases != null) {
473 				Map<String, Class<?>> classMap = toClassMap(this.aliases);
474 				for (Map.Entry<String, Class<?>> entry : classMap.entrySet()) {
475 					xstream.alias(entry.getKey(), entry.getValue());
476 				}
477 			}
478 			if (this.aliasesByType != null) {
479 				Map<String, Class<?>> classMap = toClassMap(this.aliasesByType);
480 				for (Map.Entry<String, Class<?>> entry : classMap.entrySet()) {
481 					xstream.aliasType(entry.getKey(), entry.getValue());
482 				}
483 			}
484 			if (this.fieldAliases != null) {
485 				for (Map.Entry<String, String> entry : this.fieldAliases.entrySet()) {
486 					String alias = entry.getValue();
487 					String field = entry.getKey();
488 					int idx = field.lastIndexOf('.');
489 					if (idx != -1) {
490 						String className = field.substring(0, idx);
491 						Class<?> clazz = ClassUtils.forName(className, this.beanClassLoader);
492 						String fieldName = field.substring(idx + 1);
493 						xstream.aliasField(alias, clazz, fieldName);
494 					}
495 					else {
496 						throw new IllegalArgumentException("Field name [" + field + "] does not contain '.'");
497 					}
498 				}
499 			}
500 		}
501 		catch (ClassNotFoundException ex) {
502 			throw new IllegalStateException("Failed to load specified alias class", ex);
503 		}
504 
505 		if (this.useAttributeForTypes != null) {
506 			for (Class<?> type : this.useAttributeForTypes) {
507 				xstream.useAttributeFor(type);
508 			}
509 		}
510 		if (this.useAttributeFor != null) {
511 			for (Map.Entry<?, ?> entry : this.useAttributeFor.entrySet()) {
512 				if (entry.getKey() instanceof String) {
513 					if (entry.getValue() instanceof Class) {
514 						xstream.useAttributeFor((String) entry.getKey(), (Class<?>) entry.getValue());
515 					}
516 					else {
517 						throw new IllegalArgumentException(
518 								"'useAttributesFor' takes Map<String, Class> when using a map key of type String");
519 					}
520 				}
521 				else if (entry.getKey() instanceof Class) {
522 					Class<?> key = (Class<?>) entry.getKey();
523 					if (entry.getValue() instanceof String) {
524 						xstream.useAttributeFor(key, (String) entry.getValue());
525 					}
526 					else if (entry.getValue() instanceof List) {
527 						@SuppressWarnings("unchecked")
528 						List<Object> listValue = (List<Object>) entry.getValue();
529 						for (Object element : listValue) {
530 							if (element instanceof String) {
531 								xstream.useAttributeFor(key, (String) element);
532 							}
533 						}
534 					}
535 					else {
536 						throw new IllegalArgumentException("'useAttributesFor' property takes either Map<Class, String> " +
537 								"or Map<Class, List<String>> when using a map key of type Class");
538 					}
539 				}
540 				else {
541 					throw new IllegalArgumentException(
542 							"'useAttributesFor' property takes either a map key of type String or Class");
543 				}
544 			}
545 		}
546 
547 		if (this.implicitCollections != null) {
548 			for (Map.Entry<Class<?>, String> entry : this.implicitCollections.entrySet()) {
549 				String[] collectionFields = StringUtils.commaDelimitedListToStringArray(entry.getValue());
550 				for (String collectionField : collectionFields) {
551 					xstream.addImplicitCollection(entry.getKey(), collectionField);
552 				}
553 			}
554 		}
555 		if (this.omittedFields != null) {
556 			for (Map.Entry<Class<?>, String> entry : this.omittedFields.entrySet()) {
557 				String[] fields = StringUtils.commaDelimitedListToStringArray(entry.getValue());
558 				for (String field : fields) {
559 					xstream.omitField(entry.getKey(), field);
560 				}
561 			}
562 		}
563 
564 		if (this.annotatedClasses != null) {
565 			xstream.processAnnotations(this.annotatedClasses);
566 		}
567 		if (this.autodetectAnnotations) {
568 			xstream.autodetectAnnotations(true);
569 		}
570 	}
571 
572 	private Map<String, Class<?>> toClassMap(Map<String, ?> map) throws ClassNotFoundException {
573 		Map<String, Class<?>> result = new LinkedHashMap<String, Class<?>>(map.size());
574 		for (Map.Entry<String, ?> entry : map.entrySet()) {
575 			String key = entry.getKey();
576 			Object value = entry.getValue();
577 			Class<?> type;
578 			if (value instanceof Class) {
579 				type = (Class<?>) value;
580 			}
581 			else if (value instanceof String) {
582 				String className = (String) value;
583 				type = ClassUtils.forName(className, this.beanClassLoader);
584 			}
585 			else {
586 				throw new IllegalArgumentException("Unknown value [" + value + "] - expected String or Class");
587 			}
588 			result.put(key, type);
589 		}
590 		return result;
591 	}
592 
593 	/**
594 	 * Template to allow for customizing the given {@link XStream}.
595 	 * <p>The default implementation is empty.
596 	 * @param xstream the {@code XStream} instance
597 	 */
598 	protected void customizeXStream(XStream xstream) {
599 	}
600 
601 	/**
602 	 * Return the native XStream delegate used by this marshaller.
603 	 * <p><b>NOTE: This method has been marked as final as of Spring 4.0.</b>
604 	 * It can be used to access the fully configured XStream for marshalling
605 	 * but not configuration purposes anymore.
606 	 */
607 	public final XStream getXStream() {
608 		if (this.xstream == null) {
609 			this.xstream = buildXStream();
610 		}
611 		return this.xstream;
612 	}
613 
614 
615 	@Override
616 	public boolean supports(Class<?> clazz) {
617 		if (ObjectUtils.isEmpty(this.supportedClasses)) {
618 			return true;
619 		}
620 		else {
621 			for (Class<?> supportedClass : this.supportedClasses) {
622 				if (supportedClass.isAssignableFrom(clazz)) {
623 					return true;
624 				}
625 			}
626 			return false;
627 		}
628 	}
629 
630 
631 	// Marshalling
632 
633 	@Override
634 	protected void marshalDomNode(Object graph, Node node) throws XmlMappingException {
635 		HierarchicalStreamWriter streamWriter;
636 		if (node instanceof Document) {
637 			streamWriter = new DomWriter((Document) node, this.nameCoder);
638 		}
639 		else if (node instanceof Element) {
640 			streamWriter = new DomWriter((Element) node, node.getOwnerDocument(), this.nameCoder);
641 		}
642 		else {
643 			throw new IllegalArgumentException("DOMResult contains neither Document nor Element");
644 		}
645 		doMarshal(graph, streamWriter, null);
646 	}
647 
648 	@Override
649 	protected void marshalXmlEventWriter(Object graph, XMLEventWriter eventWriter) throws XmlMappingException {
650 		ContentHandler contentHandler = StaxUtils.createContentHandler(eventWriter);
651 		LexicalHandler lexicalHandler = null;
652 		if (contentHandler instanceof LexicalHandler) {
653 			lexicalHandler = (LexicalHandler) contentHandler;
654 		}
655 		marshalSaxHandlers(graph, contentHandler, lexicalHandler);
656 	}
657 
658 	@Override
659 	protected void marshalXmlStreamWriter(Object graph, XMLStreamWriter streamWriter) throws XmlMappingException {
660 		try {
661 			doMarshal(graph, new StaxWriter(new QNameMap(), streamWriter, this.nameCoder), null);
662 		}
663 		catch (XMLStreamException ex) {
664 			throw convertXStreamException(ex, true);
665 		}
666 	}
667 
668 	@Override
669 	protected void marshalSaxHandlers(Object graph, ContentHandler contentHandler, LexicalHandler lexicalHandler)
670 			throws XmlMappingException {
671 
672 		SaxWriter saxWriter = new SaxWriter(this.nameCoder);
673 		saxWriter.setContentHandler(contentHandler);
674 		doMarshal(graph, saxWriter, null);
675 	}
676 
677 	@Override
678 	public void marshalOutputStream(Object graph, OutputStream outputStream) throws XmlMappingException, IOException {
679 		marshalOutputStream(graph, outputStream, null);
680 	}
681 
682 	public void marshalOutputStream(Object graph, OutputStream outputStream, DataHolder dataHolder)
683 			throws XmlMappingException, IOException {
684 
685 		if (this.streamDriver != null) {
686 			doMarshal(graph, this.streamDriver.createWriter(outputStream), dataHolder);
687 		}
688 		else {
689 			marshalWriter(graph, new OutputStreamWriter(outputStream, this.encoding), dataHolder);
690 		}
691 	}
692 
693 	@Override
694 	public void marshalWriter(Object graph, Writer writer) throws XmlMappingException, IOException {
695 		marshalWriter(graph, writer, null);
696 	}
697 
698 	public void marshalWriter(Object graph, Writer writer, DataHolder dataHolder)
699 			throws XmlMappingException, IOException {
700 
701 		if (this.streamDriver != null) {
702 			doMarshal(graph, this.streamDriver.createWriter(writer), dataHolder);
703 		}
704 		else {
705 			doMarshal(graph, new CompactWriter(writer), dataHolder);
706 		}
707 	}
708 
709 	/**
710 	 * Marshals the given graph to the given XStream HierarchicalStreamWriter.
711 	 * Converts exceptions using {@link #convertXStreamException}.
712 	 */
713 	private void doMarshal(Object graph, HierarchicalStreamWriter streamWriter, DataHolder dataHolder) {
714 		try {
715 			getXStream().marshal(graph, streamWriter, dataHolder);
716 		}
717 		catch (Exception ex) {
718 			throw convertXStreamException(ex, true);
719 		}
720 		finally {
721 			try {
722 				streamWriter.flush();
723 			}
724 			catch (Exception ex) {
725 				logger.debug("Could not flush HierarchicalStreamWriter", ex);
726 			}
727 		}
728 	}
729 
730 
731 	// Unmarshalling
732 
733 	@Override
734 	protected Object unmarshalStreamSource(StreamSource streamSource) throws XmlMappingException, IOException {
735 		if (streamSource.getInputStream() != null) {
736 			return unmarshalInputStream(streamSource.getInputStream());
737 		}
738 		else if (streamSource.getReader() != null) {
739 			return unmarshalReader(streamSource.getReader());
740 		}
741 		else {
742 			throw new IllegalArgumentException("StreamSource contains neither InputStream nor Reader");
743 		}
744 	}
745 
746 	@Override
747 	protected Object unmarshalDomNode(Node node) throws XmlMappingException {
748 		HierarchicalStreamReader streamReader;
749 		if (node instanceof Document) {
750 			streamReader = new DomReader((Document) node, this.nameCoder);
751 		}
752 		else if (node instanceof Element) {
753 			streamReader = new DomReader((Element) node, this.nameCoder);
754 		}
755 		else {
756 			throw new IllegalArgumentException("DOMSource contains neither Document nor Element");
757 		}
758         return doUnmarshal(streamReader, null);
759 	}
760 
761 	@Override
762 	protected Object unmarshalXmlEventReader(XMLEventReader eventReader) throws XmlMappingException {
763 		try {
764 			XMLStreamReader streamReader = StaxUtils.createEventStreamReader(eventReader);
765 			return unmarshalXmlStreamReader(streamReader);
766 		}
767 		catch (XMLStreamException ex) {
768 			throw convertXStreamException(ex, false);
769 		}
770 	}
771 
772 	@Override
773 	protected Object unmarshalXmlStreamReader(XMLStreamReader streamReader) throws XmlMappingException {
774         return doUnmarshal(new StaxReader(new QNameMap(), streamReader, this.nameCoder), null);
775 	}
776 
777 	@Override
778 	protected Object unmarshalSaxReader(XMLReader xmlReader, InputSource inputSource)
779 			throws XmlMappingException, IOException {
780 
781 		throw new UnsupportedOperationException(
782 				"XStreamMarshaller does not support unmarshalling using SAX XMLReaders");
783 	}
784 
785 	@Override
786 	public Object unmarshalInputStream(InputStream inputStream) throws XmlMappingException, IOException {
787 		return unmarshalInputStream(inputStream, null);
788 	}
789 
790 	public Object unmarshalInputStream(InputStream inputStream, DataHolder dataHolder) throws XmlMappingException, IOException {
791         if (this.streamDriver != null) {
792             return doUnmarshal(this.streamDriver.createReader(inputStream), dataHolder);
793         }
794         else {
795 		    return unmarshalReader(new InputStreamReader(inputStream, this.encoding), dataHolder);
796         }
797 	}
798 
799 	@Override
800 	public Object unmarshalReader(Reader reader) throws XmlMappingException, IOException {
801 		return unmarshalReader(reader, null);
802 	}
803 
804 	public Object unmarshalReader(Reader reader, DataHolder dataHolder) throws XmlMappingException, IOException {
805 		return doUnmarshal(getDefaultDriver().createReader(reader), dataHolder);
806 	}
807 
808     /**
809      * Unmarshals the given graph to the given XStream HierarchicalStreamWriter.
810      * Converts exceptions using {@link #convertXStreamException}.
811      */
812     private Object doUnmarshal(HierarchicalStreamReader streamReader, DataHolder dataHolder) {
813         try {
814             return getXStream().unmarshal(streamReader, null, dataHolder);
815         }
816         catch (Exception ex) {
817             throw convertXStreamException(ex, false);
818         }
819     }
820 
821 
822     /**
823      * Convert the given XStream exception to an appropriate exception from the
824      * {@code org.springframework.oxm} hierarchy.
825      * <p>A boolean flag is used to indicate whether this exception occurs during marshalling or
826      * unmarshalling, since XStream itself does not make this distinction in its exception hierarchy.
827      * @param ex XStream exception that occurred
828      * @param marshalling indicates whether the exception occurs during marshalling ({@code true}),
829      * or unmarshalling ({@code false})
830      * @return the corresponding {@code XmlMappingException}
831      */
832 	protected XmlMappingException convertXStreamException(Exception ex, boolean marshalling) {
833 		if (ex instanceof StreamException || ex instanceof CannotResolveClassException ||
834 				ex instanceof ConversionException) {
835 			if (marshalling) {
836 				return new MarshallingFailureException("XStream marshalling exception",  ex);
837 			}
838 			else {
839 				return new UnmarshallingFailureException("XStream unmarshalling exception", ex);
840 			}
841 		}
842 		else {
843 			// fallback
844 			return new UncategorizedMappingException("Unknown XStream exception", ex);
845 		}
846 	}
847 
848 }