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