From 08092f34a621f8b292ca28d45a59d5fa8ebd2879 Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Tue, 16 Feb 2010 14:13:18 +0000 Subject: [PATCH] Improved Jaxb2Marshaller.supports() git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@2989 50f2f4bb-b051-0410-bef5-90022cba6387 --- .../oxm/jaxb/Jaxb2Marshaller.java | 93 ++++++++++--- .../oxm/jaxb/Jaxb2MarshallerTests.java | 76 +++++++++-- .../springframework/oxm/jaxb/Primitives.java | 61 +++++++++ .../oxm/jaxb/StandardClasses.java | 129 ++++++++++++++++++ 4 files changed, 327 insertions(+), 32 deletions(-) create mode 100644 org.springframework.oxm/src/test/java/org/springframework/oxm/jaxb/Primitives.java create mode 100644 org.springframework.oxm/src/test/java/org/springframework/oxm/jaxb/StandardClasses.java 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 5c36ede8a6f..a98c2a6acdd 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * 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. @@ -16,20 +16,26 @@ package org.springframework.oxm.jaxb; +import java.awt.Image; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.math.BigDecimal; +import java.math.BigInteger; import java.net.URI; import java.net.URISyntaxException; import java.net.URLDecoder; import java.net.URLEncoder; import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; 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; @@ -43,10 +49,12 @@ 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; +import javax.xml.datatype.Duration; +import javax.xml.datatype.XMLGregorianCalendar; +import javax.xml.namespace.QName; import javax.xml.stream.XMLEventReader; import javax.xml.stream.XMLEventWriter; import javax.xml.stream.XMLStreamReader; @@ -68,13 +76,13 @@ import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.InitializingBean; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.io.Resource; +import org.springframework.oxm.GenericMarshaller; +import org.springframework.oxm.GenericUnmarshaller; import org.springframework.oxm.MarshallingFailureException; 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; @@ -120,7 +128,7 @@ public class Jaxb2Marshaller private String contextPath; - private Class[] classesToBeBound; + private Class[] classesToBeBound; private Map jaxbContextProperties; @@ -134,7 +142,7 @@ public class Jaxb2Marshaller private ValidationEventHandler validationEventHandler; - private XmlAdapter[] adapters; + private XmlAdapter[] adapters; private Resource[] schemaResources; @@ -177,7 +185,7 @@ public class Jaxb2Marshaller /** * Returns the list of Java classes to be recognized by a newly created JAXBContext. */ - public Class[] getClassesToBeBound() { + public Class[] getClassesToBeBound() { return classesToBeBound; } @@ -185,7 +193,7 @@ public class Jaxb2Marshaller * Set the list of Java classes to be recognized by a newly created JAXBContext. * Setting this property or {@link #setContextPath "contextPath"} is required. */ - public void setClassesToBeBound(Class[] classesToBeBound) { + public void setClassesToBeBound(Class[] classesToBeBound) { this.classesToBeBound = classesToBeBound; } @@ -247,7 +255,7 @@ public class Jaxb2Marshaller * Specify the XmlAdapters to be registered with the JAXB Marshaller * and Unmarshaller */ - public void setAdapters(XmlAdapter[] adapters) { + public void setAdapters(XmlAdapter[] adapters) { this.adapters = adapters; } @@ -394,13 +402,21 @@ public class Jaxb2Marshaller 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); + parameterizedType.getActualTypeArguments().length == 1) { + Type typeArgument = parameterizedType.getActualTypeArguments()[0]; + if (typeArgument instanceof Class) { + Class classArgument = (Class) typeArgument; + if (isPrimitiveWrapper(classArgument) || isStandardClass(classArgument)) { + return true; + } + return supportsInternal(classArgument, false); + } else if (typeArgument instanceof GenericArrayType) { + GenericArrayType arrayType = (GenericArrayType) typeArgument; + return arrayType.getGenericComponentType().equals(Byte.TYPE); + } } } else if (genericType instanceof Class) { - Class clazz = (Class) genericType; + Class clazz = (Class) genericType; return supportsInternal(clazz, true); } return false; @@ -410,9 +426,6 @@ public class Jaxb2Marshaller if (checkForXmlRootElement && AnnotationUtils.findAnnotation(clazz, XmlRootElement.class) == null) { return false; } - if (AnnotationUtils.findAnnotation(clazz, XmlType.class) == null) { - return false; - } if (StringUtils.hasLength(getContextPath())) { String packageName = ClassUtils.getPackageName(clazz); String[] contextPaths = StringUtils.tokenizeToStringArray(getContextPath(), ":"); @@ -429,6 +442,44 @@ public class Jaxb2Marshaller return false; } + /** + * Checks whether the given type is a primitive wrapper type. + * + * @see section 8.5.1 of the JAXB2 spec + */ + private boolean isPrimitiveWrapper(Class clazz) { + return Boolean.class.equals(clazz) || + Byte.class.equals(clazz) || + Short.class.equals(clazz) || + Integer.class.equals(clazz) || + Long.class.equals(clazz) || + Float.class.equals(clazz) || + Double.class.equals(clazz); + } + + /** + * Checks whether the given type is a standard class. + + * @see section 8.5.2 of the JAXB2 spec + */ + private boolean isStandardClass(Class clazz) { + return String.class.equals(clazz) || + BigInteger.class.isAssignableFrom(clazz) || + BigDecimal.class.isAssignableFrom(clazz) || + Calendar.class.isAssignableFrom(clazz) || + Date.class.isAssignableFrom(clazz) || + QName.class.isAssignableFrom(clazz) || + URI.class.equals(clazz) || + XMLGregorianCalendar.class.isAssignableFrom(clazz) || + Duration.class.isAssignableFrom(clazz) || + Image.class.equals(clazz) || + DataHandler.class.equals(clazz) || + // Source and subclasses should be supported according to the JAXB2 spec, but aren't in the RI + // Source.class.isAssignableFrom(clazz) || + UUID.class.equals(clazz); + + } + // Marshalling public void marshal(Object graph, Result result) throws XmlMappingException { @@ -504,7 +555,7 @@ public class Jaxb2Marshaller marshaller.setEventHandler(this.validationEventHandler); } if (this.adapters != null) { - for (XmlAdapter adapter : this.adapters) { + for (XmlAdapter adapter : this.adapters) { marshaller.setAdapter(adapter); } } @@ -589,7 +640,7 @@ public class Jaxb2Marshaller unmarshaller.setEventHandler(this.validationEventHandler); } if (this.adapters != null) { - for (XmlAdapter adapter : this.adapters) { + for (XmlAdapter adapter : this.adapters) { unmarshaller.setAdapter(adapter); } } 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 83ba3247416..54a6a87884e 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * 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. @@ -16,20 +16,24 @@ package org.springframework.oxm.jaxb; +import java.io.ByteArrayOutputStream; import java.io.StringWriter; +import java.lang.reflect.InvocationTargetException; 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.bind.JAXBElement; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlType; 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.assertFalse; import static org.custommonkey.xmlunit.XMLAssert.*; +import static org.custommonkey.xmlunit.XMLAssert.fail; import static org.easymock.EasyMock.*; import static org.junit.Assert.assertTrue; import org.junit.Test; @@ -39,17 +43,16 @@ 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; import org.springframework.oxm.mime.MimeContainer; import org.springframework.util.FileCopyUtils; +import org.springframework.util.ReflectionUtils; public class Jaxb2MarshallerTests extends AbstractMarshallerTests { @@ -148,7 +151,7 @@ public class Jaxb2MarshallerTests extends AbstractMarshallerTests { @Test public void supportsContextPath() throws Exception { - testSupports(marshaller); + testSupports(); } @@ -157,14 +160,15 @@ public class Jaxb2MarshallerTests extends AbstractMarshallerTests { marshaller = new Jaxb2Marshaller(); marshaller.setClassesToBeBound(new Class[]{Flights.class, FlightType.class}); marshaller.afterPropertiesSet(); - testSupports(marshaller); + testSupports(); } - private void testSupports(Jaxb2Marshaller marshaller) throws Exception { + private void testSupports() 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)); + assertFalse("Jaxb2Marshaller supports FlightType type", marshaller.supports((Type)FlightType.class)); Method method = ObjectFactory.class.getDeclaredMethod("createFlight", FlightType.class); assertTrue("Jaxb2Marshaller does not support JAXBElement", @@ -181,6 +185,55 @@ public class Jaxb2MarshallerTests extends AbstractMarshallerTests { method = getClass().getDeclaredMethod("createDummyType"); assertFalse("Jaxb2Marshaller supports JAXBElement not in context path", marshaller.supports(method.getGenericReturnType())); + + testSupportsPrimitives(); + testSupportsStandardClasses(); + } + + private void testSupportsPrimitives() { + final Primitives primitives = new Primitives(); + ReflectionUtils.doWithMethods(Primitives.class, new ReflectionUtils.MethodCallback() { + public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { + Type returnType = method.getGenericReturnType(); + assertTrue("Jaxb2Marshaller does not support JAXBElement<" + method.getName().substring(9) + ">", + marshaller.supports(returnType)); + try { + // make sure the marshalling does not result in errors + Object returnValue = method.invoke(primitives); + marshaller.marshal(returnValue, new StreamResult(new ByteArrayOutputStream())); + } + catch (InvocationTargetException e) { + fail(e.getMessage()); + } + } + }, new ReflectionUtils.MethodFilter() { + public boolean matches(Method method) { + return method.getName().startsWith("primitive"); + } + }); + } + + private void testSupportsStandardClasses() throws Exception { + final StandardClasses standardClasses = new StandardClasses(); + ReflectionUtils.doWithMethods(StandardClasses.class, new ReflectionUtils.MethodCallback() { + public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { + Type returnType = method.getGenericReturnType(); + assertTrue("Jaxb2Marshaller does not support JAXBElement<" + method.getName().substring(13) + ">", + marshaller.supports(returnType)); + try { + // make sure the marshalling does not result in errors + Object returnValue = method.invoke(standardClasses); + marshaller.marshal(returnValue, new StreamResult(new ByteArrayOutputStream())); + } + catch (InvocationTargetException e) { + fail(e.getMessage()); + } + } + }, new ReflectionUtils.MethodFilter() { + public boolean matches(Method method) { + return method.getName().startsWith("standardClass"); + } + }); } @Test @@ -220,11 +273,12 @@ public class Jaxb2MarshallerTests extends AbstractMarshallerTests { private String s = "Hello"; } - public JAXBElement createDummyRootElement() { + private JAXBElement createDummyRootElement() { return null; } - public JAXBElement createDummyType() { + private JAXBElement createDummyType() { return null; } + } diff --git a/org.springframework.oxm/src/test/java/org/springframework/oxm/jaxb/Primitives.java b/org.springframework.oxm/src/test/java/org/springframework/oxm/jaxb/Primitives.java new file mode 100644 index 00000000000..1a5c6580933 --- /dev/null +++ b/org.springframework.oxm/src/test/java/org/springframework/oxm/jaxb/Primitives.java @@ -0,0 +1,61 @@ +/* + * 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.jaxb; + +import javax.xml.bind.JAXBElement; +import javax.xml.namespace.QName; + +/** + * Used by {@link org.springframework.oxm.jaxb.Jaxb2MarshallerTests}. + * + * @author Arjen Poutsma + */ +public class Primitives { + + private static final QName NAME = new QName("http://springframework.org/oxm-test", "primitives"); + + // following methods are used to test support for primitives + public JAXBElement primitiveBoolean() { + return new JAXBElement(NAME, Boolean.class, true); + } + + public JAXBElement primitiveByte() { + return new JAXBElement(NAME, Byte.class, (byte)42); + } + + public JAXBElement primitiveShort() { + return new JAXBElement(NAME, Short.class, (short)42); + } + + public JAXBElement primitiveInteger() { + return new JAXBElement(NAME, Integer.class, 42); + } + + public JAXBElement primitiveLong() { + return new JAXBElement(NAME, Long.class, 42L); + } + + public JAXBElement primitiveDouble() { + return new JAXBElement(NAME, Double.class, 42D); + } + + public JAXBElement primitiveByteArray() { + return new JAXBElement(NAME, byte[].class, new byte[]{42}); + } + + +} diff --git a/org.springframework.oxm/src/test/java/org/springframework/oxm/jaxb/StandardClasses.java b/org.springframework.oxm/src/test/java/org/springframework/oxm/jaxb/StandardClasses.java new file mode 100644 index 00000000000..bd98d9df1a8 --- /dev/null +++ b/org.springframework.oxm/src/test/java/org/springframework/oxm/jaxb/StandardClasses.java @@ -0,0 +1,129 @@ +/* + * 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.jaxb; + +import java.awt.Image; +import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.URI; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.UUID; +import javax.activation.DataHandler; +import javax.activation.DataSource; +import javax.activation.URLDataSource; +import javax.imageio.ImageIO; +import javax.xml.bind.JAXBElement; +import javax.xml.datatype.DatatypeConfigurationException; +import javax.xml.datatype.DatatypeFactory; +import javax.xml.datatype.Duration; +import javax.xml.datatype.XMLGregorianCalendar; +import javax.xml.namespace.QName; + +/** + * Used by {@link org.springframework.oxm.jaxb.Jaxb2MarshallerTests}. + * + * @author Arjen Poutsma + */ +public class StandardClasses { + + private static final QName NAME = new QName("http://springframework.org/oxm-test", "standard-classes"); + + private DatatypeFactory factory; + + public StandardClasses() throws DatatypeConfigurationException { + factory = DatatypeFactory.newInstance(); + } + + /* + java.net.URI + javax.xml.datatype.XMLGregorianCalendarxs:anySimpleType + javax.xml.datatype.Duration + java.lang.Object + java.awt.Image + javax.activation.DataHandler + javax.xml.transform.Source + java.util.UUID + */ + public JAXBElement standardClassString() { + return new JAXBElement(NAME, String.class, "42"); + } + + public JAXBElement standardClassBigInteger() { + return new JAXBElement(NAME, BigInteger.class, new BigInteger("42")); + } + + public JAXBElement standardClassBigDecimal() { + return new JAXBElement(NAME, BigDecimal.class, new BigDecimal("42.0")); + } + + public JAXBElement standardClassCalendar() { + return new JAXBElement(NAME, Calendar.class, Calendar.getInstance()); + } + + public JAXBElement standardClassGregorianCalendar() { + return new JAXBElement(NAME, GregorianCalendar.class, (GregorianCalendar)Calendar.getInstance()); + } + + public JAXBElement standardClassDate() { + return new JAXBElement(NAME, Date.class, new Date()); + } + + public JAXBElement standardClassQName() { + return new JAXBElement(NAME, QName.class, NAME); + } + + public JAXBElement standardClassURI() { + return new JAXBElement(NAME, URI.class, URI.create("http://springframework.org")); + } + + public JAXBElement standardClassXMLGregorianCalendar() throws DatatypeConfigurationException { + XMLGregorianCalendar calendar = + factory.newXMLGregorianCalendar((GregorianCalendar) Calendar.getInstance()); + return new JAXBElement(NAME, XMLGregorianCalendar.class, calendar); + } + + public JAXBElement standardClassDuration() { + Duration duration = factory.newDuration(42000); + return new JAXBElement(NAME, Duration.class, duration); + } + + public JAXBElement standardClassImage() throws IOException { + Image image = ImageIO.read(getClass().getResourceAsStream("spring-ws.png")); + return new JAXBElement(NAME, Image.class, image); + } + + public JAXBElement standardClassDataHandler() { + DataSource dataSource = new URLDataSource(getClass().getResource("spring-ws.png")); + DataHandler dataHandler = new DataHandler(dataSource); + return new JAXBElement(NAME, DataHandler.class, dataHandler); + } + + /* The following should work according to the spec, but doesn't on the JAXB2 implementation including in JDK 1.6.0_17 + public JAXBElement standardClassSource() { + StringReader reader = new StringReader(""); + return new JAXBElement(NAME, Source.class, new StreamSource(reader)); + } + */ + + public JAXBElement standardClassUUID() { + return new JAXBElement(NAME, UUID.class, UUID.randomUUID()); + } + +}