1
2
3
4
5
6 package org.tailormap.api.geotools.featuresources;
7
8 import static org.geotools.jdbc.JDBCDataStore.JDBC_PRIMARY_KEY_COLUMN;
9 import static org.geotools.jdbc.JDBCDataStore.JDBC_READ_ONLY;
10 import static org.tailormap.api.persistence.helper.GeoToolsHelper.crsToString;
11
12 import java.io.IOException;
13 import java.lang.invoke.MethodHandles;
14 import java.util.Arrays;
15 import java.util.HashMap;
16 import java.util.List;
17 import java.util.Map;
18 import org.apache.commons.lang3.StringUtils;
19 import org.geotools.api.data.DataStore;
20 import org.geotools.api.data.DataStoreFinder;
21 import org.geotools.api.data.ResourceInfo;
22 import org.geotools.api.data.SimpleFeatureSource;
23 import org.geotools.api.feature.simple.SimpleFeatureType;
24 import org.geotools.api.feature.type.AttributeDescriptor;
25 import org.geotools.api.feature.type.AttributeType;
26 import org.geotools.jdbc.JDBCFeatureStore;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
29 import org.tailormap.api.persistence.TMFeatureSource;
30 import org.tailormap.api.persistence.TMFeatureType;
31 import org.tailormap.api.persistence.helper.GeoToolsHelper;
32 import org.tailormap.api.persistence.json.TMAttributeDescriptor;
33 import org.tailormap.api.persistence.json.TMAttributeType;
34 import org.tailormap.api.persistence.json.TMFeatureTypeInfo;
35 import org.tailormap.api.persistence.json.TMServiceCaps;
36 import org.tailormap.api.persistence.json.TMServiceInfo;
37
38 public abstract class FeatureSourceHelper {
39 private static final Logger logger =
40 LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
41
42 public DataStore createDataStore(TMFeatureSource tmfs) throws IOException {
43 return createDataStore(tmfs, null);
44 }
45
46 public abstract DataStore createDataStore(TMFeatureSource tmfs, Integer timeout) throws IOException;
47
48 public SimpleFeatureSource openGeoToolsFeatureSource(TMFeatureType tmft, Integer timeout) throws IOException {
49 DataStore ds = createDataStore(tmft.getFeatureSource(), timeout);
50 return ds.getFeatureSource(tmft.getName());
51 }
52
53 public void loadCapabilities(TMFeatureSource tmfs) throws IOException {
54 loadCapabilities(tmfs, null);
55 }
56
57 public DataStore openDatastore(Map<String, Object> params, String passwordKey) throws IOException {
58 Map<String, Object> logParams = new HashMap<>(params);
59 String passwd = (String) params.get(passwordKey);
60 if (passwd != null) {
61 logParams.put(passwordKey, String.valueOf(new char[passwd.length()]).replace("\0", "*"));
62 }
63 logger.debug("Opening datastore using parameters: {}", logParams);
64 DataStore ds;
65 try {
66 ds = DataStoreFinder.getDataStore(params);
67 } catch (Exception e) {
68 throw new IOException("Cannot open datastore using parameters: " + logParams, e);
69 }
70 if (ds == null) {
71 throw new IOException("No datastore found using parameters " + logParams);
72 }
73 return ds;
74 }
75
76 public void loadCapabilities(TMFeatureSource tmfs, Integer timeout) throws IOException {
77 DataStore ds = createDataStore(tmfs, timeout);
78 try {
79 if (StringUtils.isBlank(tmfs.getTitle())) {
80 tmfs.setTitle(ds.getInfo().getTitle());
81 }
82
83 org.geotools.api.data.ServiceInfo si = ds.getInfo();
84 tmfs.setServiceCapabilities(new TMServiceCaps()
85 .serviceInfo(new TMServiceInfo()
86 .title(si.getTitle())
87 .keywords(si.getKeywords())
88 .description(si.getDescription())
89 .publisher(si.getPublisher())
90 .schema(si.getSchema())
91 .source(si.getSource())));
92
93 List<String> typeNames = Arrays.asList(ds.getTypeNames());
94 logger.info(
95 "Type names for {} {}: {}",
96 tmfs.getProtocol().getValue(),
97 tmfs.getProtocol() == TMFeatureSource.Protocol.WFS ? tmfs.getUrl() : tmfs.getJdbcConnection(),
98 typeNames);
99
100 tmfs.getFeatureTypes().removeIf(tmft -> {
101 if (!typeNames.contains(tmft.getName())) {
102 logger.info("Feature type removed: {}", tmft.getName());
103 return true;
104 } else {
105 return false;
106 }
107 });
108
109 for (String typeName : typeNames) {
110 TMFeatureType pft = tmfs.getFeatureTypes().stream()
111 .filter(ft -> ft.getName().equals(typeName))
112 .findFirst()
113 .orElseGet(() -> new TMFeatureType().setName(typeName).setFeatureSource(tmfs));
114 if (!tmfs.getFeatureTypes().contains(pft)) {
115 tmfs.getFeatureTypes().add(pft);
116 }
117 try {
118 logger.debug("Get feature source from GeoTools datastore for type \"{}\"", typeName);
119 SimpleFeatureSource gtFs = ds.getFeatureSource(typeName);
120 ResourceInfo info = gtFs.getInfo();
121 if (info != null) {
122 pft.setTitle(info.getTitle());
123 pft.setInfo(getFeatureTypeInfo(pft, info, gtFs));
124 pft.getAttributes().clear();
125
126 SimpleFeatureType gtFt = gtFs.getSchema();
127 pft.setWriteable(gtFs instanceof JDBCFeatureStore
128 && !Boolean.TRUE.equals(gtFt.getUserData().get(JDBC_READ_ONLY)));
129 String primaryKeyName = null;
130 for (AttributeDescriptor gtAttr : gtFt.getAttributeDescriptors()) {
131 AttributeType type = gtAttr.getType();
132 if (Boolean.TRUE.equals(gtAttr.getUserData().get(JDBC_PRIMARY_KEY_COLUMN))) {
133 if (primaryKeyName == null) {
134 logger.debug(
135 "Found primary key attribute \"{}\" for type \"{}\"",
136 gtAttr.getLocalName(),
137 typeName);
138 primaryKeyName = gtAttr.getLocalName();
139 } else {
140 logger.warn(
141 "Multiple primary key attributes found for type \"{}\": \"{}\" and \"{}\". Composite primary keys are not supported for writing at the moment, setting as read-only.",
142 typeName,
143 primaryKeyName,
144 gtAttr.getLocalName());
145 pft.setWriteable(false);
146 }
147 }
148 TMAttributeDescriptor tmAttr = new TMAttributeDescriptor()
149 .name(gtAttr.getLocalName())
150 .type(GeoToolsHelper.toAttributeType(type))
151 .nullable(gtAttr.isNillable())
152 .defaultValue(
153 gtAttr.getDefaultValue() == null
154 ? null
155 : gtAttr.getDefaultValue().toString())
156 .description(
157 type.getDescription() == null
158 ? null
159 : type.getDescription().toString());
160 if (tmAttr.getType() == TMAttributeType.OBJECT) {
161 tmAttr.setUnknownTypeClassName(type.getBinding().getName());
162 }
163 pft.getAttributes().add(tmAttr);
164 }
165 pft.setPrimaryKeyAttribute(primaryKeyName);
166 }
167 } catch (Exception e) {
168 logger.error("Exception reading feature type \"{}\"", typeName, e);
169 }
170 }
171 } finally {
172 ds.dispose();
173 }
174 }
175
176 protected TMFeatureTypeInfo getFeatureTypeInfo(TMFeatureType pft, ResourceInfo info, SimpleFeatureSource gtFs) {
177 return new TMFeatureTypeInfo()
178 .keywords(info.getKeywords())
179 .description(info.getDescription())
180 .bounds(GeoToolsHelper.fromEnvelope(info.getBounds()))
181 .crs(crsToString(info.getCRS()));
182 }
183 }