diff --git a/org.springframework.context/src/main/java/org/springframework/mapping/support/MappableTypeFactory.java b/org.springframework.context/src/main/java/org/springframework/mapping/support/MappableTypeFactory.java index d844468d875..df701ee2be1 100644 --- a/org.springframework.context/src/main/java/org/springframework/mapping/support/MappableTypeFactory.java +++ b/org.springframework.context/src/main/java/org/springframework/mapping/support/MappableTypeFactory.java @@ -43,6 +43,6 @@ public class MappableTypeFactory { } } throw new IllegalArgumentException("Object of type [" + object.getClass().getName() - + "] not mappable - no suitable MappableType exists"); + + "] is not mappable - no suitable MappableType exists"); } } \ No newline at end of file diff --git a/org.springframework.context/src/main/java/org/springframework/mapping/support/SpelMapper.java b/org.springframework.context/src/main/java/org/springframework/mapping/support/SpelMapper.java index ccff06bdb92..5f7118313dd 100644 --- a/org.springframework.context/src/main/java/org/springframework/mapping/support/SpelMapper.java +++ b/org.springframework.context/src/main/java/org/springframework/mapping/support/SpelMapper.java @@ -208,7 +208,7 @@ public class SpelMapper implements Mapper { // internal helpers - private Class[] getRequiredTypeInfo(Mapper mapper) { + private Class[] getRequiredTypeInfo(Mapper mapper) { return GenericTypeResolver.resolveTypeArguments(mapper.getClass(), Mapper.class); } diff --git a/org.springframework.context/src/test/java/org/springframework/mapping/support/SpelMapperTests.java b/org.springframework.context/src/test/java/org/springframework/mapping/support/SpelMapperTests.java index a7328c121e9..38c2d86d073 100644 --- a/org.springframework.context/src/test/java/org/springframework/mapping/support/SpelMapperTests.java +++ b/org.springframework.context/src/test/java/org/springframework/mapping/support/SpelMapperTests.java @@ -7,13 +7,25 @@ import static org.junit.Assert.assertTrue; import java.math.BigDecimal; import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Set; import org.junit.Test; +import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.converter.Converter; +import org.springframework.core.io.ClassPathResource; +import org.springframework.expression.AccessException; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.PropertyAccessor; +import org.springframework.expression.TypedValue; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.expression.spel.support.StandardTypeConverter; import org.springframework.mapping.Mapper; import org.springframework.mapping.MappingException; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; public class SpelMapperTests { @@ -339,6 +351,82 @@ public class SpelMapperTests { assertEquals(source, target.getLineItem().getOrder()); } + @Test + public void mapCustomMappableType() throws Exception { + XmlDocumentLoader loader = new XmlDocumentLoader(); + loader.setValidating(false); + + Object source = loader.loadDocument(new ClassPathResource("order.xml", getClass())).getDocumentElement(); + Order target = new Order(); + + MappableTypeFactory factory = new MappableTypeFactory(); + factory.add(new ElementMappableType()); + factory.add(new BeanMappableType()); + mapper.setMappableTypeFactory(factory); + mapper.map(source, target); + + assertEquals(1, target.getNumber()); + } + + static class ElementMappableType implements MappableType { + + public boolean isInstance(Object object) { + return object instanceof Element; + } + + public Set getFields(Element object) { + NamedNodeMap map = object.getAttributes(); + Set fields = new LinkedHashSet(); + for (int i = 0; i < map.getLength(); i++) { + fields.add(map.item(i).getNodeName()); + } + return fields; + } + + public EvaluationContext getEvaluationContext(Element object, ConversionService conversionService) { + StandardEvaluationContext context = new StandardEvaluationContext(); + context.setRootObject(object); + context.setTypeConverter(new StandardTypeConverter(conversionService)); + context.addPropertyAccessor(new PropertyAccessor() { + public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException { + Element e = (Element) target; + return e.hasAttribute(name); + } + + public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException { + return canRead(context, target, name); + } + + public Class[] getSpecificTargetClasses() { + return new Class[] { Element.class }; + } + + public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException { + Element e = (Element) target; + return new TypedValue(e.getAttribute(name)); + } + + public void write(EvaluationContext context, Object target, String name, Object newValue) + throws AccessException { + Element e = (Element) target; + e.setAttribute(name, (String) newValue); + } + }); + return context; + } + + } + + @Test(expected = IllegalArgumentException.class) + public void mapCustomMappableTypeNotSupported() throws Exception { + Order source = new Order(); + Order target = new Order(); + + MappableTypeFactory factory = new MappableTypeFactory(); + mapper.setMappableTypeFactory(factory); + mapper.map(source, target); + } + public static class Order { private int number; diff --git a/org.springframework.context/src/test/java/org/springframework/mapping/support/XmlDocumentLoader.java b/org.springframework.context/src/test/java/org/springframework/mapping/support/XmlDocumentLoader.java new file mode 100644 index 00000000000..4836ad4168f --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/mapping/support/XmlDocumentLoader.java @@ -0,0 +1,96 @@ +package org.springframework.mapping.support; + +import java.io.IOException; +import java.io.InputStream; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.core.io.Resource; +import org.springframework.util.xml.SimpleSaxErrorHandler; +import org.w3c.dom.Document; +import org.xml.sax.EntityResolver; +import org.xml.sax.SAXException; + +public class XmlDocumentLoader { + + private static final Log logger = LogFactory.getLog(XmlDocumentLoader.class); + + /** + * JAXP attribute used to configure the schema language for validation. + */ + private static final String SCHEMA_LANGUAGE_ATTRIBUTE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage"; + + /** + * JAXP attribute value indicating the XSD schema language. + */ + private static final String XSD_SCHEMA_LANGUAGE = "http://www.w3.org/2001/XMLSchema"; + + /** + * Flag indicating if the XML document parser will perform schema validation. + */ + private boolean validating = true; + + /** + * The spring-webflow schema resolution strategy. + */ + private EntityResolver entityResolver; + + /** + * Returns whether or not the XML parser will validate the document. + */ + public boolean isValidating() { + return validating; + } + + /** + * Set if the XML parser should validate the document and thus enforce a schema. Defaults to true. + */ + public void setValidating(boolean validating) { + this.validating = validating; + } + + /** + * Returns the SAX entity resolver used by the XML parser. + */ + public EntityResolver getEntityResolver() { + return entityResolver; + } + + /** + * Set a SAX entity resolver to be used for parsing. Can be overridden for custom entity resolution, for example + * relative to some specific base path. + */ + public void setEntityResolver(EntityResolver entityResolver) { + this.entityResolver = entityResolver; + } + + public Document loadDocument(Resource resource) throws IOException, ParserConfigurationException, SAXException { + InputStream is = null; + try { + is = resource.getInputStream(); + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setValidating(isValidating()); + factory.setNamespaceAware(true); + try { + factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE); + } catch (IllegalArgumentException ex) { + throw new IllegalStateException("Unable to validate using XSD: Your JAXP provider [" + factory + + "] does not support XML Schema. " + + "Are you running on Java 1.4 or below with Apache Crimson? " + + "If so you must upgrade to Apache Xerces (or Java 5 or >) for full XSD support."); + } + DocumentBuilder docBuilder = factory.newDocumentBuilder(); + docBuilder.setErrorHandler(new SimpleSaxErrorHandler(logger)); + docBuilder.setEntityResolver(getEntityResolver()); + return docBuilder.parse(is); + } finally { + if (is != null) { + is.close(); + } + } + } +} \ No newline at end of file diff --git a/org.springframework.context/src/test/java/org/springframework/mapping/support/order.xml b/org.springframework.context/src/test/java/org/springframework/mapping/support/order.xml new file mode 100644 index 00000000000..51bddfe70c9 --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/mapping/support/order.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file