From 45be8c06921206bfd008bef8044f1e7bcbe27595 Mon Sep 17 00:00:00 2001 From: Sebastien Deleuze Date: Fri, 28 Feb 2014 17:04:15 +0100 Subject: [PATCH 1/2] Add marshalling hooks in Jaxb2RootElementHttpMessageConverter Allow Jaxb2RootElementHttpMessageConverter subclasses to customize the {@link Marshaller} and the {@link Unmarshaller} created by the message converter. Issue: SPR-11488 --- .../Jaxb2RootElementHttpMessageConverter.java | 27 ++++- ...2RootElementHttpMessageConverterTests.java | 113 +++++++++++++++++- 2 files changed, 135 insertions(+), 5 deletions(-) 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..4da0750a9e9 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 { @@ -90,6 +89,7 @@ public class Jaxb2RootElementHttpMessageConverter extends AbstractJaxb2HttpMessa try { source = processSource(source); Unmarshaller unmarshaller = createUnmarshaller(clazz); + this.customizeUnmarshaller(unmarshaller); if (clazz.isAnnotationPresent(XmlRootElement.class)) { return unmarshaller.unmarshal(source); } @@ -132,6 +132,7 @@ public class Jaxb2RootElementHttpMessageConverter extends AbstractJaxb2HttpMessa try { Class clazz = ClassUtils.getUserClass(o); Marshaller marshaller = createMarshaller(clazz); + this.customizeMarshaller(marshaller); setCharset(headers.getContentType(), marshaller); marshaller.marshal(o, result); } @@ -149,4 +150,26 @@ public class Jaxb2RootElementHttpMessageConverter extends AbstractJaxb2HttpMessa } } + /** + * 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) { + + } + + /** + * 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) { + + } + } 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..4bcd2cae7dc 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,25 @@ public class Jaxb2RootElementHttpMessageConverterTests { outputMessage.getBodyAsString(Charset.forName("UTF-8"))); } + @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 +203,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]); + } + + } + } From c4000727eff5d5af4ac47797121a686f07f278bd Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Fri, 28 Feb 2014 11:54:34 -0500 Subject: [PATCH 2/2] Move customize(Un)Marshaller methods to abstract class Issue: SPR-11488 --- .../AbstractJaxb2HttpMessageConverter.java | 30 +++++++++++++++++-- .../Jaxb2RootElementHttpMessageConverter.java | 24 --------------- ...2RootElementHttpMessageConverterTests.java | 2 ++ 3 files changed, 29 insertions(+), 27 deletions(-) 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 4da0750a9e9..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 @@ -89,7 +89,6 @@ public class Jaxb2RootElementHttpMessageConverter extends AbstractJaxb2HttpMessa try { source = processSource(source); Unmarshaller unmarshaller = createUnmarshaller(clazz); - this.customizeUnmarshaller(unmarshaller); if (clazz.isAnnotationPresent(XmlRootElement.class)) { return unmarshaller.unmarshal(source); } @@ -132,7 +131,6 @@ public class Jaxb2RootElementHttpMessageConverter extends AbstractJaxb2HttpMessa try { Class clazz = ClassUtils.getUserClass(o); Marshaller marshaller = createMarshaller(clazz); - this.customizeMarshaller(marshaller); setCharset(headers.getContentType(), marshaller); marshaller.marshal(o, result); } @@ -150,26 +148,4 @@ public class Jaxb2RootElementHttpMessageConverter extends AbstractJaxb2HttpMessa } } - /** - * 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) { - - } - - /** - * 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) { - - } - } 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 4bcd2cae7dc..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 @@ -154,6 +154,8 @@ public class Jaxb2RootElementHttpMessageConverterTests { outputMessage.getBodyAsString(Charset.forName("UTF-8"))); } + // SPR-11488 + @Test public void customizeMarshaller() throws Exception { MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();