FeatureSourceHelper.java
/*
* Copyright (C) 2022 B3Partners B.V.
*
* SPDX-License-Identifier: MIT
*/
package org.tailormap.api.geotools.featuresources;
import static org.geotools.jdbc.JDBCDataStore.JDBC_PRIMARY_KEY_COLUMN;
import static org.geotools.jdbc.JDBCDataStore.JDBC_READ_ONLY;
import static org.tailormap.api.persistence.helper.GeoToolsHelper.crsToString;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.geotools.api.data.DataStore;
import org.geotools.api.data.DataStoreFinder;
import org.geotools.api.data.ResourceInfo;
import org.geotools.api.data.SimpleFeatureSource;
import org.geotools.api.feature.simple.SimpleFeatureType;
import org.geotools.api.feature.type.AttributeDescriptor;
import org.geotools.api.feature.type.AttributeType;
import org.geotools.jdbc.JDBCFeatureStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tailormap.api.persistence.TMFeatureSource;
import org.tailormap.api.persistence.TMFeatureType;
import org.tailormap.api.persistence.helper.GeoToolsHelper;
import org.tailormap.api.persistence.json.TMAttributeDescriptor;
import org.tailormap.api.persistence.json.TMAttributeType;
import org.tailormap.api.persistence.json.TMFeatureTypeInfo;
import org.tailormap.api.persistence.json.TMServiceCaps;
import org.tailormap.api.persistence.json.TMServiceInfo;
public abstract class FeatureSourceHelper {
private static final Logger logger =
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
public DataStore createDataStore(TMFeatureSource tmfs) throws IOException {
return createDataStore(tmfs, null);
}
public abstract DataStore createDataStore(TMFeatureSource tmfs, Integer timeout) throws IOException;
public SimpleFeatureSource openGeoToolsFeatureSource(TMFeatureType tmft, Integer timeout) throws IOException {
DataStore ds = createDataStore(tmft.getFeatureSource(), timeout);
return ds.getFeatureSource(tmft.getName());
}
public void loadCapabilities(TMFeatureSource tmfs) throws IOException {
loadCapabilities(tmfs, null);
}
public DataStore openDatastore(Map<String, Object> params, String passwordKey) throws IOException {
Map<String, Object> logParams = new HashMap<>(params);
String passwd = (String) params.get(passwordKey);
if (passwd != null) {
logParams.put(passwordKey, String.valueOf(new char[passwd.length()]).replace("\0", "*"));
}
logger.debug("Opening datastore using parameters: {}", logParams);
DataStore ds;
try {
ds = DataStoreFinder.getDataStore(params);
} catch (Exception e) {
throw new IOException("Cannot open datastore using parameters: " + logParams, e);
}
if (ds == null) {
throw new IOException("No datastore found using parameters " + logParams);
}
return ds;
}
public void loadCapabilities(TMFeatureSource tmfs, Integer timeout) throws IOException {
DataStore ds = createDataStore(tmfs, timeout);
try {
if (StringUtils.isBlank(tmfs.getTitle())) {
tmfs.setTitle(ds.getInfo().getTitle());
}
org.geotools.api.data.ServiceInfo si = ds.getInfo();
tmfs.setServiceCapabilities(new TMServiceCaps()
.serviceInfo(new TMServiceInfo()
.title(si.getTitle())
.keywords(si.getKeywords())
.description(si.getDescription())
.publisher(si.getPublisher())
.schema(si.getSchema())
.source(si.getSource())));
List<String> typeNames = Arrays.asList(ds.getTypeNames());
logger.info(
"Type names for {} {}: {}",
tmfs.getProtocol().getValue(),
tmfs.getProtocol() == TMFeatureSource.Protocol.WFS ? tmfs.getUrl() : tmfs.getJdbcConnection(),
typeNames);
tmfs.getFeatureTypes().removeIf(tmft -> {
if (!typeNames.contains(tmft.getName())) {
logger.info("Feature type removed: {}", tmft.getName());
return true;
} else {
return false;
}
});
for (String typeName : typeNames) {
TMFeatureType pft = tmfs.getFeatureTypes().stream()
.filter(ft -> ft.getName().equals(typeName))
.findFirst()
.orElseGet(() -> new TMFeatureType().setName(typeName).setFeatureSource(tmfs));
if (!tmfs.getFeatureTypes().contains(pft)) {
tmfs.getFeatureTypes().add(pft);
}
try {
logger.debug("Get feature source from GeoTools datastore for type \"{}\"", typeName);
SimpleFeatureSource gtFs = ds.getFeatureSource(typeName);
ResourceInfo info = gtFs.getInfo();
if (info != null) {
pft.setTitle(info.getTitle());
pft.setInfo(getFeatureTypeInfo(pft, info, gtFs));
pft.getAttributes().clear();
SimpleFeatureType gtFt = gtFs.getSchema();
pft.setWriteable(gtFs instanceof JDBCFeatureStore
&& !Boolean.TRUE.equals(gtFt.getUserData().get(JDBC_READ_ONLY)));
String primaryKeyName = null;
for (AttributeDescriptor gtAttr : gtFt.getAttributeDescriptors()) {
AttributeType type = gtAttr.getType();
if (Boolean.TRUE.equals(gtAttr.getUserData().get(JDBC_PRIMARY_KEY_COLUMN))) {
if (primaryKeyName == null) {
logger.debug(
"Found primary key attribute \"{}\" for type \"{}\"",
gtAttr.getLocalName(),
typeName);
primaryKeyName = gtAttr.getLocalName();
} else {
logger.warn(
"Multiple primary key attributes found for type \"{}\": \"{}\" and \"{}\". Composite primary keys are not supported for writing at the moment, setting as read-only.",
typeName,
primaryKeyName,
gtAttr.getLocalName());
pft.setWriteable(false);
}
}
TMAttributeDescriptor tmAttr = new TMAttributeDescriptor()
.name(gtAttr.getLocalName())
.type(GeoToolsHelper.toAttributeType(type))
.nullable(gtAttr.isNillable())
.defaultValue(
gtAttr.getDefaultValue() == null
? null
: gtAttr.getDefaultValue().toString())
.description(
type.getDescription() == null
? null
: type.getDescription().toString());
if (tmAttr.getType() == TMAttributeType.OBJECT) {
tmAttr.setUnknownTypeClassName(type.getBinding().getName());
}
pft.getAttributes().add(tmAttr);
}
pft.setPrimaryKeyAttribute(primaryKeyName);
pft.setDefaultGeometryAttribute(pft.findDefaultGeometryAttribute());
}
} catch (Exception e) {
logger.error("Exception reading feature type \"{}\"", typeName, e);
}
}
} finally {
ds.dispose();
}
}
protected TMFeatureTypeInfo getFeatureTypeInfo(TMFeatureType pft, ResourceInfo info, SimpleFeatureSource gtFs) {
return new TMFeatureTypeInfo()
.keywords(info.getKeywords())
.description(info.getDescription())
.bounds(GeoToolsHelper.fromEnvelope(info.getBounds()))
.crs(crsToString(info.getCRS()));
}
}