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