Skip to content

Commit 58f63f6

Browse files
committed
Improved Jaxb2Marshaller.supports()
1 parent 60ac239 commit 58f63f6

File tree

4 files changed

+327
-32
lines changed

4 files changed

+327
-32
lines changed

org.springframework.oxm/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java

Lines changed: 72 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2009 the original author or authors.
2+
* Copyright 2002-2010 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,20 +16,26 @@
1616

1717
package org.springframework.oxm.jaxb;
1818

19+
import java.awt.Image;
1920
import java.io.ByteArrayInputStream;
2021
import java.io.IOException;
2122
import java.io.InputStream;
2223
import java.io.OutputStream;
2324
import java.io.UnsupportedEncodingException;
25+
import java.lang.reflect.GenericArrayType;
26+
import java.lang.reflect.ParameterizedType;
27+
import java.lang.reflect.Type;
28+
import java.math.BigDecimal;
29+
import java.math.BigInteger;
2430
import java.net.URI;
2531
import java.net.URISyntaxException;
2632
import java.net.URLDecoder;
2733
import java.net.URLEncoder;
2834
import java.util.Arrays;
35+
import java.util.Calendar;
36+
import java.util.Date;
2937
import java.util.Map;
3038
import java.util.UUID;
31-
import java.lang.reflect.Type;
32-
import java.lang.reflect.ParameterizedType;
3339
import javax.activation.DataHandler;
3440
import javax.activation.DataSource;
3541
import javax.xml.XMLConstants;
@@ -43,10 +49,12 @@
4349
import javax.xml.bind.ValidationEventHandler;
4450
import javax.xml.bind.ValidationException;
4551
import javax.xml.bind.annotation.XmlRootElement;
46-
import javax.xml.bind.annotation.XmlType;
4752
import javax.xml.bind.annotation.adapters.XmlAdapter;
4853
import javax.xml.bind.attachment.AttachmentMarshaller;
4954
import javax.xml.bind.attachment.AttachmentUnmarshaller;
55+
import javax.xml.datatype.Duration;
56+
import javax.xml.datatype.XMLGregorianCalendar;
57+
import javax.xml.namespace.QName;
5058
import javax.xml.stream.XMLEventReader;
5159
import javax.xml.stream.XMLEventWriter;
5260
import javax.xml.stream.XMLStreamReader;
@@ -68,13 +76,13 @@
6876
import org.springframework.beans.factory.InitializingBean;
6977
import org.springframework.core.annotation.AnnotationUtils;
7078
import org.springframework.core.io.Resource;
79+
import org.springframework.oxm.GenericMarshaller;
80+
import org.springframework.oxm.GenericUnmarshaller;
7181
import org.springframework.oxm.MarshallingFailureException;
7282
import org.springframework.oxm.UncategorizedMappingException;
7383
import org.springframework.oxm.UnmarshallingFailureException;
7484
import org.springframework.oxm.ValidationFailureException;
7585
import org.springframework.oxm.XmlMappingException;
76-
import org.springframework.oxm.GenericMarshaller;
77-
import org.springframework.oxm.GenericUnmarshaller;
7886
import org.springframework.oxm.mime.MimeContainer;
7987
import org.springframework.oxm.mime.MimeMarshaller;
8088
import org.springframework.oxm.mime.MimeUnmarshaller;
@@ -120,7 +128,7 @@ public class Jaxb2Marshaller
120128

121129
private String contextPath;
122130

123-
private Class[] classesToBeBound;
131+
private Class<?>[] classesToBeBound;
124132

125133
private Map<String, ?> jaxbContextProperties;
126134

@@ -134,7 +142,7 @@ public class Jaxb2Marshaller
134142

135143
private ValidationEventHandler validationEventHandler;
136144

137-
private XmlAdapter[] adapters;
145+
private XmlAdapter<?, ?>[] adapters;
138146

139147
private Resource[] schemaResources;
140148

