/** * Copyright (c) Codice Foundation * * This is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser * General Public License as published by the Free Software Foundation, either version 3 of the * License, or any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. A copy of the GNU Lesser General Public License * is distributed along with this program and can be found at * <http://www.gnu.org/licenses/lgpl.html>. * **/ package org.codice.ddf.spatial.ogc.csw.catalog.converter; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.io.InputStream; import java.io.StringReader; import java.io.StringWriter; import java.util.HashMap; import java.util.Map; import javax.activation.MimeType; import javax.ws.rs.core.MediaType; import org.apache.commons.io.IOUtils; import org.codice.ddf.spatial.ogc.csw.catalog.common.CswConstants; import org.codice.ddf.spatial.ogc.csw.catalog.transformer.TransformerManager; import org.custommonkey.xmlunit.XMLAssert; import org.custommonkey.xmlunit.XMLUnit; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.xmlpull.v1.XmlPullParserFactory; import com.thoughtworks.xstream.converters.ConversionException; import com.thoughtworks.xstream.converters.MarshallingContext; import com.thoughtworks.xstream.converters.UnmarshallingContext; import com.thoughtworks.xstream.core.TreeMarshaller; import com.thoughtworks.xstream.core.TreeUnmarshaller; import com.thoughtworks.xstream.io.HierarchicalStreamReader; import com.thoughtworks.xstream.io.HierarchicalStreamWriter; import com.thoughtworks.xstream.io.xml.StaxDriver; import com.thoughtworks.xstream.io.xml.WstxDriver; import com.thoughtworks.xstream.io.xml.XppReader; import ddf.catalog.data.Metacard; import ddf.catalog.data.impl.BinaryContentImpl; import ddf.catalog.data.impl.MetacardImpl; import ddf.catalog.transform.InputTransformer; import ddf.catalog.transform.MetacardTransformer; public class TestCswTransformProvider { private TransformerManager mockInputManager = mock(TransformerManager.class); private TransformerManager mockMetacardManager = mock(TransformerManager.class); private CswRecordConverter mockCswRecordConverter = mock(CswRecordConverter.class); private MetacardTransformer mockMetacardTransformer = mock(MetacardTransformer.class); private static final String OTHER_SCHEMA = "http://example.com/scheam.xsd"; @Test public void testMarshalCswRecord() throws Exception { when(mockMetacardManager.getTransformerBySchema(CswConstants.CSW_OUTPUT_SCHEMA)) .thenReturn(mockCswRecordConverter); when(mockCswRecordConverter.transform(any(Metacard.class), any(Map.class))).thenReturn( new BinaryContentImpl(IOUtils.toInputStream(getRecord()), new MimeType(MediaType.APPLICATION_XML))); StringWriter stringWriter = new StringWriter(); HierarchicalStreamWriter writer = new WstxDriver().createWriter(stringWriter); CswTransformProvider provider = new CswTransformProvider(mockMetacardManager, null); MarshallingContext context = new TreeMarshaller(writer, null, null); ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class); provider.marshal(getMetacard(), writer, context); // Verify the context arguments were set correctly verify(mockMetacardManager, times(1)).getTransformerBySchema(captor.capture()); String outputSchema = captor.getValue(); assertThat(outputSchema, is(CswConstants.CSW_OUTPUT_SCHEMA)); } @Test public void testMarshalOtherSchema() throws Exception { when(mockMetacardManager.getTransformerBySchema(OTHER_SCHEMA)) .thenReturn(mockMetacardTransformer); when(mockMetacardTransformer.transform(any(Metacard.class), any(Map.class))).thenReturn( new BinaryContentImpl(IOUtils.toInputStream(getRecord()), new MimeType(MediaType.APPLICATION_XML))); StringWriter stringWriter = new StringWriter(); HierarchicalStreamWriter writer = new WstxDriver().createWriter(stringWriter); CswTransformProvider provider = new CswTransformProvider(mockMetacardManager, null); MarshallingContext context = new TreeMarshaller(writer, null, null); context.put(CswConstants.OUTPUT_SCHEMA_PARAMETER, OTHER_SCHEMA); ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class); provider.marshal(getMetacard(), writer, context); // Verify the context arguments were set correctly verify(mockMetacardManager, times(1)).getTransformerBySchema(captor.capture()); String outputSchema = captor.getValue(); assertThat(outputSchema, is(OTHER_SCHEMA)); } @Test(expected = ConversionException.class) public void testMarshalNoTransformers() throws Exception { when(mockMetacardManager.getTransformerBySchema(anyString())).thenReturn(null); StringWriter stringWriter = new StringWriter(); HierarchicalStreamWriter writer = new WstxDriver().createWriter(stringWriter); CswTransformProvider provider = new CswTransformProvider(mockMetacardManager, null); MarshallingContext context = new TreeMarshaller(writer, null, null); provider.marshal(getMetacard(), writer, context); } @Test public void testUnmarshalCswRecord() throws Exception { when(mockInputManager.getTransformerBySchema(CswConstants.CSW_OUTPUT_SCHEMA)) .thenReturn(mockCswRecordConverter); HierarchicalStreamReader reader = new WstxDriver() .createReader(new StringReader(getRecord())); CswTransformProvider provider = new CswTransformProvider(null, mockInputManager); UnmarshallingContext context = new TreeUnmarshaller(null, null, null, null); ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class); Metacard metacard = (Metacard) provider.unmarshal(reader, context); // Verify the context arguments were set correctly verify(mockInputManager, times(1)).getTransformerBySchema(captor.capture()); String outputSchema = captor.getValue(); assertThat(outputSchema, is(CswConstants.CSW_OUTPUT_SCHEMA)); } @Test public void testUnmarshalCopyPreservesNamespaces() throws Exception { InputTransformer mockInputTransformer = mock(InputTransformer.class); when(mockInputManager.getTransformerBySchema(anyString())).thenReturn(mockInputTransformer); StringReader stringReader = new StringReader(getRecord()); StaxDriver driver = new StaxDriver(); driver.setRepairingNamespace(true); driver.getQnameMap().setDefaultNamespace(CswConstants.CSW_OUTPUT_SCHEMA); driver.getQnameMap().setDefaultPrefix(CswConstants.CSW_NAMESPACE_PREFIX); // Have to use XppReader in order to preserve the namespaces. HierarchicalStreamReader reader = new XppReader(new StringReader(getRecord()), XmlPullParserFactory.newInstance().newPullParser()); CswTransformProvider provider = new CswTransformProvider(null, mockInputManager); UnmarshallingContext context = new TreeUnmarshaller(null, null, null, null); context.put(CswConstants.OUTPUT_SCHEMA_PARAMETER, "http://example.com/schema"); ArgumentCaptor<InputStream> captor = ArgumentCaptor.forClass(InputStream.class); Metacard metacard = (Metacard) provider.unmarshal(reader, context); // Verify the context arguments were set correctly verify(mockInputTransformer, times(1)).transform(captor.capture()); InputStream inStream = captor.getValue(); String result = IOUtils.toString(inStream); XMLUnit.setIgnoreWhitespace(true); XMLAssert.assertXMLEqual(getRecord(), result); } @Test public void testUnmarshalOtherSchema() throws Exception { InputTransformer mockInputTransformer = mock(InputTransformer.class); when(mockInputManager.getTransformerBySchema(OTHER_SCHEMA)) .thenReturn(mockInputTransformer); when(mockInputTransformer.transform(any(InputStream.class))).thenReturn(getMetacard()); // XppReader is not namespace aware so it will read the XML and ignore the namespaces // WstxReader is namespace aware. It may fail for XML fragments. HierarchicalStreamReader reader = new XppReader(new StringReader(getRecord()), XmlPullParserFactory.newInstance().newPullParser()); CswTransformProvider provider = new CswTransformProvider(null, mockInputManager); UnmarshallingContext context = new TreeUnmarshaller(null, null, null, null); context.put(CswConstants.OUTPUT_SCHEMA_PARAMETER, OTHER_SCHEMA); ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class); Metacard metacard = (Metacard) provider.unmarshal(reader, context); // Verify the context arguments were set correctly verify(mockInputManager, times(1)).getTransformerBySchema(captor.capture()); String outputSchema = captor.getValue(); assertThat(outputSchema, is(OTHER_SCHEMA)); } @Test(expected = ConversionException.class) public void testUnmarshalNoTransformers() throws Exception { when(mockInputManager.getTransformerBySchema(anyString())).thenReturn(null); HierarchicalStreamReader reader = new WstxDriver() .createReader(new StringReader(getRecord())); CswTransformProvider provider = new CswTransformProvider(null, mockInputManager); UnmarshallingContext context = new TreeUnmarshaller(null, null, null, null); provider.unmarshal(reader, context); } @Test public void testUnmarshalMissingNamespaces() throws Exception { InputTransformer mockInputTransformer = mock(InputTransformer.class); when(mockInputManager.getTransformerBySchema(anyString())).thenReturn(mockInputTransformer); Map<String, String> namespaces = new HashMap<>(); namespaces.put("xmlns:csw", "http://www.opengis.net/cat/csw/2.0.2"); namespaces.put("xmlns:dc", "http://purl.org/dc/elements/1.1/"); namespaces.put("xmlns:dct", "http://purl.org/dc/terms/"); HierarchicalStreamReader reader = new XppReader( new StringReader(getRecordMissingNamespaces()), XmlPullParserFactory.newInstance().newPullParser()); CswTransformProvider provider = new CswTransformProvider(null, mockInputManager); UnmarshallingContext context = new TreeUnmarshaller(null, null, null, null); context.put(CswConstants.WRITE_NAMESPACES, namespaces); context.put(CswConstants.OUTPUT_SCHEMA_PARAMETER, OTHER_SCHEMA); ArgumentCaptor<InputStream> captor = ArgumentCaptor.forClass(InputStream.class); Metacard metacard = (Metacard) provider.unmarshal(reader, context); // Verify the context arguments were set correctly verify(mockInputTransformer, times(1)).transform(captor.capture()); InputStream inStream = captor.getValue(); String result = IOUtils.toString(inStream); XMLUnit.setIgnoreWhitespace(true); XMLAssert.assertXMLEqual(getRecord(), result); } private String getRecord() { return "<csw:Record xmlns:csw=\"http://www.opengis.net/cat/csw/2.0.2\" \n" + "xmlns:dc=\"http://purl.org/dc/elements/1.1/\" \n" + "xmlns:dct=\"http://purl.org/dc/terms/\" \n" + "xmlns:ows=\"http://www.opengis.net/ows\">\n" + " <dct:created>2014-11-01T00:00:00.000-05:00</dct:created>\n" + " <dc:title>This is my title</dc:title>\n" + " <dct:alternative>This is my title</dct:alternative>\n" + " <dc:date>2016-11-01T00:00:00.000-05:00</dc:date>\n" + " <dct:modified>2016-11-01T00:00:00.000-05:00</dct:modified>\n" + " <dct:dateSubmitted>2016-11-01T00:00:00.000-05:00</dct:dateSubmitted>\n" + " <dct:issued>2016-11-01T00:00:00.000-05:00</dct:issued>\n" + " <dc:identifier>ID</dc:identifier>\n" + " <dct:bibliographicCitation>ID</dct:bibliographicCitation>\n" + " <dct:dateAccepted>2015-11-01T00:00:00.000-05:00</dct:dateAccepted>\n" + " <dct:dateCopyrighted>2015-11-01T00:00:00.000-05:00</dct:dateCopyrighted>\n" + " <dc:type>I have some content type</dc:type>\n" + " <dc:source>http://host:port/my/product.pdf</dc:source>\n" + " <dc:publisher>sourceID</dc:publisher>\n" + " <ows:BoundingBox crs=\"urn:x-ogc:def:crs:EPSG:6.11:4326\">\n" + " <ows:LowerCorner>10.0 10.0</ows:LowerCorner>\n" + " <ows:UpperCorner>40.0 40.0</ows:UpperCorner>\n" + " </ows:BoundingBox>\n" + "</csw:Record>"; } private String getRecordMissingNamespaces() { return "<csw:Record xmlns:ows=\"http://www.opengis.net/ows\">\n" + " <dct:created>2014-11-01T00:00:00.000-05:00</dct:created>\n" + " <dc:title>This is my title</dc:title>\n" + " <dct:alternative>This is my title</dct:alternative>\n" + " <dc:date>2016-11-01T00:00:00.000-05:00</dc:date>\n" + " <dct:modified>2016-11-01T00:00:00.000-05:00</dct:modified>\n" + " <dct:dateSubmitted>2016-11-01T00:00:00.000-05:00</dct:dateSubmitted>\n" + " <dct:issued>2016-11-01T00:00:00.000-05:00</dct:issued>\n" + " <dc:identifier>ID</dc:identifier>\n" + " <dct:bibliographicCitation>ID</dct:bibliographicCitation>\n" + " <dct:dateAccepted>2015-11-01T00:00:00.000-05:00</dct:dateAccepted>\n" + " <dct:dateCopyrighted>2015-11-01T00:00:00.000-05:00</dct:dateCopyrighted>\n" + " <dc:type>I have some content type</dc:type>\n" + " <dc:source>http://host:port/my/product.pdf</dc:source>\n" + " <dc:publisher>sourceID</dc:publisher>\n" + " <ows:BoundingBox crs=\"urn:x-ogc:def:crs:EPSG:6.11:4326\">\n" + " <ows:LowerCorner>10.0 10.0</ows:LowerCorner>\n" + " <ows:UpperCorner>40.0 40.0</ows:UpperCorner>\n" + " </ows:BoundingBox>\n" + "</csw:Record>"; } private Metacard getMetacard() { MetacardImpl metacard = new MetacardImpl(); metacard.setContentTypeName("I have some content type"); metacard.setContentTypeVersion("1.0.0"); metacard.setId("ID"); metacard.setLocation("POLYGON ((30 10, 10 20, 20 40, 40 40, 30 10))"); metacard.setMetadata("metadata a whole bunch of metadata"); metacard.setResourceSize("123TB"); metacard.setSourceId("sourceID"); metacard.setTitle("This is my title"); return metacard; } }