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
306 public org.tailormap.api.viewer.model.Service.ServerTypeEnum getResolvedServerType() {
307 if (settings.getServerType() == GeoServiceSettings.ServerTypeEnum.AUTO) {
308 return GeoServiceHelper.guessServerTypeFromUrl(getUrl());
309 } else {
310 return Service.ServerTypeEnum.fromValue(settings.getServerType().getValue());
311 }
312 }
313
314 public Service toJsonPojo(GeoServiceHelper geoServiceHelper) {
315 Service s = new Service()
316 .id(this.id)
317 .title(this.title)
318 .url(Boolean.TRUE.equals(this.getSettings().getUseProxy()) ? null : this.url)
319 .protocol(this.protocol)
320 .serverType(getResolvedServerType());
321
322 if (this.protocol == GeoServiceProtocol.WMTS) {
323
324
325 s.capabilities(new String(getCapabilities(), StandardCharsets.UTF_8));
326 }
327
328 return s;
329 }
330
331 public GeoServiceLayer findLayer(String name) {
332 return getLayers().stream()
333 .filter(sl -> name.equals(sl.getName()))
334 .findFirst()
335 .orElse(null);
336 }
337
338 public GeoServiceLayerSettings getLayerSettings(String layerName) {
339 return getSettings().getLayerSettings().get(layerName);
340 }
341
342 @NonNull public String getTitleWithSettingsOverrides(String layerName) {
343
344 String title = Optional.ofNullable(getLayerSettings(layerName))
345 .map(GeoServiceLayerSettings::getTitle)
346 .map(TMStringUtils::nullIfEmpty)
347 .orElse(null);
348
349
350 if (title == null) {
351 title = Optional.ofNullable(findLayer(layerName))
352 .map(GeoServiceLayer::getTitle)
353 .map(TMStringUtils::nullIfEmpty)
354 .orElse(null);
355 }
356
357
358
359
360 if (title == null) {
361 title = layerName;
362 }
363
364 return title;
365 }
366
367 public TMFeatureType findFeatureTypeForLayer(
368 GeoServiceLayer layer, FeatureSourceRepository featureSourceRepository) {
369
370 GeoServiceDefaultLayerSettings defaultLayerSettings = getSettings().getDefaultLayerSettings();
371 GeoServiceLayerSettings layerSettings = getLayerSettings(layer.getName());
372
373 Long featureSourceId = null;
374 String featureTypeName;
375
376 if (layerSettings != null && layerSettings.getFeatureType() != null) {
377 featureTypeName = Optional.ofNullable(layerSettings.getFeatureType().getFeatureTypeName())
378 .orElse(layer.getName());
379 featureSourceId = layerSettings.getFeatureType().getFeatureSourceId();
380 } else {
381 featureTypeName = layer.getName();
382 }
383
384 if (featureSourceId == null && defaultLayerSettings != null && defaultLayerSettings.getFeatureType() != null) {
385 featureSourceId = defaultLayerSettings.getFeatureType().getFeatureSourceId();
386 }
387
388 if (featureTypeName == null) {
389 return null;
390 }
391
392 TMFeatureSource tmfs = null;
393 TMFeatureType tmft = null;
394
395 if (featureSourceId == null) {
396 List<TMFeatureSource> linkedSources = featureSourceRepository.findByLinkedServiceId(getId());
397 for (TMFeatureSource linkedFs : linkedSources) {
398 tmft = linkedFs.findFeatureTypeByName(featureTypeName);
399 if (tmft != null) {
400 tmfs = linkedFs;
401 break;
402 }
403 }
404 } else {
405 tmfs = featureSourceRepository.findById(featureSourceId).orElse(null);
406 if (tmfs != null) {
407 tmft = tmfs.findFeatureTypeByName(featureTypeName);
408 }
409 }
410
411 if (tmfs == null) {
412 return null;
413 }
414
415 if (tmft == null) {
416 String[] split = featureTypeName.split(":", 2);
417 if (split.length == 2) {
418 String shortFeatureTypeName = split[1];
419 tmft = tmfs.findFeatureTypeByName(shortFeatureTypeName);
420 if (tmft != null) {
421 logger.debug(
422 "Did not find feature type with full name \"{}\", using \"{}\" of feature source {}",
423 featureTypeName,
424 shortFeatureTypeName,
425 tmfs);
426 }
427 }
428 }
429 return tmft;
430 }
431
432
433
434
435
436
437
438 private String sanitiseUrl(String url) {
439 if (url != null && url.contains("?")) {
440 MultiValueMap<String, String> sanitisedParams = new LinkedMultiValueMap<>();
441 UriComponentsBuilder uri = UriComponentsBuilder.fromUriString(url);
442 MultiValueMap<String, String> requestParams =
443 uri.build().getQueryParams();
444 for (Map.Entry<String, List<String>> param : requestParams.entrySet()) {
445 if (!REMOVE_PARAMS.contains(param.getKey().toUpperCase(Locale.ROOT))) {
446 sanitisedParams.put(param.getKey(), param.getValue());
447 }
448 }
449
450 url = uri.replaceQueryParams(sanitisedParams).build().toUriString();
451 if (url.endsWith("?")) {
452 url = url.substring(0, url.length() - 1);
453 }
454 }
455 return url;
456 }
457
458 public GeoServiceLayer getParentLayer(String layerId) {
459 for (GeoServiceLayer layer : this.getLayers()) {
460 if (layer.getChildren().contains(layerId)) {
461 return layer;
462 }
463 }
464 return null;
465 }
466 }