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