/* * Copyright 2002-2016 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.castor; import static org.junit.Assert.*; import static org.mockito.Mockito.*; import static org.xmlunit.matchers.CompareMatcher.*; import java.io.StringWriter; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import javax.xml.transform.Source; import javax.xml.transform.sax.SAXResult; import javax.xml.transform.stream.StreamResult; import org.castor.xml.XMLProperties; import org.exolab.castor.xml.XercesXMLSerializerFactory; import org.junit.Test; import org.mockito.InOrder; import org.w3c.dom.Node; import org.xml.sax.Attributes; import org.xml.sax.ContentHandler; import org.xmlunit.builder.Input; import org.xmlunit.xpath.JAXPXPathEngine; import org.springframework.core.io.ClassPathResource; import org.springframework.oxm.AbstractMarshallerTests; /** * Tests the {@link CastorMarshaller} class. * * @author Arjen Poutsma * @author Jakub Narloch * @author Sam Brannen */ public class CastorMarshallerTests extends AbstractMarshallerTests<CastorMarshaller> { /** * Represents the expected result that doesn't contain the xml declaration. */ private static final String DOCUMENT_EXPECTED_STRING = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<tns:flights xmlns:tns=\"http://samples.springframework.org/flight\">" + "<tns:flight><tns:number>42</tns:number></tns:flight></tns:flights>"; /** * Represents the expected result that doesn't contain the xml namespaces. */ private static final String SUPPRESSED_NAMESPACE_EXPECTED_STRING = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><flights><flight><number>42</number></flight></flights>"; /** * Represents the expected result with modified root element name. */ private static final String ROOT_ELEMENT_EXPECTED_STRING = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<tns:canceledFlights xmlns:tns=\"http://samples.springframework.org/flight\">" + "<tns:flight><tns:number>42</tns:number></tns:flight></tns:canceledFlights>"; /** * Represents the expected result with 'xsi:type' attribute. */ private static final String XSI_EXPECTED_STRING = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<objects><castor-object xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"" + " xmlns:java=\"http://java.sun.com\"" + " xsi:type=\"java:org.springframework.oxm.castor.CastorObject\">" + "<name>test</name><value>8</value></castor-object></objects>"; /** * Represents the expected result with suppressed 'xsi:type' attribute. */ private static final String SUPPRESSED_XSI_EXPECTED_STRING = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<objects><castor-object><name>test</name><value>8</value></castor-object></objects>"; /** * Represents the expected result with 'xsi:type' attribute for root element. */ private static final String ROOT_WITH_XSI_EXPECTED_STRING = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<objects xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"" + " xmlns:java=\"http://java.sun.com\"" + " xsi:type=\"java:java.util.Arrays$ArrayList\">" + "<castor-object xsi:type=\"java:org.springframework.oxm.castor.CastorObject\">" + "<name>test</name><value>8</value></castor-object></objects>"; /** * Represents the expected result without 'xsi:type' attribute for root element. */ private static final String ROOT_WITHOUT_XSI_EXPECTED_STRING = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<objects><castor-object xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"" + " xmlns:java=\"http://java.sun.com\"" + " xsi:type=\"java:org.springframework.oxm.castor.CastorObject\">" + "<name>test</name><value>8</value></castor-object></objects>"; @Override protected CastorMarshaller createMarshaller() throws Exception { CastorMarshaller marshaller = new CastorMarshaller(); ClassPathResource mappingLocation = new ClassPathResource("mapping.xml", CastorMarshaller.class); marshaller.setMappingLocation(mappingLocation); Map<String, String> props = new HashMap<>(1); props.put(XMLProperties.SERIALIZER_FACTORY, XercesXMLSerializerFactory.class.getName()); marshaller.setCastorProperties(props); marshaller.afterPropertiesSet(); return marshaller; } @Override protected Object createFlights() { Flight flight = new Flight(); flight.setNumber(42L); Flights flights = new Flights(); flights.addFlight(flight); return flights; } @Test public void marshalSaxResult() throws Exception { ContentHandler contentHandler = mock(ContentHandler.class); SAXResult result = new SAXResult(contentHandler); marshaller.marshal(flights, result); InOrder ordered = inOrder(contentHandler); ordered.verify(contentHandler).startDocument(); ordered.verify(contentHandler).startPrefixMapping("tns", "http://samples.springframework.org/flight"); ordered.verify(contentHandler).startElement(eq("http://samples.springframework.org/flight"), eq("flights"), eq("tns:flights"), isA(Attributes.class)); ordered.verify(contentHandler).startElement(eq("http://samples.springframework.org/flight"), eq("flight"), eq("tns:flight"), isA(Attributes.class)); ordered.verify(contentHandler).startElement(eq("http://samples.springframework.org/flight"), eq("number"), eq("tns:number"), isA(Attributes.class)); ordered.verify(contentHandler).characters(eq(new char[]{'4', '2'}), eq(0), eq(2)); ordered.verify(contentHandler).endElement("http://samples.springframework.org/flight", "number", "tns:number"); ordered.verify(contentHandler).endElement("http://samples.springframework.org/flight", "flight", "tns:flight"); ordered.verify(contentHandler).endElement("http://samples.springframework.org/flight", "flights", "tns:flights"); ordered.verify(contentHandler).endPrefixMapping("tns"); ordered.verify(contentHandler).endDocument(); } @Test public void supports() throws Exception { assertTrue("CastorMarshaller does not support Flights", marshaller.supports(Flights.class)); assertTrue("CastorMarshaller does not support Flight", marshaller.supports(Flight.class)); } @Test public void suppressNamespacesTrue() throws Exception { marshaller.setSuppressNamespaces(true); String result = marshalFlights(); assertThat("Marshaller wrote invalid result", result, isSimilarTo(SUPPRESSED_NAMESPACE_EXPECTED_STRING)); } @Test public void suppressNamespacesFalse() throws Exception { marshaller.setSuppressNamespaces(false); String result = marshalFlights(); assertThat("Marshaller wrote invalid result", result, isSimilarTo(EXPECTED_STRING)); } @Test public void suppressXsiTypeTrue() throws Exception { CastorObject castorObject = createCastorObject(); marshaller.setSuppressXsiType(true); marshaller.setRootElement("objects"); String result = marshal(Arrays.asList(castorObject)); assertThat("Marshaller wrote invalid result", result, isSimilarTo(SUPPRESSED_XSI_EXPECTED_STRING)); } @Test public void suppressXsiTypeFalse() throws Exception { CastorObject castorObject = createCastorObject(); marshaller.setSuppressXsiType(false); marshaller.setRootElement("objects"); String result = marshal(Arrays.asList(castorObject)); assertThat("Marshaller wrote invalid result", result, isSimilarTo(XSI_EXPECTED_STRING)); } @Test public void marshalAsDocumentTrue() throws Exception { marshaller.setMarshalAsDocument(true); String result = marshalFlights(); assertThat("Marshaller wrote invalid result", result, isSimilarTo(DOCUMENT_EXPECTED_STRING)); assertTrue("Result doesn't contain xml declaration.", result.contains("<?xml version=\"1.0\" encoding=\"UTF-8\"?>")); } @Test public void marshalAsDocumentFalse() throws Exception { marshaller.setMarshalAsDocument(true); String result = marshalFlights(); assertThat("Marshaller wrote invalid result", result, isSimilarTo(EXPECTED_STRING)); assertFalse("Result contains xml declaration.", result.matches("<\\?\\s*xml")); } @Test public void rootElement() throws Exception { marshaller.setRootElement("canceledFlights"); String result = marshalFlights(); assertThat("Marshaller wrote invalid result", result, isSimilarTo(ROOT_ELEMENT_EXPECTED_STRING)); } @Test public void noNamespaceSchemaLocation() throws Exception { String noNamespaceSchemaLocation = "flights.xsd"; marshaller.setNoNamespaceSchemaLocation(noNamespaceSchemaLocation); String result = marshalFlights(); assertXpathEvaluatesTo("The xsi:noNamespaceSchemaLocation hasn't been written or has invalid value.", noNamespaceSchemaLocation, "/tns:flights/@xsi:noNamespaceSchemaLocation", result); assertThat("Marshaller wrote invalid result", result, isSimilarTo(EXPECTED_STRING)); } @Test public void schemaLocation() throws Exception { String schemaLocation = "flights.xsd"; marshaller.setSchemaLocation(schemaLocation); String result = marshalFlights(); assertXpathEvaluatesTo("The xsi:noNamespaceSchemaLocation hasn't been written or has invalid value.", schemaLocation, "/tns:flights/@xsi:schemaLocation", result); assertThat("Marshaller wrote invalid result", result, isSimilarTo(EXPECTED_STRING)); } @Test public void useXsiTypeAsRootTrue() throws Exception { CastorObject castorObject = createCastorObject(); marshaller.setSuppressXsiType(false); marshaller.setUseXSITypeAtRoot(true); marshaller.setRootElement("objects"); String result = marshal(Arrays.asList(castorObject)); assertThat("Marshaller wrote invalid result", result, isSimilarTo(ROOT_WITH_XSI_EXPECTED_STRING)); } @Test public void useXsiTypeAsRootFalse() throws Exception { CastorObject castorObject = createCastorObject(); marshaller.setSuppressXsiType(false); marshaller.setUseXSITypeAtRoot(false); marshaller.setRootElement("objects"); String result = marshal(Arrays.asList(castorObject)); assertThat("Marshaller wrote invalid result", result, isSimilarTo(ROOT_WITHOUT_XSI_EXPECTED_STRING)); } private String marshal(Object object) throws Exception { StringWriter writer = new StringWriter(); StreamResult result = new StreamResult(writer); marshaller.marshal(object, result); return writer.toString(); } private String marshalFlights() throws Exception { return marshal(flights); } /** * Assert the values of xpath expression evaluation is exactly the same as expected value. * <p>The xpath may contain the xml namespace prefixes, since namespaces from flight example * are being registered. * @param msg the error message that will be used in case of test failure * @param expected the expected value * @param xpath the xpath to evaluate * @param xmlDoc the xml to use * @throws Exception if any error occurs during xpath evaluation */ private void assertXpathEvaluatesTo(String msg, String expected, String xpath, String xmlDoc) throws Exception { Map<String, String> namespaces = new HashMap<>(); namespaces.put("tns", "http://samples.springframework.org/flight"); namespaces.put("xsi", "http://www.w3.org/2001/XMLSchema-instance"); JAXPXPathEngine engine = new JAXPXPathEngine(); engine.setNamespaceContext(namespaces); Source source = Input.fromString(xmlDoc).build(); Iterable<Node> nodeList = engine.selectNodes(xpath, source); assertEquals(msg, expected, nodeList.iterator().next().getNodeValue()); } /** * Create an instance of {@link CastorObject} for testing. */ private CastorObject createCastorObject() { CastorObject castorObject = new CastorObject(); castorObject.setName("test"); castorObject.setValue(8); return castorObject; } }