/**
* 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 java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.io.StringWriter;
import java.io.Writer;
import java.math.BigInteger;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Dictionary;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.namespace.QName;
import org.apache.commons.lang.StringUtils;
import org.apache.cxf.common.util.CollectionUtils;
import org.codice.ddf.spatial.ogc.catalog.MetadataTransformer;
import org.codice.ddf.spatial.ogc.catalog.common.AvailabilityCommand;
import org.codice.ddf.spatial.ogc.catalog.common.AvailabilityTask;
import org.codice.ddf.spatial.ogc.catalog.common.TrustedRemoteSource;
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.CswSourceConfiguration;
import org.codice.ddf.spatial.ogc.csw.catalog.common.GetCapabilitiesRequest;
import org.codice.ddf.spatial.ogc.csw.catalog.converter.CswTransformProvider;
import org.opengis.filter.Filter;
import org.opengis.filter.sort.SortOrder;
import org.osgi.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.thoughtworks.xstream.converters.Converter;
import ddf.catalog.Constants;
import ddf.catalog.data.ContentType;
import ddf.catalog.data.Metacard;
import ddf.catalog.data.MetacardType;
import ddf.catalog.data.Result;
import ddf.catalog.data.impl.ContentTypeImpl;
import ddf.catalog.data.impl.ResultImpl;
import ddf.catalog.filter.FilterAdapter;
import ddf.catalog.filter.FilterBuilder;
import ddf.catalog.operation.Query;
import ddf.catalog.operation.QueryRequest;
import ddf.catalog.operation.ResourceResponse;
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.resource.ResourceNotFoundException;
import ddf.catalog.resource.ResourceNotSupportedException;
import ddf.catalog.resource.ResourceReader;
import ddf.catalog.source.ConnectedSource;
import ddf.catalog.source.FederatedSource;
import ddf.catalog.source.SourceMonitor;
import ddf.catalog.source.UnsupportedQueryException;
import ddf.catalog.transform.CatalogTransformerException;
import ddf.catalog.util.impl.MaskableImpl;
import ddf.security.SecurityConstants;
import ddf.security.Subject;
import ddf.security.service.SecurityManager;
import ddf.security.settings.SecuritySettingsService;
import ddf.security.sts.client.configuration.STSClientConfiguration;
import net.opengis.cat.csw.v_2_0_2.CapabilitiesType;
import net.opengis.cat.csw.v_2_0_2.ElementSetNameType;
import net.opengis.cat.csw.v_2_0_2.ElementSetType;
import net.opengis.cat.csw.v_2_0_2.GetRecordsType;
import net.opengis.cat.csw.v_2_0_2.ObjectFactory;
import net.opengis.cat.csw.v_2_0_2.QueryConstraintType;
import net.opengis.cat.csw.v_2_0_2.QueryType;
import net.opengis.cat.csw.v_2_0_2.ResultType;
import net.opengis.filter.v_1_1_0.FilterCapabilities;
import net.opengis.filter.v_1_1_0.FilterType;
import net.opengis.filter.v_1_1_0.PropertyNameType;
import net.opengis.filter.v_1_1_0.SortByType;
import net.opengis.filter.v_1_1_0.SortOrderType;
import net.opengis.filter.v_1_1_0.SortPropertyType;
import net.opengis.filter.v_1_1_0.SpatialCapabilitiesType;
import net.opengis.filter.v_1_1_0.SpatialOperatorNameType;
import net.opengis.filter.v_1_1_0.SpatialOperatorType;
import net.opengis.filter.v_1_1_0.SpatialOperatorsType;
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;
/**
* CswSource provides a DDF {@link FederatedSource} and {@link ConnectedSource} for CSW 2.0.2
* services.
*/
public class CswSource extends MaskableImpl implements FederatedSource, ConnectedSource {
protected static final String CSW_SERVER_ERROR = "Error received from CSW server.";
protected static final String CSWURL_PROPERTY = "cswUrl";
protected static final String ID_PROPERTY = "id";
protected static final String USERNAME_PROPERTY = "username";
protected static final String PASSWORD_PROPERTY = "password";
protected static final String CONTENTTYPES_PROPERTY = "contentTypeNames";
protected static final String EFFECTIVE_DATE_MAPPING_PROPERTY = "effectiveDateMapping";
protected static final String CREATED_DATE_MAPPING_PROPERTY = "createdDateMapping";
protected static final String MODIFIED_DATE_MAPPING_PROPERTY = "modifiedDateMapping";
protected static final String CONTENT_TYPE_MAPPING_PROPERTY = "contentTypeMapping";
protected static final String IS_LON_LAT_ORDER_PROPERTY = "isLonLatOrder";
protected static final String POLL_INTERVAL_PROPERTY = "pollInterval";
protected static final String OUTPUT_SCHEMA_PROPERTY = "outputSchema";
protected static final String FORCE_SPATIAL_FILTER_PROPERTY = "forceSpatialFilter";
protected static final String NO_FORCE_SPATIAL_FILTER = "NO_FILTER";
protected static final String CONNECTION_TIMEOUT_PROPERTY = "connectionTimeout";
protected static final String RECEIVE_TIMEOUT_PROPERTY = "receiveTimeout";
private static final Logger LOGGER = LoggerFactory.getLogger(CswSource.class);
private static final String DEFAULT_CSW_TRANSFORMER_ID = "csw";
private static final String DESCRIBABLE_PROPERTIES_FILE = "/describable.properties";
private static final String DESCRIPTION = "description";
private static final String ORGANIZATION = "organization";
private static final String VERSION = "version";
private static final String TITLE = "name";
private static final int CONTENT_TYPE_SAMPLE_SIZE = 50;
private static final JAXBContext JAXB_CONTEXT = initJaxbContext();
private static final String USE_POS_LIST_PROPERTY = "usePosList";
private static Properties describableProperties = new Properties();
static {
try (InputStream properties = CswSource.class
.getResourceAsStream(DESCRIBABLE_PROPERTIES_FILE)) {
describableProperties.load(properties);
} catch (IOException e) {
LOGGER.info("Failed to load properties", e);
}
}
protected CswSourceConfiguration cswSourceConfiguration;
protected CswFilterDelegate cswFilterDelegate;
protected RemoteCsw remoteCsw;
protected boolean contentTypeMappingUpdated;
protected Converter cswTransformProvider;
protected String forceSpatialFilter = NO_FORCE_SPATIAL_FILTER;
protected ScheduledFuture<?> availabilityPollFuture;
protected SecuritySettingsService securitySettingsService;
protected SecurityManager securityManager;
private FilterBuilder filterBuilder;
private FilterAdapter filterAdapter;
private Set<SourceMonitor> sourceMonitors = new HashSet<SourceMonitor>();
private Map<String, ContentType> contentTypes;
private List<ResourceReader> resourceReaders;
private DomainType supportedOutputSchemas;
private Set<ElementSetType> detailLevels;
private BundleContext context;
private String description = null;
private CapabilitiesType capabilities;
private List<ServiceRegistration<?>> registeredMetacardTypes = new ArrayList<ServiceRegistration<?>>();
private String cswVersion;
private SpatialCapabilitiesType spatialCapabilities;
private ScheduledExecutorService scheduler;
private AvailabilityTask availabilityTask;
private boolean isConstraintCql;
/**
* Instantiates a CswSource. This constructor is for unit tests
*
* @param remoteCsw The JAXRS connection to a {@link org.codice.ddf.spatial.ogc.csw.catalog.common.Csw}
* @param context The {@link BundleContext} from the OSGi Framework
*/
public CswSource(RemoteCsw remoteCsw, BundleContext context,
CswSourceConfiguration cswSourceConfiguration, CswTransformProvider provider) {
this.remoteCsw = remoteCsw;
this.context = context;
this.cswSourceConfiguration = cswSourceConfiguration;
this.cswTransformProvider = provider;
scheduler = Executors.newSingleThreadScheduledExecutor();
}
/**
* Instantiates a CswSource.
*/
public CswSource() {
cswSourceConfiguration = new CswSourceConfiguration();
scheduler = Executors.newSingleThreadScheduledExecutor();
}
private static JAXBContext initJaxbContext() {
JAXBContext jaxbContext = null;
String contextPath = StringUtils
.join(new String[] {CswConstants.OGC_CSW_PACKAGE, CswConstants.OGC_FILTER_PACKAGE,
CswConstants.OGC_GML_PACKAGE, CswConstants.OGC_OWS_PACKAGE}, ":");
try {
jaxbContext = JAXBContext.newInstance(contextPath, CswSource.class.getClassLoader());
} catch (JAXBException e) {
LOGGER.error("Failed to initialize JAXBContext", e);
}
return jaxbContext;
}
/**
* Initializes the CswSource by connecting to the Server
*/
public void init() {
LOGGER.debug("{}: Entering init()", cswSourceConfiguration.getId());
setupAvailabilityPoll();
}
/**
* Clean-up when shutting down the CswSource
*/
public void destroy() {
LOGGER.debug("{}: Entering destroy()", cswSourceConfiguration.getId());
unregisterMetacardTypes();
availabilityPollFuture.cancel(true);
scheduler.shutdownNow();
}
/**
* Reinitializes the CswSource when there is a configuration change. Otherwise, it checks with
* the server to see if any capabilities have changed.
*
* @param configuration The configuration with which to connect to the server
*/
public void refresh(Map<String, Object> configuration) {
LOGGER.debug("{}: Entering refresh()", cswSourceConfiguration.getId());
if (configuration == null || configuration.isEmpty()) {
LOGGER.error("Recieved null or empty configuration during refresh for {}: {}",
this.getClass().getSimpleName(), cswSourceConfiguration.getId());
return;
}
String idProp = (String) configuration.get(ID_PROPERTY);
if (StringUtils.isNotBlank(idProp)) {
cswSourceConfiguration.setId(idProp);
}
String cswUrlProp = (String) configuration.get(CSWURL_PROPERTY);
if (StringUtils.isNotBlank(cswUrlProp)) {
cswSourceConfiguration.setCswUrl(cswUrlProp);
}
String passProp = (String) configuration.get(PASSWORD_PROPERTY);
if (StringUtils.isNotBlank(passProp)) {
cswSourceConfiguration.setPassword(passProp);
}
String userProp = (String) configuration.get(USERNAME_PROPERTY);
if (StringUtils.isNotBlank(userProp)) {
cswSourceConfiguration.setUsername(userProp);
}
Integer newConnTimeout = (Integer) configuration.get(CONNECTION_TIMEOUT_PROPERTY);
if (newConnTimeout != null) {
cswSourceConfiguration.setConnectionTimeout(newConnTimeout);
}
Integer newRecTimeout = (Integer) configuration.get(RECEIVE_TIMEOUT_PROPERTY);
if (newRecTimeout != null) {
cswSourceConfiguration.setReceiveTimeout(newRecTimeout);
}
updateTimeouts();
String schemaProp = (String) configuration.get(OUTPUT_SCHEMA_PROPERTY);
if (StringUtils.isNotBlank(schemaProp)) {
String oldOutputSchema = cswSourceConfiguration.getOutputSchema();
cswSourceConfiguration.setOutputSchema(schemaProp);
LOGGER.debug("{}: new output schema: {}", cswSourceConfiguration.getId(),
cswSourceConfiguration.getOutputSchema());
LOGGER.debug("{}: old output schema: {}", cswSourceConfiguration.getId(),
oldOutputSchema);
}
Boolean sslProp = (Boolean) configuration
.get(TrustedRemoteSource.DISABLE_CN_CHECK_PROPERTY);
if (sslProp != null) {
cswSourceConfiguration.setDisableCnCheck(sslProp);
}
Boolean latLonProp = (Boolean) configuration.get(IS_LON_LAT_ORDER_PROPERTY);
if (latLonProp != null) {
cswSourceConfiguration.setIsLonLatOrder(latLonProp);
if (cswSourceConfiguration.isLonLatOrder()) {
LOGGER.debug("{}: Setting coordinate ordering to LON/LAT.",
cswSourceConfiguration.getId());
} else {
LOGGER.debug("{}: Setting coordinate ordering to LAT/LON.",
cswSourceConfiguration.getId());
}
}
Boolean posListProp = (Boolean) configuration.get(USE_POS_LIST_PROPERTY);
if (posListProp != null) {
cswSourceConfiguration.setUsePosList(posListProp);
}
String spatialFilter = (String) configuration.get(FORCE_SPATIAL_FILTER_PROPERTY);
if (StringUtils.isBlank(spatialFilter)) {
spatialFilter = NO_FORCE_SPATIAL_FILTER;
}
forceSpatialFilter = spatialFilter;
String[] contentTypeNames = (String[]) configuration.get(CONTENTTYPES_PROPERTY);
if (contentTypeNames != null) {
setContentTypeNames(Arrays.asList(contentTypeNames));
}
String createdProp = (String) configuration.get(CREATED_DATE_MAPPING_PROPERTY);
if (StringUtils.isNotBlank(createdProp)) {
cswSourceConfiguration.setCreatedDateMapping(createdProp);
}
String effectiveProp = (String) configuration.get(EFFECTIVE_DATE_MAPPING_PROPERTY);
if (StringUtils.isNotBlank(effectiveProp)) {
cswSourceConfiguration.setEffectiveDateMapping(effectiveProp);
}
String modifiedProp = (String) configuration.get(MODIFIED_DATE_MAPPING_PROPERTY);
if (StringUtils.isNotBlank(modifiedProp)) {
cswSourceConfiguration.setModifiedDateMapping(modifiedProp);
}
String currentContentTypeMapping = ((String) configuration
.get(CONTENT_TYPE_MAPPING_PROPERTY));
if (StringUtils.isNotBlank(currentContentTypeMapping)) {
String previousContentTypeMapping = cswSourceConfiguration.getContentTypeMapping();
LOGGER.debug("{}: Previous content type mapping: {}.", cswSourceConfiguration.getId(),
previousContentTypeMapping);
contentTypeMappingUpdated = !currentContentTypeMapping
.equals(previousContentTypeMapping);
currentContentTypeMapping = currentContentTypeMapping.trim();
if (contentTypeMappingUpdated) {
LOGGER.debug("{}: The content type has been updated from {} to {}.",
cswSourceConfiguration.getId(), previousContentTypeMapping,
currentContentTypeMapping);
}
} else {
currentContentTypeMapping = CswRecordMetacardType.CSW_TYPE;
}
cswSourceConfiguration.setContentTypeMapping(currentContentTypeMapping);
LOGGER.debug("{}: Current content type mapping: {}.", cswSourceConfiguration.getId(),
currentContentTypeMapping);
connectToRemoteCsw();
configureCswSource();
Integer newPollInterval = (Integer) configuration.get(POLL_INTERVAL_PROPERTY);
if (newPollInterval != null && !newPollInterval
.equals(cswSourceConfiguration.getPollIntervalMinutes())) {
LOGGER.debug("Poll Interval was changed for source {}.",
cswSourceConfiguration.getId());
cswSourceConfiguration.setPollIntervalMinutes(newPollInterval);
availabilityPollFuture.cancel(true);
setupAvailabilityPoll();
}
}
protected void setupAvailabilityPoll() {
LOGGER.debug("Setting Availability poll task for {} minute(s) on Source {}",
cswSourceConfiguration.getPollIntervalMinutes(), cswSourceConfiguration.getId());
CswSourceAvailabilityCommand command = new CswSourceAvailabilityCommand();
long interval = TimeUnit.MINUTES.toMillis(cswSourceConfiguration.getPollIntervalMinutes());
if (availabilityPollFuture == null || availabilityPollFuture.isCancelled()) {
if (availabilityTask == null) {
availabilityTask = new AvailabilityTask(interval, command,
cswSourceConfiguration.getId());
} else {
availabilityTask.setInterval(interval);
}
// Run the availability check immediately prior to scheduling it in a thread.
// This is necessary to allow the catalog framework to have the correct
// availability when the source is bound
availabilityTask.run();
// Schedule the availability check every 1 second. The actually call to
// the remote server will only occur if the pollInterval has
// elapsed.
availabilityPollFuture = scheduler
.scheduleWithFixedDelay(availabilityTask, AvailabilityTask.NO_DELAY,
AvailabilityTask.ONE_SECOND, TimeUnit.SECONDS);
} else {
LOGGER.debug("No changes being made on the poller.");
}
}
protected void connectToRemoteCsw() {
LOGGER.debug("Connecting to remote CSW Server " + cswSourceConfiguration.getCswUrl());
try {
remoteCsw = new RemoteCsw(cswTransformProvider, cswSourceConfiguration);
remoteCsw.setSecuritySettings(securitySettingsService);
remoteCsw.setSecurityManager(securityManager);
remoteCsw.setTlsParameters();
remoteCsw.setTimeouts(cswSourceConfiguration.getConnectionTimeout(),
cswSourceConfiguration.getReceiveTimeout());
} catch (IllegalArgumentException iae) {
LOGGER.error("Unable to create RemoteCsw.", iae);
remoteCsw = null;
}
}
public void setConnectionTimeout(Integer timeout) {
this.cswSourceConfiguration.setConnectionTimeout(timeout);
}
public void setReceiveTimeout(Integer timeout) {
this.cswSourceConfiguration.setReceiveTimeout(timeout);
}
public void updateTimeouts() {
if (remoteCsw != null) {
remoteCsw.setTimeouts(cswSourceConfiguration.getConnectionTimeout(),
cswSourceConfiguration.getReceiveTimeout());
}
}
public void setContext(BundleContext context) {
this.context = context;
}
@Override
public Set<ContentType> getContentTypes() {
return new HashSet<ContentType>(contentTypes.values());
}
public List<String> getContentTypeNames() {
return new ArrayList<String>(contentTypes.keySet());
}
public void setContentTypeNames(List<String> contentTypeNames) {
this.contentTypes = new HashMap<String, ContentType>();
for (String contentType : contentTypeNames) {
addContentType(contentType);
}
}
public List<ResourceReader> getResourceReaders() {
return resourceReaders;
}
public void setResourceReaders(List<ResourceReader> resourceReaders) {
this.resourceReaders = resourceReaders;
}
public void setOutputSchema(String outputSchema) {
cswSourceConfiguration.setOutputSchema(outputSchema);
LOGGER.debug("Setting output schema to: {}", outputSchema);
}
@Override
public boolean isAvailable() {
return availabilityTask.isAvailable();
}
@Override
public boolean isAvailable(SourceMonitor sourceMonitor) {
sourceMonitors.add(sourceMonitor);
return isAvailable();
}
@Override
public SourceResponse query(QueryRequest queryRequest) throws UnsupportedQueryException {
return query(queryRequest, ElementSetType.FULL, null);
}
private SourceResponse query(QueryRequest queryRequest, ElementSetType elementSetName,
List<QName> elementNames) throws UnsupportedQueryException {
Query query = queryRequest.getQuery();
LOGGER.debug("{}: Received query:\n{}", cswSourceConfiguration.getId(), query);
GetRecordsType getRecordsType = createGetRecordsRequest(query, elementSetName,
elementNames);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("{}: GetRecords request:\n {}", cswSourceConfiguration.getId(),
getGetRecordsTypeAsXml(getRecordsType));
}
LOGGER.debug("{}: Sending query to: {}", cswSourceConfiguration.getId(),
cswSourceConfiguration.getCswUrl());
List<Result> results = null;
Long totalHits = 0L;
try {
Subject subject = (Subject) queryRequest
.getPropertyValue(SecurityConstants.SECURITY_SUBJECT);
if (subject != null) {
LOGGER.debug("Setting user credentials on outgoing CSW request.");
remoteCsw.setSubject(subject);
} else {
LOGGER.debug(
"No user credentials found, sending CSW request with no user information.");
}
CswRecordCollection cswRecordCollection = this.remoteCsw.getRecords(getRecordsType);
if (cswRecordCollection == null) {
throw new UnsupportedQueryException("Invalid results returned from server");
}
this.availabilityTask.updateLastAvailableTimestamp(System.currentTimeMillis());
LOGGER.debug("{}: Received [{}] record(s) of the [{}] record(s) matched from {}.",
cswSourceConfiguration.getId(),
cswRecordCollection.getNumberOfRecordsReturned(),
cswRecordCollection.getNumberOfRecordsMatched(),
cswSourceConfiguration.getCswUrl());
results = createResults(cswRecordCollection);
totalHits = cswRecordCollection.getNumberOfRecordsMatched();
} catch (CswException cswe) {
LOGGER.error(CSW_SERVER_ERROR, cswe);
throw new UnsupportedQueryException(CSW_SERVER_ERROR, cswe);
} catch (WebApplicationException wae) {
String msg = handleWebApplicationException(wae);
throw new UnsupportedQueryException(msg, wae);
} catch (Exception ce) {
String msg = handleClientException(ce);
throw new UnsupportedQueryException(msg, ce);
}
LOGGER.debug("{}: Adding {} result(s) to the source response.",
cswSourceConfiguration.getId(), results.size());
SourceResponseImpl sourceResponse = new SourceResponseImpl(queryRequest, results,
totalHits);
addContentTypes(sourceResponse);
unregisterMetacardTypes();
registerMetacardTypes();
return sourceResponse;
}
@Override
public String getDescription() {
StringBuilder sb = new StringBuilder();
sb.append(describableProperties.getProperty(DESCRIPTION))
.append(System.getProperty("line.separator")).append(description);
return sb.toString();
}
@Override
public String getId() {
String sourceId = super.getId();
// Note, returning "UNKNOWN" causes issues for collecting source metrics on
// ConnectedSources. This method is called initially when the connected source is first
// added and the sourceId is null at that time, but this causes metrics for an UNKNOWN
// source to be created and never deleted. Returning super.getId() for the ConnectedSources
// until a problem is discovered.
return sourceId;
}
public void setId(String id) {
cswSourceConfiguration.setId(id);
super.setId(id);
}
@Override
public void maskId(String newSourceId) {
final String methodName = "maskId";
LOGGER.debug("ENTERING: {} with sourceId = {}", methodName, newSourceId);
if (newSourceId != null) {
super.maskId(newSourceId);
}
LOGGER.debug("EXITING: {}", methodName);
}
@Override
public String getOrganization() {
return describableProperties.getProperty(ORGANIZATION);
}
@Override
public String getTitle() {
return describableProperties.getProperty(TITLE);
}
@Override
public String getVersion() {
if (StringUtils.isNotBlank(cswVersion)) {
return cswVersion;
}
return describableProperties.getProperty(VERSION);
}
@Override
public Set<String> getOptions(Metacard arg0) {
return null;
}
@Override
public Set<String> getSupportedSchemes() {
return null;
}
@Override
public ResourceResponse retrieveResource(URI resourceUri,
Map<String, Serializable> properties) throws IOException, ResourceNotFoundException,
ResourceNotSupportedException {
LOGGER.debug("retrieving resource at : {}", resourceUri);
ResourceResponse resource = null;
if (resourceUri == null) {
throw new ResourceNotFoundException("Unable to find resource due to null URI");
}
String scheme = resourceUri.getScheme();
LOGGER.debug("Searching for ResourceReader that supports scheme = " + scheme);
LOGGER.debug("resourceReaders.size() = {}", resourceReaders.size());
Iterator<ResourceReader> iterator = resourceReaders.iterator();
while (iterator.hasNext() && resource == null) {
ResourceReader reader = iterator.next();
if (reader.getSupportedSchemes() != null && reader.getSupportedSchemes()
.contains(scheme)) {
try {
LOGGER.debug("Found an acceptable resource reader ({}) for URI {}",
reader.getId(), resourceUri.toASCIIString());
resource = reader.retrieveResource(resourceUri, properties);
if (resource == null) {
LOGGER.info(
"Resource returned from ResourceReader {} was null. Checking other readers for URI: {}",
reader.getId(), resourceUri);
}
} catch (ResourceNotFoundException e) {
LOGGER.debug(
"Enterprise Search: Product not found using resource reader with name {}",
reader.getId(), e);
} catch (ResourceNotSupportedException e) {
LOGGER.debug(
"Enterprise Search: Product not found using resource reader with name {}",
reader.getId(), e);
} catch (IOException ioe) {
LOGGER.debug(
"Enterprise Search: Product not found using resource reader with name {}",
reader.getId(), ioe);
}
}
}
if (resource == null) {
throw new ResourceNotFoundException(
"Resource Readers could not find resource (or returned null resource) for URI: "
+ resourceUri);
}
LOGGER.debug("Received resource, sending back: {}", resource.getResource().getName());
return resource;
}
public void setCswUrl(String cswUrl) {
LOGGER.debug("Setting cswUrl to {}", cswUrl);
cswSourceConfiguration.setCswUrl(cswUrl);
}
public void setUsername(String username) {
cswSourceConfiguration.setUsername(username);
}
public void setPassword(String password) {
cswSourceConfiguration.setPassword(password);
}
public void setDisableCnCheck(Boolean disableCnCheck) {
cswSourceConfiguration.setDisableCnCheck(disableCnCheck);
}
public void setIsLonLatOrder(Boolean isLonLatOrder) {
cswSourceConfiguration.setIsLonLatOrder(isLonLatOrder);
LOGGER.debug("{}: LON/LAT order: {}", cswSourceConfiguration.getId(),
cswSourceConfiguration.isLonLatOrder());
}
public void setUsePosList(Boolean usePosList) {
cswSourceConfiguration.setUsePosList(usePosList);
LOGGER.debug("Using posList rather than individual pos elements?: {}", usePosList);
}
public void setIsCqlForced(Boolean isCqlForced) {
cswSourceConfiguration.setIsCqlForced(isCqlForced);
}
public void setEffectiveDateMapping(String effectiveDateMapping) {
cswSourceConfiguration.setEffectiveDateMapping(effectiveDateMapping);
}
public void setCreatedDateMapping(String createdDateMapping) {
cswSourceConfiguration.setCreatedDateMapping(createdDateMapping);
}
public void setModifiedDateMapping(String modifiedDateMapping) {
cswSourceConfiguration.setModifiedDateMapping(modifiedDateMapping);
}
public void setResourceUriMapping(String resourceUriMapping) {
cswSourceConfiguration.setResourceUriMapping(resourceUriMapping);
}
public void setThumbnailMapping(String thumbnailMapping) {
cswSourceConfiguration.setThumbnailMapping(thumbnailMapping);
}
public void setFilterAdapter(FilterAdapter filterAdapter) {
this.filterAdapter = filterAdapter;
}
public void setFilterBuilder(FilterBuilder filterBuilder) {
this.filterBuilder = filterBuilder;
}
public void setContentTypeMapping(String contentTypeMapping) {
if (StringUtils.isEmpty(contentTypeMapping)) {
contentTypeMapping = CswRecordMetacardType.CSW_TYPE;
}
cswSourceConfiguration.setContentTypeMapping(contentTypeMapping);
}
public void setPollInterval(Integer interval) {
this.cswSourceConfiguration.setPollIntervalMinutes(interval);
}
public Converter getCswTransformProvider() {
return this.cswTransformProvider;
}
public void setCswTransformProvider(Converter provider) {
this.cswTransformProvider = provider;
}
public String getForceSpatialFilter() {
return forceSpatialFilter;
}
public void setForceSpatialFilter(String forceSpatialFilter) {
this.forceSpatialFilter = forceSpatialFilter;
}
private GetRecordsType createGetRecordsRequest(Query query, ElementSetType elementSetName,
List<QName> elementNames) throws UnsupportedQueryException {
GetRecordsType getRecordsType = new GetRecordsType();
getRecordsType.setVersion(cswVersion);
getRecordsType.setService(CswConstants.CSW);
getRecordsType.setResultType(ResultType.RESULTS);
getRecordsType.setStartPosition(BigInteger.valueOf(query.getStartIndex()));
getRecordsType.setMaxRecords(BigInteger.valueOf(query.getPageSize()));
getRecordsType.setOutputFormat(MediaType.APPLICATION_XML);
if (!isOutputSchemaSupported()) {
String msg = "CSW Source: " + cswSourceConfiguration.getId()
+ " does not support output schema: " + cswSourceConfiguration.getOutputSchema()
+ ".";
throw new UnsupportedQueryException(msg);
}
getRecordsType.setOutputSchema(cswSourceConfiguration.getOutputSchema());
getRecordsType.setAbstractQuery(createQuery(query, elementSetName, elementNames));
return getRecordsType;
}
private ElementSetNameType createElementSetName(ElementSetType type) {
ElementSetNameType elementSetNameType = new ElementSetNameType();
elementSetNameType.setValue(type);
return elementSetNameType;
}
private JAXBElement<QueryType> createQuery(Query query, ElementSetType elementSetType,
List<QName> elementNames) throws UnsupportedQueryException {
QueryType queryType = new QueryType();
queryType.setTypeNames(Arrays.asList(new QName[] {
new QName(CswConstants.CSW_OUTPUT_SCHEMA, CswConstants.CSW_RECORD_LOCAL_NAME,
CswConstants.CSW_NAMESPACE_PREFIX)}));
if (null != elementSetType) {
queryType.setElementSetName(createElementSetName(elementSetType));
} else if (!CollectionUtils.isEmpty(elementNames)) {
queryType.setElementName(elementNames);
} else {
queryType.setElementSetName(createElementSetName(ElementSetType.FULL));
}
SortByType sortBy = createSortBy(query);
if (sortBy != null) {
queryType.setSortBy(sortBy);
}
QueryConstraintType constraint = createQueryConstraint(query);
if (null != constraint) {
queryType.setConstraint(constraint);
}
ObjectFactory objectFactory = new ObjectFactory();
return objectFactory.createQuery(queryType);
}
private SortByType createSortBy(Query query) {
SortByType sortBy = null;
if (query.getSortBy() != null) {
sortBy = new SortByType();
SortPropertyType sortProperty = new SortPropertyType();
PropertyNameType propertyName = new PropertyNameType();
String propName = query.getSortBy().getPropertyName().getPropertyName();
if (propName != null) {
if (Result.TEMPORAL.equals(propName) || Metacard.ANY_DATE.equals(propName)) {
propName = Metacard.MODIFIED;
} else if (Result.RELEVANCE.equals(propName) || Metacard.ANY_TEXT
.equals(propName)) {
propName = Metacard.TITLE;
} else if (Result.DISTANCE.equals(propName) || Metacard.ANY_GEO.equals(propName)) {
return null;
}
}
propertyName.setContent(
Arrays.asList((Object) cswFilterDelegate.mapPropertyName(propName)));
sortProperty.setPropertyName(propertyName);
if (SortOrder.DESCENDING.equals(query.getSortBy().getSortOrder())) {
sortProperty.setSortOrder(SortOrderType.DESC);
} else {
sortProperty.setSortOrder(SortOrderType.ASC);
}
sortBy.getSortProperty().add(sortProperty);
}
return sortBy;
}
private QueryConstraintType createQueryConstraint(Query query) throws
UnsupportedQueryException {
FilterType filter = createFilter(query);
if (null == filter) {
return null;
}
QueryConstraintType queryConstraintType = new QueryConstraintType();
queryConstraintType.setVersion(CswConstants.CONSTRAINT_VERSION);
if (isConstraintCql || cswSourceConfiguration.isCqlForced()) {
queryConstraintType.setCqlText(CswCqlTextFilter.getInstance().getCqlText(filter));
} else {
queryConstraintType.setFilter(filter);
}
return queryConstraintType;
}
private FilterType createFilter(Query query) throws UnsupportedQueryException {
OperationsMetadata operationsMetadata = capabilities.getOperationsMetadata();
if (null == operationsMetadata) {
LOGGER.error("{}: CSW Source contains no operations", cswSourceConfiguration.getId());
return new FilterType();
}
if (null == capabilities.getFilterCapabilities()) {
LOGGER.warn(
"{}: CSW Source did not provide Filter Capabilities, unable to preform query.",
cswSourceConfiguration.getId());
throw new UnsupportedQueryException(cswSourceConfiguration.getId()
+ ": CSW Source did not provide Filter Capabilities, unable to preform query.");
}
return this.filterAdapter.adapt(query, cswFilterDelegate);
}
protected List<Result> createResults(CswRecordCollection cswRecordCollection) {
List<Result> results = new ArrayList<Result>();
LOGGER.debug("Found {} metacard(s) in the CswRecordCollection.",
cswRecordCollection.getCswRecords().size());
String transformerId = getMetadataTransformerId();
MetadataTransformer transformer = lookupMetadataTransformer(transformerId);
for (Metacard metacard : cswRecordCollection.getCswRecords()) {
metacard.setSourceId(getId());
if (transformer != null) {
metacard = transform(metacard, transformer);
}
Result result = new ResultImpl(metacard);
results.add(result);
}
return results;
}
protected String getMetadataTransformerId() {
return DEFAULT_CSW_TRANSFORMER_ID;
}
/**
* Transforms the Metacard created from the CSW Record using the transformer specified by its
* ID.
*
* @param metacard
* @return
*/
protected Metacard transform(Metacard metacard, MetadataTransformer transformer) {
if (metacard == null) {
throw new IllegalArgumentException(
cswSourceConfiguration.getId() + ": Metacard is null.");
}
try {
return transformer.transform(metacard);
} catch (CatalogTransformerException e) {
LOGGER.warn("{} :Metadata Transformation Failed for metacard: {}",
cswSourceConfiguration.getId(), metacard.getId(), e);
}
return metacard;
}
protected MetadataTransformer lookupMetadataTransformer(String transformerId) {
ServiceReference<?>[] refs = null;
try {
refs = context.getServiceReferences(MetadataTransformer.class.getName(),
"(" + Constants.SERVICE_ID + "=" + transformerId + ")");
} catch (InvalidSyntaxException e) {
LOGGER.warn(cswSourceConfiguration.getId() + ": Invalid transformer ID.", e);
return null;
}
if (refs == null || refs.length == 0) {
LOGGER.info("{}: Metadata Transformer " + transformerId + " not found.",
cswSourceConfiguration.getId());
return null;
} else {
return (MetadataTransformer) context.getService(refs[0]);
}
}
private String getGetRecordsTypeAsXml(GetRecordsType getRecordsType) {
Writer writer = new StringWriter();
try {
Marshaller marshaller = JAXB_CONTEXT.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
JAXBElement<GetRecordsType> jaxbElement = new JAXBElement<GetRecordsType>(
new QName(CswConstants.CSW_OUTPUT_SCHEMA, CswConstants.GET_RECORDS),
GetRecordsType.class, getRecordsType);
marshaller.marshal(jaxbElement, writer);
} catch (JAXBException e) {
LOGGER.error("{}: Unable to marshall {} to XML.", cswSourceConfiguration.getId(),
GetRecordsType.class, e);
}
return writer.toString();
}
protected CapabilitiesType getCapabilities() {
CapabilitiesType caps = null;
try {
if (remoteCsw != null) {
LOGGER.debug("Doing getCapabilities() call for CSW");
GetCapabilitiesRequest request = new GetCapabilitiesRequest(CswConstants.CSW);
request.setAcceptVersions(
CswConstants.VERSION_2_0_2 + "," + CswConstants.VERSION_2_0_1);
if (context != null) {
LOGGER.debug("Checking if STSClientConfiguration is in OSGi registry");
ServiceReference ref = context
.getServiceReference(STSClientConfiguration.class.getName());
if (ref != null) {
STSClientConfiguration stsClientConfig = (STSClientConfiguration) context
.getService(ref);
if (stsClientConfig != null) {
LOGGER.debug("stsClientConfig is not null - setting SAML assertion");
remoteCsw.setSAMLAssertion(stsClientConfig);
} else {
LOGGER.debug("stsClientConfig = null, so no security configured");
}
}
}
caps = remoteCsw.getCapabilities(request);
}
} catch (CswException cswe) {
LOGGER.error(CSW_SERVER_ERROR, cswe);
} catch (WebApplicationException wae) {
LOGGER.error(wae.getMessage(), wae);
handleWebApplicationException(wae);
} catch (Exception ce) {
handleClientException(ce);
}
return caps;
}
protected void configureCswSource() {
detailLevels = EnumSet.noneOf(ElementSetType.class);
capabilities = getCapabilities();
if (null != capabilities) {
cswVersion = capabilities.getVersion();
if (CswConstants.VERSION_2_0_1.equals(cswVersion)) {
remoteCsw.setCsw201();
}
if (capabilities.getFilterCapabilities() == null) {
return;
}
readGetRecordsOperation(capabilities);
loadContentTypes();
LOGGER.debug("{}: {}", cswSourceConfiguration.getId(), capabilities.toString());
} else {
LOGGER.error("{}: CSW Server did not return any capabilities.",
cswSourceConfiguration.getId());
}
}
private Operation getOperation(OperationsMetadata operations, String operation) {
for (Operation op : operations.getOperation()) {
if (operation.equals(op.getName())) {
return op;
}
}
LOGGER.error("{}: CSW Server did not contain getRecords operation",
cswSourceConfiguration.getId());
return null;
}
/**
* Parses the getRecords {@link Operation} to understand the capabilities of the org.codice.ddf.spatial.ogc.csw.catalog.common.Csw Server. A
* sample GetRecords Operation may look like this:
* <p/>
* <pre>
* <ows:Operation name="GetRecords">
* <ows:DCP>
* <ows:HTTP>
* <ows:Get xlink:href="http://www.cubewerx.com/cwcsw.cgi?" />
* <ows:Post xlink:href="http://www.cubewerx.com/cwcsw.cgi" />
* </ows:HTTP>
* </ows:DCP>
* <ows:Parameter name="TypeName">
* <ows:Value>csw:Record</ows:Value>
* </ows:Parameter>
* <ows:Parameter name="outputFormat">
* <ows:Value>application/xml</ows:Value>
* <ows:Value>text/html</ows:Value>
* <ows:Value>text/plain</ows:Value>
* </ows:Parameter>
* <ows:Parameter name="outputSchema">
* <ows:Value>http://www.opengis.net/cat/csw/2.0.2</ows:Value>
* </ows:Parameter>
* <ows:Parameter name="resultType">
* <ows:Value>hits</ows:Value>
* <ows:Value>results</ows:Value>
* <ows:Value>validate</ows:Value>
* </ows:Parameter>
* <ows:Parameter name="ElementSetName">
* <ows:Value>brief</ows:Value>
* <ows:Value>summary</ows:Value>
* <ows:Value>full</ows:Value>
* </ows:Parameter>
* <ows:Parameter name="CONSTRAINTLANGUAGE">
* <ows:Value>Filter</ows:Value>
* </ows:Parameter>
* </ows:Operation>
* </pre>
*
* @param capabilitiesType
* The capabilities the org.codice.ddf.spatial.ogc.csw.catalog.common.Csw Server supports
*/
private void readGetRecordsOperation(CapabilitiesType capabilitiesType) {
OperationsMetadata operationsMetadata = capabilitiesType.getOperationsMetadata();
if (null == operationsMetadata) {
LOGGER.error("{}: CSW Source contains no operations", cswSourceConfiguration.getId());
return;
}
description = capabilitiesType.getServiceIdentification().getAbstract();
Operation getRecordsOp = getOperation(operationsMetadata, CswConstants.GET_RECORDS);
if (null == getRecordsOp) {
LOGGER.error("{}: CSW Source contains no getRecords Operation",
cswSourceConfiguration.getId());
return;
}
this.supportedOutputSchemas = getParameter(getRecordsOp,
CswConstants.OUTPUT_SCHEMA_PARAMETER);
DomainType constraintLanguage = getParameter(getRecordsOp,
CswConstants.CONSTRAINT_LANGUAGE_PARAMETER);
if (null != constraintLanguage) {
DomainType outputFormatValues = getParameter(getRecordsOp,
CswConstants.OUTPUT_FORMAT_PARAMETER);
DomainType resultTypesValues = getParameter(getRecordsOp,
CswConstants.RESULT_TYPE_PARAMETER);
readSetDetailLevels(
getParameter(getRecordsOp, CswConstants.ELEMENT_SET_NAME_PARAMETER));
List<String> constraints = new ArrayList<String>();
for (String s : constraintLanguage.getValue()) {
constraints.add(s.toLowerCase());
}
if (constraints.contains(CswConstants.CONSTRAINT_LANGUAGE_CQL.toLowerCase())
&& !constraints
.contains(CswConstants.CONSTRAINT_LANGUAGE_FILTER.toLowerCase())) {
isConstraintCql = true;
} else {
isConstraintCql = false;
}
setFilterDelegate(new CswRecordMetacardType(), getRecordsOp,
capabilitiesType.getFilterCapabilities(), outputFormatValues, resultTypesValues,
cswSourceConfiguration);
spatialCapabilities = capabilitiesType.getFilterCapabilities().getSpatialCapabilities();
if (!NO_FORCE_SPATIAL_FILTER.equals(forceSpatialFilter)) {
SpatialOperatorType sot = new SpatialOperatorType();
SpatialOperatorNameType sont = SpatialOperatorNameType
.fromValue(forceSpatialFilter);
sot.setName(sont);
sot.setGeometryOperands(cswFilterDelegate.getGeoOpsForSpatialOp(sont));
SpatialOperatorsType spatialOperators = new SpatialOperatorsType();
spatialOperators.setSpatialOperator(Arrays.asList(sot));
cswFilterDelegate.setSpatialOps(spatialOperators);
}
}
}
/**
* Sets the {@link ddf.catalog.filter.FilterDelegate} used by the CswSource. May be overridden
* in order to provide a custom ddf.catalog.filter.FilterDelegate implementation.
*
* @param cswRecordMetacardType
* @param getRecordsOp
* @param filterCapabilities
* @param outputFormatValues
* @param resultTypesValues
* @param cswSourceConfiguration
*/
protected void setFilterDelegate(CswRecordMetacardType cswRecordMetacardType,
Operation getRecordsOp, FilterCapabilities filterCapabilities,
DomainType outputFormatValues, DomainType resultTypesValues,
CswSourceConfiguration cswSourceConfiguration) {
LOGGER.trace("Setting cswFilterDelegate to default CswFilterDelegate");
cswFilterDelegate = new CswFilterDelegate(cswRecordMetacardType, getRecordsOp,
filterCapabilities, outputFormatValues, resultTypesValues, cswSourceConfiguration);
}
private void readSetDetailLevels(DomainType elementSetNamesValues) {
if (null != elementSetNamesValues) {
for (String esn : elementSetNamesValues.getValue()) {
try {
detailLevels.add(ElementSetType.fromValue(esn.toLowerCase()));
} catch (IllegalArgumentException iae) {
LOGGER.warn("{}: \"{}\" is not a ElementSetType, Error: {}",
cswSourceConfiguration.getId(), esn, iae);
}
}
}
}
protected void loadContentTypes() {
Filter filter = filterBuilder.attribute(CswConstants.ANY_TEXT).is().like()
.text(CswConstants.WILD_CARD);
Query query = new QueryImpl(filter, 1, CONTENT_TYPE_SAMPLE_SIZE, null, true, 0);
QueryRequest queryReq = new QueryRequestImpl(query);
try {
query(queryReq);
} catch (UnsupportedQueryException e) {
LOGGER.error("{}: Failed to read Content-Types from CSW Server, Error: {}", getId(), e);
}
}
/**
* Searches every query response for previously unknown content types
*
* @param response A Query Response
*/
private void addContentTypes(SourceResponse response) {
if (response == null || response.getResults() == null) {
return;
}
if (contentTypeMappingUpdated) {
LOGGER.debug(
"{}: The content type mapping has been updated. Removing all old content types.",
cswSourceConfiguration.getId());
contentTypes.clear();
}
for (Result result : response.getResults()) {
Metacard metacard = result.getMetacard();
if (metacard != null) {
addContentType(metacard.getContentTypeName(), metacard.getContentTypeVersion(),
metacard.getContentTypeNamespace());
}
}
Configuration[] managedConfigs = getManagedConfigs();
if (managedConfigs != null) {
for (Configuration managedConfig : managedConfigs) {
Dictionary<String, Object> properties = managedConfig.getProperties();
Set<String> current = new HashSet<String>(
Arrays.asList((String[]) properties.get(CONTENTTYPES_PROPERTY)));
if (contentTypeMappingUpdated || (current != null && !current
.containsAll(contentTypes.keySet()))) {
LOGGER.debug("{}: Adding new content types {} for content type mapping: {}.",
cswSourceConfiguration.getId(), contentTypes.toString(),
cswSourceConfiguration.getContentTypeMapping());
properties.put(CONTENTTYPES_PROPERTY,
contentTypes.keySet().toArray(new String[0]));
properties.put(CONTENT_TYPE_MAPPING_PROPERTY,
cswSourceConfiguration.getContentTypeMapping());
try {
LOGGER.debug("{}: Updating CSW Federated Source configuration with {}.",
cswSourceConfiguration.getId(), properties.toString());
managedConfig.update(properties);
} catch (IOException e) {
LOGGER.warn(
"{}: Failed to update managedConfiguration with new contentTypes, Error: {}",
cswSourceConfiguration.getId(), e);
}
}
}
}
}
private Configuration[] getManagedConfigs() {
Configuration[] managedConfig = null;
ServiceReference configurationAdminReference = context
.getServiceReference(ConfigurationAdmin.class.getName());
if (configurationAdminReference != null) {
ConfigurationAdmin confAdmin = (ConfigurationAdmin) context
.getService(configurationAdminReference);
try {
managedConfig = confAdmin.listConfigurations(
"(&(" + ID_PROPERTY + "=" + cswSourceConfiguration.getId() + ")" + "("
+ CSWURL_PROPERTY + "=" + cswSourceConfiguration.getCswUrl()
+ "))");
} catch (IOException e) {
LOGGER.warn("{}: Failed to capture managedConfig. Exception: {}",
cswSourceConfiguration.getId(), e);
} catch (InvalidSyntaxException e) {
LOGGER.warn("{}: Failed to capture managedConfig. Exception: {}",
cswSourceConfiguration.getId(), e);
}
}
if (managedConfig != null) {
LOGGER.debug("{}: managedConfig length: {}.", cswSourceConfiguration.getId(),
managedConfig.length);
}
return managedConfig;
}
private boolean addContentType(String name) {
return addContentType(name, null, null);
}
private boolean addContentType(String name, String version, URI namespace) {
if (!StringUtils.isEmpty(name) && !contentTypes.containsKey(name)) {
if (version == null) {
version = "";
}
contentTypes.put(name, new ContentTypeImpl(name, version, namespace));
return true;
}
return false;
}
public DomainType getParameter(Operation operation, String name) {
for (DomainType parameter : operation.getParameter()) {
if (name.equalsIgnoreCase(parameter.getName())) {
return parameter;
}
}
if (LOGGER.isWarnEnabled()) {
LOGGER.warn("{}: CSW Operation \"{}\" did not contain the \"{}\" parameter",
new Object[] {cswSourceConfiguration.getId(), operation.getName(), name});
}
return null;
}
protected String handleWebApplicationException(WebApplicationException wae) {
Response response = wae.getResponse();
CswException cswException = new CswResponseExceptionMapper().fromResponse(response);
// Add the CswException message to the error message being logged. Do
// not include the CswException stack trace because it will not be
// meaningful since it will not show the root cause of the exception
// because the ExceptionReport was sent from CSW as an "OK" JAX-RS
// status rather than an error status.
String msg = "Error received from CSW Server " + cswSourceConfiguration.getId() + "\n"
+ cswException.getMessage();
LOGGER.error(msg, wae);
return msg;
}
private String handleClientException(Exception ce) {
String msg = "";
if (ce.getCause() instanceof WebApplicationException) {
msg = handleWebApplicationException((WebApplicationException) ce.getCause());
} else {
msg = "Error received from CSW Server " + cswSourceConfiguration.getId();
}
LOGGER.error(msg);
return msg;
}
private void availabilityChanged(boolean isAvailable) {
if (isAvailable) {
LOGGER.info("CSW source {} is available.", cswSourceConfiguration.getId());
} else {
LOGGER.info("CSW source {} is unavailable.", cswSourceConfiguration.getId());
this.remoteCsw = null;
}
for (SourceMonitor monitor : this.sourceMonitors) {
if (isAvailable) {
LOGGER.debug("Notifying source monitor that CSW source {} is available.",
cswSourceConfiguration.getId());
monitor.setAvailable();
} else {
LOGGER.debug("Notifying source monitor that CSW source {} is unavailable.",
cswSourceConfiguration.getId());
monitor.setUnavailable();
}
}
}
private void registerMetacardTypes() {
List<String> contentTypesNames = getContentTypeNames();
if (!contentTypesNames.isEmpty()) {
Dictionary<String, Object> metacardTypeProperties = new Hashtable<String, Object>();
metacardTypeProperties.put(Metacard.CONTENT_TYPE,
contentTypesNames.toArray(new String[contentTypesNames.size()]));
CswRecordMetacardType cswRecordMetacardType = new CswRecordMetacardType(
cswSourceConfiguration.getId());
LOGGER.debug("{}: CSW Record Metacard Type hash code: {}",
cswSourceConfiguration.getId(), cswRecordMetacardType.hashCode());
LOGGER.debug("{}: Registering CSW Record Metacard Type {} with content types: {}",
cswSourceConfiguration.getId(), cswRecordMetacardType.getClass().getName(),
contentTypesNames);
ServiceRegistration<?> registeredMetacardType = context
.registerService(MetacardType.class.getName(), cswRecordMetacardType,
metacardTypeProperties);
registeredMetacardTypes.add(registeredMetacardType);
} else {
LOGGER.debug(
"{}: There are no metadata content types to register for CSW Record Metacard Type {}. Not registering CSW Record Metacard Type {}.",
cswSourceConfiguration.getId(), CswRecordMetacardType.class.getName(),
CswRecordMetacardType.class.getName());
}
}
private void unregisterMetacardTypes() {
for (ServiceRegistration<?> metacardType : registeredMetacardTypes) {
LOGGER.debug(
"{}: Unregistering CSW Record Metacard Type {} with metadata content types {}",
cswSourceConfiguration.getId(), CswRecordMetacardType.class.getName(),
metacardType.getReference().getProperty(Metacard.CONTENT_TYPE));
metacardType.unregister();
}
registeredMetacardTypes.removeAll(registeredMetacardTypes);
}
private boolean isOutputSchemaSupported() {
return this.cswSourceConfiguration.getOutputSchema() != null
&& this.supportedOutputSchemas != null ?
this.supportedOutputSchemas.getValue()
.contains(cswSourceConfiguration.getOutputSchema()) :
false;
}
public void setAvailabilityTask(AvailabilityTask availabilityTask) {
this.availabilityTask = availabilityTask;
}
public void setSecuritySettings(SecuritySettingsService securitySettings) {
this.securitySettingsService = securitySettings;
}
public void setSecurityManager(SecurityManager securityManager) {
this.securityManager = securityManager;
}
/**
* Callback class to check the Availability of the CswSource.
* <p/>
* NOTE: Ideally, the framework would call isAvailable on the Source and the SourcePoller would
* have an AvailabilityTask that cached each Source's availability. Until that is done, allow
* the command to handle the logic of managing availability.
*
*/
private class CswSourceAvailabilityCommand implements AvailabilityCommand {
@Override
public boolean isAvailable() {
LOGGER.debug("Checking availability for source {} ", cswSourceConfiguration.getId());
boolean oldAvailability = CswSource.this.isAvailable();
boolean newAvailability = false;
// If the Remote object is null attempt to initialize it and
// configure
// all the capabilities.
if (remoteCsw == null) {
connectToRemoteCsw();
}
// Simple "ping" to ensure the source is responding
newAvailability = (getCapabilities() != null);
if (oldAvailability != newAvailability) {
availabilityChanged(newAvailability);
// If the source becomes available, configure it.
if (newAvailability) {
configureCswSource();
}
}
return newAvailability;
}
}
}