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