/* (c) 2017 Open Source Geospatial Foundation - all rights reserved
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.opensearch.eo;
import static org.geoserver.opensearch.eo.ComplexFeatureAccessor.value;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;
import org.geoserver.catalog.DataStoreInfo;
import org.geoserver.catalog.Predicates;
import org.geoserver.config.GeoServer;
import org.geoserver.opensearch.eo.store.OpenSearchAccess;
import org.geoserver.opensearch.eo.store.OpenSearchAccess.ProductClass;
import org.geoserver.platform.OWS20Exception;
import org.geoserver.platform.OWS20Exception.OWSExceptionCode;
import org.geotools.data.DataAccess;
import org.geotools.data.DataUtilities;
import org.geotools.data.FeatureSource;
import org.geotools.data.Parameter;
import org.geotools.data.Query;
import org.geotools.data.memory.MemoryFeatureCollection;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.NameImpl;
import org.geotools.util.logging.Logging;
import org.opengis.feature.Feature;
import org.opengis.feature.Property;
import org.opengis.feature.type.FeatureType;
import org.opengis.feature.type.Name;
import org.opengis.feature.type.PropertyDescriptor;
import org.opengis.filter.FilterFactory2;
import org.opengis.filter.PropertyIsEqualTo;
import org.opengis.filter.expression.PropertyName;
import com.vividsolutions.jts.geom.Geometry;
/**
* Default implementation of {@link OpenSearchEoService}
*
* @author Andrea Aime - GeoSolutions
*/
public class DefaultOpenSearchEoService implements OpenSearchEoService {
static final Logger LOGGER = Logging.getLogger(DefaultOpenSearchEoService.class);
static final FilterFactory2 FF = CommonFactoryFinder.getFilterFactory2();
/* Some mime types for quicklooks */
private static String JPEG_MIME = "image/jpeg";
private static String PNG_MIME = "image/png";
private static String BINARY_MIME = "application/octet-stream ";
GeoServer geoServer;
public DefaultOpenSearchEoService(GeoServer geoServer) {
this.geoServer = geoServer;
}
@Override
public OSEODescription description(OSEODescriptionRequest request) throws IOException {
final OSEOInfo service = getService();
final String parentId = request.getParentId();
List<Parameter<?>> searchParameters;
if (parentId != null) {
searchParameters = getProductSearchParameters(parentId);
} else {
searchParameters = getCollectionSearchParameters();
}
return new OSEODescription(request, service, geoServer.getGlobal(), searchParameters);
}
public List<Parameter<?>> getProductSearchParameters(final String parentId) throws IOException {
OSEOInfo service = getService();
List<Parameter<?>> searchParameters = new ArrayList<>();
searchParameters.addAll(OpenSearchParameters.getBasicOpensearch(service));
searchParameters.addAll(OpenSearchParameters.getGeoTimeOpensearch());
// product search for a given collection, figure out what parameters apply to it
Feature match = getCollectionByParentIdentifier(parentId);
Property sensorTypeProperty = match
.getProperty(new NameImpl(OpenSearchAccess.EO_NAMESPACE, "sensorType"));
if (sensorTypeProperty == null || !(sensorTypeProperty.getValue() instanceof String)) {
throw new OWS20Exception("Unknown or missing sensory type " + sensorTypeProperty);
}
// go to the classification
String sensorType = (String) sensorTypeProperty.getValue();
ProductClass collectionClass = null;
try {
collectionClass = OpenSearchAccess.ProductClass.valueOf(sensorType);
} catch (Exception e) {
LOGGER.warning("Could not understand sensor type " + sensorType
+ ", will only return generic product properties");
}
OpenSearchAccess access = getOpenSearchAccess();
FeatureType productSchema = access.getProductSource().getSchema();
searchParameters.addAll(getSearchParametersByClass(ProductClass.EOP_GENERIC, productSchema));
if (collectionClass != null) {
searchParameters.addAll(getSearchParametersByClass(collectionClass, productSchema));
}
return searchParameters;
}
/**
* Returns the complex feature representing a collection by parentId
*
* @param parentId
* @return
* @throws IOException
*/
private Feature getCollectionByParentIdentifier(final String parentId) throws IOException {
OpenSearchAccess access = getOpenSearchAccess();
final FeatureSource<FeatureType, Feature> collectionSource = access.getCollectionSource();
// build the query
final NameImpl identifier = new NameImpl(OpenSearchAccess.EO_NAMESPACE, "identifier");
final PropertyIsEqualTo filter = FF.equal(FF.property(identifier), FF.literal(parentId),
true);
Query query = new Query(collectionSource.getName().getLocalPart(), filter);
FeatureCollection<FeatureType, Feature> features = collectionSource.getFeatures(query);
// get the expected maching feature
Feature match = DataUtilities.first(features);
if (match == null) {
throw new OWS20Exception("Unknown parentId '" + parentId + "'",
OWSExceptionCode.InvalidParameterValue);
}
return match;
}
private List<Parameter<?>> getSearchParametersByClass(ProductClass pc,
FeatureType productSchema) {
List<Parameter<?>> result = new ArrayList<>();
final String targetNamespace = pc.getNamespace();
for (PropertyDescriptor pd : productSchema.getDescriptors()) {
Name name = pd.getName();
final String propertyNs = name.getNamespaceURI();
if (targetNamespace.equals(propertyNs)) {
Parameter parameter = new ParameterBuilder(name.getLocalPart(),
pd.getType().getBinding()).prefix(pc.getPrefix()).build();
result.add(parameter);
}
}
return result;
}
public List<Parameter<?>> getCollectionSearchParameters() throws IOException {
OSEOInfo service = getService();
List<Parameter<?>> searchParameters = new ArrayList<>();
searchParameters.addAll(OpenSearchParameters.getBasicOpensearch(service));
searchParameters.addAll(OpenSearchParameters.getGeoTimeOpensearch());
searchParameters.addAll(getCollectionEoSearchParameters());
return searchParameters;
}
private Collection<? extends Parameter<?>> getCollectionEoSearchParameters()
throws IOException {
final OpenSearchAccess osAccess = getOpenSearchAccess();
FeatureType schema = osAccess.getCollectionSource().getSchema();
List<Parameter<?>> result = new ArrayList<>();
for (PropertyDescriptor pd : schema.getDescriptors()) {
final Class<?> type = pd.getType().getBinding();
if (OpenSearchAccess.EO_NAMESPACE.equals(pd.getName().getNamespaceURI())
&& !Geometry.class.isAssignableFrom(type)) {
Parameter<?> parameter = new ParameterBuilder(pd.getName().getLocalPart(), type)
.prefix("eo").build();
result.add(parameter);
}
}
return result;
}
@Override
public SearchResults search(SearchRequest request) throws IOException {
// grab the right feature source for the request
final OpenSearchAccess access = getOpenSearchAccess();
final FeatureSource<FeatureType, Feature> featureSource;
Query resultsQuery = request.getQuery();
final String parentId = request.getParentId();
if (parentId == null) {
featureSource = access.getCollectionSource();
} else {
featureSource = access.getProductSource();
// need to determine if the collection is primary or virtual
Feature collection = getCollectionByParentIdentifier(parentId);
if (Boolean.FALSE.equals(value(collection, "primary"))) {
// TODO: parse and integrate virtual collection filter
throw new OWS20Exception("Virtual collection support not implemented yet");
} else {
// adding parent id filter for primary collections
final PropertyName parentIdProperty = FF
.property(new NameImpl(OpenSearchAccess.EO_NAMESPACE, "parentIdentifier"));
PropertyIsEqualTo parentIdFilter = FF.equal(parentIdProperty, FF.literal(parentId),
true);
resultsQuery = new Query(resultsQuery);
resultsQuery.setFilter(Predicates.and(resultsQuery.getFilter(), parentIdFilter));
}
}
// count
Query countQuery = new Query(resultsQuery);
countQuery.setMaxFeatures(Query.DEFAULT_MAX);
countQuery.setStartIndex(null);
int totalResults = featureSource.getCount(countQuery);
// get actual features
FeatureCollection<FeatureType, Feature> features;
if(resultsQuery.getMaxFeatures() == 0) {
// pure count query
features = new ListComplexFeatureCollection(featureSource.getSchema(), Collections.emptyList());
} else {
features = featureSource.getFeatures(resultsQuery);
}
SearchResults results = new SearchResults(request, features, totalResults);
return results;
}
OpenSearchAccess getOpenSearchAccess() throws IOException {
OSEOInfo service = getService();
String openSearchAccessStoreId = service.getOpenSearchAccessStoreId();
if (openSearchAccessStoreId == null) {
throw new OWS20Exception("OpenSearchAccess is not configured in the"
+ " OpenSearch for EO panel, please do so");
}
DataStoreInfo dataStore = this.geoServer.getCatalog().getDataStore(openSearchAccessStoreId);
if (dataStore == null) {
throw new OWS20Exception("Could not locate OpenSearch data access with identifier "
+ openSearchAccessStoreId
+ ", please correct the configuration in the OpenSearch for EO panel");
}
DataAccess result = dataStore.getDataStore(null);
if (result == null) {
throw new OWS20Exception("Failed to locate OpenSearch data access with identifier "
+ openSearchAccessStoreId
+ ", please correct the configuration in the OpenSearch for EO panel");
} else if (!(result instanceof OpenSearchAccess)) {
throw new OWS20Exception("Data access with identifier " + openSearchAccessStoreId
+ " does not point to a valid OpenSearchDataAccess, "
+ "please correct the configuration in the OpenSearch for EO panel");
}
return (OpenSearchAccess) result;
}
private OSEOInfo getService() {
return this.geoServer.getService(OSEOInfo.class);
}
@Override
public MetadataResults metadata(MetadataRequest request) throws IOException {
OpenSearchAccess access = getOpenSearchAccess();
// build the query
Query query = queryByIdentifier(request.getId());
query.setProperties(Arrays.asList(FF.property(OpenSearchAccess.METADATA_PROPERTY_NAME)));
// run it
FeatureSource<FeatureType, Feature> source;
if (request.getParentId() == null) {
// collection request
source = access.getCollectionSource();
} else {
source = access.getProductSource();
}
FeatureCollection<FeatureType, Feature> features = source.getFeatures(query);
// get the metadata from the feature
String metadata = (String) getPropertyFromFirstFeature(features,
OpenSearchAccess.METADATA_PROPERTY_NAME);
if (metadata == null) {
throw new OWS20Exception("Could not locate the requested metadata for uid = "
+ request.getId() + " and parentId = " + request.getParentId());
}
return new MetadataResults(request, metadata);
}
@Override
public QuicklookResults quicklook(QuicklookRequest request) throws IOException {
OpenSearchAccess access = getOpenSearchAccess();
// build the query
Query query = queryByIdentifier(request.getId());
query.setProperties(Arrays.asList(FF.property(OpenSearchAccess.QUICKLOOK_PROPERTY_NAME)));
// run it
FeatureSource<FeatureType, Feature> source;
if (request.getParentId() == null) {
// collection request
source = access.getCollectionSource();
} else {
source = access.getProductSource();
}
FeatureCollection<FeatureType, Feature> features = source.getFeatures(query);
byte[] payload = (byte[]) getPropertyFromFirstFeature(features,
OpenSearchAccess.QUICKLOOK_PROPERTY_NAME);
if (payload == null) {
throw new OWS20Exception("Could not locate the quicklook for uid = " + request.getId()
+ " and parentId = " + request.getParentId());
}
return new QuicklookResults(request, payload, guessImageMimeType(payload));
}
private String guessImageMimeType(byte[] payload) {
// guesses jpeg and png by the magic number
if (payload.length >= 4 && //
(payload[0] == (byte) 0xFF) && //
(payload[1] == (byte) 0xD8) && //
(payload[2] == (byte) 0xFF) && //
(payload[3] == (byte) 0xE0)) {
return JPEG_MIME;
} else if (payload.length >= 8 && //
(payload[0] == (byte) 0x89) && //
(payload[1] == (byte) 0x50) && //
(payload[2] == (byte) 0x4E) && //
(payload[3] == (byte) 0x47) && //
(payload[4] == (byte) 0x0D) && //
(payload[5] == (byte) 0x0A) && //
(payload[6] == (byte) 0x1A) && //
(payload[7] == (byte) 0x0A)) {
return PNG_MIME;
} else {
return BINARY_MIME;
}
}
private Object getPropertyFromFirstFeature(FeatureCollection<FeatureType, Feature> features,
Name propertyName) {
Feature feature = DataUtilities.first(features);
Property property;
Object value;
if (feature == null || //
((property = feature.getProperty(propertyName)) == null) || //
((value = property.getValue()) == null)) {
return null;
}
return value;
}
private Query queryByIdentifier(String identifier) {
Query query = new Query();
PropertyName idProperty = FF
.property(new NameImpl(OpenSearchAccess.EO_NAMESPACE, "identifier"));
final PropertyIsEqualTo idFilter = FF.equal(idProperty, FF.literal(identifier), true);
query.setFilter(idFilter);
return query;
}
}