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;
}
}