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