/**
* 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.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.InputStream;
import java.io.StringWriter;
import java.io.Writer;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Dictionary;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
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.CswConstants;
import org.codice.ddf.spatial.ogc.csw.catalog.common.CswException;
import org.codice.ddf.spatial.ogc.csw.catalog.common.CswRecordCollection;
import org.codice.ddf.spatial.ogc.csw.catalog.common.CswRecordMetacardType;
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.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
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 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.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 {
/**
* ISO 8601 date time format with milliseconds and colon between hours/minutes in time zone,
* e.g., 2013-05-22T16:28:38.345-07:00
*
* The ZZ gives the colon in the time zone.
*/
protected static final String ISO_DATE_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZZ";
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 final GeotoolsFilterBuilder builder = new GeotoolsFilterBuilder();
protected RemoteCsw mockCsw = mock(RemoteCsw.class);
protected BundleContext mockContext = mock(BundleContext.class);
protected AvailabilityTask mockAvailabilityTask = mock(AvailabilityTask.class);
protected String getRecordsControlXml202 =
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
+ "<ns4: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:ns2=\"http://www.w3.org/1999/xlink\""
+ " xmlns=\"http://www.opengis.net/ows\" xmlns:ns4=\"http://www.opengis.net/cat/csw/2.0.2\""
+ " xmlns:ns3=\"http://www.opengis.net/ogc\" xmlns:ns9=\"http://www.w3.org/2001/SMIL20/Language\""
+ " xmlns:ns5=\"http://www.opengis.net/gml\" xmlns:ns6=\"http://purl.org/dc/elements/1.1/\""
+ " xmlns:ns7=\"http://purl.org/dc/terms/\" xmlns:ns8=\"http://www.w3.org/2001/SMIL20/\">"
+ "<ns4:Query typeNames=\"Record\" xmlns=\"\" xmlns:ns10=\"http://www.opengis.net/ows\">"
+ "<ns4:ElementSetName>full</ns4:ElementSetName>"
+ "<ns4:Constraint version=\"1.1.0\">" + "<ns3:Filter>"
+ "<ns3:PropertyIsLike wildCard=\"*\" singleChar=\"#\" escapeChar=\"!\">"
+ "<ns3:PropertyName>" + CswConstants.ANY_TEXT + "</ns3:PropertyName>"
+ "<ns3:Literal>*th*e</ns3:Literal>" + "</ns3:PropertyIsLike>" + "</ns3:Filter>"
+ "</ns4:Constraint>" + "</ns4:Query>" + "</ns4:GetRecords>";
protected String getRecordsControlXml201 =
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
+ "<ns4:GetRecords resultType=\"results\" outputFormat=\"application/xml\""
+ " outputSchema=\"OGCCORE\" startPosition=\"1\""
+ " maxRecords=\"10\" service=\"CSW\" version=\"2.0.1\" xmlns:ns2=\"http://www.w3.org/1999/xlink\""
+ " xmlns=\"http://www.opengis.net/ows\" xmlns:ns4=\"http://www.opengis.net/cat/csw\""
+ " xmlns:ns3=\"http://www.opengis.net/ogc\" xmlns:ns9=\"http://www.w3.org/2001/SMIL20/Language\""
+ " xmlns:ns5=\"http://www.opengis.net/gml\" xmlns:ns6=\"http://purl.org/dc/elements/1.1/\""
+ " xmlns:ns7=\"http://purl.org/dc/terms/\" xmlns:ns8=\"http://www.w3.org/2001/SMIL20/\">"
+ "<ns4:Query typeNames=\"csw:Record\" xmlns=\"\" xmlns:ns10=\"http://www.opengis.net/ows\">"
+ "<ns4:ElementSetName>full</ns4:ElementSetName>"
+ "<ns4:Constraint version=\"1.1.0\">" + "<ns3:Filter>"
+ "<ns3:PropertyIsLike wildCard=\"*\" singleChar=\"#\" escapeChar=\"!\">"
+ "<ns3:PropertyName>" + CswConstants.ANY_TEXT + "</ns3:PropertyName>"
+ "<ns3:Literal>*th*e</ns3:Literal>" + "</ns3:PropertyIsLike>" + "</ns3:Filter>"
+ "</ns4:Constraint>" + "</ns4:Query>" + "</ns4: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:ns2=\"http://www.opengis.net/ogc\""
+ " xmlns=\"http://www.opengis.net/cat/csw/2.0.2\" xmlns:ns4=\"http://www.w3.org/1999/xlink\""
+ " xmlns:ns3=\"http://www.opengis.net/gml\" xmlns:ns9=\"http://www.w3.org/2001/SMIL20/Language\""
+ " xmlns:ns5=\"http://purl.org/dc/elements/1.1/\" xmlns:ns6=\"http://www.opengis.net/ows\""
+ " xmlns:ns7=\"http://purl.org/dc/terms/\" xmlns:ns8=\"http://www.w3.org/2001/SMIL20/\">"
+ "<ns10:Query typeNames=\"Record\" xmlns=\"\" xmlns:ns10=\"http://www.opengis.net/cat/csw/2.0.2\">"
+ "<ns10:ElementSetName>full</ns10:ElementSetName>"
+ "<ns10:Constraint version=\"1.1.0\">" + "<ns2:Filter>"
+ "<ns2:PropertyIsEqualTo matchCase=\"true\">" + "<ns2:PropertyName>"
+ CswRecordMetacardType.CSW_FORMAT + "</ns2:PropertyName>"
+ "<ns2:Literal>myContentType</ns2:Literal>" + "</ns2:PropertyIsEqualTo>"
+ "</ns2:Filter>" + "</ns10:Constraint>" + "</ns10:Query>" + "</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);
}
@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(new Answer<Metacard>() {
@Override
public Metacard answer(InvocationOnMock invocation) throws Throwable {
Object[] args = invocation.getArguments();
return (Metacard) args[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<ContentType>();
for (String name : names) {
ContentType ct = new ContentTypeImpl(name, "");
contentTypes.add(ct);
}
return contentTypes;
}
protected RemoteCsw createRemoteCsw() throws CswException {
RemoteCsw mockRemoteCsw = mock(RemoteCsw.class);
InputStream stream = getClass().getResourceAsStream("/getCapabilities.xml");
CapabilitiesType capabilities = parseXml(stream);
when(mockRemoteCsw.getCapabilities(any(GetCapabilitiesRequest.class)))
.thenReturn(capabilities);
CswRecordCollection collection = generateCswCollection("/getBriefRecordsResponse.xml");
when(mockRemoteCsw.getRecords(any(GetRecordsType.class))).thenReturn(collection);
return mockRemoteCsw;
}
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<Metacard>();
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;
}
protected <T> T parseXml(InputStream stream) {
JAXBElement<T> jaxb = null;
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) {
LOGGER.error("failed to parse xml", e);
}
return (T) jaxb.getValue();
}
protected String getGetRecordsTypeAsXml(GetRecordsType getRecordsType, String cswVersion) {
Writer writer = new StringWriter();
try {
JAXBContext jaxbContext = JAXBContext.newInstance(
"net.opengis.cat.csw.v_2_0_2:net.opengis.filter.v_1_1_0:net.opengis.gml.v_3_1_1:net.opengis.ows.v_1_0_0");
Marshaller marshaller = jaxbContext.createMarshaller();
JAXBElement<GetRecordsType> jaxbElement = null;
if (CswConstants.VERSION_2_0_2.equals(cswVersion)) {
// QName("http://www.opengis.net/cat/csw/2.0.2", ""GetRecords")
jaxbElement = new JAXBElement<GetRecordsType>(
new QName(CswConstants.CSW_OUTPUT_SCHEMA, CswConstants.GET_RECORDS),
GetRecordsType.class, getRecordsType);
} else {
// CSW 2.0.1
// QName("http://www.opengis.net/cat/csw", ""GetRecords")
LOGGER.debug("Called 2.0.1");
jaxbElement = new JAXBElement<GetRecordsType>(
new QName("http://www.opengis.net/cat/csw", CswConstants.GET_RECORDS),
GetRecordsType.class, getRecordsType);
}
marshaller.marshal(jaxbElement, writer);
} catch (JAXBException e) {
String message = "Unable to marshall " + GetRecordsResponseType.class + " to XML.";
LOGGER.debug(message, e);
}
return writer.toString();
}
// //////////////////////////////////////////////////////////////////////////////////////////////////
protected void configureMockRemoteCsw() throws CswException {
configureMockRemoteCsw(0, 0L, CswConstants.VERSION_2_0_2);
}
protected void configureMockRemoteCsw(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<ComparisonOperatorType>();
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>();
DomainType typeName = new DomainType();
typeName.setName(CswConstants.TYPE_NAME_PARAMETER);
typeName.setValue(Arrays.asList(new String[] {"csw:Record"}));
getRecordsParameters.add(typeName);
DomainType typeNames = new DomainType();
typeNames.setName(CswConstants.TYPE_NAMES_PARAMETER);
getRecordsParameters.add(typeNames);
DomainType outputSchema = new DomainType();
outputSchema.setName(CswConstants.OUTPUT_SCHEMA_PARAMETER);
outputSchema.getValue().add(CswConstants.CSW_OUTPUT_SCHEMA);
getRecordsParameters.add(outputSchema);
DomainType constraintLang = new DomainType();
constraintLang.setName(CswConstants.CONSTRAINT_LANGUAGE_PARAMETER);
constraintLang.setValue(Arrays.asList(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);
Operation getRecords = new Operation();
getRecords.setName(CswConstants.GET_RECORDS);
getRecords.setParameter(getRecordsParameters);
List<Operation> operations = new ArrayList<Operation>(1);
operations.add(getRecords);
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<Metacard>(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(CswRecordMetacardType.class),
Matchers.<Dictionary<String, ?>>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<String>(Arrays.asList(new String[] {"myContentType"}));
}
}