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