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