diff --git a/spring-web/src/main/java/org/springframework/http/converter/xml/AbstractJaxb2HttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/xml/AbstractJaxb2HttpMessageConverter.java index e2ba765a5ce..ca465a1698d 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/xml/AbstractJaxb2HttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/xml/AbstractJaxb2HttpMessageConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 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. @@ -47,7 +47,9 @@ public abstract class AbstractJaxb2HttpMessageConverter extends AbstractXmlHt protected final Marshaller createMarshaller(Class clazz) { try { JAXBContext jaxbContext = getJaxbContext(clazz); - return jaxbContext.createMarshaller(); + Marshaller marshaller = jaxbContext.createMarshaller(); + customizeMarshaller(marshaller); + return marshaller; } catch (JAXBException ex) { throw new HttpMessageConversionException( @@ -55,6 +57,16 @@ public abstract class AbstractJaxb2HttpMessageConverter extends AbstractXmlHt } } + /** + * Customize the {@link Marshaller} created by this + * message converter before using it to write the object to the output. + * @param marshaller the marshaller to customize + * @see #createMarshaller(Class) + * @since 4.0.3 + */ + protected void customizeMarshaller(Marshaller marshaller) { + } + /** * Create a new {@link Unmarshaller} for the given class. * @param clazz the class to create the unmarshaller for @@ -64,7 +76,9 @@ public abstract class AbstractJaxb2HttpMessageConverter extends AbstractXmlHt protected final Unmarshaller createUnmarshaller(Class clazz) throws JAXBException { try { JAXBContext jaxbContext = getJaxbContext(clazz); - return jaxbContext.createUnmarshaller(); + Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); + customizeUnmarshaller(unmarshaller); + return unmarshaller; } catch (JAXBException ex) { throw new HttpMessageConversionException( @@ -72,6 +86,16 @@ public abstract class AbstractJaxb2HttpMessageConverter extends AbstractXmlHt } } + /** + * Customize the {@link Unmarshaller} created by this + * message converter before using it to read the object from the input. + * @param unmarshaller the unmarshaller to customize + * @see #createUnmarshaller(Class) + * @since 4.0.3 + */ + protected void customizeUnmarshaller(Unmarshaller unmarshaller) { + } + /** * Return a {@link JAXBContext} for the given class. * @param clazz the class to return the context for diff --git a/spring-web/src/main/java/org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverter.java index ad8d7d90b10..6c1b0c7913b 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverter.java @@ -28,7 +28,6 @@ import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlType; import javax.xml.transform.Result; import javax.xml.transform.Source; -import javax.xml.transform.dom.DOMSource; import javax.xml.transform.sax.SAXSource; import javax.xml.transform.stream.StreamSource; @@ -39,7 +38,6 @@ import org.springframework.http.converter.HttpMessageConversionException; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.converter.HttpMessageNotWritableException; import org.springframework.util.ClassUtils; -import org.springframework.util.xml.StaxUtils; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; @@ -53,6 +51,7 @@ import org.xml.sax.helpers.XMLReaderFactory; * annotated with with {@link XmlRootElement}, or subclasses thereof. * * @author Arjen Poutsma + * @author Sebastien Deleuze * @since 3.0 */ public class Jaxb2RootElementHttpMessageConverter extends AbstractJaxb2HttpMessageConverter { diff --git a/spring-web/src/test/java/org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverterTests.java index fe1e39283de..a4fcd106eef 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverterTests.java @@ -17,15 +17,20 @@ package org.springframework.http.converter.xml; import java.nio.charset.Charset; +import javax.xml.bind.Marshaller; +import javax.xml.bind.Unmarshaller; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; 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.XmlJavaTypeAdapter; import static org.custommonkey.xmlunit.XMLAssert.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; + import org.junit.Before; import org.junit.Test; @@ -37,10 +42,13 @@ import org.springframework.core.io.Resource; import org.springframework.http.MediaType; import org.springframework.http.MockHttpInputMessage; import org.springframework.http.MockHttpOutputMessage; -import org.springframework.http.converter.HttpMessageNotReadableException; -import org.xml.sax.SAXParseException; -/** @author Arjen Poutsma */ +/** + * Tests for {@link Jaxb2RootElementHttpMessageConverter}. + * + * @author Arjen Poutsma + * @author Sebastien Deleuze + */ public class Jaxb2RootElementHttpMessageConverterTests { private Jaxb2RootElementHttpMessageConverter converter; @@ -146,6 +154,27 @@ public class Jaxb2RootElementHttpMessageConverterTests { outputMessage.getBodyAsString(Charset.forName("UTF-8"))); } + // SPR-11488 + + @Test + public void customizeMarshaller() throws Exception { + MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); + MyJaxb2RootElementHttpMessageConverter myConverter = new MyJaxb2RootElementHttpMessageConverter(); + myConverter.write(new MyRootElement(new MyCustomElement("a", "b")), null, outputMessage); + assertXMLEqual("Invalid result", "a|||b", + outputMessage.getBodyAsString(Charset.forName("UTF-8"))); + } + + @Test + public void customizeUnmarshaller() throws Exception { + byte[] body = "a|||b".getBytes("UTF-8"); + MyJaxb2RootElementHttpMessageConverter myConverter = new MyJaxb2RootElementHttpMessageConverter(); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(body); + MyRootElement result = (MyRootElement) myConverter.read(MyRootElement.class, inputMessage); + assertEquals("a", result.getElement().getField1()); + assertEquals("b", result.getElement().getField2()); + } + @XmlRootElement public static class RootElement { @@ -176,4 +205,84 @@ public class Jaxb2RootElementHttpMessageConverterTests { } + public static class MyJaxb2RootElementHttpMessageConverter extends Jaxb2RootElementHttpMessageConverter { + + @Override + protected void customizeMarshaller(Marshaller marshaller) { + marshaller.setAdapter(new MyCustomElementAdapter()); + } + + @Override + protected void customizeUnmarshaller(Unmarshaller unmarshaller) { + unmarshaller.setAdapter(new MyCustomElementAdapter()); + } + } + + public static class MyCustomElement { + private String field1; + private String field2; + + public MyCustomElement() { + } + + public MyCustomElement(String field1, String field2) { + this.field1 = field1; + this.field2 = field2; + } + + public String getField1() { + return field1; + } + + public void setField1(String field1) { + this.field1 = field1; + } + + public String getField2() { + return field2; + } + + public void setField2(String field2) { + this.field2 = field2; + } + } + + @XmlRootElement + public static class MyRootElement { + + private MyCustomElement element; + + public MyRootElement() { + + } + + public MyRootElement(MyCustomElement element) { + this.element = element; + } + + @XmlJavaTypeAdapter(MyCustomElementAdapter.class) + public MyCustomElement getElement() { + return element; + } + + public void setElement(MyCustomElement element) { + this.element = element; + } + } + + public static class MyCustomElementAdapter extends XmlAdapter { + + @Override + public String marshal(MyCustomElement c) throws Exception { + return c.getField1() + "|||" + c.getField2(); + } + + @Override + public MyCustomElement unmarshal(String c) throws Exception { + String[] t = c.split("\\|\\|\\|"); + return new MyCustomElement(t[0], t[1]); + } + + } + }