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 df1100e471c..596b587fa29 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2020 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.
@@ -113,6 +113,7 @@ import org.springframework.util.xml.StaxUtils;
* @author Peter Meijer
* @author Arjen Poutsma
* @author Juergen Hoeller
+ * @author Sam Brannen
* @since 3.0
*/
public class XStreamMarshaller extends AbstractMarshaller implements BeanClassLoaderAware, InitializingBean {
@@ -187,7 +188,7 @@ public class XStreamMarshaller extends AbstractMarshaller implements BeanClassLo
private ClassLoader beanClassLoader = new CompositeClassLoader();
@Nullable
- private XStream xstream;
+ private volatile XStream xstream;
/**
@@ -616,12 +617,21 @@ public class XStreamMarshaller extends AbstractMarshaller implements BeanClassLo
*
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.
+ *
As of Spring Framework 5.2.7, creation of the {@link XStream} instance
+ * returned by this method is thread safe.
*/
public final XStream getXStream() {
- if (this.xstream == null) {
- this.xstream = buildXStream();
+ XStream xs = this.xstream;
+ if (xs == null) {
+ synchronized (this) {
+ xs = this.xstream;
+ if (xs == null) {
+ xs = buildXStream();
+ this.xstream = xs;
+ }
+ }
}
- return this.xstream;
+ return xs;
}
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 5d06efc4f4c..5f05936d751 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2020 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.
@@ -21,11 +21,10 @@ import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
-import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
-import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.IntStream;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
@@ -69,28 +68,24 @@ import static org.mockito.Mockito.mock;
* @author Arjen Poutsma
* @author Sam Brannen
*/
-public class XStreamMarshallerTests {
+class XStreamMarshallerTests {
private static final String EXPECTED_STRING = "42";
- private XStreamMarshaller marshaller;
+ private final XStreamMarshaller marshaller = new XStreamMarshaller();
- private Flight flight;
+ private final Flight flight = new Flight();
@BeforeEach
- public void createMarshaller() {
- marshaller = new XStreamMarshaller();
- Map aliases = new HashMap<>();
- aliases.put("flight", Flight.class.getName());
- marshaller.setAliases(aliases);
- flight = new Flight();
+ void createMarshaller() {
+ marshaller.setAliases(Collections.singletonMap("flight", Flight.class.getName()));
flight.setFlightNumber(42L);
}
@Test
- public void marshalDOMResult() throws Exception {
+ void marshalDOMResult() throws Exception {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = documentBuilderFactory.newDocumentBuilder();
Document document = builder.newDocument();
@@ -108,7 +103,7 @@ public class XStreamMarshallerTests {
// see SWS-392
@Test
- public void marshalDOMResultToExistentDocument() throws Exception {
+ void marshalDOMResultToExistentDocument() throws Exception {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = documentBuilderFactory.newDocumentBuilder();
Document existent = builder.newDocument();
@@ -136,7 +131,7 @@ public class XStreamMarshallerTests {
}
@Test
- public void marshalStreamResultWriter() throws Exception {
+ void marshalStreamResultWriter() throws Exception {
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult(writer);
marshaller.marshal(flight, result);
@@ -144,7 +139,7 @@ public class XStreamMarshallerTests {
}
@Test
- public void marshalStreamResultOutputStream() throws Exception {
+ void marshalStreamResultOutputStream() throws Exception {
ByteArrayOutputStream os = new ByteArrayOutputStream();
StreamResult result = new StreamResult(os);
marshaller.marshal(flight, result);
@@ -153,7 +148,7 @@ public class XStreamMarshallerTests {
}
@Test
- public void marshalSaxResult() throws Exception {
+ void marshalSaxResult() throws Exception {
ContentHandler contentHandler = mock(ContentHandler.class);
SAXResult result = new SAXResult(contentHandler);
marshaller.marshal(flight, result);
@@ -168,7 +163,7 @@ public class XStreamMarshallerTests {
}
@Test
- public void marshalStaxResultXMLStreamWriter() throws Exception {
+ void marshalStaxResultXMLStreamWriter() throws Exception {
XMLOutputFactory outputFactory = XMLOutputFactory.newInstance();
StringWriter writer = new StringWriter();
XMLStreamWriter streamWriter = outputFactory.createXMLStreamWriter(writer);
@@ -178,7 +173,7 @@ public class XStreamMarshallerTests {
}
@Test
- public void marshalStaxResultXMLEventWriter() throws Exception {
+ void marshalStaxResultXMLEventWriter() throws Exception {
XMLOutputFactory outputFactory = XMLOutputFactory.newInstance();
StringWriter writer = new StringWriter();
XMLEventWriter eventWriter = outputFactory.createXMLEventWriter(writer);
@@ -188,19 +183,29 @@ public class XStreamMarshallerTests {
}
@Test
- public void converters() throws Exception {
+ void converters() throws Exception {
marshaller.setConverters(new EncodedByteArrayConverter());
- byte[] buf = new byte[]{0x1, 0x2};
- Writer writer = new StringWriter();
- marshaller.marshal(buf, new StreamResult(writer));
- assertThat(XmlContent.from(writer)).isSimilarTo("AQI=");
- Reader reader = new StringReader(writer.toString());
- byte[] bufResult = (byte[]) marshaller.unmarshal(new StreamSource(reader));
- assertThat(Arrays.equals(buf, bufResult)).as("Invalid result").isTrue();
+ byte[] buf = {0x1, 0x2};
+
+ // Execute multiple times concurrently to ensure there are no concurrency issues.
+ // See https://github.com/spring-projects/spring-framework/issues/25017
+ IntStream.rangeClosed(1, 100).parallel().forEach(n -> {
+ try {
+ Writer writer = new StringWriter();
+ marshaller.marshal(buf, new StreamResult(writer));
+ assertThat(XmlContent.from(writer)).isSimilarTo("AQI=");
+ Reader reader = new StringReader(writer.toString());
+ byte[] bufResult = (byte[]) marshaller.unmarshal(new StreamSource(reader));
+ assertThat(bufResult).as("Invalid result").isEqualTo(buf);
+ }
+ catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ });
}
@Test
- public void useAttributesFor() throws Exception {
+ void useAttributesFor() throws Exception {
marshaller.setUseAttributeForTypes(Long.TYPE);
Writer writer = new StringWriter();
marshaller.marshal(flight, new StreamResult(writer));
@@ -209,7 +214,7 @@ public class XStreamMarshallerTests {
}
@Test
- public void useAttributesForStringClassMap() throws Exception {
+ void useAttributesForStringClassMap() throws Exception {
marshaller.setUseAttributeFor(Collections.singletonMap("flightNumber", Long.TYPE));
Writer writer = new StringWriter();
marshaller.marshal(flight, new StreamResult(writer));
@@ -218,7 +223,7 @@ public class XStreamMarshallerTests {
}
@Test
- public void useAttributesForClassStringMap() throws Exception {
+ void useAttributesForClassStringMap() throws Exception {
marshaller.setUseAttributeFor(Collections.singletonMap(Flight.class, "flightNumber"));
Writer writer = new StringWriter();
marshaller.marshal(flight, new StreamResult(writer));
@@ -227,7 +232,7 @@ public class XStreamMarshallerTests {
}
@Test
- public void useAttributesForClassStringListMap() throws Exception {
+ void useAttributesForClassStringListMap() throws Exception {
marshaller.setUseAttributeFor(Collections.singletonMap(Flight.class, Collections.singletonList("flightNumber")));
Writer writer = new StringWriter();
marshaller.marshal(flight, new StreamResult(writer));
@@ -236,7 +241,7 @@ public class XStreamMarshallerTests {
}
@Test
- public void aliasesByTypeStringClassMap() throws Exception {
+ void aliasesByTypeStringClassMap() throws Exception {
Map> aliases = new HashMap<>();
aliases.put("flight", Flight.class);
FlightSubclass flight = new FlightSubclass();
@@ -249,7 +254,7 @@ public class XStreamMarshallerTests {
}
@Test
- public void aliasesByTypeStringStringMap() throws Exception {
+ void aliasesByTypeStringStringMap() throws Exception {
Map aliases = new HashMap<>();
aliases.put("flight", Flight.class.getName());
FlightSubclass flight = new FlightSubclass();
@@ -262,7 +267,7 @@ public class XStreamMarshallerTests {
}
@Test
- public void fieldAliases() throws Exception {
+ void fieldAliases() throws Exception {
marshaller.setFieldAliases(Collections.singletonMap("org.springframework.oxm.xstream.Flight.flightNumber", "flightNo"));
Writer writer = new StringWriter();
marshaller.marshal(flight, new StreamResult(writer));
@@ -272,17 +277,17 @@ public class XStreamMarshallerTests {
@Test
@SuppressWarnings({ "rawtypes", "unchecked" })
- public void omitFields() throws Exception {
+ void omitFields() throws Exception {
Map omittedFieldsMap = Collections.singletonMap(Flight.class, "flightNumber");
marshaller.setOmittedFields(omittedFieldsMap);
Writer writer = new StringWriter();
marshaller.marshal(flight, new StreamResult(writer));
- assertXpathNotExists("/flight/flightNumber", writer.toString());
+ assertXpathDoesNotExist("/flight/flightNumber", writer.toString());
}
@Test
@SuppressWarnings({ "rawtypes", "unchecked" })
- public void implicitCollections() throws Exception {
+ void implicitCollections() throws Exception {
Flights flights = new Flights();
flights.getFlights().add(flight);
flights.getStrings().add("42");
@@ -298,14 +303,14 @@ public class XStreamMarshallerTests {
Writer writer = new StringWriter();
marshaller.marshal(flights, new StreamResult(writer));
String result = writer.toString();
- assertXpathNotExists("/flights/flights", result);
+ assertXpathDoesNotExist("/flights/flights", result);
assertXpathExists("/flights/flight", result);
- assertXpathNotExists("/flights/strings", result);
+ assertXpathDoesNotExist("/flights/strings", result);
assertXpathExists("/flights/string", result);
}
@Test
- public void jettisonDriver() throws Exception {
+ void jettisonDriver() throws Exception {
marshaller.setStreamDriver(new JettisonMappedXmlDriver());
Writer writer = new StringWriter();
marshaller.marshal(flight, new StreamResult(writer));
@@ -318,7 +323,7 @@ public class XStreamMarshallerTests {
}
@Test
- public void jsonDriver() throws Exception {
+ void jsonDriver() throws Exception {
marshaller.setStreamDriver(new JsonHierarchicalStreamDriver() {
@Override
public HierarchicalStreamWriter createWriter(Writer writer) {
@@ -334,7 +339,7 @@ public class XStreamMarshallerTests {
}
@Test
- public void annotatedMarshalStreamResultWriter() throws Exception {
+ void annotatedMarshalStreamResultWriter() throws Exception {
marshaller.setAnnotatedClasses(Flight.class);
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult(writer);
@@ -349,20 +354,13 @@ public class XStreamMarshallerTests {
private static void assertXpathExists(String xPathExpression, String inXMLString){
Source source = Input.fromString(inXMLString).build();
Iterable nodes = new JAXPXPathEngine().selectNodes(xPathExpression, source);
- assertThat(count(nodes) > 0).as("Expecting to find matches for Xpath " + xPathExpression).isTrue();
+ assertThat(nodes).as("Expecting to find matches for Xpath " + xPathExpression).hasSizeGreaterThan(0);
}
- private static void assertXpathNotExists(String xPathExpression, String inXMLString){
+ private static void assertXpathDoesNotExist(String xPathExpression, String inXMLString){
Source source = Input.fromString(inXMLString).build();
Iterable nodes = new JAXPXPathEngine().selectNodes(xPathExpression, source);
- assertThat(count(nodes)).as("Should be zero matches for Xpath " + xPathExpression).isEqualTo(0);
- }
-
- private static int count(Iterable nodes) {
- assertThat(nodes).isNotNull();
- AtomicInteger count = new AtomicInteger();
- nodes.forEach(n -> count.incrementAndGet());
- return count.get();
+ assertThat(nodes).as("Should be zero matches for Xpath " + xPathExpression).isEmpty();
}
}