/** * Copyright (c) Codice Foundation * <p> * 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. * <p> * 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.transformer; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyMap; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.notNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.mockito.internal.verification.VerificationModeFactory.atLeastOnce; import static org.mockito.internal.verification.VerificationModeFactory.times; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.Serializable; import java.math.BigInteger; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicLong; import javax.activation.MimeType; import javax.ws.rs.WebApplicationException; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBElement; import javax.xml.bind.JAXBException; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.codice.ddf.spatial.ogc.csw.catalog.common.CswConstants; import org.codice.ddf.spatial.ogc.csw.catalog.common.CswJAXBElementProvider; import org.codice.ddf.spatial.ogc.csw.catalog.common.transformer.TransformerManager; import org.hamcrest.core.IsEqual; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.mockito.ArgumentCaptor; import org.opengis.filter.Filter; import com.thoughtworks.xstream.io.HierarchicalStreamWriter; import ddf.catalog.data.BinaryContent; import ddf.catalog.data.Metacard; import ddf.catalog.data.Result; import ddf.catalog.data.impl.BinaryContentImpl; import ddf.catalog.data.impl.MetacardImpl; import ddf.catalog.data.impl.ResultImpl; import ddf.catalog.operation.Query; import ddf.catalog.operation.QueryRequest; import ddf.catalog.operation.SourceResponse; import ddf.catalog.operation.impl.QueryImpl; import ddf.catalog.operation.impl.QueryRequestImpl; import ddf.catalog.operation.impl.SourceResponseImpl; import ddf.catalog.transform.CatalogTransformerException; import ddf.catalog.transform.MetacardTransformer; import ddf.catalog.transformer.api.PrintWriter; import ddf.catalog.transformer.api.PrintWriterProvider; import net.opengis.cat.csw.v_2_0_2.AcknowledgementType; import net.opengis.cat.csw.v_2_0_2.GetRecordsType; import net.opengis.cat.csw.v_2_0_2.ResultType; public class TestCswQueryResponseTransformer { private CswQueryResponseTransformer transformer; private Filter filter = mock(Filter.class); private TransformerManager mockTransformerManager; private PrintWriterProvider mockPrintWriterProvider; private PrintWriter mockPrintWriter; private MetacardTransformer mockMetacardTransformer; private Query mockQuery; private SourceResponse mockSourceResponse; private QueryRequest mockQueryRequest; private Map<String, Serializable> mockArguments; private List<Result> mockResults; @Rule public ExpectedException thrown = ExpectedException.none(); @Before public void before() { mockTransformerManager = mock(TransformerManager.class); mockPrintWriterProvider = mock(PrintWriterProvider.class); mockPrintWriter = mock(PrintWriter.class); mockMetacardTransformer = mock(MetacardTransformer.class); mockQuery = mock(Query.class); mockSourceResponse = mock(SourceResponse.class); mockQueryRequest = mock(QueryRequest.class); mockArguments = mock(Map.class); mockResults = mock(List.class); transformer = new CswQueryResponseTransformer(mockTransformerManager, mockPrintWriterProvider); } @Test public void whenNullArgumentsThenThrowException() throws CatalogTransformerException { thrown.expect(IllegalArgumentException.class); thrown.expectMessage("Null argument map."); transformer.transform(mockSourceResponse, null); } @Test public void whenNullSourceResponseThenThrowException() throws CatalogTransformerException { thrown.expect(IllegalArgumentException.class); thrown.expectMessage("Null source response."); transformer.transform(null, mockArguments); } @Test public void whenNullResultListThenThrowException() throws CatalogTransformerException { thrown.expect(IllegalArgumentException.class); thrown.expectMessage("Null results list."); when(mockSourceResponse.getResults()).thenReturn(null); transformer.transform(mockSourceResponse, mockArguments); } @Test public void whenNullResultTypeArgumentThenThrowException() throws CatalogTransformerException { thrown.expect(IllegalArgumentException.class); thrown.expectMessage("Null result type argument."); when(mockSourceResponse.getResults()).thenReturn(mockResults); when(mockArguments.get(anyString())).thenReturn(null); transformer.transform(mockSourceResponse, mockArguments); } @Test public void whenNullQueryRequestThenThrowException() throws CatalogTransformerException { thrown.expect(IllegalArgumentException.class); thrown.expectMessage("Null source response query request."); when(mockSourceResponse.getResults()).thenReturn(mockResults); when(mockArguments.get(anyString())).thenReturn(ResultType.RESULTS); when(mockSourceResponse.getRequest()).thenReturn(null); transformer.transform(mockSourceResponse, mockArguments); } @Test public void whenNullQueryThenThrowException() throws CatalogTransformerException { thrown.expect(IllegalArgumentException.class); thrown.expectMessage("Null source response query."); when(mockSourceResponse.getResults()).thenReturn(mockResults); when(mockArguments.get(anyString())).thenReturn(ResultType.RESULTS); when(mockSourceResponse.getRequest()).thenReturn(mockQueryRequest); when(mockQueryRequest.getQuery()).thenReturn(null); transformer.transform(mockSourceResponse, mockArguments); } @Test public void whenQueryByIdThenExpectOnlyOneXMLNode() throws CatalogTransformerException, IOException { // when when(mockPrintWriterProvider.build((Class<Metacard>) notNull())).thenReturn(mockPrintWriter); when(mockPrintWriter.makeString()).thenReturn(new String()); when(mockSourceResponse.getResults()).thenReturn(Collections.emptyList()); when(mockSourceResponse.getRequest()).thenReturn(mockQueryRequest); when(mockQueryRequest.getQuery()).thenReturn(mockQuery); when(mockArguments.get(CswConstants.RESULT_TYPE_PARAMETER)).thenReturn(ResultType.RESULTS); when(mockArguments.get(CswConstants.IS_BY_ID_QUERY)).thenReturn(true); when(mockTransformerManager.getTransformerBySchema(anyString())).thenReturn( mockMetacardTransformer); // given transformer.init(); BinaryContent bc = transformer.transform(mockSourceResponse, mockArguments); transformer.destroy(); // then ArgumentCaptor<String> strArgCaptor = ArgumentCaptor.forClass(String.class); verify(mockPrintWriter, times(1)).startNode(strArgCaptor.capture()); List<String> values = strArgCaptor.getAllValues(); assertThat("Missing root GetRecordByIdResponse node.", values.get(0), is( CswQueryResponseTransformer.RECORD_BY_ID_RESPONSE_QNAME)); } @Test public void whenQueryByHitsThenTransformZeroMetacards() throws CatalogTransformerException, IOException { // when when(mockPrintWriterProvider.build((Class<Metacard>) notNull())).thenReturn(mockPrintWriter); when(mockPrintWriter.makeString()).thenReturn(new String()); when(mockSourceResponse.getResults()).thenReturn(createResults(1, 10)); when(mockSourceResponse.getRequest()).thenReturn(mockQueryRequest); when(mockQueryRequest.getQuery()).thenReturn(mockQuery); when(mockArguments.get(CswConstants.RESULT_TYPE_PARAMETER)).thenReturn(ResultType.HITS); // given transformer.init(); transformer.transform(mockSourceResponse, mockArguments); transformer.destroy(); // then ArgumentCaptor<String> tmCaptor = ArgumentCaptor.forClass(String.class); verify(mockTransformerManager, never()).getTransformerBySchema(tmCaptor.capture()); ArgumentCaptor<Map> mapCaptor = ArgumentCaptor.forClass(Map.class); ArgumentCaptor<Metacard> mcCaptor = ArgumentCaptor.forClass(Metacard.class); verify(mockMetacardTransformer, never()).transform(mcCaptor.capture(), mapCaptor.capture()); ArgumentCaptor<String> keyCaptor = ArgumentCaptor.forClass(String.class); ArgumentCaptor<String> valCaptor = ArgumentCaptor.forClass(String.class); verify(mockPrintWriter, atLeastOnce()).addAttribute(keyCaptor.capture(), valCaptor.capture()); List<String> keys = keyCaptor.getAllValues(); List<String> vals = valCaptor.getAllValues(); int numAttrKeyIndex = keys.indexOf(CswQueryResponseTransformer.NUMBER_OF_RECORDS_RETURNED_ATTRIBUTE); assertThat("Missing XML attribute.", numAttrKeyIndex != -1, is(true)); String numAttrValue = vals.get(numAttrKeyIndex); assertThat("Missing XML attribute value.", numAttrValue, new IsEqual("0")); } @Test public void whenEmptyResultListThenTransformZeroMetacards() throws CatalogTransformerException, IOException { // when when(mockPrintWriterProvider.build((Class<Metacard>) notNull())).thenReturn(mockPrintWriter); when(mockPrintWriter.makeString()).thenReturn(new String()); when(mockSourceResponse.getResults()).thenReturn(Collections.emptyList()); when(mockSourceResponse.getRequest()).thenReturn(mockQueryRequest); when(mockQueryRequest.getQuery()).thenReturn(mockQuery); when(mockArguments.get(CswConstants.RESULT_TYPE_PARAMETER)).thenReturn(ResultType.RESULTS); when(mockTransformerManager.getTransformerBySchema(anyString())).thenReturn( mockMetacardTransformer); // given transformer.init(); transformer.transform(mockSourceResponse, mockArguments); transformer.destroy(); // then ArgumentCaptor<String> tmCaptor = ArgumentCaptor.forClass(String.class); verify(mockTransformerManager, times(1)).getTransformerBySchema(tmCaptor.capture()); ArgumentCaptor<Map> mapCaptor = ArgumentCaptor.forClass(Map.class); ArgumentCaptor<Metacard> mcCaptor = ArgumentCaptor.forClass(Metacard.class); verify(mockMetacardTransformer, never()).transform(mcCaptor.capture(), mapCaptor.capture()); } @Test public void whenEmptyResultListExpectOnlyThreeXMLNodes() throws CatalogTransformerException, IOException { // when when(mockPrintWriterProvider.build((Class<Metacard>) notNull())).thenReturn(mockPrintWriter); when(mockPrintWriter.makeString()).thenReturn(new String()); when(mockSourceResponse.getResults()).thenReturn(Collections.emptyList()); when(mockSourceResponse.getRequest()).thenReturn(mockQueryRequest); when(mockQueryRequest.getQuery()).thenReturn(mockQuery); when(mockArguments.get(CswConstants.RESULT_TYPE_PARAMETER)).thenReturn(ResultType.RESULTS); when(mockTransformerManager.getTransformerBySchema(anyString())).thenReturn( mockMetacardTransformer); // given transformer.init(); transformer.transform(mockSourceResponse, mockArguments); transformer.destroy(); // then ArgumentCaptor<String> pwCaptor = ArgumentCaptor.forClass(String.class); verify(mockPrintWriter, times(3)).startNode(pwCaptor.capture()); List<String> values = pwCaptor.getAllValues(); assertThat("Missing XML node.", values.get(0), new IsEqual(CswQueryResponseTransformer.RECORDS_RESPONSE_QNAME)); assertThat("Missing XML node.", values.get(1), new IsEqual(CswQueryResponseTransformer.SEARCH_STATUS_QNAME)); assertThat("Missing XML node.", values.get(2), new IsEqual(CswQueryResponseTransformer.SEARCH_RESULTS_QNAME)); } @Test public void testMarshalAcknowledgement() throws WebApplicationException, IOException, JAXBException, CatalogTransformerException { GetRecordsType query = new GetRecordsType(); query.setResultType(ResultType.VALIDATE); query.setMaxRecords(BigInteger.valueOf(6)); query.setStartPosition(BigInteger.valueOf(4)); SourceResponse sourceResponse = createSourceResponse(query, 22); Map<String, Serializable> args = new HashMap<>(); args.put(CswConstants.RESULT_TYPE_PARAMETER, ResultType.VALIDATE); args.put(CswConstants.GET_RECORDS, query); BinaryContent content = transformer.transform(sourceResponse, args); String xml = new String(content.getByteArray()); JAXBElement<?> jaxb = (JAXBElement<?>) getJaxBContext().createUnmarshaller() .unmarshal(new ByteArrayInputStream(xml.getBytes("UTF-8"))); assertThat(jaxb.getValue(), is(instanceOf(AcknowledgementType.class))); AcknowledgementType response = (AcknowledgementType) jaxb.getValue(); assertThat(response.getEchoedRequest() .getAny(), is(instanceOf(JAXBElement.class))); JAXBElement<?> jaxB = (JAXBElement<?>) response.getEchoedRequest() .getAny(); assertThat(jaxB.getValue(), is(instanceOf(GetRecordsType.class))); } @Test public void testMarshalAcknowledgementWithFailedTransforms() throws WebApplicationException, IOException, JAXBException, CatalogTransformerException { GetRecordsType query = new GetRecordsType(); query.setResultType(ResultType.RESULTS); query.setMaxRecords(BigInteger.valueOf(6)); query.setStartPosition(BigInteger.valueOf(0)); SourceResponse sourceResponse = createSourceResponse(query, 6); Map<String, Serializable> args = new HashMap<>(); args.put(CswConstants.RESULT_TYPE_PARAMETER, ResultType.RESULTS); args.put(CswConstants.GET_RECORDS, query); PrintWriter printWriter = getSimplePrintWriter(); MetacardTransformer mockMetacardTransformer = mock(MetacardTransformer.class); final AtomicLong atomicLong = new AtomicLong(0); when(mockMetacardTransformer.transform(any(Metacard.class), anyMap())).then(invocationOnMock -> { if (atomicLong.incrementAndGet() == 2) { throw new CatalogTransformerException(""); } Metacard metacard = (Metacard) invocationOnMock.getArguments()[0]; BinaryContentImpl bci = new BinaryContentImpl(IOUtils.toInputStream( metacard.getId() + ","), new MimeType("application/xml")); return bci; }); when(mockPrintWriterProvider.build((Class<Metacard>) notNull())).thenReturn(printWriter); when(mockTransformerManager.getTransformerBySchema(anyString())).thenReturn( mockMetacardTransformer); CswQueryResponseTransformer cswQueryResponseTransformer = new CswQueryResponseTransformer( mockTransformerManager, mockPrintWriterProvider); cswQueryResponseTransformer.init(); BinaryContent content = cswQueryResponseTransformer.transform(sourceResponse, args); cswQueryResponseTransformer.destroy(); String xml = new String(content.getByteArray()); assertThat(xml, containsString( CswQueryResponseTransformer.NUMBER_OF_RECORDS_MATCHED_ATTRIBUTE + " 6")); assertThat(xml, containsString( CswQueryResponseTransformer.NUMBER_OF_RECORDS_RETURNED_ATTRIBUTE + " 5")); assertThat(xml, containsString(CswQueryResponseTransformer.NEXT_RECORD_ATTRIBUTE + " 0")); } @Test public void verifyResultOrderIsMaintained() throws CatalogTransformerException, IOException { // when when(mockPrintWriterProvider.build((Class<Metacard>) notNull())).thenReturn(mockPrintWriter); when(mockPrintWriter.makeString()).thenReturn(new String()); when(mockSourceResponse.getResults()).thenReturn(createResults(1, 10)); when(mockSourceResponse.getRequest()).thenReturn(mockQueryRequest); when(mockQueryRequest.getQuery()).thenReturn(mockQuery); when(mockArguments.get(CswConstants.RESULT_TYPE_PARAMETER)).thenReturn(ResultType.RESULTS); when(mockTransformerManager.getTransformerBySchema(anyString())).thenReturn( mockMetacardTransformer); when(mockMetacardTransformer.transform(any(Metacard.class), any(Map.class))).thenAnswer( invocationOnMock -> { Metacard metacard = (Metacard) invocationOnMock.getArguments()[0]; BinaryContentImpl bci = new BinaryContentImpl(IOUtils.toInputStream( metacard.getId() + ","), new MimeType("application/xml")); return bci; }); // given transformer.init(); transformer.transform(mockSourceResponse, mockArguments); transformer.destroy(); // then ArgumentCaptor<String> tmCaptor = ArgumentCaptor.forClass(String.class); verify(mockTransformerManager, times(1)).getTransformerBySchema(tmCaptor.capture()); ArgumentCaptor<Map> mapCaptor = ArgumentCaptor.forClass(Map.class); ArgumentCaptor<Metacard> mcCaptor = ArgumentCaptor.forClass(Metacard.class); verify(mockMetacardTransformer, times(10)).transform(mcCaptor.capture(), mapCaptor.capture()); ArgumentCaptor<String> strCaptor = ArgumentCaptor.forClass(String.class); verify(mockPrintWriter, times(2)).setRawValue(strCaptor.capture()); String order = strCaptor.getAllValues() .get(1); String[] ids = order.split(","); for (int i = 1; i < ids.length; i++) { assertThat(ids[i - 1], is(String.valueOf("id_" + i))); } } private SourceResponse createSourceResponse(GetRecordsType request, int resultCount) { int first = 1; int last = 2; int max = 0; if (request != null) { first = request.getStartPosition() .intValue(); max = request.getMaxRecords() .intValue(); int next = request.getMaxRecords() .intValue() + first; last = next - 1; if (last >= resultCount) { last = resultCount; } } QueryImpl query = new QueryImpl(filter, first, max, null, true, 0); SourceResponseImpl sourceResponse = new SourceResponseImpl(new QueryRequestImpl(query), createResults(first, last)); sourceResponse.setHits(resultCount); return sourceResponse; } private List<Result> createResults(int start, int finish) { List<Result> list = new LinkedList<>(); for (int i = start; i <= finish; i++) { MetacardImpl metacard = new MetacardImpl(); metacard.setId("id_" + i); metacard.setSourceId("source_" + i); metacard.setTitle("title " + i); list.add(new ResultImpl(metacard)); } return list; } private JAXBContext getJaxBContext() throws JAXBException { JAXBContext context; String contextPath = StringUtils.join(new String[] {CswConstants.OGC_CSW_PACKAGE, CswConstants.OGC_FILTER_PACKAGE, CswConstants.OGC_GML_PACKAGE, CswConstants.OGC_OWS_PACKAGE}, ":"); context = JAXBContext.newInstance(contextPath, CswJAXBElementProvider.class.getClassLoader()); return context; } private PrintWriter getSimplePrintWriter() { return new PrintWriter() { StringBuilder stringBuilder = new StringBuilder(); @Override public void setRawValue(String s) { stringBuilder.append(s); } @Override public String makeString() { return stringBuilder.toString(); } @Override public void startNode(String s, Class aClass) { stringBuilder.append(s); } @Override public void startNode(String s) { stringBuilder.append(s); } @Override public void addAttribute(String s, String s1) { stringBuilder.append(s); stringBuilder.append(" "); stringBuilder.append(s1); } @Override public void setValue(String s) { stringBuilder.append(s); } @Override public void endNode() { } @Override public void flush() { } @Override public void close() { } @Override public HierarchicalStreamWriter underlyingWriter() { return null; } }; } }