View Javadoc
1   /*
2    * Copyright (C) 2022 B3Partners B.V.
3    *
4    * SPDX-License-Identifier: MIT
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 }