1
2
3
4
5
6 package org.tailormap.api.persistence;
7
8 import com.fasterxml.jackson.annotation.JsonIgnore;
9 import io.hypersistence.tsid.TSID;
10 import jakarta.persistence.Basic;
11 import jakarta.persistence.Column;
12 import jakarta.persistence.Entity;
13 import jakarta.persistence.EntityListeners;
14 import jakarta.persistence.EnumType;
15 import jakarta.persistence.Enumerated;
16 import jakarta.persistence.FetchType;
17 import jakarta.persistence.Id;
18 import jakarta.persistence.PrePersist;
19 import jakarta.persistence.Transient;
20 import jakarta.persistence.Version;
21 import jakarta.validation.constraints.NotNull;
22 import java.lang.invoke.MethodHandles;
23 import java.nio.charset.StandardCharsets;
24 import java.time.Instant;
25 import java.util.ArrayList;
26 import java.util.List;
27 import java.util.Locale;
28 import java.util.Map;
29 import java.util.Optional;
30 import org.apache.commons.lang3.StringUtils;
31 import org.hibernate.annotations.Type;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34 import org.springframework.lang.NonNull;
35 import org.springframework.util.LinkedMultiValueMap;
36 import org.springframework.util.MultiValueMap;
37 import org.springframework.web.util.UriComponentsBuilder;
38 import org.tailormap.api.persistence.helper.GeoServiceHelper;
39 import org.tailormap.api.persistence.json.AuthorizationRule;
40 import org.tailormap.api.persistence.json.GeoServiceDefaultLayerSettings;
41 import org.tailormap.api.persistence.json.GeoServiceLayer;
42 import org.tailormap.api.persistence.json.GeoServiceLayerSettings;
43 import org.tailormap.api.persistence.json.GeoServiceProtocol;
44 import org.tailormap.api.persistence.json.GeoServiceSettings;
45 import org.tailormap.api.persistence.json.ServiceAuthentication;
46 import org.tailormap.api.persistence.json.TMServiceCaps;
47 import org.tailormap.api.persistence.listener.EntityEventPublisher;
48 import org.tailormap.api.repository.FeatureSourceRepository;
49 import org.tailormap.api.util.TMStringUtils;
50 import org.tailormap.api.viewer.model.Service;
51
52 @Entity
53 @EntityListeners(EntityEventPublisher.class)
54 public class GeoService {
55 private static final Logger logger =
56 LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
57
58 private static final List<String> REMOVE_PARAMS = List.of("REQUEST");
59
60 @Id
61 private String id;
62
63 @Version
64 private Long version;
65
66 @Column(columnDefinition = "text")
67 private String notes;
68
69 @NotNull @Enumerated(EnumType.STRING)
70 private GeoServiceProtocol protocol;
71
72
73
74
75
76
77
78 @NotNull @Column(length = 2048)
79 private String url;
80
81 @Transient
82 private boolean refreshCapabilities;
83
84
85
86
87
88 @Type(value = io.hypersistence.utils.hibernate.type.json.JsonBinaryType.class)
89 @Column(columnDefinition = "jsonb")
90 private ServiceAuthentication authentication;
91
92
93
94
95
96 @Basic(fetch = FetchType.LAZY)
97 @JsonIgnore
98 private byte[] capabilities;
99
100
101 private String capabilitiesContentType;
102
103
104 private Instant capabilitiesFetched;
105
106
107 @NotNull @Column(length = 2048)
108 private String title;
109
110
111
112
113
114 @Column(length = 2048)
115 private String advertisedUrl;
116
117 @Type(value = io.hypersistence.utils.hibernate.type.json.JsonBinaryType.class)
118 @Column(columnDefinition = "jsonb")
119 private TMServiceCaps serviceCapabilities;
120
121 @Type(value = io.hypersistence.utils.hibernate.type.json.JsonBinaryType.class)
122 @Column(columnDefinition = "jsonb")
123 @NotNull private List<AuthorizationRule> authorizationRules = new ArrayList<>();
124
125 @Type(value = io.hypersistence.utils.hibernate.type.json.JsonBinaryType.class)
126 @Column(columnDefinition = "jsonb")
127 @NotNull private List<GeoServiceLayer> layers = new ArrayList<>();
128
129 private boolean published;
130
131
132
133
134
135 @Type(value = io.hypersistence.utils.hibernate.type.json.JsonBinaryType.class)
136 @Column(columnDefinition = "jsonb")
137 @NotNull private GeoServiceSettings settings = new GeoServiceSettings();
138
139
140 public String getId() {
141 return id;
142 }
143
144 public GeoService setId(String id) {
145 this.id = id;
146 return this;
147 }
148
149 public Long getVersion() {
150 return version;
151 }
152
153 public GeoService setVersion(Long version) {
154 this.version = version;
155 return this;
156 }
157
158 public String getNotes() {
159 return notes;
160 }
161
162 public GeoService setNotes(String adminComments) {
163 this.notes = adminComments;
164 return this;
165 }
166
167 public GeoServiceProtocol getProtocol() {
168 return protocol;
169 }
170
171 public GeoService setProtocol(GeoServiceProtocol protocol) {
172 this.protocol = protocol;
173 return this;
174 }
175
176 public String getUrl() {
177 return url;
178 }
179
180
181 public GeoService setUrl(String url) {
182 this.url = sanitiseUrl(url);
183 return this;
184 }
185
186 public boolean isRefreshCapabilities() {
187 return refreshCapabilities;
188 }
189
190 public void setRefreshCapabilities(boolean refreshCapabilities) {
191 this.refreshCapabilities = refreshCapabilities;
192 }
193
194 public ServiceAuthentication getAuthentication() {
195 return authentication;
196 }
197
198 public GeoService setAuthentication(ServiceAuthentication authentication) {
199 this.authentication = authentication;
200 return this;
201 }
202
203 public byte[] getCapabilities() {
204 return capabilities;
205 }
206
207 public GeoService setCapabilities(byte[] capabilities) {
208 this.capabilities = capabilities;
209 return this;
210 }
211
212 public String getCapabilitiesContentType() {
213 return capabilitiesContentType;
214 }
215
216 public GeoService setCapabilitiesContentType(String capabilitiesContentType) {
217 this.capabilitiesContentType = capabilitiesContentType;
218 return this;
219 }
220
221 public Instant getCapabilitiesFetched() {
222 return capabilitiesFetched;
223 }
224
225 public GeoService setCapabilitiesFetched(Instant capabilitiesFetched) {
226 this.capabilitiesFetched = capabilitiesFetched;
227 return this;
228 }
229
230 public String getTitle() {
231 return title;
232 }
233
234 public GeoService setTitle(String title) {
235 this.title = title;
236 return this;
237 }
238
239 public String getAdvertisedUrl() {
240 return advertisedUrl;
241 }
242
243 public GeoService setAdvertisedUrl(String advertisedUrl) {
244 this.advertisedUrl = advertisedUrl;
245 return this;
246 }
247
248 public TMServiceCaps getServiceCapabilities() {
249 return serviceCapabilities;
250 }
251
252 public GeoService setServiceCapabilities(TMServiceCaps serviceCapabilities) {
253 this.serviceCapabilities = serviceCapabilities;
254 return this;
255 }
256
257 public List<AuthorizationRule> getAuthorizationRules() {
258 return authorizationRules;
259 }
260
261 public GeoService setAuthorizationRules(List<AuthorizationRule> authorizationRules) {
262 this.authorizationRules = authorizationRules;
263 return this;
264 }
265
266 public List<GeoServiceLayer> getLayers() {
267 return layers;
268 }
269
270 public GeoService setLayers(List<GeoServiceLayer> layers) {
271 this.layers = layers;
272 return this;
273 }
274
275 public boolean isPublished() {
276 return published;
277 }
278
279 public GeoService setPublished(boolean published) {
280 this.published = published;
281 return this;
282 }
283
284 public GeoServiceSettings getSettings() {
285 return settings;
286 }
287
288 public GeoService setSettings(GeoServiceSettings settings) {
289 this.settings = settings;
290 return this;
291 }
292
293
294
295 @PrePersist
296 public void assignId() {
297 if (StringUtils.isBlank(getId())) {
298
299
300
301 setId(TSID.fast().toString());
302 }
303 }
304
305 public Service toJsonPojo(GeoServiceHelper geoServiceHelper) {
306 Service.ServerTypeEnum serverTypeEnum;
307 if (settings.getServerType() == GeoServiceSettings.ServerTypeEnum.AUTO) {
308 serverTypeEnum = geoServiceHelper.guessServerTypeFromUrl(getUrl());
309 } else {
310 serverTypeEnum =
311 Service.ServerTypeEnum.fromValue(settings.getServerType().getValue());
312 }
313
314 Service s = new Service()
315 .id(this.id)
316 .title(this.title)
317 .url(Boolean.TRUE.equals(this.getSettings().getUseProxy()) ? null : this.url)
318 .protocol(this.protocol)
319 .serverType(serverTypeEnum);
320
321 if (this.protocol == GeoServiceProtocol.WMTS) {
322
323
324 s.capabilities(new String(getCapabilities(), StandardCharsets.UTF_8));
325 }
326
327 return s;
328 }
329
330 public GeoServiceLayer findLayer(String name) {
331 return getLayers().stream()
332 .filter(sl -> name.equals(sl.getName()))
333 .findFirst()
334 .orElse(null);
335 }
336
337 public GeoServiceLayerSettings getLayerSettings(String layerName) {
338 return getSettings().getLayerSettings().get(layerName);
339 }
340
341 @NonNull public String getTitleWithSettingsOverrides(String layerName) {
342
343 String title = Optional.ofNullable(getLayerSettings(layerName))
344 .map(GeoServiceLayerSettings::getTitle)
345 .map(TMStringUtils::nullIfEmpty)
346 .orElse(null);
347
348
349 if (title == null) {
350 title = Optional.ofNullable(findLayer(layerName))
351 .map(GeoServiceLayer::getTitle)
352 .map(TMStringUtils::nullIfEmpty)
353 .orElse(null);
354 }
355
356
357
358
359 if (title == null) {
360 title = layerName;
361 }
362
363 return title;
364 }
365
366 public TMFeatureType findFeatureTypeForLayer(
367 GeoServiceLayer layer, FeatureSourceRepository featureSourceRepository) {
368
369 GeoServiceDefaultLayerSettings defaultLayerSettings = getSettings().getDefaultLayerSettings();
370 GeoServiceLayerSettings layerSettings = getLayerSettings(layer.getName());
371
372 Long featureSourceId = null;
373 String featureTypeName;
374
375 if (layerSettings != null && layerSettings.getFeatureType() != null) {
376 featureTypeName = Optional.ofNullable(layerSettings.getFeatureType().getFeatureTypeName())
377 .orElse(layer.getName());
378 featureSourceId = layerSettings.getFeatureType().getFeatureSourceId();
379 } else {
380 featureTypeName = layer.getName();
381 }
382
383 if (featureSourceId == null && defaultLayerSettings != null && defaultLayerSettings.getFeatureType() != null) {
384 featureSourceId = defaultLayerSettings.getFeatureType().getFeatureSourceId();
385 }
386
387 if (featureTypeName == null) {
388 return null;
389 }
390
391 TMFeatureSource tmfs = null;
392 TMFeatureType tmft = null;
393
394 if (featureSourceId == null) {
395 List<TMFeatureSource> linkedSources = featureSourceRepository.findByLinkedServiceId(getId());
396 for (TMFeatureSource linkedFs : linkedSources) {
397 tmft = linkedFs.findFeatureTypeByName(featureTypeName);
398 if (tmft != null) {
399 tmfs = linkedFs;
400 break;
401 }
402 }
403 } else {
404 tmfs = featureSourceRepository.findById(featureSourceId).orElse(null);
405 if (tmfs != null) {
406 tmft = tmfs.findFeatureTypeByName(featureTypeName);
407 }
408 }
409
410 if (tmfs == null) {
411 return null;
412 }
413
414 if (tmft == null) {
415 String[] split = featureTypeName.split(":", 2);
416 if (split.length == 2) {
417 String shortFeatureTypeName = split[1];
418 tmft = tmfs.findFeatureTypeByName(shortFeatureTypeName);
419 if (tmft != null) {
420 logger.debug(
421 "Did not find feature type with full name \"{}\", using \"{}\" of feature source {}",
422 featureTypeName,
423 shortFeatureTypeName,
424 tmfs);
425 }
426 }
427 }
428 return tmft;
429 }
430
431
432
433
434
435
436
437 private String sanitiseUrl(String url) {
438 if (url != null && url.contains("?")) {
439 MultiValueMap<String, String> sanitisedParams = new LinkedMultiValueMap<>();
440 UriComponentsBuilder uri = UriComponentsBuilder.fromUriString(url);
441 MultiValueMap<String, String> requestParams =
442 uri.build().getQueryParams();
443 for (Map.Entry<String, List<String>> param : requestParams.entrySet()) {
444 if (!REMOVE_PARAMS.contains(param.getKey().toUpperCase(Locale.ROOT))) {
445 sanitisedParams.put(param.getKey(), param.getValue());
446 }
447 }
448
449 url = uri.replaceQueryParams(sanitisedParams).build().toUriString();
450 if (url.endsWith("?")) {
451 url = url.substring(0, url.length() - 1);
452 }
453 }
454 return url;
455 }
456 }