SPR-6577 - MarshallingView auto detect model with Jaxb2Marshaller chooses the wrong object
This commit is contained in:
parent
71d7b22d5e
commit
b1e2a2ec3c
|
@ -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 <code>true</code> if this marshaller can indeed marshal instances of the supplied type;
|
||||
* <code>false</code> otherwise
|
||||
*/
|
||||
boolean supports(Type genericType);
|
||||
|
||||
}
|
|
@ -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 <code>true</code> if this marshaller can indeed marshal instances of the supplied type;
|
||||
* <code>false</code> otherwise
|
||||
*/
|
||||
boolean supports(Type genericType);
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
else if (AnnotationUtils.findAnnotation(clazz, XmlRootElement.class) != null) {
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean supportsInternal(Class<?> clazz, boolean checkForXmlRootElement) {
|
||||
if (checkForXmlRootElement && AnnotationUtils.findAnnotation(clazz, XmlRootElement.class) == null) {
|
||||
return false;
|
||||
}
|
||||
if (StringUtils.hasLength(this.contextPath)) {
|
||||
if (AnnotationUtils.findAnnotation(clazz, XmlType.class) == null) {
|
||||
return false;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<FlightsType>",
|
||||
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<FlightsType>",
|
||||
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<DummyRootElement> createDummyRootElement() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public JAXBElement<DummyType> createDummyType() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue