7575/**
7676 * Implementation of the {@code Marshaller} interface for XStream.
7777 *
78- * <p>By default, XStream does not require any further configuration,
79- * though class aliases can be used to have more control over the behavior of XStream.
78+ * <p>By default, XStream does not require any further configuration and can (un)marshal
79+ * any class on the classpath. As such, it is <b>not recommended to use the
80+ * {@code XStreamMarshaller} to unmarshal XML from external sources</b> (i.e. the Web),
81+ * as this can result in <b>security vulnerabilities</b>. If you do use the
82+ * {@code XStreamMarshaller} to unmarshal external XML, set the
83+ * {@link #setConverters(ConverterMatcher[]) converters} and
84+ * {@link #setSupportedClasses(Class[]) supportedClasses} properties or override the
85+ * {@link #customizeXStream(XStream)} method to make sure it only accepts the classes
86+ * you want it to support.
8087 *
8188 * <p>Due to XStream's API, it is required to set the encoding used for writing to OutputStreams.
8289 * It defaults to {@code UTF-8}.
8390 *
8491 * <p><b>NOTE:</b> XStream is an XML serialization library, not a data binding library.
8592 * Therefore, it has limited namespace support. As such, it is rather unsuitable for
86- * usage within Web services.
93+ * usage within Web Services.
94+ *
95+ * <p>This marshaller is compatible with XStream 1.3 and 1.4.
8796 *
8897 * @author Peter Meijer
8998 * @author Arjen Poutsma
9099 * @since 3.0
91- * @see #setAliases
92- * @see #setConverters
93- * @see #setEncoding
94100 */
95101public class XStreamMarshaller extends AbstractMarshaller implements InitializingBean , BeanClassLoaderAware {
96102
@@ -106,15 +112,19 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin
106112
107113 private String encoding = DEFAULT_ENCODING ;
108114
109- private Class [] supportedClasses ;
115+ private Class <?> [] supportedClasses ;
110116
111- private ClassLoader classLoader ;
117+ private ClassLoader beanClassLoader ;
112118
113119
114120 /**
115- * Returns the XStream instance used by this marshaller.
121+ * Return the XStream instance used by this marshaller.
122+ * <p><b>NOTE:</b> While this method can be overridden in Spring 3.x,
123+ * it wasn't originally meant to be. As of Spring 4.0, it will be
124+ * marked as final, with all of XStream 1.4's configurable strategies
125+ * to be exposed on XStreamMarshaller itself.
116126 */
117- public final XStream getXStream () {
127+ public XStream getXStream () {
118128 return this .xstream ;
119129 }
120130
@@ -125,7 +135,7 @@ public final XStream getXStream() {
125135 * @see XStream#NO_REFERENCES
126136 */
127137 public void setMode (int mode ) {
128- this . xstream .setMode (mode );
138+ getXStream () .setMode (mode );
129139 }
130140
131141 /**
@@ -137,10 +147,10 @@ public void setMode(int mode) {
137147 public void setConverters (ConverterMatcher [] converters ) {
138148 for (int i = 0 ; i < converters .length ; i ++) {
139149 if (converters [i ] instanceof Converter ) {
140- this . xstream .registerConverter ((Converter ) converters [i ], i );
150+ getXStream () .registerConverter ((Converter ) converters [i ], i );
141151 }
142152 else if (converters [i ] instanceof SingleValueConverter ) {
143- this . xstream .registerConverter ((SingleValueConverter ) converters [i ], i );
153+ getXStream () .registerConverter ((SingleValueConverter ) converters [i ], i );
144154 }
145155 else {
146156 throw new IllegalArgumentException ("Invalid ConverterMatcher [" + converters [i ] + "]" );
@@ -156,7 +166,7 @@ else if (converters[i] instanceof SingleValueConverter) {
156166 public void setAliases (Map <String , ?> aliases ) throws ClassNotFoundException {
157167 Map <String , Class <?>> classMap = toClassMap (aliases );
158168 for (Map .Entry <String , Class <?>> entry : classMap .entrySet ()) {
159- this . xstream .alias (entry .getKey (), entry .getValue ());
169+ getXStream () .alias (entry .getKey (), entry .getValue ());
160170 }
161171 }
162172
@@ -169,7 +179,7 @@ public void setAliases(Map<String, ?> aliases) throws ClassNotFoundException {
169179 public void setAliasesByType (Map <String , ?> aliases ) throws ClassNotFoundException {
170180 Map <String , Class <?>> classMap = toClassMap (aliases );
171181 for (Map .Entry <String , Class <?>> entry : classMap .entrySet ()) {
172- this . xstream .aliasType (entry .getKey (), entry .getValue ());
182+ getXStream () .aliasType (entry .getKey (), entry .getValue ());
173183 }
174184 }
175185
@@ -178,16 +188,16 @@ private Map<String, Class<?>> toClassMap(Map<String, ?> map) throws ClassNotFoun
178188 for (Map .Entry <String , ?> entry : map .entrySet ()) {
179189 String key = entry .getKey ();
180190 Object value = entry .getValue ();
181- Class type ;
191+ Class <?> type ;
182192 if (value instanceof Class ) {
183- type = (Class ) value ;
193+ type = (Class <?> ) value ;
184194 }
185195 else if (value instanceof String ) {
186- String s = (String ) value ;
187- type = ClassUtils .forName (s , classLoader );
196+ String className = (String ) value ;
197+ type = ClassUtils .forName (className , this . beanClassLoader );
188198 }
189199 else {
190- throw new IllegalArgumentException ("Unknown value [" + value + "], expected String or Class" );
200+ throw new IllegalArgumentException ("Unknown value [" + value + "] - expected String or Class" );
191201 }
192202 result .put (key , type );
193203 }
@@ -205,9 +215,9 @@ public void setFieldAliases(Map<String, String> aliases) throws ClassNotFoundExc
205215 int idx = field .lastIndexOf ('.' );
206216 if (idx != -1 ) {
207217 String className = field .substring (0 , idx );
208- Class clazz = ClassUtils .forName (className , classLoader );
218+ Class <?> clazz = ClassUtils .forName (className , this . beanClassLoader );
209219 String fieldName = field .substring (idx + 1 );
210- this . xstream .aliasField (alias , clazz , fieldName );
220+ getXStream () .aliasField (alias , clazz , fieldName );
211221 }
212222 else {
213223 throw new IllegalArgumentException ("Field name [" + field + "] does not contain '.'" );
@@ -219,9 +229,9 @@ public void setFieldAliases(Map<String, String> aliases) throws ClassNotFoundExc
219229 * Set types to use XML attributes for.
220230 * @see XStream#useAttributeFor(Class)
221231 */
222- public void setUseAttributeForTypes (Class [] types ) {
223- for (Class type : types ) {
224- this . xstream .useAttributeFor (type );
232+ public void setUseAttributeForTypes (Class <?> [] types ) {
233+ for (Class <?> type : types ) {
234+ getXStream () .useAttributeFor (type );
225235 }
226236 }
227237
@@ -237,36 +247,33 @@ public void setUseAttributeFor(Map<?, ?> attributes) {
237247 for (Map .Entry <?, ?> entry : attributes .entrySet ()) {
238248 if (entry .getKey () instanceof String ) {
239249 if (entry .getValue () instanceof Class ) {
240- this . xstream .useAttributeFor ((String ) entry .getKey (), (Class ) entry .getValue ());
250+ getXStream () .useAttributeFor ((String ) entry .getKey (), (Class ) entry .getValue ());
241251 }
242252 else {
243253 throw new IllegalArgumentException (
244- "Invalid argument 'attributes'. 'useAttributesFor' property takes map of <String, Class>," +
245- " when using a map key of type String" );
254+ "'useAttributesFor' takes Map<String, Class> when using a map key of type String" );
246255 }
247256 }
248257 else if (entry .getKey () instanceof Class ) {
249258 Class <?> key = (Class <?>) entry .getKey ();
250259 if (entry .getValue () instanceof String ) {
251- this . xstream .useAttributeFor (key , (String ) entry .getValue ());
260+ getXStream () .useAttributeFor (key , (String ) entry .getValue ());
252261 }
253262 else if (entry .getValue () instanceof List ) {
254- List list = (List ) entry .getValue ();
255-
256- for (Object o : list ) {
257- if (o instanceof String ) {
258- this .xstream .useAttributeFor (key , (String ) o );
263+ List listValue = (List ) entry .getValue ();
264+ for (Object element : listValue ) {
265+ if (element instanceof String ) {
266+ getXStream ().useAttributeFor (key , (String ) element );
259267 }
260268 }
261269 }
262270 else {
263- throw new IllegalArgumentException ("Invalid argument 'attributes'. " +
264- "'useAttributesFor' property takes either <Class, String> or <Class, List<String>> map," +
265- " when using a map key of type Class" );
271+ throw new IllegalArgumentException ("'useAttributesFor' property takes either Map<Class, String> " +
272+ "or Map<Class, List<String>> when using a map key of type Class" );
266273 }
267274 }
268275 else {
269- throw new IllegalArgumentException ("Invalid argument 'attributes. " +
276+ throw new IllegalArgumentException (
270277 "'useAttributesFor' property takes either a map key of type String or Class" );
271278 }
272279 }
@@ -281,7 +288,7 @@ public void setImplicitCollections(Map<Class<?>, String> implicitCollections) {
281288 for (Map .Entry <Class <?>, String > entry : implicitCollections .entrySet ()) {
282289 String [] collectionFields = StringUtils .commaDelimitedListToStringArray (entry .getValue ());
283290 for (String collectionField : collectionFields ) {
284- this . xstream .addImplicitCollection (entry .getKey (), collectionField );
291+ getXStream () .addImplicitCollection (entry .getKey (), collectionField );
285292 }
286293 }
287294 }
@@ -295,7 +302,7 @@ public void setOmittedFields(Map<Class<?>, String> omittedFields) {
295302 for (Map .Entry <Class <?>, String > entry : omittedFields .entrySet ()) {
296303 String [] fields = StringUtils .commaDelimitedListToStringArray (entry .getValue ());
297304 for (String field : fields ) {
298- this . xstream .omitField (entry .getKey (), field );
305+ getXStream () .omitField (entry .getKey (), field );
299306 }
300307 }
301308 }
@@ -306,7 +313,7 @@ public void setOmittedFields(Map<Class<?>, String> omittedFields) {
306313 */
307314 public void setAnnotatedClass (Class <?> annotatedClass ) {
308315 Assert .notNull (annotatedClass , "'annotatedClass' must not be null" );
309- this . xstream .processAnnotations (annotatedClass );
316+ getXStream () .processAnnotations (annotatedClass );
310317 }
311318
312319 /**
@@ -315,17 +322,17 @@ public void setAnnotatedClass(Class<?> annotatedClass) {
315322 */
316323 public void setAnnotatedClasses (Class <?>[] annotatedClasses ) {
317324 Assert .notEmpty (annotatedClasses , "'annotatedClasses' must not be empty" );
318- this . xstream .processAnnotations (annotatedClasses );
325+ getXStream () .processAnnotations (annotatedClasses );
319326 }
320327
321328 /**
322- * Set the autodetection mode of XStream .
323- * <p><strong >Note</strong> that auto-detection implies that the XStream is configured while
329+ * Activate XStream's autodetection mode.
330+ * <p><b >Note</b>: Autodetection implies that the XStream instance is being configured while
324331 * it is processing the XML streams, and thus introduces a potential concurrency problem.
325332 * @see XStream#autodetectAnnotations(boolean)
326333 */
327334 public void setAutodetectAnnotations (boolean autodetectAnnotations ) {
328- this . xstream .autodetectAnnotations (autodetectAnnotations );
335+ getXStream () .autodetectAnnotations (autodetectAnnotations );
329336 }
330337
331338 /**
@@ -348,17 +355,18 @@ public void setEncoding(String encoding) {
348355 * <p>If this property is empty (the default), all classes are supported.
349356 * @see #supports(Class)
350357 */
351- public void setSupportedClasses (Class [] supportedClasses ) {
358+ public void setSupportedClasses (Class <?> [] supportedClasses ) {
352359 this .supportedClasses = supportedClasses ;
353360 }
354361
355362 public void setBeanClassLoader (ClassLoader classLoader ) {
356- this .classLoader = classLoader ;
363+ this .beanClassLoader = classLoader ;
364+ getXStream ().setClassLoader (classLoader );
357365 }
358366
359367
360368 public final void afterPropertiesSet () throws Exception {
361- customizeXStream (this . xstream );
369+ customizeXStream (getXStream () );
362370 }
363371
364372 /**
@@ -370,12 +378,12 @@ protected void customizeXStream(XStream xstream) {
370378 }
371379
372380
373- public boolean supports (Class clazz ) {
381+ public boolean supports (Class <?> clazz ) {
374382 if (ObjectUtils .isEmpty (this .supportedClasses )) {
375383 return true ;
376384 }
377385 else {
378- for (Class supportedClass : this .supportedClasses ) {
386+ for (Class <?> supportedClass : this .supportedClasses ) {
379387 if (supportedClass .isAssignableFrom (clazz )) {
380388 return true ;
381389 }
@@ -453,7 +461,7 @@ protected void marshalWriter(Object graph, Writer writer) throws XmlMappingExcep
453461 */
454462 private void marshal (Object graph , HierarchicalStreamWriter streamWriter ) {
455463 try {
456- this . xstream .marshal (graph , streamWriter );
464+ getXStream () .marshal (graph , streamWriter );
457465 }
458466 catch (Exception ex ) {
459467 throw convertXStreamException (ex , true );
@@ -536,7 +544,7 @@ protected Object unmarshalSaxReader(XMLReader xmlReader, InputSource inputSource
536544 */
537545 private Object unmarshal (HierarchicalStreamReader streamReader ) {
538546 try {
539- return this . xstream .unmarshal (streamReader );
547+ return getXStream () .unmarshal (streamReader );
540548 }
541549 catch (Exception ex ) {
542550 throw convertXStreamException (ex , false );
0 commit comments