/*
 * Copyright 2002-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.oxm.xstream;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.Constructor;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLEventWriter;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.transform.stream.StreamSource;

import com.thoughtworks.xstream.MarshallingStrategy;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.converters.ConversionException;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.ConverterLookup;
import com.thoughtworks.xstream.converters.ConverterMatcher;
import com.thoughtworks.xstream.converters.ConverterRegistry;
import com.thoughtworks.xstream.converters.DataHolder;
import com.thoughtworks.xstream.converters.SingleValueConverter;
import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
import com.thoughtworks.xstream.core.ClassLoaderReference;
import com.thoughtworks.xstream.core.DefaultConverterLookup;
import com.thoughtworks.xstream.core.util.CompositeClassLoader;
import com.thoughtworks.xstream.io.HierarchicalStreamDriver;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.StreamException;
import com.thoughtworks.xstream.io.naming.NameCoder;
import com.thoughtworks.xstream.io.xml.CompactWriter;
import com.thoughtworks.xstream.io.xml.DomReader;
import com.thoughtworks.xstream.io.xml.DomWriter;
import com.thoughtworks.xstream.io.xml.QNameMap;
import com.thoughtworks.xstream.io.xml.SaxWriter;
import com.thoughtworks.xstream.io.xml.StaxReader;
import com.thoughtworks.xstream.io.xml.StaxWriter;
import com.thoughtworks.xstream.io.xml.XmlFriendlyNameCoder;
import com.thoughtworks.xstream.io.xml.XppDriver;
import com.thoughtworks.xstream.mapper.CannotResolveClassException;
import com.thoughtworks.xstream.mapper.Mapper;
import com.thoughtworks.xstream.mapper.MapperWrapper;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;
import org.xml.sax.ext.LexicalHandler;

import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.lang.Nullable;
import org.springframework.oxm.MarshallingFailureException;
import org.springframework.oxm.UncategorizedMappingException;
import org.springframework.oxm.UnmarshallingFailureException;
import org.springframework.oxm.XmlMappingException;
import org.springframework.oxm.support.AbstractMarshaller;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.StaxUtils;

Implementation of the Marshaller interface for XStream.

By default, XStream does not require any further configuration and can (un)marshal any class on the classpath. As such, it is not recommended to use the XStreamMarshaller to unmarshal XML from external sources (i.e. the Web), as this can result in security vulnerabilities. If you do use the XStreamMarshaller to unmarshal external XML, set the supportedClasses and converters properties (possibly using a CatchAllConverter) or override the customizeXStream(XStream) method to make sure it only accepts the classes you want it to support.

Due to XStream's API, it is required to set the encoding used for writing to OutputStreams. It defaults to UTF-8.

NOTE: XStream is an XML serialization library, not a data binding library. Therefore, it has limited namespace support. As such, it is rather unsuitable for usage within Web Services.

This marshaller requires XStream 1.4.5 or higher, as of Spring 4.3. Note that XStream construction has been reworked in 4.0, with the stream driver and the class loader getting passed into XStream itself now.

Author:Peter Meijer, Arjen Poutsma, Juergen Hoeller
Since:3.0
/** * Implementation of the {@code Marshaller} interface for XStream. * * <p>By default, XStream does not require any further configuration and can (un)marshal * any class on the classpath. As such, it is <b>not recommended to use the * {@code XStreamMarshaller} to unmarshal XML from external sources</b> (i.e. the Web), * as this can result in <b>security vulnerabilities</b>. If you do use the * {@code XStreamMarshaller} to unmarshal external XML, set the * {@link #setSupportedClasses(Class[]) supportedClasses} and * {@link #setConverters(ConverterMatcher[]) converters} properties (possibly using * a {@link CatchAllConverter}) or override the {@link #customizeXStream(XStream)} * method to make sure it only accepts the classes you want it to support. * * <p>Due to XStream's API, it is required to set the encoding used for writing to * OutputStreams. It defaults to {@code UTF-8}. * * <p><b>NOTE:</b> XStream is an XML serialization library, not a data binding library. * Therefore, it has limited namespace support. As such, it is rather unsuitable for * usage within Web Services. * * <p>This marshaller requires XStream 1.4.5 or higher, as of Spring 4.3. * Note that {@link XStream} construction has been reworked in 4.0, with the * stream driver and the class loader getting passed into XStream itself now. * * @author Peter Meijer * @author Arjen Poutsma * @author Juergen Hoeller * @since 3.0 */
public class XStreamMarshaller extends AbstractMarshaller implements BeanClassLoaderAware, InitializingBean {
The default encoding used for stream access: UTF-8.
/** * The default encoding used for stream access: UTF-8. */
public static final String DEFAULT_ENCODING = "UTF-8"; @Nullable private ReflectionProvider reflectionProvider; @Nullable private HierarchicalStreamDriver streamDriver; @Nullable private HierarchicalStreamDriver defaultDriver; @Nullable private Mapper mapper; @Nullable private Class<? extends MapperWrapper>[] mapperWrappers; private ConverterLookup converterLookup = new DefaultConverterLookup(); private ConverterRegistry converterRegistry = (ConverterRegistry) this.converterLookup; @Nullable private ConverterMatcher[] converters; @Nullable private MarshallingStrategy marshallingStrategy; @Nullable private Integer mode; @Nullable private Map<String, ?> aliases; @Nullable private Map<String, ?> aliasesByType; @Nullable private Map<String, String> fieldAliases; @Nullable private Class<?>[] useAttributeForTypes; @Nullable private Map<?, ?> useAttributeFor; @Nullable private Map<Class<?>, String> implicitCollections; @Nullable private Map<Class<?>, String> omittedFields; @Nullable private Class<?>[] annotatedClasses; private boolean autodetectAnnotations; private String encoding = DEFAULT_ENCODING; private NameCoder nameCoder = new XmlFriendlyNameCoder(); @Nullable private Class<?>[] supportedClasses; private ClassLoader beanClassLoader = new CompositeClassLoader(); @Nullable private XStream xstream;
Set a custom XStream ReflectionProvider to use.
Since:4.0
/** * Set a custom XStream {@link ReflectionProvider} to use. * @since 4.0 */
public void setReflectionProvider(ReflectionProvider reflectionProvider) { this.reflectionProvider = reflectionProvider; }
Set a XStream HierarchicalStreamDriver to be used for readers and writers.

As of Spring 4.0, this stream driver will also be passed to the XStream constructor and therefore used by streaming-related native API methods themselves.

/** * Set a XStream {@link HierarchicalStreamDriver} to be used for readers and writers. * <p>As of Spring 4.0, this stream driver will also be passed to the {@link XStream} * constructor and therefore used by streaming-related native API methods themselves. */
public void setStreamDriver(HierarchicalStreamDriver streamDriver) { this.streamDriver = streamDriver; this.defaultDriver = streamDriver; } private HierarchicalStreamDriver getDefaultDriver() { if (this.defaultDriver == null) { this.defaultDriver = new XppDriver(); } return this.defaultDriver; }
Set a custom XStream Mapper to use.
Since:4.0
/** * Set a custom XStream {@link Mapper} to use. * @since 4.0 */
public void setMapper(Mapper mapper) { this.mapper = mapper; }
Set one or more custom XStream MapperWrapper classes. Each of those classes needs to have a constructor with a single argument of type Mapper or MapperWrapper.
Since:4.0
/** * Set one or more custom XStream {@link MapperWrapper} classes. * Each of those classes needs to have a constructor with a single argument * of type {@link Mapper} or {@link MapperWrapper}. * @since 4.0 */
@SuppressWarnings("unchecked") public void setMapperWrappers(Class<? extends MapperWrapper>... mapperWrappers) { this.mapperWrappers = mapperWrappers; }
Set a custom XStream ConverterLookup to use. Also used as ConverterRegistry if the given reference implements it as well.
See Also:
Since:4.0
/** * Set a custom XStream {@link ConverterLookup} to use. * Also used as {@link ConverterRegistry} if the given reference implements it as well. * @since 4.0 * @see DefaultConverterLookup */
public void setConverterLookup(ConverterLookup converterLookup) { this.converterLookup = converterLookup; if (converterLookup instanceof ConverterRegistry) { this.converterRegistry = (ConverterRegistry) converterLookup; } }
Set a custom XStream ConverterRegistry to use.
See Also:
Since:4.0
/** * Set a custom XStream {@link ConverterRegistry} to use. * @since 4.0 * @see #setConverterLookup * @see DefaultConverterLookup */
public void setConverterRegistry(ConverterRegistry converterRegistry) { this.converterRegistry = converterRegistry; }
Set the Converters or SingleValueConverters to be registered with the XStream instance.
See Also:
/** * Set the {@code Converters} or {@code SingleValueConverters} to be registered * with the {@code XStream} instance. * @see Converter * @see SingleValueConverter */
public void setConverters(ConverterMatcher... converters) { this.converters = converters; }
Set a custom XStream MarshallingStrategy to use.
Since:4.0
/** * Set a custom XStream {@link MarshallingStrategy} to use. * @since 4.0 */
public void setMarshallingStrategy(MarshallingStrategy marshallingStrategy) { this.marshallingStrategy = marshallingStrategy; }
Set the XStream mode to use.
See Also:
/** * Set the XStream mode to use. * @see XStream#ID_REFERENCES * @see XStream#NO_REFERENCES */
public void setMode(int mode) { this.mode = mode; }
Set the alias/type map, consisting of string aliases mapped to classes.

Keys are aliases; values are either Class instances, or String class names.

See Also:
/** * Set the alias/type map, consisting of string aliases mapped to classes. * <p>Keys are aliases; values are either {@code Class} instances, or String class names. * @see XStream#alias(String, Class) */
public void setAliases(Map<String, ?> aliases) { this.aliases = aliases; }
Set the aliases by type map, consisting of string aliases mapped to classes.

Any class that is assignable to this type will be aliased to the same name. Keys are aliases; values are either Class instances, or String class names.

See Also:
/** * Set the <em>aliases by type</em> map, consisting of string aliases mapped to classes. * <p>Any class that is assignable to this type will be aliased to the same name. * Keys are aliases; values are either {@code Class} instances, or String class names. * @see XStream#aliasType(String, Class) */
public void setAliasesByType(Map<String, ?> aliasesByType) { this.aliasesByType = aliasesByType; }
Set the field alias/type map, consisting of field names.
See Also:
  • aliasField.aliasField(String, Class, String)
/** * Set the field alias/type map, consisting of field names. * @see XStream#aliasField(String, Class, String) */
public void setFieldAliases(Map<String, String> fieldAliases) { this.fieldAliases = fieldAliases; }
Set types to use XML attributes for.
See Also:
  • useAttributeFor.useAttributeFor(Class)
/** * Set types to use XML attributes for. * @see XStream#useAttributeFor(Class) */
public void setUseAttributeForTypes(Class<?>... useAttributeForTypes) { this.useAttributeForTypes = useAttributeForTypes; }
Set the types to use XML attributes for. The given map can contain either <String, Class> pairs, in which case XStream.useAttributeFor(String, Class) is called. Alternatively, the map can contain <Class, String> or <Class, List<String>> pairs, which results in XStream.useAttributeFor(Class, String) calls.
/** * Set the types to use XML attributes for. The given map can contain * either {@code <String, Class>} pairs, in which case * {@link XStream#useAttributeFor(String, Class)} is called. * Alternatively, the map can contain {@code <Class, String>} * or {@code <Class, List<String>>} pairs, which results * in {@link XStream#useAttributeFor(Class, String)} calls. */
public void setUseAttributeFor(Map<?, ?> useAttributeFor) { this.useAttributeFor = useAttributeFor; }
Specify implicit collection fields, as a Map consisting of Class instances mapped to comma separated collection field names.
See Also:
/** * Specify implicit collection fields, as a Map consisting of {@code Class} instances * mapped to comma separated collection field names. * @see XStream#addImplicitCollection(Class, String) */
public void setImplicitCollections(Map<Class<?>, String> implicitCollections) { this.implicitCollections = implicitCollections; }
Specify omitted fields, as a Map consisting of Class instances mapped to comma separated field names.
See Also:
/** * Specify omitted fields, as a Map consisting of {@code Class} instances * mapped to comma separated field names. * @see XStream#omitField(Class, String) */
public void setOmittedFields(Map<Class<?>, String> omittedFields) { this.omittedFields = omittedFields; }
Set annotated classes for which aliases will be read from class-level annotation metadata.
See Also:
  • processAnnotations.processAnnotations(Class[])
/** * Set annotated classes for which aliases will be read from class-level annotation metadata. * @see XStream#processAnnotations(Class[]) */
public void setAnnotatedClasses(Class<?>... annotatedClasses) { this.annotatedClasses = annotatedClasses; }
Activate XStream's autodetection mode.

Note: Autodetection implies that the XStream instance is being configured while it is processing the XML streams, and thus introduces a potential concurrency problem.

See Also:
  • autodetectAnnotations.autodetectAnnotations(boolean)
/** * Activate XStream's autodetection mode. * <p><b>Note</b>: Autodetection implies that the XStream instance is being configured while * it is processing the XML streams, and thus introduces a potential concurrency problem. * @see XStream#autodetectAnnotations(boolean) */
public void setAutodetectAnnotations(boolean autodetectAnnotations) { this.autodetectAnnotations = autodetectAnnotations; }
Set the encoding to be used for stream access.
See Also:
  • DEFAULT_ENCODING
/** * Set the encoding to be used for stream access. * @see #DEFAULT_ENCODING */
public void setEncoding(String encoding) { this.encoding = encoding; } @Override protected String getDefaultEncoding() { return this.encoding; }
Set a custom XStream NameCoder to use. The default is an XmlFriendlyNameCoder.
Since:4.0.4
/** * Set a custom XStream {@link NameCoder} to use. * The default is an {@link XmlFriendlyNameCoder}. * @since 4.0.4 */
public void setNameCoder(NameCoder nameCoder) { this.nameCoder = nameCoder; }
Set the classes supported by this marshaller.

If this property is empty (the default), all classes are supported.

See Also:
  • supports(Class)
/** * Set the classes supported by this marshaller. * <p>If this property is empty (the default), all classes are supported. * @see #supports(Class) */
public void setSupportedClasses(Class<?>... supportedClasses) { this.supportedClasses = supportedClasses; } @Override public void setBeanClassLoader(ClassLoader classLoader) { this.beanClassLoader = classLoader; } @Override public void afterPropertiesSet() { this.xstream = buildXStream(); }
Build the native XStream delegate to be used by this marshaller, delegating to constructXStream(), configureXStream and customizeXStream.
/** * Build the native XStream delegate to be used by this marshaller, * delegating to {@link #constructXStream()}, {@link #configureXStream} * and {@link #customizeXStream}. */
protected XStream buildXStream() { XStream xstream = constructXStream(); configureXStream(xstream); customizeXStream(xstream); return xstream; }
Construct an XStream instance, either using one of the standard constructors or creating a custom subclass.
Returns:the XStream instance
/** * Construct an XStream instance, either using one of the * standard constructors or creating a custom subclass. * @return the {@code XStream} instance */
protected XStream constructXStream() { return new XStream(this.reflectionProvider, getDefaultDriver(), new ClassLoaderReference(this.beanClassLoader), this.mapper, this.converterLookup, this.converterRegistry) { @Override protected MapperWrapper wrapMapper(MapperWrapper next) { MapperWrapper mapperToWrap = next; if (mapperWrappers != null) { for (Class<? extends MapperWrapper> mapperWrapper : mapperWrappers) { Constructor<? extends MapperWrapper> ctor; try { ctor = mapperWrapper.getConstructor(Mapper.class); } catch (NoSuchMethodException ex) { try { ctor = mapperWrapper.getConstructor(MapperWrapper.class); } catch (NoSuchMethodException ex2) { throw new IllegalStateException("No appropriate MapperWrapper constructor found: " + mapperWrapper); } } try { mapperToWrap = ctor.newInstance(mapperToWrap); } catch (Throwable ex) { throw new IllegalStateException("Failed to construct MapperWrapper: " + mapperWrapper); } } } return mapperToWrap; } }; }
Configure the XStream instance with this marshaller's bean properties.
Params:
  • xstream – the XStream instance
/** * Configure the XStream instance with this marshaller's bean properties. * @param xstream the {@code XStream} instance */
protected void configureXStream(XStream xstream) { if (this.converters != null) { for (int i = 0; i < this.converters.length; i++) { if (this.converters[i] instanceof Converter) { xstream.registerConverter((Converter) this.converters[i], i); } else if (this.converters[i] instanceof SingleValueConverter) { xstream.registerConverter((SingleValueConverter) this.converters[i], i); } else { throw new IllegalArgumentException("Invalid ConverterMatcher [" + this.converters[i] + "]"); } } } if (this.marshallingStrategy != null) { xstream.setMarshallingStrategy(this.marshallingStrategy); } if (this.mode != null) { xstream.setMode(this.mode); } try { if (this.aliases != null) { Map<String, Class<?>> classMap = toClassMap(this.aliases); classMap.forEach(xstream::alias); } if (this.aliasesByType != null) { Map<String, Class<?>> classMap = toClassMap(this.aliasesByType); classMap.forEach(xstream::aliasType); } if (this.fieldAliases != null) { for (Map.Entry<String, String> entry : this.fieldAliases.entrySet()) { String alias = entry.getValue(); String field = entry.getKey(); int idx = field.lastIndexOf('.'); if (idx != -1) { String className = field.substring(0, idx); Class<?> clazz = ClassUtils.forName(className, this.beanClassLoader); String fieldName = field.substring(idx + 1); xstream.aliasField(alias, clazz, fieldName); } else { throw new IllegalArgumentException("Field name [" + field + "] does not contain '.'"); } } } } catch (ClassNotFoundException ex) { throw new IllegalStateException("Failed to load specified alias class", ex); } if (this.useAttributeForTypes != null) { for (Class<?> type : this.useAttributeForTypes) { xstream.useAttributeFor(type); } } if (this.useAttributeFor != null) { for (Map.Entry<?, ?> entry : this.useAttributeFor.entrySet()) { if (entry.getKey() instanceof String) { if (entry.getValue() instanceof Class) { xstream.useAttributeFor((String) entry.getKey(), (Class<?>) entry.getValue()); } else { throw new IllegalArgumentException( "'useAttributesFor' takes Map<String, Class> when using a map key of type String"); } } else if (entry.getKey() instanceof Class) { Class<?> key = (Class<?>) entry.getKey(); if (entry.getValue() instanceof String) { xstream.useAttributeFor(key, (String) entry.getValue()); } else if (entry.getValue() instanceof List) { @SuppressWarnings("unchecked") List<Object> listValue = (List<Object>) entry.getValue(); for (Object element : listValue) { if (element instanceof String) { xstream.useAttributeFor(key, (String) element); } } } else { throw new IllegalArgumentException("'useAttributesFor' property takes either Map<Class, String> " + "or Map<Class, List<String>> when using a map key of type Class"); } } else { throw new IllegalArgumentException( "'useAttributesFor' property takes either a map key of type String or Class"); } } } if (this.implicitCollections != null) { this.implicitCollections.forEach((key, fields) -> { String[] collectionFields = StringUtils.commaDelimitedListToStringArray(fields); for (String collectionField : collectionFields) { xstream.addImplicitCollection(key, collectionField); } }); } if (this.omittedFields != null) { this.omittedFields.forEach((key, value) -> { String[] fields = StringUtils.commaDelimitedListToStringArray(value); for (String field : fields) { xstream.omitField(key, field); } }); } if (this.annotatedClasses != null) { xstream.processAnnotations(this.annotatedClasses); } if (this.autodetectAnnotations) { xstream.autodetectAnnotations(true); } } private Map<String, Class<?>> toClassMap(Map<String, ?> map) throws ClassNotFoundException { Map<String, Class<?>> result = new LinkedHashMap<>(map.size()); for (Map.Entry<String, ?> entry : map.entrySet()) { String key = entry.getKey(); Object value = entry.getValue(); Class<?> type; if (value instanceof Class) { type = (Class<?>) value; } else if (value instanceof String) { String className = (String) value; type = ClassUtils.forName(className, this.beanClassLoader); } else { throw new IllegalArgumentException("Unknown value [" + value + "] - expected String or Class"); } result.put(key, type); } return result; }
Template to allow for customizing the given XStream.

The default implementation is empty.

Params:
  • xstream – the XStream instance
/** * Template to allow for customizing the given {@link XStream}. * <p>The default implementation is empty. * @param xstream the {@code XStream} instance */
protected void customizeXStream(XStream xstream) { }
Return the native XStream delegate used by this marshaller.

NOTE: This method has been marked as final as of Spring 4.0. It can be used to access the fully configured XStream for marshalling but not configuration purposes anymore.

/** * Return the native XStream delegate used by this marshaller. * <p><b>NOTE: This method has been marked as final as of Spring 4.0.</b> * It can be used to access the fully configured XStream for marshalling * but not configuration purposes anymore. */
public final XStream getXStream() { if (this.xstream == null) { this.xstream = buildXStream(); } return this.xstream; } @Override public boolean supports(Class<?> clazz) { if (ObjectUtils.isEmpty(this.supportedClasses)) { return true; } else { for (Class<?> supportedClass : this.supportedClasses) { if (supportedClass.isAssignableFrom(clazz)) { return true; } } return false; } } // Marshalling @Override protected void marshalDomNode(Object graph, Node node) throws XmlMappingException { HierarchicalStreamWriter streamWriter; if (node instanceof Document) { streamWriter = new DomWriter((Document) node, this.nameCoder); } else if (node instanceof Element) { streamWriter = new DomWriter((Element) node, node.getOwnerDocument(), this.nameCoder); } else { throw new IllegalArgumentException("DOMResult contains neither Document nor Element"); } doMarshal(graph, streamWriter, null); } @Override protected void marshalXmlEventWriter(Object graph, XMLEventWriter eventWriter) throws XmlMappingException { ContentHandler contentHandler = StaxUtils.createContentHandler(eventWriter); LexicalHandler lexicalHandler = null; if (contentHandler instanceof LexicalHandler) { lexicalHandler = (LexicalHandler) contentHandler; } marshalSaxHandlers(graph, contentHandler, lexicalHandler); } @Override protected void marshalXmlStreamWriter(Object graph, XMLStreamWriter streamWriter) throws XmlMappingException { try { doMarshal(graph, new StaxWriter(new QNameMap(), streamWriter, this.nameCoder), null); } catch (XMLStreamException ex) { throw convertXStreamException(ex, true); } } @Override protected void marshalSaxHandlers(Object graph, ContentHandler contentHandler, @Nullable LexicalHandler lexicalHandler) throws XmlMappingException { SaxWriter saxWriter = new SaxWriter(this.nameCoder); saxWriter.setContentHandler(contentHandler); doMarshal(graph, saxWriter, null); } @Override public void marshalOutputStream(Object graph, OutputStream outputStream) throws XmlMappingException, IOException { marshalOutputStream(graph, outputStream, null); } public void marshalOutputStream(Object graph, OutputStream outputStream, @Nullable DataHolder dataHolder) throws XmlMappingException, IOException { if (this.streamDriver != null) { doMarshal(graph, this.streamDriver.createWriter(outputStream), dataHolder); } else { marshalWriter(graph, new OutputStreamWriter(outputStream, this.encoding), dataHolder); } } @Override public void marshalWriter(Object graph, Writer writer) throws XmlMappingException, IOException { marshalWriter(graph, writer, null); } public void marshalWriter(Object graph, Writer writer, @Nullable DataHolder dataHolder) throws XmlMappingException, IOException { if (this.streamDriver != null) { doMarshal(graph, this.streamDriver.createWriter(writer), dataHolder); } else { doMarshal(graph, new CompactWriter(writer), dataHolder); } }
Marshals the given graph to the given XStream HierarchicalStreamWriter. Converts exceptions using convertXStreamException.
/** * Marshals the given graph to the given XStream HierarchicalStreamWriter. * Converts exceptions using {@link #convertXStreamException}. */
private void doMarshal(Object graph, HierarchicalStreamWriter streamWriter, @Nullable DataHolder dataHolder) { try { getXStream().marshal(graph, streamWriter, dataHolder); } catch (Exception ex) { throw convertXStreamException(ex, true); } finally { try { streamWriter.flush(); } catch (Exception ex) { logger.debug("Could not flush HierarchicalStreamWriter", ex); } } } // Unmarshalling @Override protected Object unmarshalStreamSource(StreamSource streamSource) throws XmlMappingException, IOException { if (streamSource.getInputStream() != null) { return unmarshalInputStream(streamSource.getInputStream()); } else if (streamSource.getReader() != null) { return unmarshalReader(streamSource.getReader()); } else { throw new IllegalArgumentException("StreamSource contains neither InputStream nor Reader"); } } @Override protected Object unmarshalDomNode(Node node) throws XmlMappingException { HierarchicalStreamReader streamReader; if (node instanceof Document) { streamReader = new DomReader((Document) node, this.nameCoder); } else if (node instanceof Element) { streamReader = new DomReader((Element) node, this.nameCoder); } else { throw new IllegalArgumentException("DOMSource contains neither Document nor Element"); } return doUnmarshal(streamReader, null); } @Override protected Object unmarshalXmlEventReader(XMLEventReader eventReader) throws XmlMappingException { try { XMLStreamReader streamReader = StaxUtils.createEventStreamReader(eventReader); return unmarshalXmlStreamReader(streamReader); } catch (XMLStreamException ex) { throw convertXStreamException(ex, false); } } @Override protected Object unmarshalXmlStreamReader(XMLStreamReader streamReader) throws XmlMappingException { return doUnmarshal(new StaxReader(new QNameMap(), streamReader, this.nameCoder), null); } @Override protected Object unmarshalSaxReader(XMLReader xmlReader, InputSource inputSource) throws XmlMappingException, IOException { throw new UnsupportedOperationException( "XStreamMarshaller does not support unmarshalling using SAX XMLReaders"); } @Override public Object unmarshalInputStream(InputStream inputStream) throws XmlMappingException, IOException { return unmarshalInputStream(inputStream, null); } public Object unmarshalInputStream(InputStream inputStream, @Nullable DataHolder dataHolder) throws XmlMappingException, IOException { if (this.streamDriver != null) { return doUnmarshal(this.streamDriver.createReader(inputStream), dataHolder); } else { return unmarshalReader(new InputStreamReader(inputStream, this.encoding), dataHolder); } } @Override public Object unmarshalReader(Reader reader) throws XmlMappingException, IOException { return unmarshalReader(reader, null); } public Object unmarshalReader(Reader reader, @Nullable DataHolder dataHolder) throws XmlMappingException, IOException { return doUnmarshal(getDefaultDriver().createReader(reader), dataHolder); }
Unmarshals the given graph to the given XStream HierarchicalStreamWriter. Converts exceptions using convertXStreamException.
/** * Unmarshals the given graph to the given XStream HierarchicalStreamWriter. * Converts exceptions using {@link #convertXStreamException}. */
private Object doUnmarshal(HierarchicalStreamReader streamReader, @Nullable DataHolder dataHolder) { try { return getXStream().unmarshal(streamReader, null, dataHolder); } catch (Exception ex) { throw convertXStreamException(ex, false); } }
Convert the given XStream exception to an appropriate exception from the org.springframework.oxm hierarchy.

A boolean flag is used to indicate whether this exception occurs during marshalling or unmarshalling, since XStream itself does not make this distinction in its exception hierarchy.

Params:
  • ex – the XStream exception that occurred
  • marshalling – indicates whether the exception occurs during marshalling (true), or unmarshalling (false)
Returns:the corresponding XmlMappingException
/** * Convert the given XStream exception to an appropriate exception from the * {@code org.springframework.oxm} hierarchy. * <p>A boolean flag is used to indicate whether this exception occurs during marshalling or * unmarshalling, since XStream itself does not make this distinction in its exception hierarchy. * @param ex the XStream exception that occurred * @param marshalling indicates whether the exception occurs during marshalling ({@code true}), * or unmarshalling ({@code false}) * @return the corresponding {@code XmlMappingException} */
protected XmlMappingException convertXStreamException(Exception ex, boolean marshalling) { if (ex instanceof StreamException || ex instanceof CannotResolveClassException || ex instanceof ConversionException) { if (marshalling) { return new MarshallingFailureException("XStream marshalling exception", ex); } else { return new UnmarshallingFailureException("XStream unmarshalling exception", ex); } } else { // fallback return new UncategorizedMappingException("Unknown XStream exception", ex); } } }