/** * 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.common.source; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.math.BigInteger; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import javax.ws.rs.core.GenericType; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedHashMap; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBElement; import javax.xml.bind.JAXBException; import javax.xml.bind.Unmarshaller; import javax.xml.namespace.QName; import org.apache.commons.collections.CollectionUtils; import org.codice.ddf.spatial.ogc.catalog.MetadataTransformer; import org.codice.ddf.spatial.ogc.catalog.common.AvailabilityTask; import org.codice.ddf.spatial.ogc.csw.catalog.common.Csw; import org.codice.ddf.spatial.ogc.csw.catalog.common.CswConstants; import org.codice.ddf.spatial.ogc.csw.catalog.common.CswException; import org.codice.ddf.spatial.ogc.csw.catalog.common.CswJAXBElementProvider; import org.codice.ddf.spatial.ogc.csw.catalog.common.CswRecordCollection; import org.codice.ddf.spatial.ogc.csw.catalog.common.GetCapabilitiesRequest; import org.custommonkey.xmlunit.XMLUnit; import org.junit.Before; import org.junit.BeforeClass; import org.mockito.Matchers; import org.osgi.framework.BundleContext; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceReference; import org.osgi.framework.ServiceRegistration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import ddf.catalog.data.ContentType; import ddf.catalog.data.Metacard; import ddf.catalog.data.MetacardType; import ddf.catalog.data.impl.ContentTypeImpl; import ddf.catalog.data.impl.MetacardImpl; import ddf.catalog.data.types.Core; import ddf.catalog.filter.proxy.builder.GeotoolsFilterBuilder; import ddf.catalog.transform.CatalogTransformerException; import ddf.security.sts.client.configuration.STSClientConfiguration; import net.opengis.cat.csw.v_2_0_2.AbstractRecordType; import net.opengis.cat.csw.v_2_0_2.BriefRecordType; import net.opengis.cat.csw.v_2_0_2.CapabilitiesType; import net.opengis.cat.csw.v_2_0_2.GetRecordsResponseType; import net.opengis.cat.csw.v_2_0_2.GetRecordsType; import net.opengis.cat.csw.v_2_0_2.RecordType; import net.opengis.cat.csw.v_2_0_2.SearchResultsType; import net.opengis.cat.csw.v_2_0_2.SummaryRecordType; import net.opengis.cat.csw.v_2_0_2.dc.elements.SimpleLiteral; import net.opengis.filter.v_1_1_0.ComparisonOperatorType; import net.opengis.filter.v_1_1_0.ComparisonOperatorsType; import net.opengis.filter.v_1_1_0.FilterCapabilities; import net.opengis.filter.v_1_1_0.LogicalOperators; import net.opengis.filter.v_1_1_0.ScalarCapabilitiesType; import net.opengis.ows.v_1_0_0.DomainType; import net.opengis.ows.v_1_0_0.Operation; import net.opengis.ows.v_1_0_0.OperationsMetadata; import net.opengis.ows.v_1_0_0.ServiceIdentification; public class TestCswSourceBase { private static final Logger LOGGER = LoggerFactory.getLogger(TestCswSourceBase.class); protected static final String ID = "MyCswSource"; protected static final String URL = "http://www.example.com/csw"; protected static final String USERNAME = "myUsername"; protected static final String PASSWORD = "myPass"; protected static final Integer CONNECTION_TIMEOUT = 11; protected static final Integer RECEIVE_TIMEOUT = 12; protected static final String OUTPUT_SCHEMA = "outputSchema"; protected static final String QUERY_TYPE_NAME = "queryTypeName"; protected static final String QUERY_TYPE_NAMESPACE = "http://example.com/namespace"; protected static final String IDENTIFIER_MAPPING = "idMapping"; protected static final String COORDINATE_ORDER = "LAT_LON"; protected static final String CREATED_DATE = "createdProperty"; protected static final String EFFECTIVE_DATE = "effectiveProperty"; protected static final String MODIFIED_DATE = "modifiedProperty"; protected static final String CONTENT_TYPE = "contentTypeProperty"; protected static String[] metacardMappings; protected static final Integer POLL_INTERVAL = 100; private static final List<String> JAXB_ELEMENT_CLASS_NAMES = ImmutableList.of(GetRecordsType.class.getName()); private static final Map<String, String> JAXB_ELEMENT_CLASS_MAP = ImmutableMap.of(GetRecordsType.class.getName(), new QName(CswConstants.CSW_OUTPUT_SCHEMA, CswConstants.GET_RECORDS).toString()); protected final GeotoolsFilterBuilder builder = new GeotoolsFilterBuilder(); protected Csw mockCsw = mock(Csw.class); protected BundleContext mockContext = mock(BundleContext.class); protected AvailabilityTask mockAvailabilityTask = mock(AvailabilityTask.class); protected List<MetacardType> mockRegistry = new ArrayList<>(); protected String getRecordsControlXml202 = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" + "<GetRecords resultType=\"results\" outputFormat=\"application/xml\"" + " outputSchema=\"http://www.opengis.net/cat/csw/2.0.2\" startPosition=\"1\"" + " maxRecords=\"10\" service=\"CSW\" version=\"2.0.2\"" + " xmlns=\"http://www.opengis.net/cat/csw/2.0.2\"" + " xmlns:csw=\"http://www.opengis.net/cat/csw/2.0.2\"" + " xmlns:ogc=\"http://www.opengis.net/ogc\">" + " <Query typeNames=\"csw:Record\">" + " <ElementSetName>full</ElementSetName>" + " <Constraint version=\"1.1.0\">" + " <ogc:Filter>" + " <ogc:PropertyIsLike wildCard=\"*\" singleChar=\"#\" escapeChar=\"!\" matchCase=\"false\">" + " <ogc:PropertyName>" + CswConstants.ANY_TEXT + " </ogc:PropertyName>" + " <ogc:Literal>*th*e</ogc:Literal>" + " </ogc:PropertyIsLike>" + " </ogc:Filter>" + " </Constraint>" + " </Query>" + "</GetRecords>"; protected String getRecordsControlXml202ContentTypeMappedToFormat = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" + "<GetRecords resultType=\"results\" outputFormat=\"application/xml\"" + " outputSchema=\"http://www.opengis.net/cat/csw/2.0.2\" startPosition=\"1\"" + " maxRecords=\"10\" service=\"CSW\" version=\"2.0.2\"" + " xmlns=\"http://www.opengis.net/cat/csw/2.0.2\"" + " xmlns:csw=\"http://www.opengis.net/cat/csw/2.0.2\"" + " xmlns:ogc=\"http://www.opengis.net/ogc\">" + " <Query typeNames=\"csw:Record\">" + "<ElementSetName>full</ElementSetName>" + " <Constraint version=\"1.1.0\">" + " <ogc:Filter>" + " <ogc:PropertyIsEqualTo matchCase=\"true\">" + " <ogc:PropertyName>" + CswConstants.CSW_FORMAT + " </ogc:PropertyName>" + " <ogc:Literal>myContentType</ogc:Literal>" + " </ogc:PropertyIsEqualTo>" + " </ogc:Filter>" + " </Constraint>" + " </Query>" + "</GetRecords>"; protected String getRecordsControlXml202ConteTypeAndVersion = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n" + "<csw:GetRecords xmlns:csw=\"http://www.opengis.net/cat/csw/2.0.2\"\n" + " xmlns:ogc=\"http://www.opengis.net/ogc\" xmlns:gml=\"http://www.opengis.net/gml\"\n" + " xmlns:ns4=\"http://www.w3.org/1999/xlink\" xmlns:ns5=\"http://www.w3.org/2001/SMIL20/\"\n" + " xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:ows=\"http://www.opengis.net/ows\"\n" + " xmlns:dct=\"http://purl.org/dc/terms/\" xmlns:ns9=\"http://www.w3.org/2001/SMIL20/Language\"\n" + " resultType=\"results\" outputFormat=\"application/xml\"\n" + " outputSchema=\"http://www.opengis.net/cat/csw/2.0.2\" startPosition=\"1\" maxRecords=\"10\"\n" + " service=\"CSW\" version=\"2.0.2\">\n" + " <csw:Query typeNames=\"csw:Record\">\n" + " <csw:ElementSetName>full</csw:ElementSetName>\n" + " <csw:Constraint version=\"1.1.0\">\n" + " <ogc:Filter>\n" + " <ogc:And>\n" + " <ogc:PropertyIsEqualTo matchCase=\"true\">\n" + " <ogc:PropertyName>" + CswConstants.CSW_FORMAT + "</ogc:PropertyName>\n" + " <ogc:Literal>myContentType</ogc:Literal>\n" + " </ogc:PropertyIsEqualTo>\n" + " <ogc:PropertyIsLike wildCard=\"*\" singleChar=\"#\" escapeChar=\"!\" matchCase=\"false\">\n" + " <ogc:PropertyName>metadata-content-type-version</ogc:PropertyName>\n" + " <ogc:Literal>2.0</ogc:Literal>\n" + " </ogc:PropertyIsLike>\n" + " </ogc:And>\n" + " </ogc:Filter>\n" + " </csw:Constraint>\n" + " </csw:Query>\n" + "</csw:GetRecords>"; @BeforeClass public static void init() { // The magic setting - besides ignoring whitespace, this setting configures XMLUnit to // treat namespaces that are equivalent but that use different prefixes to be considered // a recoverable difference, hence XMLUnit's diff will mark the docs as similar if this is // their only difference. XMLUnit.setIgnoreWhitespace(true); XMLUnit.setIgnoreAttributeOrder(true); XMLUnit.setIgnoreComments(true); metacardMappings = new String[] {Metacard.ID + "=" + IDENTIFIER_MAPPING, Core.CREATED + "=" + CREATED_DATE, Metacard.EFFECTIVE + "=" + EFFECTIVE_DATE, Core.MODIFIED + "=" + MODIFIED_DATE, Metacard.CONTENT_TYPE + "=" + CONTENT_TYPE}; } @Before public void setUp() { ServiceReference ref = mock(ServiceReference.class); ServiceReference[] serviceRefs = new ServiceReference[] {ref}; try { when(mockContext.getServiceReferences(eq(MetadataTransformer.class.getName()), anyString())).thenReturn(serviceRefs); when(mockContext.getServiceReferences(eq(STSClientConfiguration.class.getName()), anyString())).thenReturn(null); } catch (InvalidSyntaxException e) { LOGGER.error(e.getMessage(), e); } MetadataTransformer transformer = mock(MetadataTransformer.class); // Just return same Metacard that was passed in when(mockContext.getService(any(ServiceReference.class))).thenReturn(transformer); try { when(transformer.transform(any(Metacard.class))).thenAnswer(invocation -> invocation.getArguments()[0]); } catch (CatalogTransformerException e) { LOGGER.error(e.getMessage(), e); } when(mockAvailabilityTask.isAvailable()).thenReturn(true); } protected Set<ContentType> generateContentType(List<String> names) { Set<ContentType> contentTypes = new HashSet<>(); for (String name : names) { ContentType ct = new ContentTypeImpl(name, ""); contentTypes.add(ct); } return contentTypes; } protected Csw createMockCsw() throws CswException { Csw mockCsw = mock(Csw.class); InputStream stream = getClass().getResourceAsStream("/getCapabilities.xml"); CapabilitiesType capabilities = parseXml(stream); when(mockCsw.getCapabilities(any(GetCapabilitiesRequest.class))).thenReturn(capabilities); CswRecordCollection collection = generateCswCollection("/getBriefRecordsResponse.xml"); when(mockCsw.getRecords(any(GetRecordsType.class))).thenReturn(collection); return mockCsw; } protected CswRecordCollection generateCswCollection(String file) { InputStream stream = getClass().getResourceAsStream(file); GetRecordsResponseType recordsResponse = parseXml(stream); GetRecordsResponseType records = new GetRecordsResponseType(); recordsResponse.copyTo(records); List<Metacard> cswRecords = new LinkedList<>(); for (JAXBElement<? extends AbstractRecordType> rec : records.getSearchResults() .getAbstractRecord()) { MetacardImpl metacard = new MetacardImpl(); cswRecords.add(metacard); if (rec.getValue() instanceof BriefRecordType) { BriefRecordType record = (BriefRecordType) rec.getValue(); metacard.setId(record.getIdentifier() .get(0) .getValue() .getContent() .get(0)); if (!CollectionUtils.isEmpty(record.getType() .getContent())) { metacard.setContentTypeName(record.getType() .getContent() .get(0)); } } else if (rec.getValue() instanceof SummaryRecordType) { SummaryRecordType record = (SummaryRecordType) rec.getValue(); metacard.setId(record.getIdentifier() .get(0) .getValue() .getContent() .get(0)); if (!CollectionUtils.isEmpty(record.getType() .getContent())) { metacard.setContentTypeName(record.getType() .getContent() .get(0)); } } else if (rec.getValue() instanceof RecordType) { RecordType record = (RecordType) rec.getValue(); for (JAXBElement<SimpleLiteral> jb : record.getDCElement()) { if ("identifier".equals(jb.getName() .getLocalPart())) { metacard.setId(jb.getValue() .getContent() .get(0)); } if ("type".equals(jb.getName() .getLocalPart()) && !CollectionUtils.isEmpty(jb.getValue() .getContent())) { metacard.setContentTypeName(jb.getValue() .getContent() .get(0)); } } } } CswRecordCollection collection = new CswRecordCollection(); collection.setCswRecords(cswRecords); collection.setNumberOfRecordsMatched(records.getSearchResults() .getNumberOfRecordsMatched() .intValue()); collection.setNumberOfRecordsReturned(records.getSearchResults() .getNumberOfRecordsReturned() .intValue()); return collection; } @SuppressWarnings("unchecked") protected <T> T parseXml(InputStream stream) { JAXBElement<T> jaxb; try { JAXBContext jc = JAXBContext.newInstance( "net.opengis.cat.csw.v_2_0_2:net.opengis.gml.v_3_1_1"); Unmarshaller u = jc.createUnmarshaller(); Object o = u.unmarshal(stream); jaxb = (JAXBElement<T>) o; } catch (JAXBException e) { throw new AssertionError("failed to parse xml", e); } return jaxb.getValue(); } protected String getGetRecordsTypeAsXml(GetRecordsType getRecordsType) throws IOException { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); CswJAXBElementProvider<GetRecordsType> getRecordsTypeProvider = new CswJAXBElementProvider<>(); getRecordsTypeProvider.setMarshallAsJaxbElement(true); getRecordsTypeProvider.setJaxbElementClassNames(JAXB_ELEMENT_CLASS_NAMES); getRecordsTypeProvider.setJaxbElementClassMap(JAXB_ELEMENT_CLASS_MAP); getRecordsTypeProvider.writeTo(getRecordsType, GenericType.class, GetRecordsType.class.getAnnotations(), MediaType.APPLICATION_XML_TYPE, new MultivaluedHashMap<>(0), outputStream); return outputStream.toString(); } protected void configureMockCsw() throws CswException { configureMockCsw(0, 0L, CswConstants.VERSION_2_0_2); } protected void configureMockCsw(int numRecordsReturned, long numRecordsMatched, String cswVersion) throws CswException { ServiceIdentification mockServiceIdentification = mock(ServiceIdentification.class); when(mockServiceIdentification.getAbstract()).thenReturn("myDescription"); CapabilitiesType mockCapabilities = mock(CapabilitiesType.class); when(mockCapabilities.getVersion()).thenReturn(cswVersion); when(mockCapabilities.getServiceIdentification()).thenReturn(mockServiceIdentification); when(mockCapabilities.getServiceIdentification()).thenReturn(mockServiceIdentification); when(mockCsw.getCapabilities(any(GetCapabilitiesRequest.class))).thenReturn(mockCapabilities); FilterCapabilities mockFilterCapabilities = mock(FilterCapabilities.class); when(mockCapabilities.getFilterCapabilities()).thenReturn(mockFilterCapabilities); List<ComparisonOperatorType> comparisonOpsList = new ArrayList<>(); comparisonOpsList.add(ComparisonOperatorType.EQUAL_TO); comparisonOpsList.add(ComparisonOperatorType.LIKE); comparisonOpsList.add(ComparisonOperatorType.NOT_EQUAL_TO); comparisonOpsList.add(ComparisonOperatorType.GREATER_THAN); comparisonOpsList.add(ComparisonOperatorType.GREATER_THAN_EQUAL_TO); comparisonOpsList.add(ComparisonOperatorType.LESS_THAN); comparisonOpsList.add(ComparisonOperatorType.LESS_THAN_EQUAL_TO); comparisonOpsList.add(ComparisonOperatorType.BETWEEN); comparisonOpsList.add(ComparisonOperatorType.NULL_CHECK); ComparisonOperatorsType comparisonOps = new ComparisonOperatorsType(); comparisonOps.setComparisonOperator(comparisonOpsList); ScalarCapabilitiesType mockScalarCapabilities = mock(ScalarCapabilitiesType.class); when(mockScalarCapabilities.getLogicalOperators()).thenReturn(mock(LogicalOperators.class)); mockScalarCapabilities.setComparisonOperators(comparisonOps); when(mockScalarCapabilities.getComparisonOperators()).thenReturn(comparisonOps); when(mockFilterCapabilities.getScalarCapabilities()).thenReturn(mockScalarCapabilities); List<DomainType> getRecordsParameters = new ArrayList<>(); DomainType typeName = new DomainType(); typeName.setName(CswConstants.TYPE_NAME_PARAMETER); typeName.setValue(Collections.singletonList("csw:Record")); getRecordsParameters.add(typeName); DomainType typeNames = new DomainType(); typeNames.setName(CswConstants.TYPE_NAMES_PARAMETER); getRecordsParameters.add(typeNames); DomainType getRecordsOutputSchema = new DomainType(); getRecordsOutputSchema.setName(CswConstants.OUTPUT_SCHEMA_PARAMETER); getRecordsOutputSchema.getValue() .add(CswConstants.CSW_OUTPUT_SCHEMA); getRecordsParameters.add(getRecordsOutputSchema); DomainType constraintLang = new DomainType(); constraintLang.setName(CswConstants.CONSTRAINT_LANGUAGE_PARAMETER); constraintLang.setValue(Collections.singletonList(CswConstants.CONSTRAINT_LANGUAGE_FILTER)); getRecordsParameters.add(constraintLang); DomainType outputFormat = new DomainType(); outputFormat.setName(CswConstants.OUTPUT_FORMAT_PARAMETER); getRecordsParameters.add(outputFormat); DomainType resultType = new DomainType(); resultType.setName(CswConstants.RESULT_TYPE_PARAMETER); getRecordsParameters.add(resultType); DomainType elementSetName = new DomainType(); elementSetName.setName(CswConstants.ELEMENT_SET_NAME_PARAMETER); getRecordsParameters.add(elementSetName); List<DomainType> getRecordByIdParameters = new ArrayList<>(); DomainType getRecordByIdOutputSchema = new DomainType(); getRecordByIdOutputSchema.setName(CswConstants.OUTPUT_SCHEMA_PARAMETER); List<String> outputSchemas = new ArrayList<>(); outputSchemas.add(CswConstants.CSW_OUTPUT_SCHEMA); getRecordByIdOutputSchema.setValue(outputSchemas); getRecordByIdParameters.add(getRecordByIdOutputSchema); Operation getRecords = new Operation(); getRecords.setName(CswConstants.GET_RECORDS); getRecords.setParameter(getRecordsParameters); Operation getRecordById = new Operation(); getRecordById.setName(CswConstants.GET_RECORD_BY_ID); getRecordById.setParameter(getRecordByIdParameters); List<Operation> operations = new ArrayList<>(2); operations.add(getRecords); operations.add(getRecordById); OperationsMetadata mockOperationsMetadata = mock(OperationsMetadata.class); mockOperationsMetadata.setOperation(operations); when(mockCapabilities.getOperationsMetadata()).thenReturn(mockOperationsMetadata); when(mockOperationsMetadata.getOperation()).thenReturn(operations); if (numRecordsReturned > 0) { List<Metacard> metacards = new ArrayList<>(numRecordsReturned); for (int i = 1; i <= numRecordsReturned; i++) { String id = "ID_" + String.valueOf(i); MetacardImpl metacard = new MetacardImpl(); metacard.setId(id); metacard.setContentTypeName("myContentType"); metacards.add(metacard); } SearchResultsType searchResults = mock(SearchResultsType.class); when(searchResults.getNumberOfRecordsMatched()).thenReturn(BigInteger.valueOf( numRecordsMatched)); when(searchResults.getNumberOfRecordsReturned()).thenReturn(BigInteger.valueOf( numRecordsReturned)); CswRecordCollection mockCswRecordCollection = mock(CswRecordCollection.class); when(mockCswRecordCollection.getCswRecords()).thenReturn(metacards); when(mockCswRecordCollection.getNumberOfRecordsMatched()).thenReturn(numRecordsMatched); when(mockCswRecordCollection.getNumberOfRecordsReturned()).thenReturn((long) numRecordsReturned); when(mockCsw.getRecords(any(GetRecordsType.class))).thenReturn(mockCswRecordCollection); } } protected void setupMockContextForMetacardTypeRegistrationAndUnregistration( List<String> contentTypes) { ServiceRegistration<?> mockRegisteredMetacardType = (ServiceRegistration<?>) mock( ServiceRegistration.class); doReturn(mockRegisteredMetacardType).when(mockContext) .registerService(eq(MetacardType.class.getName()), any(MetacardType.class), Matchers.any()); ServiceReference<?> mockServiceReference = (ServiceReference<?>) mock(ServiceReference.class); doReturn(mockServiceReference).when(mockRegisteredMetacardType) .getReference(); when(mockServiceReference.getProperty(eq(Metacard.CONTENT_TYPE))).thenReturn(contentTypes); } protected List<String> getDefaultContentTypes() { return new LinkedList<>(Collections.singletonList("myContentType")); } }