diff --git a/org.springframework.oxm/src/main/java/org/springframework/oxm/GenericMarshaller.java b/org.springframework.oxm/src/main/java/org/springframework/oxm/GenericMarshaller.java new file mode 100644 index 0000000000000000000000000000000000000000..315682cc812a5249ddf46712d9e72f70be2e2a1a --- /dev/null +++ b/org.springframework.oxm/src/main/java/org/springframework/oxm/GenericMarshaller.java @@ -0,0 +1,37 @@ +/* + * Copyright 2002-2010 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.oxm; + +import java.lang.reflect.Type; + +/** + * Subinterface of {@link Marshaller} that has support for Java 5 generics. + * + * @author Arjen Poutsma + * @sicne 3.0.1 + */ +public interface GenericMarshaller extends Marshaller { + + /** + * Indicates whether this marshaller can marshal instances of the supplied generic type. + * @param genericType the type that this marshaller is being asked if it can marshal + * @return true if this marshaller can indeed marshal instances of the supplied type; + * false otherwise + */ + boolean supports(Type genericType); + +} diff --git a/org.springframework.oxm/src/main/java/org/springframework/oxm/GenericUnmarshaller.java b/org.springframework.oxm/src/main/java/org/springframework/oxm/GenericUnmarshaller.java new file mode 100644 index 0000000000000000000000000000000000000000..b0a595dbe5376c1a8aa1493787109b0044371cc1 --- /dev/null +++ b/org.springframework.oxm/src/main/java/org/springframework/oxm/GenericUnmarshaller.java @@ -0,0 +1,37 @@ +/* + * Copyright 2002-2010 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.oxm; + +import java.lang.reflect.Type; + +/** + * Subinterface of {@link Unmarshaller} that has support for Java 5 generics. + * + * @author Arjen Poutsma + * @sicne 3.0.1 + */ +public interface GenericUnmarshaller extends Unmarshaller { + + /** + * Indicates whether this marshaller can marshal instances of the supplied generic type. + * @param genericType the type that this marshaller is being asked if it can marshal + * @return true if this marshaller can indeed marshal instances of the supplied type; + * false otherwise + */ + boolean supports(Type genericType); + +} \ No newline at end of file diff --git a/org.springframework.oxm/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java b/org.springframework.oxm/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java index 30ac57a3659e18bb00ad5ef723a326b4d1c69deb..5c36ede8a6f22eb97a690ba8404d1511cb250e55 100644 --- a/org.springframework.oxm/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java +++ b/org.springframework.oxm/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java @@ -28,6 +28,8 @@ import java.net.URLEncoder; import java.util.Arrays; import java.util.Map; import java.util.UUID; +import java.lang.reflect.Type; +import java.lang.reflect.ParameterizedType; import javax.activation.DataHandler; import javax.activation.DataSource; import javax.xml.XMLConstants; @@ -41,6 +43,7 @@ import javax.xml.bind.Unmarshaller; import javax.xml.bind.ValidationEventHandler; import javax.xml.bind.ValidationException; import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlType; import javax.xml.bind.annotation.adapters.XmlAdapter; import javax.xml.bind.attachment.AttachmentMarshaller; import javax.xml.bind.attachment.AttachmentUnmarshaller; @@ -70,6 +73,8 @@ import org.springframework.oxm.UncategorizedMappingException; import org.springframework.oxm.UnmarshallingFailureException; import org.springframework.oxm.ValidationFailureException; import org.springframework.oxm.XmlMappingException; +import org.springframework.oxm.GenericMarshaller; +import org.springframework.oxm.GenericUnmarshaller; import org.springframework.oxm.mime.MimeContainer; import org.springframework.oxm.mime.MimeMarshaller; import org.springframework.oxm.mime.MimeUnmarshaller; @@ -101,7 +106,9 @@ import org.springframework.util.xml.StaxUtils; * @see #setAdapters(XmlAdapter[]) * @since 3.0 */ -public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, BeanClassLoaderAware, InitializingBean { +public class Jaxb2Marshaller + implements MimeMarshaller, MimeUnmarshaller, GenericMarshaller, GenericUnmarshaller, BeanClassLoaderAware, + InitializingBean { private static final String CID = "cid:"; @@ -143,6 +150,12 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, BeanCl private boolean lazyInit = false; + /** + * Returns the JAXB context path. + */ + public String getContextPath() { + return contextPath; + } /** * Set a JAXB context path. @@ -161,6 +174,13 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, BeanCl this.contextPath = StringUtils.arrayToDelimitedString(contextPaths, ":"); } + /** + * Returns the list of Java classes to be recognized by a newly created JAXBContext. + */ + public Class[] getClassesToBeBound() { + return classesToBeBound; + } + /** * Set the list of Java classes to be recognized by a newly created JAXBContext. * Setting this property or {@link #setContextPath "contextPath"} is required. @@ -280,10 +300,10 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, BeanCl public final void afterPropertiesSet() throws Exception { - if (StringUtils.hasLength(this.contextPath) && !ObjectUtils.isEmpty(this.classesToBeBound)) { + if (StringUtils.hasLength(getContextPath()) && !ObjectUtils.isEmpty(getClassesToBeBound())) { throw new IllegalArgumentException("Specify either 'contextPath' or 'classesToBeBound property'; not both"); } - else if (!StringUtils.hasLength(this.contextPath) && ObjectUtils.isEmpty(this.classesToBeBound)) { + else if (!StringUtils.hasLength(getContextPath()) && ObjectUtils.isEmpty(getClassesToBeBound())) { throw new IllegalArgumentException("Setting either 'contextPath' or 'classesToBeBound' is required"); } if (!lazyInit) { @@ -297,10 +317,10 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, BeanCl protected synchronized JAXBContext getJaxbContext() { if (this.jaxbContext == null) { try { - if (StringUtils.hasLength(this.contextPath)) { + if (StringUtils.hasLength(getContextPath())) { this.jaxbContext = createJaxbContextFromContextPath(); } - else if (!ObjectUtils.isEmpty(this.classesToBeBound)) { + else if (!ObjectUtils.isEmpty(getClassesToBeBound())) { this.jaxbContext = createJaxbContextFromClasses(); } } @@ -313,22 +333,22 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, BeanCl private JAXBContext createJaxbContextFromContextPath() throws JAXBException { if (logger.isInfoEnabled()) { - logger.info("Creating JAXBContext with context path [" + this.contextPath + "]"); + logger.info("Creating JAXBContext with context path [" + getContextPath() + "]"); } if (this.jaxbContextProperties != null) { if (this.beanClassLoader != null) { - return JAXBContext.newInstance(this.contextPath, this.beanClassLoader, this.jaxbContextProperties); + return JAXBContext.newInstance(getContextPath(), this.beanClassLoader, this.jaxbContextProperties); } else { - return JAXBContext.newInstance(this.contextPath, ClassUtils.getDefaultClassLoader(), this.jaxbContextProperties); + return JAXBContext.newInstance(getContextPath(), ClassUtils.getDefaultClassLoader(), this.jaxbContextProperties); } } else { if (this.beanClassLoader != null) { - return JAXBContext.newInstance(this.contextPath, this.beanClassLoader); + return JAXBContext.newInstance(getContextPath(), this.beanClassLoader); } else { - return JAXBContext.newInstance(this.contextPath); + return JAXBContext.newInstance(getContextPath()); } } } @@ -336,13 +356,13 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, BeanCl private JAXBContext createJaxbContextFromClasses() throws JAXBException { if (logger.isInfoEnabled()) { logger.info("Creating JAXBContext with classes to be bound [" + - StringUtils.arrayToCommaDelimitedString(this.classesToBeBound) + "]"); + StringUtils.arrayToCommaDelimitedString(getClassesToBeBound()) + "]"); } if (this.jaxbContextProperties != null) { - return JAXBContext.newInstance(this.classesToBeBound, this.jaxbContextProperties); + return JAXBContext.newInstance(getClassesToBeBound(), this.jaxbContextProperties); } else { - return JAXBContext.newInstance(this.classesToBeBound); + return JAXBContext.newInstance(getClassesToBeBound()); } } @@ -367,15 +387,35 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, BeanCl public boolean supports(Class clazz) { - if (JAXBElement.class.isAssignableFrom(clazz)) { - return true; + return supportsInternal(clazz, true); + } + + public boolean supports(Type genericType) { + if (genericType instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) genericType; + if (JAXBElement.class.equals(parameterizedType.getRawType()) && + parameterizedType.getActualTypeArguments().length == 1 && + parameterizedType.getActualTypeArguments()[0] instanceof Class) { + Class typeArgument = (Class) parameterizedType.getActualTypeArguments()[0]; + return supportsInternal(typeArgument, false); + } + } else if (genericType instanceof Class) { + Class clazz = (Class) genericType; + return supportsInternal(clazz, true); + } + return false; + } + + private boolean supportsInternal(Class clazz, boolean checkForXmlRootElement) { + if (checkForXmlRootElement && AnnotationUtils.findAnnotation(clazz, XmlRootElement.class) == null) { + return false; } - else if (AnnotationUtils.findAnnotation(clazz, XmlRootElement.class) != null) { - return true; + if (AnnotationUtils.findAnnotation(clazz, XmlType.class) == null) { + return false; } - if (StringUtils.hasLength(this.contextPath)) { + if (StringUtils.hasLength(getContextPath())) { String packageName = ClassUtils.getPackageName(clazz); - String[] contextPaths = StringUtils.tokenizeToStringArray(this.contextPath, ":"); + String[] contextPaths = StringUtils.tokenizeToStringArray(getContextPath(), ":"); for (String contextPath : contextPaths) { if (contextPath.equals(packageName)) { return true; @@ -383,8 +423,8 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, BeanCl } return false; } - else if (!ObjectUtils.isEmpty(this.classesToBeBound)) { - return Arrays.asList(this.classesToBeBound).contains(clazz); + else if (!ObjectUtils.isEmpty(getClassesToBeBound())) { + return Arrays.asList(getClassesToBeBound()).contains(clazz); } return false; } diff --git a/org.springframework.oxm/src/main/java/org/springframework/oxm/mime/MimeUnmarshaller.java b/org.springframework.oxm/src/main/java/org/springframework/oxm/mime/MimeUnmarshaller.java index 9526c18446d5dab270e2335b9864b842efcd82cb..1ad90e182c743430927caf2a9b2d68af14f0f962 100644 --- a/org.springframework.oxm/src/main/java/org/springframework/oxm/mime/MimeUnmarshaller.java +++ b/org.springframework.oxm/src/main/java/org/springframework/oxm/mime/MimeUnmarshaller.java @@ -23,7 +23,7 @@ import org.springframework.oxm.Unmarshaller; import org.springframework.oxm.XmlMappingException; /** - * Subinterface of {@link org.springframework.oxm.Marshaller} that can use MIME attachments + * Subinterface of {@link org.springframework.oxm.Unmarshaller} that can use MIME attachments * to optimize storage of binary data. Attachments can be added as MTOM, XOP, or SwA. * * @author Arjen Poutsma diff --git a/org.springframework.oxm/src/test/java/org/springframework/oxm/jaxb/Jaxb2MarshallerTests.java b/org.springframework.oxm/src/test/java/org/springframework/oxm/jaxb/Jaxb2MarshallerTests.java index 9fea0c4b56b4237b5ee7935be3acf53a732e9120..83ba32474166b051ad5f1a89ab83ec370a2db577 100644 --- a/org.springframework.oxm/src/test/java/org/springframework/oxm/jaxb/Jaxb2MarshallerTests.java +++ b/org.springframework.oxm/src/test/java/org/springframework/oxm/jaxb/Jaxb2MarshallerTests.java @@ -18,12 +18,16 @@ package org.springframework.oxm.jaxb; import java.io.StringWriter; import java.lang.reflect.Method; +import java.lang.reflect.Type; import java.util.Collections; import javax.activation.DataHandler; import javax.activation.FileDataSource; import javax.xml.transform.Result; import javax.xml.transform.sax.SAXResult; import javax.xml.transform.stream.StreamResult; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlType; +import javax.xml.bind.JAXBElement; import static org.custommonkey.xmlunit.XMLAssert.*; import static org.easymock.EasyMock.*; @@ -35,10 +39,12 @@ import org.xml.sax.Locator; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; +import org.springframework.core.GenericTypeResolver; import org.springframework.oxm.AbstractMarshallerTests; import org.springframework.oxm.Marshaller; import org.springframework.oxm.UncategorizedMappingException; import org.springframework.oxm.XmlMappingException; +import org.springframework.oxm.GenericMarshaller; import org.springframework.oxm.jaxb.test.FlightType; import org.springframework.oxm.jaxb.test.Flights; import org.springframework.oxm.jaxb.test.ObjectFactory; @@ -142,11 +148,8 @@ public class Jaxb2MarshallerTests extends AbstractMarshallerTests { @Test public void supportsContextPath() throws Exception { - Method createFlights = ObjectFactory.class.getDeclaredMethod("createFlights"); - assertTrue("Jaxb2Marshaller does not support Flights", marshaller.supports(createFlights.getReturnType())); - Method createFlight = ObjectFactory.class.getDeclaredMethod("createFlight", FlightType.class); - assertTrue("Jaxb2Marshaller does not support JAXBElement", - marshaller.supports(createFlight.getReturnType())); + testSupports(marshaller); + } @Test @@ -154,11 +157,30 @@ public class Jaxb2MarshallerTests extends AbstractMarshallerTests { marshaller = new Jaxb2Marshaller(); marshaller.setClassesToBeBound(new Class[]{Flights.class, FlightType.class}); marshaller.afterPropertiesSet(); - Method createFlights = ObjectFactory.class.getDeclaredMethod("createFlights"); - assertTrue("Jaxb2Marshaller does not support Flights", marshaller.supports(createFlights.getReturnType())); - Method createFlight = ObjectFactory.class.getDeclaredMethod("createFlight", FlightType.class); + testSupports(marshaller); + } + + private void testSupports(Jaxb2Marshaller marshaller) throws Exception { + assertTrue("Jaxb2Marshaller does not support Flights class", marshaller.supports(Flights.class)); + assertTrue("Jaxb2Marshaller does not support Flights generic type", marshaller.supports((Type)Flights.class)); + + assertFalse("Jaxb2Marshaller supports FlightType class", marshaller.supports(FlightType.class)); + + Method method = ObjectFactory.class.getDeclaredMethod("createFlight", FlightType.class); assertTrue("Jaxb2Marshaller does not support JAXBElement", - marshaller.supports(createFlight.getReturnType())); + marshaller.supports(method.getGenericReturnType())); + + assertFalse("Jaxb2Marshaller supports class not in context path", marshaller.supports(DummyRootElement.class)); + assertFalse("Jaxb2Marshaller supports type not in context path", marshaller.supports((Type)DummyRootElement.class)); + method = getClass().getDeclaredMethod("createDummyRootElement"); + assertFalse("Jaxb2Marshaller supports JAXBElement not in context path", + marshaller.supports(method.getGenericReturnType())); + + assertFalse("Jaxb2Marshaller supports class not in context path", marshaller.supports(DummyType.class)); + assertFalse("Jaxb2Marshaller supports type not in context path", marshaller.supports((Type)DummyType.class)); + method = getClass().getDeclaredMethod("createDummyType"); + assertFalse("Jaxb2Marshaller supports JAXBElement not in context path", + marshaller.supports(method.getGenericReturnType())); } @Test @@ -185,19 +207,24 @@ public class Jaxb2MarshallerTests extends AbstractMarshallerTests { assertTrue("No XML written", writer.toString().length() > 0); } - @Test - public void subclass() throws Exception { - assertTrue("Flights subclass is not supported", marshaller.supports(FlightsSubclass.class)); - FlightType flight = new FlightType(); - flight.setNumber(42L); - FlightsSubclass flights = new FlightsSubclass(); - flights.getFlight().add(flight); - StringWriter writer = new StringWriter(); - marshaller.marshal(flights, new StreamResult(writer)); - assertXMLEqual("Marshaller writes invalid StreamResult", EXPECTED_STRING, writer.toString()); + @XmlRootElement + public static class DummyRootElement { + + private DummyType t = new DummyType(); + } - private static class FlightsSubclass extends Flights { + @XmlType + public static class DummyType { + + private String s = "Hello"; + } + + public JAXBElement createDummyRootElement() { + return null; + } + public JAXBElement createDummyType() { + return null; } }