75
75
/**
76
76
* Implementation of the {@code Marshaller} interface for XStream.
77
77
*
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.
80
87
*
81
88
* <p>Due to XStream's API, it is required to set the encoding used for writing to OutputStreams.
82
89
* It defaults to {@code UTF-8}.
83
90
*
84
91
* <p><b>NOTE:</b> XStream is an XML serialization library, not a data binding library.
85
92
* 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.
87
96
*
88
97
* @author Peter Meijer
89
98
* @author Arjen Poutsma
90
99
* @since 3.0
91
- * @see #setAliases
92
- * @see #setConverters
93
- * @see #setEncoding
94
100
*/
95
101
public class XStreamMarshaller extends AbstractMarshaller implements InitializingBean , BeanClassLoaderAware {
96
102
@@ -106,15 +112,19 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin
106
112
107
113
private String encoding = DEFAULT_ENCODING ;
108
114
109
- private Class [] supportedClasses ;
115
+ private Class <?> [] supportedClasses ;
110
116
111
- private ClassLoader classLoader ;
117
+ private ClassLoader beanClassLoader ;
112
118
113
119
114
120
/**
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.
116
126
*/
117
- public final XStream getXStream () {
127
+ public XStream getXStream () {
118
128
return this .xstream ;
119
129
}
120
130
@@ -125,7 +135,7 @@ public final XStream getXStream() {
125
135
* @see XStream#NO_REFERENCES
126
136
*/
127
137
public void setMode (int mode ) {
128
- this . xstream .setMode (mode );
138
+ getXStream () .setMode (mode );
129
139
}
130
140
131
141
/**
@@ -137,10 +147,10 @@ public void setMode(int mode) {
137
147
public void setConverters (ConverterMatcher [] converters ) {
138
148
for (int i = 0 ; i < converters .length ; i ++) {
139
149
if (converters [i ] instanceof Converter ) {
140
- this . xstream .registerConverter ((Converter ) converters [i ], i );
150
+ getXStream () .registerConverter ((Converter ) converters [i ], i );
141
151
}
142
152
else if (converters [i ] instanceof SingleValueConverter ) {
143
- this . xstream .registerConverter ((SingleValueConverter ) converters [i ], i );
153
+ getXStream () .registerConverter ((SingleValueConverter ) converters [i ], i );
144
154
}
145
155
else {
146
156
throw new IllegalArgumentException ("Invalid ConverterMatcher [" + converters [i ] + "]" );
@@ -156,7 +166,7 @@ else if (converters[i] instanceof SingleValueConverter) {
156
166
public void setAliases (Map <String , ?> aliases ) throws ClassNotFoundException {
157
167
Map <String , Class <?>> classMap = toClassMap (aliases );
158
168
for (Map .Entry <String , Class <?>> entry : classMap .entrySet ()) {
159
- this . xstream .alias (entry .getKey (), entry .getValue ());
169
+ getXStream () .alias (entry .getKey (), entry .getValue ());
160
170
}
161
171
}
162
172
@@ -169,7 +179,7 @@ public void setAliases(Map<String, ?> aliases) throws ClassNotFoundException {
169
179
public void setAliasesByType (Map <String , ?> aliases ) throws ClassNotFoundException {
170
180
Map <String , Class <?>> classMap = toClassMap (aliases );
171
181
for (Map .Entry <String , Class <?>> entry : classMap .entrySet ()) {
172
- this . xstream .aliasType (entry .getKey (), entry .getValue ());
182
+ getXStream () .aliasType (entry .getKey (), entry .getValue ());
173
183
}
174
184
}
175
185
@@ -178,16 +188,16 @@ private Map<String, Class<?>> toClassMap(Map<String, ?> map) throws ClassNotFoun
178
188
for (Map .Entry <String , ?> entry : map .entrySet ()) {
179
189
String key = entry .getKey ();
180
190
Object value = entry .getValue ();
181
- Class type ;
191
+ Class <?> type ;
182
192
if (value instanceof Class ) {
183
- type = (Class ) value ;
193
+ type = (Class <?> ) value ;
184
194
}
185
195
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 );
188
198
}
189
199
else {
190
- throw new IllegalArgumentException ("Unknown value [" + value + "], expected String or Class" );
200
+ throw new IllegalArgumentException ("Unknown value [" + value + "] - expected String or Class" );
191
201
}
192
202
result .put (key , type );
193
203
}
@@ -205,9 +215,9 @@ public void setFieldAliases(Map<String, String> aliases) throws ClassNotFoundExc
205
215
int idx = field .lastIndexOf ('.' );
206
216
if (idx != -1 ) {
207
217
String className = field .substring (0 , idx );
208
- Class clazz = ClassUtils .forName (className , classLoader );
218
+ Class <?> clazz = ClassUtils .forName (className , this . beanClassLoader );
209
219
String fieldName = field .substring (idx + 1 );
210
- this . xstream .aliasField (alias , clazz , fieldName );
220
+ getXStream () .aliasField (alias , clazz , fieldName );
211
221
}
212
222
else {
213
223
throw new IllegalArgumentException ("Field name [" + field + "] does not contain '.'" );
@@ -219,9 +229,9 @@ public void setFieldAliases(Map<String, String> aliases) throws ClassNotFoundExc
219
229
* Set types to use XML attributes for.
220
230
* @see XStream#useAttributeFor(Class)
221
231
*/
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 );
225
235
}
226
236
}
227
237
@@ -237,36 +247,33 @@ public void setUseAttributeFor(Map<?, ?> attributes) {
237
247
for (Map .Entry <?, ?> entry : attributes .entrySet ()) {
238
248
if (entry .getKey () instanceof String ) {
239
249
if (entry .getValue () instanceof Class ) {
240
- this . xstream .useAttributeFor ((String ) entry .getKey (), (Class ) entry .getValue ());
250
+ getXStream () .useAttributeFor ((String ) entry .getKey (), (Class ) entry .getValue ());
241
251
}
242
252
else {
243
253
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" );
246
255
}
247
256
}
248
257
else if (entry .getKey () instanceof Class ) {
249
258
Class <?> key = (Class <?>) entry .getKey ();
250
259
if (entry .getValue () instanceof String ) {
251
- this . xstream .useAttributeFor (key , (String ) entry .getValue ());
260
+ getXStream () .useAttributeFor (key , (String ) entry .getValue ());
252
261
}
253
262
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 );
259
267
}
260
268
}
261
269
}
262
270
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" );
266
273
}
267
274
}
268
275
else {
269
- throw new IllegalArgumentException ("Invalid argument 'attributes. " +
276
+ throw new IllegalArgumentException (
270
277
"'useAttributesFor' property takes either a map key of type String or Class" );
271
278
}
272
279
}
@@ -281,7 +288,7 @@ public void setImplicitCollections(Map<Class<?>, String> implicitCollections) {
281
288
for (Map .Entry <Class <?>, String > entry : implicitCollections .entrySet ()) {
282
289
String [] collectionFields = StringUtils .commaDelimitedListToStringArray (entry .getValue ());
283
290
for (String collectionField : collectionFields ) {
284
- this . xstream .addImplicitCollection (entry .getKey (), collectionField );
291
+ getXStream () .addImplicitCollection (entry .getKey (), collectionField );
285
292
}
286
293
}
287
294
}
@@ -295,7 +302,7 @@ public void setOmittedFields(Map<Class<?>, String> omittedFields) {
295
302
for (Map .Entry <Class <?>, String > entry : omittedFields .entrySet ()) {
296
303
String [] fields = StringUtils .commaDelimitedListToStringArray (entry .getValue ());
297
304
for (String field : fields ) {
298
- this . xstream .omitField (entry .getKey (), field );
305
+ getXStream () .omitField (entry .getKey (), field );
299
306
}
300
307
}
301
308
}
@@ -306,7 +313,7 @@ public void setOmittedFields(Map<Class<?>, String> omittedFields) {
306
313
*/
307
314
public void setAnnotatedClass (Class <?> annotatedClass ) {
308
315
Assert .notNull (annotatedClass , "'annotatedClass' must not be null" );
309
- this . xstream .processAnnotations (annotatedClass );
316
+ getXStream () .processAnnotations (annotatedClass );
310
317
}
311
318
312
319
/**
@@ -315,17 +322,17 @@ public void setAnnotatedClass(Class<?> annotatedClass) {
315
322
*/
316
323
public void setAnnotatedClasses (Class <?>[] annotatedClasses ) {
317
324
Assert .notEmpty (annotatedClasses , "'annotatedClasses' must not be empty" );
318
- this . xstream .processAnnotations (annotatedClasses );
325
+ getXStream () .processAnnotations (annotatedClasses );
319
326
}
320
327
321
328
/**
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
324
331
* it is processing the XML streams, and thus introduces a potential concurrency problem.
325
332
* @see XStream#autodetectAnnotations(boolean)
326
333
*/
327
334
public void setAutodetectAnnotations (boolean autodetectAnnotations ) {
328
- this . xstream .autodetectAnnotations (autodetectAnnotations );
335
+ getXStream () .autodetectAnnotations (autodetectAnnotations );
329
336
}
330
337
331
338
/**
@@ -348,17 +355,18 @@ public void setEncoding(String encoding) {
348
355
* <p>If this property is empty (the default), all classes are supported.
349
356
* @see #supports(Class)
350
357
*/
351
- public void setSupportedClasses (Class [] supportedClasses ) {
358
+ public void setSupportedClasses (Class <?> [] supportedClasses ) {
352
359
this .supportedClasses = supportedClasses ;
353
360
}
354
361
355
362
public void setBeanClassLoader (ClassLoader classLoader ) {
356
- this .classLoader = classLoader ;
363
+ this .beanClassLoader = classLoader ;
364
+ getXStream ().setClassLoader (classLoader );
357
365
}
358
366
359
367
360
368
public final void afterPropertiesSet () throws Exception {
361
- customizeXStream (this . xstream );
369
+ customizeXStream (getXStream () );
362
370
}
363
371
364
372
/**
@@ -370,12 +378,12 @@ protected void customizeXStream(XStream xstream) {
370
378
}
371
379
372
380
373
- public boolean supports (Class clazz ) {
381
+ public boolean supports (Class <?> clazz ) {
374
382
if (ObjectUtils .isEmpty (this .supportedClasses )) {
375
383
return true ;
376
384
}
377
385
else {
378
- for (Class supportedClass : this .supportedClasses ) {
386
+ for (Class <?> supportedClass : this .supportedClasses ) {
379
387
if (supportedClass .isAssignableFrom (clazz )) {
380
388
return true ;
381
389
}
@@ -453,7 +461,7 @@ protected void marshalWriter(Object graph, Writer writer) throws XmlMappingExcep
453
461
*/
454
462
private void marshal (Object graph , HierarchicalStreamWriter streamWriter ) {
455
463
try {
456
- this . xstream .marshal (graph , streamWriter );
464
+ getXStream () .marshal (graph , streamWriter );
457
465
}
458
466
catch (Exception ex ) {
459
467
throw convertXStreamException (ex , true );
@@ -536,7 +544,7 @@ protected Object unmarshalSaxReader(XMLReader xmlReader, InputSource inputSource
536
544
*/
537
545
private Object unmarshal (HierarchicalStreamReader streamReader ) {
538
546
try {
539
- return this . xstream .unmarshal (streamReader );
547
+ return getXStream () .unmarshal (streamReader );
540
548
}
541
549
catch (Exception ex ) {
542
550
throw convertXStreamException (ex , false );
0 commit comments