/* (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.store;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.lang.StringUtils;
import org.geotools.data.DataStore;
import org.geotools.data.FeatureSource;
import org.geotools.data.Repository;
import org.geotools.data.ServiceInfo;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.feature.AttributeTypeBuilder;
import org.geotools.feature.NameImpl;
import org.geotools.feature.TypeBuilder;
import org.opengis.feature.Feature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.feature.type.AttributeType;
import org.opengis.feature.type.FeatureType;
import org.opengis.feature.type.GeometryDescriptor;
import org.opengis.feature.type.GeometryType;
import org.opengis.feature.type.Name;
/**
* A data store building OpenSearch for EO records based on a wrapped data store providing all expected tables in form of simple features (and
* leveraging joins to put them together into complex features as needed).
*
* The delegate store is fetched on demand to avoid being caught in a ResourcePool dispose
*
* @author Andrea Aime - GeoSolutions
*/
public class JDBCOpenSearchAccess implements OpenSearchAccess {
public static final String COLLECTION = "collection";
public static final String PRODUCT = "product";
static final String EO_PREFIX = "eo";
static final String SAR_PREFIX = "sar";
static final String SOURCE_ATTRIBUTE = "sourceAttribute";
Repository repository;
Name delegateStoreName;
String namespaceURI;
FeatureType collectionFeatureType;
FeatureType productFeatureType;
List<Name> typeNames;
public JDBCOpenSearchAccess(Repository repository, Name delegateStoreName, String namespaceURI)
throws IOException {
// TODO: maybe get a direct Catalog reference so that we can lookup by store id, which is
// stable though renames?
this.repository = repository;
this.delegateStoreName = delegateStoreName;
this.namespaceURI = namespaceURI;
// check the expected feature types are available
DataStore delegate = getDelegateStore();
List<String> missingTables = getMissingRequiredTables(delegate, COLLECTION, PRODUCT);
if (!missingTables.isEmpty()) {
throw new IOException("Missing required tables in the backing store " + missingTables);
}
collectionFeatureType = buildCollectionFeatureType(delegate);
productFeatureType = buildProductFeatureType(delegate);
}
private FeatureType buildCollectionFeatureType(DataStore delegate) throws IOException {
SimpleFeatureType flatSchema = delegate.getSchema(COLLECTION);
TypeBuilder typeBuilder = new TypeBuilder(CommonFactoryFinder.getFeatureTypeFactory(null));
// map the source attributes
for (AttributeDescriptor ad : flatSchema.getAttributeDescriptors()) {
AttributeTypeBuilder ab = new AttributeTypeBuilder();
String name = ad.getLocalName();
String namespaceURI = this.namespaceURI;
if (name.startsWith(EO_PREFIX)) {
name = name.substring(EO_PREFIX.length());
char c[] = name.toCharArray();
c[0] = Character.toLowerCase(c[0]);
name = new String(c);
namespaceURI = EO_NAMESPACE;
}
// get a more predictable name structure (will have to do something for oracle
// like names too I guess)
if (StringUtils.isAllUpperCase(name)) {
name = name.toLowerCase();
}
// map into output type
ab.init(ad);
ab.name(name).namespaceURI(namespaceURI).userData(SOURCE_ATTRIBUTE, ad.getLocalName());
AttributeDescriptor mappedDescriptor;
if (ad instanceof GeometryDescriptor) {
GeometryType at = ab.buildGeometryType();
ab.setCRS(((GeometryDescriptor) ad).getCoordinateReferenceSystem());
mappedDescriptor = ab.buildDescriptor(new NameImpl(namespaceURI, name), at);
} else {
AttributeType at = ab.buildType();
mappedDescriptor = ab.buildDescriptor(new NameImpl(namespaceURI, name), at);
}
typeBuilder.add(mappedDescriptor);
}
// adding the metadata property
AttributeDescriptor metadataDescriptor = buildSimpleDescriptor(METADATA_PROPERTY_NAME,
String.class);
typeBuilder.add(metadataDescriptor);
// map OGC links
AttributeDescriptor linksDescriptor = buildFeatureListDescriptor(OGC_LINKS_PROPERTY_NAME,
delegate.getSchema("collection_ogclink"));
typeBuilder.add(linksDescriptor);
typeBuilder.setName(COLLECTION);
typeBuilder.setNamespaceURI(namespaceURI);
return typeBuilder.feature();
}
private AttributeDescriptor buildSimpleDescriptor(Name name, Class binding) {
AttributeTypeBuilder ab = new AttributeTypeBuilder();
ab.name(name.getLocalPart()).namespaceURI(name.getNamespaceURI());
ab.setBinding(String.class);
AttributeDescriptor descriptor = ab.buildDescriptor(name, ab.buildType());
return descriptor;
}
private AttributeDescriptor buildFeatureListDescriptor(Name name, SimpleFeatureType schema) {
AttributeTypeBuilder ab = new AttributeTypeBuilder();
ab.name(name.getLocalPart()).namespaceURI(name.getNamespaceURI());
ab.setMinOccurs(0);
ab.setMaxOccurs(Integer.MAX_VALUE);
AttributeDescriptor descriptor = ab.buildDescriptor(name, schema);
return descriptor;
}
private FeatureType buildProductFeatureType(DataStore delegate) throws IOException {
SimpleFeatureType flatSchema = delegate.getSchema(PRODUCT);
TypeBuilder typeBuilder = new TypeBuilder(CommonFactoryFinder.getFeatureTypeFactory(null));
// map the source attributes
AttributeTypeBuilder ab = new AttributeTypeBuilder();
for (AttributeDescriptor ad : flatSchema.getAttributeDescriptors()) {
String name = ad.getLocalName();
String namespaceURI = this.namespaceURI;
// hack to avoid changing the whole product attributes prefixes from eo to eop
if (name.startsWith(EO_PREFIX)) {
name = "eop" + name.substring(2);
}
for (ProductClass pc : ProductClass.values()) {
String prefix = pc.getPrefix();
if (name.startsWith(prefix)) {
name = name.substring(prefix.length());
char c[] = name.toCharArray();
c[0] = Character.toLowerCase(c[0]);
name = new String(c);
namespaceURI = pc.getNamespace();
break;
}
}
// get a more predictable name structure (will have to do something for oracle
// like names too I guess)
if (StringUtils.isAllUpperCase(name)) {
name = name.toLowerCase();
}
// map into output type
ab.init(ad);
ab.name(name).namespaceURI(namespaceURI).userData(SOURCE_ATTRIBUTE, ad.getLocalName());
AttributeDescriptor mappedDescriptor;
if (ad instanceof GeometryDescriptor) {
GeometryType at = ab.buildGeometryType();
ab.setCRS(((GeometryDescriptor) ad).getCoordinateReferenceSystem());
mappedDescriptor = ab.buildDescriptor(new NameImpl(namespaceURI, name), at);
} else {
AttributeType at = ab.buildType();
mappedDescriptor = ab.buildDescriptor(new NameImpl(namespaceURI, name), at);
}
typeBuilder.add(mappedDescriptor);
}
// adding the metadata property
AttributeDescriptor metadataDescriptor = buildSimpleDescriptor(METADATA_PROPERTY_NAME,
String.class);
typeBuilder.add(metadataDescriptor);
// adding the quicklook property
AttributeDescriptor quicklookDescriptor = buildSimpleDescriptor(QUICKLOOK_PROPERTY_NAME,
byte[].class);
typeBuilder.add(quicklookDescriptor);
// map OGC links
AttributeDescriptor linksDescriptor = buildFeatureListDescriptor(OGC_LINKS_PROPERTY_NAME,
delegate.getSchema("product_ogclink"));
typeBuilder.add(linksDescriptor);
typeBuilder.setName(PRODUCT);
typeBuilder.setNamespaceURI(namespaceURI);
return typeBuilder.feature();
}
private List<String> getMissingRequiredTables(DataStore delegate, String... tables)
throws IOException {
Set<String> availableNames = new HashSet<>(Arrays.asList(delegate.getTypeNames()));
return Arrays.stream(tables).map(String::toLowerCase)
.filter(table -> !availableNames.contains(table)).collect(Collectors.toList());
}
/**
* Returns the store from the repository (which is based on GeoServer own resource pool)
*
* @return
* @throws IOException
*/
DataStore getDelegateStore() throws IOException {
DataStore store = repository.dataStore(delegateStoreName);
return new LowercasingDataStore(store);
}
@Override
public ServiceInfo getInfo() {
// TODO Auto-generated method stub
return null;
}
@Override
public void createSchema(FeatureType featureType) throws IOException {
throw new UnsupportedOperationException();
}
@Override
public void updateSchema(Name typeName, FeatureType featureType) throws IOException {
throw new UnsupportedOperationException();
}
@Override
public void removeSchema(Name typeName) throws IOException {
throw new UnsupportedOperationException();
}
@Override
public List<Name> getNames() throws IOException {
return Arrays.asList(collectionFeatureType.getName(), productFeatureType.getName());
}
@Override
public FeatureType getSchema(Name name) throws IOException {
for (FeatureType ft : Arrays.asList(collectionFeatureType, productFeatureType)) {
if (name.equals(ft.getName())) {
return ft;
}
}
return null;
}
@Override
public FeatureSource<FeatureType, Feature> getFeatureSource(Name typeName) throws IOException {
if (collectionFeatureType.getName().equals(typeName)) {
return getCollectionSource();
} else if (productFeatureType.getName().equals(typeName)) {
return getProductSource();
}
return null;
}
public FeatureSource<FeatureType, Feature> getProductSource() throws IOException {
return new JDBCProductFeatureSource(this, productFeatureType);
}
public FeatureSource<FeatureType, Feature> getCollectionSource() throws IOException {
return new JDBCCollectionFeatureSource(this, collectionFeatureType);
}
@Override
public void dispose() {
// nothing to dispose, the delegate store is managed by the resource pool
}
}