1
2
3
4
5
6 package org.tailormap.api.controller;
7
8 import static org.springframework.http.HttpStatus.BAD_REQUEST;
9 import static org.springframework.http.HttpStatus.NOT_FOUND;
10 import static org.springframework.http.HttpStatus.NOT_MODIFIED;
11 import static org.tailormap.api.util.TMStringUtils.nullIfEmpty;
12
13 import jakarta.servlet.http.HttpServletRequest;
14 import java.util.Optional;
15 import java.util.UUID;
16 import java.util.regex.Pattern;
17 import org.apache.commons.lang3.ObjectUtils;
18 import org.gaul.modernizer_maven_annotations.SuppressModernizer;
19 import org.springframework.http.CacheControl;
20 import org.springframework.http.ResponseEntity;
21 import org.springframework.validation.annotation.Validated;
22 import org.springframework.web.bind.annotation.GetMapping;
23 import org.springframework.web.bind.annotation.ModelAttribute;
24 import org.springframework.web.bind.annotation.PathVariable;
25 import org.springframework.web.server.ResponseStatusException;
26 import org.tailormap.api.annotation.AppRestController;
27 import org.tailormap.api.persistence.Application;
28 import org.tailormap.api.persistence.GeoService;
29 import org.tailormap.api.persistence.Upload;
30 import org.tailormap.api.persistence.UploadCategory;
31 import org.tailormap.api.persistence.json.AppLayerSettings;
32 import org.tailormap.api.persistence.json.AppTreeLayerNode;
33 import org.tailormap.api.persistence.json.GeoServiceDefaultLayerSettings;
34 import org.tailormap.api.persistence.json.GeoServiceLayer;
35 import org.tailormap.api.persistence.json.GeoServiceLayerSettings;
36 import org.tailormap.api.repository.UploadRepository;
37 import org.tailormap.api.service.UploadsService;
38
39 @AppRestController
40 @Validated
41 public class LayerAttachedUploadsController {
42 private final UploadsService uploadsService;
43 private final UploadRepository uploadRepository;
44 private final UploadsController uploadsController;
45
46 public LayerAttachedUploadsController(
47 UploadsService uploadsService, UploadRepository uploadRepository, UploadsController uploadsController) {
48 this.uploadsService = uploadsService;
49 this.uploadRepository = uploadRepository;
50 this.uploadsController = uploadsController;
51 }
52
53 @GetMapping(
54 path = {
55
56 "/api/app/{viewerName}/layer/{appLayerId}/uploads/{category}/{id}",
57 "/api/app/{viewerName}/layer/{appLayerId}/uploads/{category}/{id}/{filename}"
58 })
59 public ResponseEntity<byte[]> getLayerAttachedUpload(
60 @ModelAttribute AppTreeLayerNode appTreeLayerNode,
61 @ModelAttribute GeoService service,
62 @ModelAttribute GeoServiceLayer layer,
63 @ModelAttribute Application application,
64 HttpServletRequest request,
65 @PathVariable UploadCategory category,
66 @PathVariable(name = "id") UUID id,
67 @PathVariable(name = "filename", required = false) String filename) {
68
69 if (UploadCategory.getUnrestrictedCategories().contains(category)) {
70
71
72
73 return uploadsController.getUpload(request, category, id, filename);
74 }
75
76 switch (category) {
77 case LAYER_ATTACHED_FILE ->
78 validateUploadIsInDescription(id, service, layer, application, appTreeLayerNode);
79 case LEGEND -> {
80 validateLegendIsAttached(id, service, layer, application, appTreeLayerNode);
81 }
82 default ->
83 throw new ResponseStatusException(
84 BAD_REQUEST, "Uploads for category " + category + " are not accessible via this endpoint");
85 }
86
87 if (!uploadsService.checkIfModifiedSince(id, request.getDateHeader("If-Modified-Since"))) {
88 return ResponseEntity.status(NOT_MODIFIED).build();
89 }
90
91
92
93 Upload upload = uploadRepository
94 .findWithContentByIdAndCategory(id, category)
95 .orElseThrow(() -> new ResponseStatusException(NOT_FOUND));
96
97 return ResponseEntity.ok()
98 .header("Content-Type", upload.getMimeType())
99 .header(UploadsService.DESCRIPTION_HEADER_NAME, upload.getDescription())
100 .lastModified(upload.getLastModified().toInstant())
101 .contentLength(upload.getContentLength())
102 .cacheControl(CacheControl.noCache().cachePublic())
103 .body(upload.getContent());
104 }
105
106
107
108
109
110
111
112
113
114
115
116
117 private void validateUploadIsInDescription(
118 UUID id,
119 GeoService service,
120 GeoServiceLayer layer,
121 Application application,
122 AppTreeLayerNode appTreeLayerNode)
123 throws ResponseStatusException {
124
125 Pattern pattern =
126 Pattern.compile(Pattern.quote(UploadsService.UPLOAD_MARKDOWN_SCHEME) + Pattern.quote(id.toString()));
127
128 GeoServiceDefaultLayerSettings defaultLayerSettings = Optional.ofNullable(
129 service.getSettings().getDefaultLayerSettings())
130 .orElseGet(GeoServiceDefaultLayerSettings::new);
131 GeoServiceLayerSettings serviceLayerSettings = Optional.ofNullable(
132 service.getSettings().getLayerSettings().get(layer.getName()))
133 .orElseGet(GeoServiceLayerSettings::new);
134 AppLayerSettings appLayerSettings = application.getAppLayerSettings(appTreeLayerNode);
135
136 @SuppressModernizer
137
138 String description = ObjectUtils.firstNonNull(
139 nullIfEmpty(appLayerSettings.getDescription()),
140 nullIfEmpty(serviceLayerSettings.getDescription()),
141 nullIfEmpty(defaultLayerSettings.getDescription()));
142
143 if (description == null
144 || description.isBlank()
145 || !pattern.matcher(description).find()) {
146 throw new ResponseStatusException(
147 BAD_REQUEST, "Upload with id '" + id + "' is not attached to layer '" + layer.getName() + "'");
148 }
149 }
150
151
152
153
154
155
156
157
158
159
160
161 private void validateLegendIsAttached(
162 UUID id,
163 GeoService service,
164 GeoServiceLayer layer,
165 Application application,
166 AppTreeLayerNode appTreeLayerNode)
167 throws ResponseStatusException {
168
169 GeoServiceDefaultLayerSettings defaultLayerSettings = Optional.ofNullable(
170 service.getSettings().getDefaultLayerSettings())
171 .orElseGet(GeoServiceDefaultLayerSettings::new);
172
173 GeoServiceLayerSettings serviceLayerSettings = Optional.ofNullable(
174 service.getSettings().getLayerSettings().get(layer.getName()))
175 .orElseGet(GeoServiceLayerSettings::new);
176
177 @SuppressModernizer
178
179
180 String legendImageId = ObjectUtils.firstNonNull(
181 serviceLayerSettings.getLegendImageId(), defaultLayerSettings.getLegendImageId());
182
183 UUID legendUuid = null;
184 if (legendImageId != null && !legendImageId.isBlank()) {
185 try {
186 legendUuid = UUID.fromString(legendImageId);
187 } catch (IllegalArgumentException ignored) {
188
189 }
190 }
191
192 if (!id.equals(legendUuid)) {
193
194 validateUploadIsInDescription(id, service, layer, application, appTreeLayerNode);
195 }
196 }
197 }