@@ -177,15 +185,15 @@ public void setContextPaths(String[] contextPaths) {
177185
/**
178186
* Returns the list of Java classes to be recognized by a newly created JAXBContext.
179187
*/
180-
public Class[] getClassesToBeBound() {
188+
public Class<?>[] getClassesToBeBound() {
181189
return classesToBeBound;
182190
}
183191

184192
/**
185193
* Set the list of Java classes to be recognized by a newly created JAXBContext.
186194
* Setting this property or {@link #setContextPath "contextPath"} is required.
187195
*/
188-
public void setClassesToBeBound(Class[] classesToBeBound) {
196+
public void setClassesToBeBound(Class<?>[] classesToBeBound) {
189197
this.classesToBeBound = classesToBeBound;
190198
}
191199

@@ -247,7 +255,7 @@ public void setValidationEventHandler(ValidationEventHandler validationEventHand
247255
* Specify the <code>XmlAdapter</code>s to be registered with the JAXB <code>Marshaller</code>
248256
* and <code>Unmarshaller</code>
249257
*/
250-
public void setAdapters(XmlAdapter[] adapters) {
258+
public void setAdapters(XmlAdapter<?, ?>[] adapters) {
251259
this.adapters = adapters;
252260
}
253261

@@ -394,13 +402,21 @@ public boolean supports(Type genericType) {
394402
if (genericType instanceof ParameterizedType) {
395403
ParameterizedType parameterizedType = (ParameterizedType) genericType;
396404
if (JAXBElement.class.equals(parameterizedType.getRawType()) &&
397-
parameterizedType.getActualTypeArguments().length == 1 &&
398-
parameterizedType.getActualTypeArguments()[0] instanceof Class) {
399-
Class typeArgument = (Class) parameterizedType.getActualTypeArguments()[0];
400-
return supportsInternal(typeArgument, false);
405+
parameterizedType.getActualTypeArguments().length == 1) {
406+
Type typeArgument = parameterizedType.getActualTypeArguments()[0];
407+
if (typeArgument instanceof Class) {
408+
Class<?> classArgument = (Class<?>) typeArgument;
409+
if (isPrimitiveWrapper(classArgument) || isStandardClass(classArgument)) {
410+
return true;
411+
}
412+
return supportsInternal(classArgument, false);
413+
} else if (typeArgument instanceof GenericArrayType) {
414+
GenericArrayType arrayType = (GenericArrayType) typeArgument;
415+
return arrayType.getGenericComponentType().equals(Byte.TYPE);
416+
}
401417
}
402418
} else if (genericType instanceof Class) {
403-
Class clazz = (Class) genericType;
419+
Class<?> clazz = (Class<?>) genericType;
404420
return supportsInternal(clazz, true);
405421
}
406422
return false;
@@ -410,9 +426,6 @@ private boolean supportsInternal(Class<?> clazz, boolean checkForXmlRootElement)
410426
if (checkForXmlRootElement && AnnotationUtils.findAnnotation(clazz, XmlRootElement.class) == null) {
411427
return false;
412428
}
413-
if (AnnotationUtils.findAnnotation(clazz, XmlType.class) == null) {
414-
return false;
415-
}
416429
if (StringUtils.hasLength(getContextPath())) {
417430
String packageName = ClassUtils.getPackageName(clazz);
418431
String[] contextPaths = StringUtils.tokenizeToStringArray(getContextPath(), ":");
@@ -429,6 +442,44 @@ else if (!ObjectUtils.isEmpty(getClassesToBeBound())) {
429442
return false;
430443
}
431444

445+
/**
446+
* Checks whether the given type is a primitive wrapper type.
447+
*
448+
* @see section 8.5.1 of the JAXB2 spec
449+
*/
450+
private boolean isPrimitiveWrapper(Class<?> clazz) {
451+
return Boolean.class.equals(clazz) ||
452+
Byte.class.equals(clazz) ||
453+
Short.class.equals(clazz) ||
454+
Integer.class.equals(clazz) ||
455+
Long.class.equals(clazz) ||
456+
Float.class.equals(clazz) ||
457+
Double.class.equals(clazz);
458+
}
459+
460+
/**
461+
* Checks whether the given type is a standard class.
462+
463+
* @see section 8.5.2 of the JAXB2 spec
464+
*/
465+
private boolean isStandardClass(Class<?> clazz) {
466+
return String.class.equals(clazz) ||
467+
BigInteger.class.isAssignableFrom(clazz) ||
468+
BigDecimal.class.isAssignableFrom(clazz) ||
469+
Calendar.class.isAssignableFrom(clazz) ||
470+
Date.class.isAssignableFrom(clazz) ||
471+
QName.class.isAssignableFrom(clazz) ||
472+
URI.class.equals(clazz) ||
473+
XMLGregorianCalendar.class.isAssignableFrom(clazz) ||
474+
Duration.class.isAssignableFrom(clazz) ||
475+
Image.class.equals(clazz) ||
476+
DataHandler.class.equals(clazz) ||
477+
// Source and subclasses should be supported according to the JAXB2 spec, but aren't in the RI
478+
// Source.class.isAssignableFrom(clazz) ||
479+
UUID.class.equals(clazz);
480+
481+
}
482+
432483
// Marshalling
433484

434485
public void marshal(Object graph, Result result) throws XmlMappingException {
@@ -504,7 +555,7 @@ protected void initJaxbMarshaller(Marshaller marshaller) throws JAXBException {
504555
marshaller.setEventHandler(this.validationEventHandler);
505556
}
506557
if (this.adapters != null) {
507-
for (XmlAdapter adapter : this.adapters) {
558+
for (XmlAdapter<?, ?> adapter : this.adapters) {
508559
marshaller.setAdapter(adapter);
509560
}
510561
}
@@ -589,7 +640,7 @@ protected void initJaxbUnmarshaller(Unmarshaller unmarshaller) throws JAXBExcept
589640
unmarshaller.setEventHandler(this.validationEventHandler);
590641
}
591642
if (this.adapters != null) {
592-
for (XmlAdapter adapter : this.adapters) {
643+
for (XmlAdapter<?, ?> adapter : this.adapters) {
593644
unmarshaller.setAdapter(adapter);
594645
}
595646
}

org.springframework.oxm/src/test/java/org/springframework/oxm/jaxb/Jaxb2MarshallerTests.java

Lines changed: 65 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2009 the original author or authors.
2+
* Copyright 2002-2010 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,20 +16,24 @@
1616

1717
package org.springframework.oxm.jaxb;
1818

19+
import java.io.ByteArrayOutputStream;
1920
import java.io.StringWriter;
21+
import java.lang.reflect.InvocationTargetException;
2022
import java.lang.reflect.Method;
2123
import java.lang.reflect.Type;
2224
import java.util.Collections;
2325
import javax.activation.DataHandler;
2426
import javax.activation.FileDataSource;
27+
import javax.xml.bind.JAXBElement;
28+
import javax.xml.bind.annotation.XmlRootElement;
29+
import javax.xml.bind.annotation.XmlType;
2530
import javax.xml.transform.Result;
2631
import javax.xml.transform.sax.SAXResult;
2732
import javax.xml.transform.stream.StreamResult;
28-
import javax.xml.bind.annotation.XmlRootElement;
29-
import javax.xml.bind.annotation.XmlType;
30-
import javax.xml.bind.JAXBElement;
3133

34+
import static org.custommonkey.xmlunit.XMLAssert.assertFalse;
3235
import static org.custommonkey.xmlunit.XMLAssert.*;
36+
import static org.custommonkey.xmlunit.XMLAssert.fail;
3337
import static org.easymock.EasyMock.*;
3438
import static org.junit.Assert.assertTrue;
3539
import org.junit.Test;
@@ -39,17 +43,16 @@
3943

4044
import org.springframework.core.io.ClassPathResource;
4145
import org.springframework.core.io.Resource;
42-
import org.springframework.core.GenericTypeResolver;
4346
import org.springframework.oxm.AbstractMarshallerTests;
4447
import org.springframework.oxm.Marshaller;
4548
import org.springframework.oxm.UncategorizedMappingException;
4649
import org.springframework.oxm.XmlMappingException;
47-
import org.springframework.oxm.GenericMarshaller;
4850
import org.springframework.oxm.jaxb.test.FlightType;
4951
import org.springframework.oxm.jaxb.test.Flights;
5052
import org.springframework.oxm.jaxb.test.ObjectFactory;
5153
import org.springframework.oxm.mime.MimeContainer;
5254
import org.springframework.util.FileCopyUtils;
55+
import org.springframework.util.ReflectionUtils;
5356

5457
public class Jaxb2MarshallerTests extends AbstractMarshallerTests {
5558

@@ -148,7 +151,7 @@ public void marshalInvalidClass() throws Exception {
148151

149152
@Test
150153
public void supportsContextPath() throws Exception {
151-
testSupports(marshaller);
154+
testSupports();
152155

153156
}
154157

@@ -157,14 +160,15 @@ public void supportsClassesToBeBound() throws Exception {
157160
marshaller = new Jaxb2Marshaller();
158161
marshaller.setClassesToBeBound(new Class[]{Flights.class, FlightType.class});
159162
marshaller.afterPropertiesSet();
160-
testSupports(marshaller);
163+
testSupports();
161164
}
162165

163-
private void testSupports(Jaxb2Marshaller marshaller) throws Exception {
166+
private void testSupports() throws Exception {
164167
assertTrue("Jaxb2Marshaller does not support Flights class", marshaller.supports(Flights.class));
165168
assertTrue("Jaxb2Marshaller does not support Flights generic type", marshaller.supports((Type)Flights.class));
166169

167170
assertFalse("Jaxb2Marshaller supports FlightType class", marshaller.supports(FlightType.class));
171+
assertFalse("Jaxb2Marshaller supports FlightType type", marshaller.supports((Type)FlightType.class));
168172

169173
Method method = ObjectFactory.class.getDeclaredMethod("createFlight", FlightType.class);
170174
assertTrue("Jaxb2Marshaller does not support JAXBElement<FlightsType>",
@@ -181,6 +185,55 @@ private void testSupports(Jaxb2Marshaller marshaller) throws Exception {
181185
method = getClass().getDeclaredMethod("createDummyType");
182186
assertFalse("Jaxb2Marshaller supports JAXBElement not in context path",
183187
marshaller.supports(method.getGenericReturnType()));
188+
189+
testSupportsPrimitives();
190+
testSupportsStandardClasses();
191+
}
192+
193+
private void testSupportsPrimitives() {
194+
final Primitives primitives = new Primitives();
195+
ReflectionUtils.doWithMethods(Primitives.class, new ReflectionUtils.MethodCallback() {
196+
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
197+
Type returnType = method.getGenericReturnType();
198+
assertTrue("Jaxb2Marshaller does not support JAXBElement<" + method.getName().substring(9) + ">",
199+
marshaller.supports(returnType));
200+
try {
201+
// make sure the marshalling does not result in errors
202+
Object returnValue = method.invoke(primitives);
203+
marshaller.marshal(returnValue, new StreamResult(new ByteArrayOutputStream()));
204+
}
205+
catch (InvocationTargetException e) {
206+
fail(e.getMessage());
207+
}
208+
}
209+
}, new ReflectionUtils.MethodFilter() {
210+
public boolean matches(Method method) {
211+
return method.getName().startsWith("primitive");
212+
}
213+
});
214+
}
215+
216+
private void testSupportsStandardClasses() throws Exception {
217+
final StandardClasses standardClasses = new StandardClasses();
218+
ReflectionUtils.doWithMethods(StandardClasses.class, new ReflectionUtils.MethodCallback() {
219+
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
220+
Type returnType = method.getGenericReturnType();
221+
assertTrue("Jaxb2Marshaller does not support JAXBElement<" + method.getName().substring(13) + ">",
222+
marshaller.supports(returnType));
223+
try {
224+
// make sure the marshalling does not result in errors
225+
Object returnValue = method.invoke(standardClasses);
226+
marshaller.marshal(returnValue, new StreamResult(new ByteArrayOutputStream()));
227+
}
228+
catch (InvocationTargetException e) {
229+
fail(e.getMessage());
230+
}
231+
}
232+
}, new ReflectionUtils.MethodFilter() {
233+
public boolean matches(Method method) {
234+
return method.getName().startsWith("standardClass");
235+
}
236+
});
184237
}
185238

186239
@Test
@@ -220,11 +273,12 @@ public static class DummyType {
220273
private String s = "Hello";
221274
}
222275

223-
public JAXBElement<DummyRootElement> createDummyRootElement() {
276+
private JAXBElement<DummyRootElement> createDummyRootElement() {
224277
return null;
225278
}
226279

227-
public JAXBElement<DummyType> createDummyType() {
280+
private JAXBElement<DummyType> createDummyType() {
228281
return null;
229282
}
283+
230284
}

0 commit comments

Comments
 (0)