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)
44        throws IOException;
45  
46    public SimpleFeatureSource openGeoToolsFeatureSource(TMFeatureType tmft, Integer timeout)
47        throws IOException {
48      DataStore ds = createDataStore(tmft.getFeatureSource(), timeout);
49      return ds.getFeatureSource(tmft.getName());
50    }
51  
52    public void loadCapabilities(TMFeatureSource tmfs) throws IOException {
53      loadCapabilities(tmfs, null);
54    }
55  
56    public DataStore openDatastore(Map<String, Object> params, String passwordKey)
57        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(
85            new TMServiceCaps()
86                .serviceInfo(
87                    new TMServiceInfo()
88                        .title(si.getTitle())
89                        .keywords(si.getKeywords())
90                        .description(si.getDescription())
91                        .publisher(si.getPublisher())
92                        .schema(si.getSchema())
93                        .source(si.getSource())));
94  
95        List<String> typeNames = Arrays.asList(ds.getTypeNames());
96        logger.info(
97            "Type names for {} {}: {}",
98            tmfs.getProtocol().getValue(),
99            tmfs.getProtocol() == TMFeatureSource.Protocol.WFS
100               ? tmfs.getUrl()
101               : tmfs.getJdbcConnection(),
102           typeNames);
103 
104       tmfs.getFeatureTypes()
105           .removeIf(
106               tmft -> {
107                 if (!typeNames.contains(tmft.getName())) {
108                   logger.info("Feature type removed: {}", tmft.getName());
109                   return true;
110                 } else {
111                   return false;
112                 }
113               });
114 
115       for (String typeName : typeNames) {
116         TMFeatureType pft =
117             tmfs.getFeatureTypes().stream()
118                 .filter(ft -> ft.getName().equals(typeName))
119                 .findFirst()
120                 .orElseGet(
121                     () ->
122                         new TMFeatureType()
123                             .setName(typeName)
124                             .setFeatureSource(tmfs)
125                             // TODO set writeable meaningfully
126                             .setWriteable(tmfs.getProtocol() == TMFeatureSource.Protocol.JDBC));
127         if (!tmfs.getFeatureTypes().contains(pft)) {
128           tmfs.getFeatureTypes().add(pft);
129         }
130         try {
131           logger.debug("Get feature source from GeoTools datastore for type \"{}\"", typeName);
132           SimpleFeatureSource gtFs = ds.getFeatureSource(typeName);
133           ResourceInfo info = gtFs.getInfo();
134           if (info != null) {
135             pft.setTitle(info.getTitle());
136             pft.setInfo(getFeatureTypeInfo(pft, info, gtFs));
137             pft.getAttributes().clear();
138 
139             SimpleFeatureType gtFt = gtFs.getSchema();
140             String primaryKeyName = null;
141             for (AttributeDescriptor gtAttr : gtFt.getAttributeDescriptors()) {
142               AttributeType type = gtAttr.getType();
143               Boolean isPk = (Boolean) gtAttr.getUserData().get("org.geotools.jdbc.pk.column");
144               if (null != isPk && isPk) {
145                 logger.debug(
146                     "Found primary key attribute \"{}\" for type \"{}\"",
147                     gtAttr.getLocalName(),
148                     typeName);
149                 primaryKeyName = gtAttr.getLocalName();
150               }
151               TMAttributeDescriptor tmAttr =
152                   new TMAttributeDescriptor()
153                       .name(gtAttr.getLocalName())
154                       .type(GeoToolsHelper.toAttributeType(type))
155                       .nullable(gtAttr.isNillable())
156                       .defaultValue(
157                           gtAttr.getDefaultValue() == null
158                               ? null
159                               : gtAttr.getDefaultValue().toString())
160                       .description(
161                           type.getDescription() == null ? null : type.getDescription().toString());
162               if (tmAttr.getType() == TMAttributeType.OBJECT) {
163                 tmAttr.setUnknownTypeClassName(type.getBinding().getName());
164               }
165               pft.getAttributes().add(tmAttr);
166             }
167             pft.setPrimaryKeyAttribute(primaryKeyName);
168           }
169         } catch (Exception e) {
170           logger.error("Exception reading feature type \"{}\"", typeName, e);
171         }
172       }
173     } finally {
174       ds.dispose();
175     }
176   }
177 
178   protected TMFeatureTypeInfo getFeatureTypeInfo(
179       TMFeatureType pft, ResourceInfo info, SimpleFeatureSource gtFs) {
180     return new TMFeatureTypeInfo()
181         .keywords(info.getKeywords())
182         .description(info.getDescription())
183         .bounds(GeoToolsHelper.fromEnvelope(info.getBounds()))
184         .crs(crsToString(info.getCRS()));
185   }
186 }