diff --git a/build.gradle b/build.gradle index da2f7e84be..300a5423a8 100644 --- a/build.gradle +++ b/build.gradle @@ -732,7 +732,7 @@ project("spring-test-mvc") { testCompile("com.fasterxml.jackson.core:jackson-databind:2.2.0") testCompile(project(":spring-context-support")) testCompile(project(":spring-oxm")) - testCompile("com.thoughtworks.xstream:xstream:1.3.1") + testCompile("com.thoughtworks.xstream:xstream:1.4.4") testCompile("rome:rome:1.0") testCompile("javax.activation:activation:1.1") testCompile("javax.mail:mail:1.4.7") diff --git a/spring-oxm/src/main/java/org/springframework/oxm/xstream/XStreamMarshaller.java b/spring-oxm/src/main/java/org/springframework/oxm/xstream/XStreamMarshaller.java index 8bddd56dc1..48df330efb 100644 --- a/spring-oxm/src/main/java/org/springframework/oxm/xstream/XStreamMarshaller.java +++ b/spring-oxm/src/main/java/org/springframework/oxm/xstream/XStreamMarshaller.java @@ -37,6 +37,7 @@ import com.thoughtworks.xstream.converters.ConversionException; import com.thoughtworks.xstream.converters.Converter; import com.thoughtworks.xstream.converters.ConverterMatcher; import com.thoughtworks.xstream.converters.SingleValueConverter; +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; @@ -48,9 +49,10 @@ 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.XmlFriendlyReplacer; -import com.thoughtworks.xstream.io.xml.XppReader; +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 org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; @@ -66,7 +68,6 @@ 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.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; @@ -77,8 +78,8 @@ import org.springframework.util.xml.StaxUtils; * *
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 - * {@code XStreamMarshaller} to unmarshal XML from external sources (i.e. the Web), as - * this can result in security vulnerabilities. If you do use the + * {@code XStreamMarshaller} to unmarshal XML from external sources (i.e. the Web), + * as this can result in security vulnerabilities. If you do use the * {@code XStreamMarshaller} to unmarshal external XML, set the * {@link #setConverters(ConverterMatcher[]) converters} and * {@link #setSupportedClasses(Class[]) supportedClasses} properties or override the @@ -90,14 +91,16 @@ import org.springframework.util.xml.StaxUtils; * *
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. + * usage within Web Services. + * + *
This marshaller requires XStream 1.4 or higher, as of Spring 4.0.
+ * 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
- * @see #setAliases
- * @see #setConverters
- * @see #setEncoding
*/
public class XStreamMarshaller extends AbstractMarshaller implements InitializingBean, BeanClassLoaderAware {
@@ -107,32 +110,67 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin
public static final String DEFAULT_ENCODING = "UTF-8";
- private final XStream xstream = new XStream();
-
private HierarchicalStreamDriver streamDriver;
+ private final XppDriver fallbackDriver = new XppDriver();
+
+ private Mapper mapper;
+
+ private Integer mode;
+
+ private ConverterMatcher[] converters;
+
+ private Map 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 final XStream getXStream() {
- return this.xstream;
+ public void setStreamDriver(HierarchicalStreamDriver streamDriver) {
+ this.streamDriver = streamDriver;
}
/**
- * Set the XStream mode.
- * @see XStream#XPATH_REFERENCES
+ * Set a custom XStream Mapper to use.
+ * @since 4.0
+ */
+ public void setMapper(Mapper mapper) {
+ this.mapper = mapper;
+ }
+
+ /**
+ * Set the XStream mode to use.
* @see XStream#ID_REFERENCES
* @see XStream#NO_REFERENCES
*/
public void setMode(int mode) {
- this.xstream.setMode(mode);
+ this.mode = mode;
}
/**
@@ -141,142 +179,55 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin
* @see Converter
* @see SingleValueConverter
*/
- public void setConverters(ConverterMatcher[] converters) {
- for (int i = 0; i < converters.length; i++) {
- if (converters[i] instanceof Converter) {
- this.xstream.registerConverter((Converter) converters[i], i);
- }
- else if (converters[i] instanceof SingleValueConverter) {
- this.xstream.registerConverter((SingleValueConverter) converters[i], i);
- }
- else {
- throw new IllegalArgumentException("Invalid ConverterMatcher [" + converters[i] + "]");
- }
- }
+ public void setConverters(ConverterMatcher... converters) {
+ this.converters = converters;
}
/**
- * Sets an alias/type map, consisting of string aliases mapped to classes. Keys are aliases; values are either
- * {@code Class} instances, or String class names.
+ * Set an alias/type map, consisting of string aliases mapped to classes.
+ * Keys are aliases; values are either {@code Class} instances, or String class names.
* @see XStream#alias(String, Class)
*/
- public void setAliases(Map 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 Note that auto-detection implies that the XStream is configured while
+ * Activate the autodetection mode of XStream.
+ * Note that auto-detection implies that the XStream is 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.xstream.autodetectAnnotations(autodetectAnnotations);
- }
-
- /**
- * Set the XStream hierarchical stream driver to be used with stream readers and writers.
- */
- public void setStreamDriver(HierarchicalStreamDriver streamDriver) {
- this.streamDriver = streamDriver;
+ this.autodetectAnnotations = autodetectAnnotations;
}
/**
@@ -355,29 +279,192 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin
* If this property is empty (the default), all classes are supported.
* @see #supports(Class)
*/
- public void setSupportedClasses(Class[] supportedClasses) {
+ public void setSupportedClasses(Class>... supportedClasses) {
this.supportedClasses = supportedClasses;
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
- this.classLoader = classLoader;
+ this.beanClassLoader = classLoader;
}
@Override
- public final void afterPropertiesSet() throws Exception {
- customizeXStream(this.xstream);
+ public final void afterPropertiesSet() {
+ this.xstream = buildXStream();
}
/**
- * Template to allow for customizing of the given {@link XStream}.
+ * Build the native XStream delegate to be used by this marshaller.
+ */
+ protected XStream buildXStream() {
+ XStream xstream = new XStream(null, this.streamDriver, this.beanClassLoader, this.mapper);
+ if (this.mode != null) {
+ xstream.setMode(this.mode);
+ }
+
+ 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] + "]");
+ }
+ }
+ }
+
+ try {
+ if (this.aliases != null) {
+ Map 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.
+ */
+ public final XStream getXStream() {
+ if (this.xstream == null) {
+ this.xstream = buildXStream();
+ }
+ return this.xstream;
+ }
+
@Override
public boolean supports(Class clazz) {
@@ -385,7 +472,7 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin
return true;
}
else {
- for (Class supportedClass : this.supportedClasses) {
+ for (Class> supportedClass : this.supportedClasses) {
if (supportedClass.isAssignableFrom(clazz)) {
return true;
}
@@ -404,7 +491,7 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin
streamWriter = new DomWriter((Document) node);
}
else if (node instanceof Element) {
- streamWriter = new DomWriter((Element) node, node.getOwnerDocument(), new XmlFriendlyReplacer());
+ streamWriter = new DomWriter((Element) node, node.getOwnerDocument(), new XmlFriendlyNameCoder());
}
else {
throw new IllegalArgumentException("DOMResult contains neither Document nor Element");
@@ -463,7 +550,7 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin
*/
private void marshal(Object graph, HierarchicalStreamWriter streamWriter) {
try {
- this.xstream.marshal(graph, streamWriter);
+ getXStream().marshal(graph, streamWriter);
}
catch (Exception ex) {
throw convertXStreamException(ex, true);
@@ -528,7 +615,7 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin
return unmarshal(this.streamDriver.createReader(reader));
}
else {
- return unmarshal(new XppReader(reader));
+ return unmarshal(this.fallbackDriver.createReader(reader));
}
}
@@ -546,7 +633,7 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin
*/
private Object unmarshal(HierarchicalStreamReader streamReader) {
try {
- return this.xstream.unmarshal(streamReader);
+ return getXStream().unmarshal(streamReader);
}
catch (Exception ex) {
throw convertXStreamException(ex, false);
diff --git a/spring-oxm/src/test/java/org/springframework/oxm/xstream/XStreamMarshallerTests.java b/spring-oxm/src/test/java/org/springframework/oxm/xstream/XStreamMarshallerTests.java
index ce49ce166d..eba5a3caac 100644
--- a/spring-oxm/src/test/java/org/springframework/oxm/xstream/XStreamMarshallerTests.java
+++ b/spring-oxm/src/test/java/org/springframework/oxm/xstream/XStreamMarshallerTests.java
@@ -25,7 +25,6 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
-
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.stream.XMLEventWriter;
@@ -37,27 +36,27 @@ import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.InOrder;
-import org.springframework.util.xml.StaxUtils;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.Text;
-import org.xml.sax.Attributes;
-import org.xml.sax.ContentHandler;
-
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.extended.EncodedByteArrayConverter;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.json.JettisonMappedXmlDriver;
import com.thoughtworks.xstream.io.json.JsonHierarchicalStreamDriver;
import com.thoughtworks.xstream.io.json.JsonWriter;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InOrder;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Text;
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
-import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual;
-import static org.custommonkey.xmlunit.XMLAssert.assertXpathExists;
-import static org.custommonkey.xmlunit.XMLAssert.assertXpathNotExists;
-import static org.junit.Assert.*;
+import org.springframework.util.xml.StaxUtils;
+
+import static org.custommonkey.xmlunit.XMLAssert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
import static org.mockito.BDDMockito.*;
/**
@@ -315,7 +314,9 @@ public class XStreamMarshallerTests {
marshaller.setStreamDriver(new JsonHierarchicalStreamDriver() {
@Override
public HierarchicalStreamWriter createWriter(Writer writer) {
- return new JsonWriter(writer, new char[0], "", JsonWriter.DROP_ROOT_MODE);
+ return new JsonWriter(writer, JsonWriter.DROP_ROOT_MODE,
+ new JsonWriter.Format(new char[0], new char[0],
+ JsonWriter.Format.SPACE_AFTER_LABEL | JsonWriter.Format.COMPACT_EMPTY_ELEMENT));
}
});
@@ -326,7 +327,7 @@ public class XStreamMarshallerTests {
@Test
public void annotatedMarshalStreamResultWriter() throws Exception {
- marshaller.setAnnotatedClass(Flight.class);
+ marshaller.setAnnotatedClasses(Flight.class);
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult(writer);
Flight flight = new Flight();