View Javadoc
1   /*
2    * Copyright 2002-2014 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.http.converter.xml;
18  
19  import java.nio.charset.Charset;
20  import javax.xml.bind.Marshaller;
21  import javax.xml.bind.Unmarshaller;
22  import javax.xml.bind.annotation.XmlAttribute;
23  import javax.xml.bind.annotation.XmlElement;
24  import javax.xml.bind.annotation.XmlRootElement;
25  import javax.xml.bind.annotation.XmlType;
26  import javax.xml.bind.annotation.adapters.XmlAdapter;
27  import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
28  
29  import org.junit.Before;
30  import org.junit.Test;
31  
32  import org.springframework.aop.framework.AdvisedSupport;
33  import org.springframework.aop.framework.AopProxy;
34  import org.springframework.aop.framework.DefaultAopProxyFactory;
35  import org.springframework.core.io.ClassPathResource;
36  import org.springframework.core.io.Resource;
37  import org.springframework.http.MediaType;
38  import org.springframework.http.MockHttpInputMessage;
39  import org.springframework.http.MockHttpOutputMessage;
40  
41  import static org.custommonkey.xmlunit.XMLAssert.*;
42  import static org.junit.Assert.assertEquals;
43  import static org.junit.Assert.assertFalse;
44  import static org.junit.Assert.assertTrue;
45  
46  /**
47   * Tests for {@link Jaxb2RootElementHttpMessageConverter}.
48   *
49   * @author Arjen Poutsma
50   * @author Sebastien Deleuze
51   */
52  public class Jaxb2RootElementHttpMessageConverterTests {
53  
54  	private Jaxb2RootElementHttpMessageConverter converter;
55  
56  	private RootElement rootElement;
57  
58  	private RootElement rootElementCglib;
59  
60  	@Before
61  	public void setUp() {
62  		converter = new Jaxb2RootElementHttpMessageConverter();
63  		rootElement = new RootElement();
64  		DefaultAopProxyFactory proxyFactory = new DefaultAopProxyFactory();
65  		AdvisedSupport advisedSupport = new AdvisedSupport();
66  		advisedSupport.setTarget(rootElement);
67  		advisedSupport.setProxyTargetClass(true);
68  		AopProxy proxy = proxyFactory.createAopProxy(advisedSupport);
69  		rootElementCglib = (RootElement) proxy.getProxy();
70  	}
71  
72  	@Test
73  	public void canRead() throws Exception {
74  		assertTrue("Converter does not support reading @XmlRootElement", converter.canRead(RootElement.class, null));
75  		assertTrue("Converter does not support reading @XmlType", converter.canRead(Type.class, null));
76  	}
77  
78  	@Test
79  	public void canWrite() throws Exception {
80  		assertTrue("Converter does not support writing @XmlRootElement", converter.canWrite(RootElement.class, null));
81  		assertTrue("Converter does not support writing @XmlRootElement subclass", converter.canWrite(RootElementSubclass.class, null));
82  		assertTrue("Converter does not support writing @XmlRootElement subclass", converter.canWrite(rootElementCglib.getClass(), null));
83  		assertFalse("Converter supports writing @XmlType", converter.canWrite(Type.class, null));
84  	}
85  
86  	@Test
87  	public void readXmlRootElement() throws Exception {
88  		byte[] body = "<rootElement><type s=\"Hello World\"/></rootElement>".getBytes("UTF-8");
89  		MockHttpInputMessage inputMessage = new MockHttpInputMessage(body);
90  		RootElement result = (RootElement) converter.read(RootElement.class, inputMessage);
91  		assertEquals("Invalid result", "Hello World", result.type.s);
92  	}
93  
94  	@Test
95  	public void readXmlRootElementSubclass() throws Exception {
96  		byte[] body = "<rootElement><type s=\"Hello World\"/></rootElement>".getBytes("UTF-8");
97  		MockHttpInputMessage inputMessage = new MockHttpInputMessage(body);
98  		RootElementSubclass result = (RootElementSubclass) converter.read(RootElementSubclass.class, inputMessage);
99  		assertEquals("Invalid result", "Hello World", result.getType().s);
100 	}
101 
102 	@Test
103 	public void readXmlType() throws Exception {
104 		byte[] body = "<foo s=\"Hello World\"/>".getBytes("UTF-8");
105 		MockHttpInputMessage inputMessage = new MockHttpInputMessage(body);
106 		Type result = (Type) converter.read(Type.class, inputMessage);
107 		assertEquals("Invalid result", "Hello World", result.s);
108 	}
109 
110 	@Test
111 	public void readXmlRootElementExternalEntityDisabled() throws Exception {
112 		Resource external = new ClassPathResource("external.txt", getClass());
113 		String content =  "<!DOCTYPE root SYSTEM \"http://192.168.28.42/1.jsp\" [" +
114 				"  <!ELEMENT external ANY >\n" +
115 				"  <!ENTITY ext SYSTEM \"" + external.getURI() + "\" >]>" +
116 				"  <rootElement><external>&ext;</external></rootElement>";
117 		MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes("UTF-8"));
118 		RootElement rootElement = (RootElement) converter.read(RootElement.class, inputMessage);
119 
120 		assertEquals("", rootElement.external);
121 	}
122 
123 	@Test
124 	public void readXmlRootElementExternalEntityEnabled() throws Exception {
125 		Resource external = new ClassPathResource("external.txt", getClass());
126 		String content =  "<!DOCTYPE root [" +
127 				"  <!ELEMENT external ANY >\n" +
128 				"  <!ENTITY ext SYSTEM \"" + external.getURI() + "\" >]>" +
129 				"  <rootElement><external>&ext;</external></rootElement>";
130 		MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes("UTF-8"));
131 		this.converter.setProcessExternalEntities(true);
132 		RootElement rootElement = (RootElement) converter.read(RootElement.class, inputMessage);
133 
134 		assertEquals("Foo Bar", rootElement.external);
135 	}
136 
137 	@Test
138 	public void writeXmlRootElement() throws Exception {
139 		MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
140 		converter.write(rootElement, null, outputMessage);
141 		assertEquals("Invalid content-type", new MediaType("application", "xml"),
142 				outputMessage.getHeaders().getContentType());
143 		assertXMLEqual("Invalid result", "<rootElement><type s=\"Hello World\"/></rootElement>",
144 				outputMessage.getBodyAsString(Charset.forName("UTF-8")));
145 	}
146 
147 	@Test
148 	public void writeXmlRootElementSubclass() throws Exception {
149 		MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
150 		converter.write(rootElementCglib, null, outputMessage);
151 		assertEquals("Invalid content-type", new MediaType("application", "xml"),
152 				outputMessage.getHeaders().getContentType());
153 		assertXMLEqual("Invalid result", "<rootElement><type s=\"Hello World\"/></rootElement>",
154 				outputMessage.getBodyAsString(Charset.forName("UTF-8")));
155 	}
156 
157 	// SPR-11488
158 
159 	@Test
160 	public void customizeMarshaller() throws Exception {
161 		MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
162 		MyJaxb2RootElementHttpMessageConverter myConverter = new MyJaxb2RootElementHttpMessageConverter();
163 		myConverter.write(new MyRootElement(new MyCustomElement("a", "b")), null, outputMessage);
164 		assertXMLEqual("Invalid result", "<myRootElement><element>a|||b</element></myRootElement>",
165 				outputMessage.getBodyAsString(Charset.forName("UTF-8")));
166 	}
167 
168 	@Test
169 	public void customizeUnmarshaller() throws Exception {
170 		byte[] body = "<myRootElement><element>a|||b</element></myRootElement>".getBytes("UTF-8");
171 		MyJaxb2RootElementHttpMessageConverter myConverter = new MyJaxb2RootElementHttpMessageConverter();
172 		MockHttpInputMessage inputMessage = new MockHttpInputMessage(body);
173 		MyRootElement result = (MyRootElement) myConverter.read(MyRootElement.class, inputMessage);
174 		assertEquals("a", result.getElement().getField1());
175 		assertEquals("b", result.getElement().getField2());
176 	}
177 
178 	@XmlRootElement
179 	public static class RootElement {
180 
181 		private Type type = new Type();
182 
183 		@XmlElement(required=false)
184 		public String external;
185 
186 		public Type getType() {
187 			return this.type;
188 		}
189 
190 		@XmlElement
191 		public void setType(Type type) {
192 			this.type = type;
193 		}
194 	}
195 
196 	@XmlType
197 	public static class Type {
198 
199 		@XmlAttribute
200 		public String s = "Hello World";
201 
202 	}
203 
204 	public static class RootElementSubclass extends RootElement {
205 
206 	}
207 
208 	public static class MyJaxb2RootElementHttpMessageConverter extends Jaxb2RootElementHttpMessageConverter {
209 
210 		@Override
211 		protected void customizeMarshaller(Marshaller marshaller) {
212 			marshaller.setAdapter(new MyCustomElementAdapter());
213 		}
214 
215 		@Override
216 		protected void customizeUnmarshaller(Unmarshaller unmarshaller) {
217 			unmarshaller.setAdapter(new MyCustomElementAdapter());
218 		}
219 	}
220 
221 	public static class MyCustomElement {
222 		private String field1;
223 		private String field2;
224 
225 		public MyCustomElement() {
226 		}
227 
228 		public MyCustomElement(String field1, String field2) {
229 			this.field1 = field1;
230 			this.field2 = field2;
231 		}
232 
233 		public String getField1() {
234 			return field1;
235 		}
236 
237 		public void setField1(String field1) {
238 			this.field1 = field1;
239 		}
240 
241 		public String getField2() {
242 			return field2;
243 		}
244 
245 		public void setField2(String field2) {
246 			this.field2 = field2;
247 		}
248 	}
249 
250 	@XmlRootElement
251 	public static class MyRootElement {
252 
253 		private MyCustomElement element;
254 
255 		public MyRootElement() {
256 
257 		}
258 
259 		public MyRootElement(MyCustomElement element) {
260 			this.element = element;
261 		}
262 
263 		@XmlJavaTypeAdapter(MyCustomElementAdapter.class)
264 		public MyCustomElement getElement() {
265 			return element;
266 		}
267 
268 		public void setElement(MyCustomElement element) {
269 			this.element = element;
270 		}
271 	}
272 
273 	public static class MyCustomElementAdapter extends XmlAdapter<String, MyCustomElement> {
274 
275 		@Override
276 		public String marshal(MyCustomElement c) throws Exception {
277 			return c.getField1() + "|||" + c.getField2();
278 		}
279 
280 		@Override
281 		public MyCustomElement unmarshal(String c) throws Exception {
282 			String[] t = c.split("\\|\\|\\|");
283 			return new MyCustomElement(t[0], t[1]);
284 		}
285 
286 	}
287 
288 }