提交 b1e2a2ec 编写于 作者: A Arjen Poutsma

SPR-6577 - MarshallingView auto detect model with Jaxb2Marshaller chooses the wrong object

上级 71d7b22d
/*
* 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);
}
/*
* 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);
}
\ No newline at end of file
...@@ -28,6 +28,8 @@ import java.net.URLEncoder; ...@@ -28,6 +28,8 @@ import java.net.URLEncoder;
import java.util.Arrays; import java.util.Arrays;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.lang.reflect.Type;
import java.lang.reflect.ParameterizedType;
import javax.activation.DataHandler; import javax.activation.DataHandler;
import javax.activation.DataSource; import javax.activation.DataSource;
import javax.xml.XMLConstants; import javax.xml.XMLConstants;
...@@ -41,6 +43,7 @@ import javax.xml.bind.Unmarshaller; ...@@ -41,6 +43,7 @@ import javax.xml.bind.Unmarshaller;
import javax.xml.bind.ValidationEventHandler; import javax.xml.bind.ValidationEventHandler;
import javax.xml.bind.ValidationException; import javax.xml.bind.ValidationException;
import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.XmlAdapter; import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.bind.attachment.AttachmentMarshaller; import javax.xml.bind.attachment.AttachmentMarshaller;
import javax.xml.bind.attachment.AttachmentUnmarshaller; import javax.xml.bind.attachment.AttachmentUnmarshaller;
...@@ -70,6 +73,8 @@ import org.springframework.oxm.UncategorizedMappingException; ...@@ -70,6 +73,8 @@ import org.springframework.oxm.UncategorizedMappingException;
import org.springframework.oxm.UnmarshallingFailureException; import org.springframework.oxm.UnmarshallingFailureException;
import org.springframework.oxm.ValidationFailureException; import org.springframework.oxm.ValidationFailureException;
import org.springframework.oxm.XmlMappingException; 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.MimeContainer;
import org.springframework.oxm.mime.MimeMarshaller; import org.springframework.oxm.mime.MimeMarshaller;
import org.springframework.oxm.mime.MimeUnmarshaller; import org.springframework.oxm.mime.MimeUnmarshaller;
...@@ -101,7 +106,9 @@ import org.springframework.util.xml.StaxUtils; ...@@ -101,7 +106,9 @@ import org.springframework.util.xml.StaxUtils;
* @see #setAdapters(XmlAdapter[]) * @see #setAdapters(XmlAdapter[])
* @since 3.0 * @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:"; private static final String CID = "cid:";
...@@ -143,6 +150,12 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, BeanCl ...@@ -143,6 +150,12 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, BeanCl
private boolean lazyInit = false; private boolean lazyInit = false;
/**
* Returns the JAXB context path.
*/
public String getContextPath() {
return contextPath;
}
/** /**
* Set a JAXB context path. * Set a JAXB context path.
...@@ -161,6 +174,13 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, BeanCl ...@@ -161,6 +174,13 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, BeanCl
this.contextPath = StringUtils.arrayToDelimitedString(contextPaths, ":"); 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. * Set the list of Java classes to be recognized by a newly created JAXBContext.
* Setting this property or {@link #setContextPath "contextPath"} is required. * Setting this property or {@link #setContextPath "contextPath"} is required.
...@@ -280,10 +300,10 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, BeanCl ...@@ -280,10 +300,10 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, BeanCl
public final void afterPropertiesSet() throws Exception { 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"); 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"); throw new IllegalArgumentException("Setting either 'contextPath' or 'classesToBeBound' is required");
} }
if (!lazyInit) { if (!lazyInit) {
...@@ -297,10 +317,10 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, BeanCl ...@@ -297,10 +317,10 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, BeanCl
protected synchronized JAXBContext getJaxbContext() { protected synchronized JAXBContext getJaxbContext() {
if (this.jaxbContext == null) { if (this.jaxbContext == null) {
try { try {
if (StringUtils.hasLength(this.contextPath)) { if (StringUtils.hasLength(getContextPath())) {
this.jaxbContext = createJaxbContextFromContextPath(); this.jaxbContext = createJaxbContextFromContextPath();
} }
else if (!ObjectUtils.isEmpty(this.classesToBeBound)) { else if (!ObjectUtils.isEmpty(getClassesToBeBound())) {
this.jaxbContext = createJaxbContextFromClasses(); this.jaxbContext = createJaxbContextFromClasses();
} }
} }
...@@ -313,22 +333,22 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, BeanCl ...@@ -313,22 +333,22 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, BeanCl
private JAXBContext createJaxbContextFromContextPath() throws JAXBException { private JAXBContext createJaxbContextFromContextPath() throws JAXBException {
if (logger.isInfoEnabled()) { 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.jaxbContextProperties != null) {
if (this.beanClassLoader != null) { if (this.beanClassLoader != null) {
return JAXBContext.newInstance(this.contextPath, this.beanClassLoader, this.jaxbContextProperties); return JAXBContext.newInstance(getContextPath(), this.beanClassLoader, this.jaxbContextProperties);
} }
else { else {
return JAXBContext.newInstance(this.contextPath, ClassUtils.getDefaultClassLoader(), this.jaxbContextProperties); return JAXBContext.newInstance(getContextPath(), ClassUtils.getDefaultClassLoader(), this.jaxbContextProperties);
} }
} }
else { else {
if (this.beanClassLoader != null) { if (this.beanClassLoader != null) {
return JAXBContext.newInstance(this.contextPath, this.beanClassLoader); return JAXBContext.newInstance(getContextPath(), this.beanClassLoader);
} }
else { else {
return JAXBContext.newInstance(this.contextPath); return JAXBContext.newInstance(getContextPath());
} }
} }
} }
...@@ -336,13 +356,13 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, BeanCl ...@@ -336,13 +356,13 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, BeanCl
private JAXBContext createJaxbContextFromClasses() throws JAXBException { private JAXBContext createJaxbContextFromClasses() throws JAXBException {
if (logger.isInfoEnabled()) { if (logger.isInfoEnabled()) {
logger.info("Creating JAXBContext with classes to be bound [" + logger.info("Creating JAXBContext with classes to be bound [" +
StringUtils.arrayToCommaDelimitedString(this.classesToBeBound) + "]"); StringUtils.arrayToCommaDelimitedString(getClassesToBeBound()) + "]");
} }
if (this.jaxbContextProperties != null) { if (this.jaxbContextProperties != null) {
return JAXBContext.newInstance(this.classesToBeBound, this.jaxbContextProperties); return JAXBContext.newInstance(getClassesToBeBound(), this.jaxbContextProperties);
} }
else { else {
return JAXBContext.newInstance(this.classesToBeBound); return JAXBContext.newInstance(getClassesToBeBound());
} }
} }
...@@ -367,15 +387,35 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, BeanCl ...@@ -367,15 +387,35 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, BeanCl
public boolean supports(Class<?> clazz) { public boolean supports(Class<?> clazz) {
if (JAXBElement.class.isAssignableFrom(clazz)) { return supportsInternal(clazz, true);
return 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) { if (AnnotationUtils.findAnnotation(clazz, XmlType.class) == null) {
return true; return false;
} }
if (StringUtils.hasLength(this.contextPath)) { if (StringUtils.hasLength(getContextPath())) {
String packageName = ClassUtils.getPackageName(clazz); String packageName = ClassUtils.getPackageName(clazz);
String[] contextPaths = StringUtils.tokenizeToStringArray(this.contextPath, ":"); String[] contextPaths = StringUtils.tokenizeToStringArray(getContextPath(), ":");
for (String contextPath : contextPaths) { for (String contextPath : contextPaths) {
if (contextPath.equals(packageName)) { if (contextPath.equals(packageName)) {
return true; return true;
...@@ -383,8 +423,8 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, BeanCl ...@@ -383,8 +423,8 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, BeanCl
} }
return false; return false;
} }
else if (!ObjectUtils.isEmpty(this.classesToBeBound)) { else if (!ObjectUtils.isEmpty(getClassesToBeBound())) {
return Arrays.asList(this.classesToBeBound).contains(clazz); return Arrays.asList(getClassesToBeBound()).contains(clazz);
} }
return false; return false;
} }
......
...@@ -23,7 +23,7 @@ import org.springframework.oxm.Unmarshaller; ...@@ -23,7 +23,7 @@ import org.springframework.oxm.Unmarshaller;
import org.springframework.oxm.XmlMappingException; 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. * to optimize storage of binary data. Attachments can be added as MTOM, XOP, or SwA.
* *
* @author Arjen Poutsma * @author Arjen Poutsma
......
...@@ -18,12 +18,16 @@ package org.springframework.oxm.jaxb; ...@@ -18,12 +18,16 @@ package org.springframework.oxm.jaxb;
import java.io.StringWriter; import java.io.StringWriter;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Collections; import java.util.Collections;
import javax.activation.DataHandler; import javax.activation.DataHandler;
import javax.activation.FileDataSource; import javax.activation.FileDataSource;
import javax.xml.transform.Result; import javax.xml.transform.Result;
import javax.xml.transform.sax.SAXResult; import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stream.StreamResult; 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.custommonkey.xmlunit.XMLAssert.*;
import static org.easymock.EasyMock.*; import static org.easymock.EasyMock.*;
...@@ -35,10 +39,12 @@ import org.xml.sax.Locator; ...@@ -35,10 +39,12 @@ import org.xml.sax.Locator;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.core.GenericTypeResolver;
import org.springframework.oxm.AbstractMarshallerTests; import org.springframework.oxm.AbstractMarshallerTests;
import org.springframework.oxm.Marshaller; import org.springframework.oxm.Marshaller;
import org.springframework.oxm.UncategorizedMappingException; import org.springframework.oxm.UncategorizedMappingException;
import org.springframework.oxm.XmlMappingException; import org.springframework.oxm.XmlMappingException;
import org.springframework.oxm.GenericMarshaller;
import org.springframework.oxm.jaxb.test.FlightType; import org.springframework.oxm.jaxb.test.FlightType;
import org.springframework.oxm.jaxb.test.Flights; import org.springframework.oxm.jaxb.test.Flights;
import org.springframework.oxm.jaxb.test.ObjectFactory; import org.springframework.oxm.jaxb.test.ObjectFactory;
...@@ -142,11 +148,8 @@ public class Jaxb2MarshallerTests extends AbstractMarshallerTests { ...@@ -142,11 +148,8 @@ public class Jaxb2MarshallerTests extends AbstractMarshallerTests {
@Test @Test
public void supportsContextPath() throws Exception { public void supportsContextPath() throws Exception {
Method createFlights = ObjectFactory.class.getDeclaredMethod("createFlights"); testSupports(marshaller);
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()));
} }
@Test @Test
...@@ -154,11 +157,30 @@ public class Jaxb2MarshallerTests extends AbstractMarshallerTests { ...@@ -154,11 +157,30 @@ public class Jaxb2MarshallerTests extends AbstractMarshallerTests {
marshaller = new Jaxb2Marshaller(); marshaller = new Jaxb2Marshaller();
marshaller.setClassesToBeBound(new Class[]{Flights.class, FlightType.class}); marshaller.setClassesToBeBound(new Class[]{Flights.class, FlightType.class});
marshaller.afterPropertiesSet(); marshaller.afterPropertiesSet();
Method createFlights = ObjectFactory.class.getDeclaredMethod("createFlights"); testSupports(marshaller);
assertTrue("Jaxb2Marshaller does not support Flights", marshaller.supports(createFlights.getReturnType())); }
Method createFlight = ObjectFactory.class.getDeclaredMethod("createFlight", FlightType.class);
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>", 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 @Test
...@@ -185,19 +207,24 @@ public class Jaxb2MarshallerTests extends AbstractMarshallerTests { ...@@ -185,19 +207,24 @@ public class Jaxb2MarshallerTests extends AbstractMarshallerTests {
assertTrue("No XML written", writer.toString().length() > 0); assertTrue("No XML written", writer.toString().length() > 0);
} }
@Test @XmlRootElement
public void subclass() throws Exception { public static class DummyRootElement {
assertTrue("Flights subclass is not supported", marshaller.supports(FlightsSubclass.class));
FlightType flight = new FlightType(); private DummyType t = new DummyType();
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());
} }
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;
} }
} }
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册