Restored overridable getXStream() method in Spring Framework 3.2.x

Also setting the bean class loader on the XStream instance itself now.

Issue: SPR-10421
This commit is contained in:
Juergen Hoeller 2013-08-05 23:05:15 +02:00
parent 20ddd3254b
commit cd523c7051
1 changed files with 60 additions and 52 deletions

View File

@ -75,22 +75,28 @@ import org.springframework.util.xml.StaxUtils;
/** /**
* Implementation of the {@code Marshaller} interface for XStream. * Implementation of the {@code Marshaller} interface for XStream.
* *
* <p>By default, XStream does not require any further configuration, * <p>By default, XStream does not require any further configuration and can (un)marshal
* though class aliases can be used to have more control over the behavior of XStream. * 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 #setConverters(ConverterMatcher[]) converters} and
* {@link #setSupportedClasses(Class[]) supportedClasses} properties 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. * <p>Due to XStream's API, it is required to set the encoding used for writing to OutputStreams.
* It defaults to {@code UTF-8}. * It defaults to {@code UTF-8}.
* *
* <p><b>NOTE:</b> XStream is an XML serialization library, not a data binding library. * <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 * Therefore, it has limited namespace support. As such, it is rather unsuitable for
* usage within Web services. * usage within Web Services.
*
* <p>This marshaller is compatible with XStream 1.3 and 1.4.
* *
* @author Peter Meijer * @author Peter Meijer
* @author Arjen Poutsma * @author Arjen Poutsma
* @since 3.0 * @since 3.0
* @see #setAliases
* @see #setConverters
* @see #setEncoding
*/ */
public class XStreamMarshaller extends AbstractMarshaller implements InitializingBean, BeanClassLoaderAware { public class XStreamMarshaller extends AbstractMarshaller implements InitializingBean, BeanClassLoaderAware {
@ -106,15 +112,19 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin
private String encoding = DEFAULT_ENCODING; private String encoding = DEFAULT_ENCODING;
private Class[] supportedClasses; private Class<?>[] supportedClasses;
private ClassLoader classLoader; private ClassLoader beanClassLoader;
/** /**
* Returns the XStream instance used by this marshaller. * Return the XStream instance used by this marshaller.
* <p><b>NOTE:</b> While this method can be overridden in Spring 3.x,
* it wasn't originally meant to be. As of Spring 4.0, it will be
* marked as final, with all of XStream 1.4's configurable strategies
* to be exposed on XStreamMarshaller itself.
*/ */
public final XStream getXStream() { public XStream getXStream() {
return this.xstream; return this.xstream;
} }
@ -125,7 +135,7 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin
* @see XStream#NO_REFERENCES * @see XStream#NO_REFERENCES
*/ */
public void setMode(int mode) { public void setMode(int mode) {
this.xstream.setMode(mode); getXStream().setMode(mode);
} }
/** /**
@ -137,10 +147,10 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin
public void setConverters(ConverterMatcher[] converters) { public void setConverters(ConverterMatcher[] converters) {
for (int i = 0; i < converters.length; i++) { for (int i = 0; i < converters.length; i++) {
if (converters[i] instanceof Converter) { if (converters[i] instanceof Converter) {
this.xstream.registerConverter((Converter) converters[i], i); getXStream().registerConverter((Converter) converters[i], i);
} }
else if (converters[i] instanceof SingleValueConverter) { else if (converters[i] instanceof SingleValueConverter) {
this.xstream.registerConverter((SingleValueConverter) converters[i], i); getXStream().registerConverter((SingleValueConverter) converters[i], i);
} }
else { else {
throw new IllegalArgumentException("Invalid ConverterMatcher [" + converters[i] + "]"); throw new IllegalArgumentException("Invalid ConverterMatcher [" + converters[i] + "]");
@ -156,7 +166,7 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin
public void setAliases(Map<String, ?> aliases) throws ClassNotFoundException { public void setAliases(Map<String, ?> aliases) throws ClassNotFoundException {
Map<String, Class<?>> classMap = toClassMap(aliases); Map<String, Class<?>> classMap = toClassMap(aliases);
for (Map.Entry<String, Class<?>> entry : classMap.entrySet()) { for (Map.Entry<String, Class<?>> entry : classMap.entrySet()) {
this.xstream.alias(entry.getKey(), entry.getValue()); getXStream().alias(entry.getKey(), entry.getValue());
} }
} }
@ -169,7 +179,7 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin
public void setAliasesByType(Map<String, ?> aliases) throws ClassNotFoundException { public void setAliasesByType(Map<String, ?> aliases) throws ClassNotFoundException {
Map<String, Class<?>> classMap = toClassMap(aliases); Map<String, Class<?>> classMap = toClassMap(aliases);
for (Map.Entry<String, Class<?>> entry : classMap.entrySet()) { for (Map.Entry<String, Class<?>> entry : classMap.entrySet()) {
this.xstream.aliasType(entry.getKey(), entry.getValue()); getXStream().aliasType(entry.getKey(), entry.getValue());
} }
} }
@ -178,16 +188,16 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin
for (Map.Entry<String, ?> entry : map.entrySet()) { for (Map.Entry<String, ?> entry : map.entrySet()) {
String key = entry.getKey(); String key = entry.getKey();
Object value = entry.getValue(); Object value = entry.getValue();
Class type; Class<?> type;
if (value instanceof Class) { if (value instanceof Class) {
type = (Class) value; type = (Class<?>) value;
} }
else if (value instanceof String) { else if (value instanceof String) {
String s = (String) value; String className = (String) value;
type = ClassUtils.forName(s, classLoader); type = ClassUtils.forName(className, this.beanClassLoader);
} }
else { else {
throw new IllegalArgumentException("Unknown value [" + value + "], expected String or Class"); throw new IllegalArgumentException("Unknown value [" + value + "] - expected String or Class");
} }
result.put(key, type); result.put(key, type);
} }
@ -205,9 +215,9 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin
int idx = field.lastIndexOf('.'); int idx = field.lastIndexOf('.');
if (idx != -1) { if (idx != -1) {
String className = field.substring(0, idx); String className = field.substring(0, idx);
Class clazz = ClassUtils.forName(className, classLoader); Class<?> clazz = ClassUtils.forName(className, this.beanClassLoader);
String fieldName = field.substring(idx + 1); String fieldName = field.substring(idx + 1);
this.xstream.aliasField(alias, clazz, fieldName); getXStream().aliasField(alias, clazz, fieldName);
} }
else { else {
throw new IllegalArgumentException("Field name [" + field + "] does not contain '.'"); throw new IllegalArgumentException("Field name [" + field + "] does not contain '.'");
@ -219,9 +229,9 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin
* Set types to use XML attributes for. * Set types to use XML attributes for.
* @see XStream#useAttributeFor(Class) * @see XStream#useAttributeFor(Class)
*/ */
public void setUseAttributeForTypes(Class[] types) { public void setUseAttributeForTypes(Class<?>[] types) {
for (Class type : types) { for (Class<?> type : types) {
this.xstream.useAttributeFor(type); getXStream().useAttributeFor(type);
} }
} }
@ -237,36 +247,33 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin
for (Map.Entry<?, ?> entry : attributes.entrySet()) { for (Map.Entry<?, ?> entry : attributes.entrySet()) {
if (entry.getKey() instanceof String) { if (entry.getKey() instanceof String) {
if (entry.getValue() instanceof Class) { if (entry.getValue() instanceof Class) {
this.xstream.useAttributeFor((String) entry.getKey(), (Class) entry.getValue()); getXStream().useAttributeFor((String) entry.getKey(), (Class) entry.getValue());
} }
else { else {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"Invalid argument 'attributes'. 'useAttributesFor' property takes map of <String, Class>," + "'useAttributesFor' takes Map<String, Class> when using a map key of type String");
" when using a map key of type String");
} }
} }
else if (entry.getKey() instanceof Class) { else if (entry.getKey() instanceof Class) {
Class<?> key = (Class<?>) entry.getKey(); Class<?> key = (Class<?>) entry.getKey();
if (entry.getValue() instanceof String) { if (entry.getValue() instanceof String) {
this.xstream.useAttributeFor(key, (String) entry.getValue()); getXStream().useAttributeFor(key, (String) entry.getValue());
} }
else if (entry.getValue() instanceof List) { else if (entry.getValue() instanceof List) {
List list = (List) entry.getValue(); List listValue = (List) entry.getValue();
for (Object element : listValue) {
for (Object o : list) { if (element instanceof String) {
if (o instanceof String) { getXStream().useAttributeFor(key, (String) element);
this.xstream.useAttributeFor(key, (String) o);
} }
} }
} }
else { else {
throw new IllegalArgumentException("Invalid argument 'attributes'. " + throw new IllegalArgumentException("'useAttributesFor' property takes either Map<Class, String> " +
"'useAttributesFor' property takes either <Class, String> or <Class, List<String>> map," + "or Map<Class, List<String>> when using a map key of type Class");
" when using a map key of type Class");
} }
} }
else { else {
throw new IllegalArgumentException("Invalid argument 'attributes. " + throw new IllegalArgumentException(
"'useAttributesFor' property takes either a map key of type String or Class"); "'useAttributesFor' property takes either a map key of type String or Class");
} }
} }
@ -281,7 +288,7 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin
for (Map.Entry<Class<?>, String> entry : implicitCollections.entrySet()) { for (Map.Entry<Class<?>, String> entry : implicitCollections.entrySet()) {
String[] collectionFields = StringUtils.commaDelimitedListToStringArray(entry.getValue()); String[] collectionFields = StringUtils.commaDelimitedListToStringArray(entry.getValue());
for (String collectionField : collectionFields) { for (String collectionField : collectionFields) {
this.xstream.addImplicitCollection(entry.getKey(), collectionField); getXStream().addImplicitCollection(entry.getKey(), collectionField);
} }
} }
} }
@ -295,7 +302,7 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin
for (Map.Entry<Class<?>, String> entry : omittedFields.entrySet()) { for (Map.Entry<Class<?>, String> entry : omittedFields.entrySet()) {
String[] fields = StringUtils.commaDelimitedListToStringArray(entry.getValue()); String[] fields = StringUtils.commaDelimitedListToStringArray(entry.getValue());
for (String field : fields) { for (String field : fields) {
this.xstream.omitField(entry.getKey(), field); getXStream().omitField(entry.getKey(), field);
} }
} }
} }
@ -306,7 +313,7 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin
*/ */
public void setAnnotatedClass(Class<?> annotatedClass) { public void setAnnotatedClass(Class<?> annotatedClass) {
Assert.notNull(annotatedClass, "'annotatedClass' must not be null"); Assert.notNull(annotatedClass, "'annotatedClass' must not be null");
this.xstream.processAnnotations(annotatedClass); getXStream().processAnnotations(annotatedClass);
} }
/** /**
@ -315,17 +322,17 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin
*/ */
public void setAnnotatedClasses(Class<?>[] annotatedClasses) { public void setAnnotatedClasses(Class<?>[] annotatedClasses) {
Assert.notEmpty(annotatedClasses, "'annotatedClasses' must not be empty"); Assert.notEmpty(annotatedClasses, "'annotatedClasses' must not be empty");
this.xstream.processAnnotations(annotatedClasses); getXStream().processAnnotations(annotatedClasses);
} }
/** /**
* Set the autodetection mode of XStream. * Activate XStream's autodetection mode.
* <p><strong>Note</strong> that auto-detection implies that the XStream is configured while * <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. * it is processing the XML streams, and thus introduces a potential concurrency problem.
* @see XStream#autodetectAnnotations(boolean) * @see XStream#autodetectAnnotations(boolean)
*/ */
public void setAutodetectAnnotations(boolean autodetectAnnotations) { public void setAutodetectAnnotations(boolean autodetectAnnotations) {
this.xstream.autodetectAnnotations(autodetectAnnotations); getXStream().autodetectAnnotations(autodetectAnnotations);
} }
/** /**
@ -348,17 +355,18 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin
* <p>If this property is empty (the default), all classes are supported. * <p>If this property is empty (the default), all classes are supported.
* @see #supports(Class) * @see #supports(Class)
*/ */
public void setSupportedClasses(Class[] supportedClasses) { public void setSupportedClasses(Class<?>[] supportedClasses) {
this.supportedClasses = supportedClasses; this.supportedClasses = supportedClasses;
} }
public void setBeanClassLoader(ClassLoader classLoader) { public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader; this.beanClassLoader = classLoader;
getXStream().setClassLoader(classLoader);
} }
public final void afterPropertiesSet() throws Exception { public final void afterPropertiesSet() throws Exception {
customizeXStream(this.xstream); customizeXStream(getXStream());
} }
/** /**
@ -370,12 +378,12 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin
} }
public boolean supports(Class clazz) { public boolean supports(Class<?> clazz) {
if (ObjectUtils.isEmpty(this.supportedClasses)) { if (ObjectUtils.isEmpty(this.supportedClasses)) {
return true; return true;
} }
else { else {
for (Class supportedClass : this.supportedClasses) { for (Class<?> supportedClass : this.supportedClasses) {
if (supportedClass.isAssignableFrom(clazz)) { if (supportedClass.isAssignableFrom(clazz)) {
return true; return true;
} }
@ -453,7 +461,7 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin
*/ */
private void marshal(Object graph, HierarchicalStreamWriter streamWriter) { private void marshal(Object graph, HierarchicalStreamWriter streamWriter) {
try { try {
this.xstream.marshal(graph, streamWriter); getXStream().marshal(graph, streamWriter);
} }
catch (Exception ex) { catch (Exception ex) {
throw convertXStreamException(ex, true); throw convertXStreamException(ex, true);
@ -536,7 +544,7 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin
*/ */
private Object unmarshal(HierarchicalStreamReader streamReader) { private Object unmarshal(HierarchicalStreamReader streamReader) {
try { try {
return this.xstream.unmarshal(streamReader); return getXStream().unmarshal(streamReader);
} }
catch (Exception ex) { catch (Exception ex) {
throw convertXStreamException(ex, false); throw convertXStreamException(ex, false);