1   /*
2    * Copyright 2006 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.oxm.jaxb;
18  
19  import java.awt.*;
20  import java.io.ByteArrayOutputStream;
21  import java.io.StringWriter;
22  import java.lang.reflect.Method;
23  import java.lang.reflect.ParameterizedType;
24  import java.lang.reflect.Type;
25  import java.math.BigDecimal;
26  import java.math.BigInteger;
27  import java.net.URI;
28  import java.util.Calendar;
29  import java.util.Collections;
30  import java.util.Date;
31  import java.util.UUID;
32  import javax.activation.DataHandler;
33  import javax.activation.FileDataSource;
34  import javax.xml.bind.JAXBElement;
35  import javax.xml.datatype.Duration;
36  import javax.xml.datatype.XMLGregorianCalendar;
37  import javax.xml.namespace.QName;
38  import javax.xml.parsers.DocumentBuilder;
39  import javax.xml.parsers.DocumentBuilderFactory;
40  import javax.xml.stream.XMLEventWriter;
41  import javax.xml.stream.XMLOutputFactory;
42  import javax.xml.stream.XMLStreamWriter;
43  import javax.xml.transform.Result;
44  import javax.xml.transform.Source;
45  import javax.xml.transform.dom.DOMResult;
46  import javax.xml.transform.sax.SAXResult;
47  import javax.xml.transform.stax.StAXResult;
48  import javax.xml.transform.stream.StreamResult;
49  
50  import org.custommonkey.xmlunit.XMLTestCase;
51  import static org.easymock.EasyMock.*;
52  import org.w3c.dom.Document;
53  import org.w3c.dom.Element;
54  import org.w3c.dom.Text;
55  import org.xml.sax.Attributes;
56  import org.xml.sax.ContentHandler;
57  import org.xml.sax.Locator;
58  
59  import org.springframework.core.io.ClassPathResource;
60  import org.springframework.core.io.Resource;
61  import org.springframework.oxm.XmlMappingException;
62  import org.springframework.oxm.jaxb2.FlightType;
63  import org.springframework.oxm.jaxb2.Flights;
64  import org.springframework.oxm.jaxb2.ObjectFactory;
65  import org.springframework.oxm.mime.MimeContainer;
66  import org.springframework.util.FileCopyUtils;
67  import org.springframework.xml.transform.StaxResult;
68  import org.springframework.xml.transform.StringResult;
69  
70  public class Jaxb2MarshallerTest extends XMLTestCase {
71  
72      private static final String CONTEXT_PATH = "org.springframework.oxm.jaxb2";
73  
74      private static final String EXPECTED_STRING =
75              "<tns:flights xmlns:tns=\"http://samples.springframework.org/flight\">" +
76                      "<tns:flight><tns:number>42</tns:number></tns:flight></tns:flights>";
77  
78      private Jaxb2Marshaller marshaller;
79  
80      private Flights flights;
81  
82      protected void setUp() throws Exception {
83          marshaller = new Jaxb2Marshaller();
84          marshaller.setContextPath(CONTEXT_PATH);
85          marshaller.afterPropertiesSet();
86          FlightType flight = new FlightType();
87          flight.setNumber(42L);
88          flights = new Flights();
89          flights.getFlight().add(flight);
90      }
91  
92      public void testMarshalDOMResult() throws Exception {
93          DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
94          DocumentBuilder builder = documentBuilderFactory.newDocumentBuilder();
95          Document document = builder.newDocument();
96          DOMResult domResult = new DOMResult(document);
97          marshaller.marshal(flights, domResult);
98          Document expected = builder.newDocument();
99          Element flightsElement = expected.createElementNS("http://samples.springframework.org/flight", "tns:flights");
100         expected.appendChild(flightsElement);
101         Element flightElement = expected.createElementNS("http://samples.springframework.org/flight", "tns:flight");
102         flightsElement.appendChild(flightElement);
103         Element numberElement = expected.createElementNS("http://samples.springframework.org/flight", "tns:number");
104         flightElement.appendChild(numberElement);
105         Text text = expected.createTextNode("42");
106         numberElement.appendChild(text);
107         assertXMLEqual("Marshaller writes invalid DOMResult", expected, document);
108     }
109 
110     public void testMarshalStreamResultWriter() throws Exception {
111         StringWriter writer = new StringWriter();
112         StreamResult result = new StreamResult(writer);
113         marshaller.marshal(flights, result);
114         assertXMLEqual("Marshaller writes invalid StreamResult", EXPECTED_STRING, writer.toString());
115     }
116 
117     public void testMarshalStreamResultOutputStream() throws Exception {
118         ByteArrayOutputStream os = new ByteArrayOutputStream();
119         StreamResult result = new StreamResult(os);
120         marshaller.marshal(flights, result);
121         assertXMLEqual("Marshaller writes invalid StreamResult", EXPECTED_STRING,
122                 new String(os.toByteArray(), "UTF-8"));
123     }
124 
125     public void testMarshalStaxResultXMLStreamWriter() throws Exception {
126         XMLOutputFactory outputFactory = XMLOutputFactory.newInstance();
127         StringWriter writer = new StringWriter();
128         XMLStreamWriter streamWriter = outputFactory.createXMLStreamWriter(writer);
129         StaxResult result = new StaxResult(streamWriter);
130         marshaller.marshal(flights, result);
131         assertXMLEqual("Marshaller writes invalid StreamResult", EXPECTED_STRING, writer.toString());
132     }
133 
134     public void testMarshalStaxResultXMLEventWriter() throws Exception {
135         XMLOutputFactory outputFactory = XMLOutputFactory.newInstance();
136         StringWriter writer = new StringWriter();
137         XMLEventWriter eventWriter = outputFactory.createXMLEventWriter(writer);
138         StaxResult result = new StaxResult(eventWriter);
139         marshaller.marshal(flights, result);
140         assertXMLEqual("Marshaller writes invalid StreamResult", EXPECTED_STRING, writer.toString());
141     }
142 
143     public void testMarshalStaxResultXMLStreamWriterJaxp14() throws Exception {
144         XMLOutputFactory outputFactory = XMLOutputFactory.newInstance();
145         StringWriter writer = new StringWriter();
146         XMLStreamWriter streamWriter = outputFactory.createXMLStreamWriter(writer);
147         StAXResult result = new StAXResult(streamWriter);
148         marshaller.marshal(flights, result);
149         assertXMLEqual("Marshaller writes invalid StreamResult", EXPECTED_STRING, writer.toString());
150     }
151 
152     public void testMarshalStaxResultXMLEventWriterJaxp14() throws Exception {
153         XMLOutputFactory outputFactory = XMLOutputFactory.newInstance();
154         StringWriter writer = new StringWriter();
155         XMLEventWriter eventWriter = outputFactory.createXMLEventWriter(writer);
156         StAXResult result = new StAXResult(eventWriter);
157         marshaller.marshal(flights, result);
158         assertXMLEqual("Marshaller writes invalid StreamResult", EXPECTED_STRING, writer.toString());
159     }
160 
161     public void testProperties() throws Exception {
162         Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
163         marshaller.setContextPath(CONTEXT_PATH);
164         marshaller.setMarshallerProperties(
165                 Collections.singletonMap(javax.xml.bind.Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE));
166         marshaller.afterPropertiesSet();
167     }
168 
169     public void testNoContextPathOrClassesToBeBound() throws Exception {
170         try {
171             Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
172             marshaller.afterPropertiesSet();
173             fail("Should have thrown an IllegalArgumentException");
174         }
175         catch (IllegalArgumentException e) {
176         }
177     }
178 
179     public void testInvalidContextPath() throws Exception {
180         try {
181             Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
182             marshaller.setContextPath("ab");
183             marshaller.afterPropertiesSet();
184             fail("Should have thrown an XmlMappingException");
185         }
186         catch (XmlMappingException ex) {
187         }
188     }
189 
190     public void testMarshalInvalidClass() throws Exception {
191         Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
192         marshaller.setClassesToBeBound(new Class[]{FlightType.class});
193         marshaller.afterPropertiesSet();
194         Result result = new StreamResult(new StringWriter());
195         Flights flights = new Flights();
196         try {
197             marshaller.marshal(flights, result);
198             fail("Should have thrown an MarshallingFailureException");
199         }
200         catch (XmlMappingException ex) {
201             // expected
202         }
203     }
204 
205     public void testMarshalSaxResult() throws Exception {
206         ContentHandler handlerMock = createStrictMock(ContentHandler.class);
207         handlerMock.setDocumentLocator(isA(Locator.class));
208         handlerMock.startDocument();
209         handlerMock.startPrefixMapping("", "http://samples.springframework.org/flight");
210         handlerMock.startElement(eq("http://samples.springframework.org/flight"), eq("flights"), eq("flights"),
211                 isA(Attributes.class));
212         handlerMock.startElement(eq("http://samples.springframework.org/flight"), eq("flight"), eq("flight"),
213                 isA(Attributes.class));
214         handlerMock.startElement(eq("http://samples.springframework.org/flight"), eq("number"), eq("number"),
215                 isA(Attributes.class));
216         handlerMock.characters(isA(char[].class), eq(0), eq(2));
217         handlerMock.endElement("http://samples.springframework.org/flight", "number", "number");
218         handlerMock.endElement("http://samples.springframework.org/flight", "flight", "flight");
219         handlerMock.endElement("http://samples.springframework.org/flight", "flights", "flights");
220         handlerMock.endPrefixMapping("");
221         handlerMock.endDocument();
222         replay(handlerMock);
223 
224         SAXResult result = new SAXResult(handlerMock);
225         marshaller.marshal(flights, result);
226         verify(handlerMock);
227     }
228 
229     public void testSupportsContextPath() throws Exception {
230         Method createFlights = ObjectFactory.class.getDeclaredMethod("createFlights");
231         assertTrue("Jaxb2Marshaller does not support Flights",
232                 marshaller.supports(createFlights.getGenericReturnType()));
233         Method createFlight = ObjectFactory.class.getDeclaredMethod("createFlight", FlightType.class);
234         assertTrue("Jaxb2Marshaller does not support JAXBElement<FlightsType>",
235                 marshaller.supports(createFlight.getGenericReturnType()));
236         assertFalse("Jaxb2Marshaller supports non-parameterized JAXBElement", marshaller.supports(JAXBElement.class));
237         JAXBElement<Jaxb2MarshallerTest> testElement =
238                 new JAXBElement<Jaxb2MarshallerTest>(new QName("something"), Jaxb2MarshallerTest.class, null, this);
239         assertFalse("Jaxb2Marshaller supports wrong JAXBElement", marshaller.supports(testElement.getClass()));
240     }
241 
242     public void testSupportsClassesToBeBound() throws Exception {
243         marshaller = new Jaxb2Marshaller();
244         marshaller.setClassesToBeBound(new Class[]{Flights.class, FlightType.class});
245         marshaller.afterPropertiesSet();
246         Method createFlights = ObjectFactory.class.getDeclaredMethod("createFlights");
247         assertTrue("Jaxb2Marshaller does not support Flights",
248                 marshaller.supports(createFlights.getGenericReturnType()));
249         Method createFlight = ObjectFactory.class.getDeclaredMethod("createFlight", FlightType.class);
250         assertTrue("Jaxb2Marshaller does not support JAXBElement<FlightsType>",
251                 marshaller.supports(createFlight.getGenericReturnType()));
252         assertFalse("Jaxb2Marshaller supports non-parameterized JAXBElement", marshaller.supports(JAXBElement.class));
253         JAXBElement<Jaxb2MarshallerTest> testElement =
254                 new JAXBElement<Jaxb2MarshallerTest>(new QName("something"), Jaxb2MarshallerTest.class, null, this);
255         assertFalse("Jaxb2Marshaller supports wrong JAXBElement", marshaller.supports(testElement.getClass()));
256     }
257 
258     public void testSupportsPrimitives() throws Exception {
259         Method primitives = getClass().getDeclaredMethod("primitives", JAXBElement.class, JAXBElement.class,
260                 JAXBElement.class, JAXBElement.class, JAXBElement.class, JAXBElement.class, JAXBElement.class,
261                 JAXBElement.class);
262         Type[] types = primitives.getGenericParameterTypes();
263         for (int i = 0; i < types.length; i++) {
264             ParameterizedType type = (ParameterizedType) types[i];
265             assertTrue("Jaxb2Marshaller does not support " + type, marshaller.supports(types[i]));
266         }
267     }
268 
269     public void testSupportsStandards() throws Exception {
270         Method standards = getClass().getDeclaredMethod("standards", JAXBElement.class, JAXBElement.class,
271                 JAXBElement.class, JAXBElement.class, JAXBElement.class, JAXBElement.class, JAXBElement.class,
272                 JAXBElement.class, JAXBElement.class, JAXBElement.class, JAXBElement.class, JAXBElement.class,
273                 JAXBElement.class, JAXBElement.class);
274         Type[] types = standards.getGenericParameterTypes();
275         for (int i = 0; i < types.length; i++) {
276             ParameterizedType type = (ParameterizedType) types[i];
277             assertTrue("Jaxb2Marshaller does not support " + type, marshaller.supports(types[i]));
278         }
279     }
280 
281     public void testMarshalAttachments() throws Exception {
282         marshaller = new Jaxb2Marshaller();
283         marshaller.setClassesToBeBound(new Class[]{BinaryObject.class});
284         marshaller.setMtomEnabled(true);
285         marshaller.afterPropertiesSet();
286         MimeContainer mimeContainer = createMock(MimeContainer.class);
287 
288         Resource logo = new ClassPathResource("spring-ws.png", getClass());
289         DataHandler dataHandler = new DataHandler(new FileDataSource(logo.getFile()));
290 
291         expect(mimeContainer.convertToXopPackage()).andReturn(true);
292         mimeContainer.addAttachment(isA(String.class), isA(DataHandler.class));
293         expectLastCall().times(3);
294 
295         replay(mimeContainer);
296         byte[] bytes = FileCopyUtils.copyToByteArray(logo.getInputStream());
297         BinaryObject object = new BinaryObject(bytes, dataHandler);
298         Result result = new StringResult();
299         marshaller.marshal(object, result, mimeContainer);
300         verify(mimeContainer);
301         assertTrue("No XML written", result.toString().length() > 0);
302     }
303 
304     private void primitives(JAXBElement<Boolean> bool,
305                             JAXBElement<Byte> aByte,
306                             JAXBElement<Short> aShort,
307                             JAXBElement<Integer> anInteger,
308                             JAXBElement<Long> aLong,
309                             JAXBElement<Float> aFloat,
310                             JAXBElement<Double> aDouble,
311                             JAXBElement<byte[]> byteArray) {
312     }
313 
314     private void standards(JAXBElement<String> string,
315                            JAXBElement<BigInteger> integer,
316                            JAXBElement<BigDecimal> decimal,
317                            JAXBElement<Calendar> calendar,
318                            JAXBElement<Date> date,
319                            JAXBElement<QName> qName,
320                            JAXBElement<URI> uri,
321                            JAXBElement<XMLGregorianCalendar> xmlGregorianCalendar,
322                            JAXBElement<Duration> duration,
323                            JAXBElement<Object> object,
324                            JAXBElement<Image> image,
325                            JAXBElement<DataHandler> dataHandler,
326                            JAXBElement<Source> source,
327                            JAXBElement<UUID> uuid) {
328     }
329 }