Skip to content

Commit 9bdc8a0

Browse files
committed
apply code review comments
1 parent 0e47f01 commit 9bdc8a0

8 files changed

Lines changed: 38 additions & 25 deletions

File tree

src/main/java/org/tailormap/api/controller/LayerExtractController.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public LayerExtractController(
6969

7070
/**
7171
* Download the result of an extract request. The extract generation should be initiated first by a POST to
72-
* {@code /{viewerKind}/{viewerName}/layer/{appLayerId}/extract}.
72+
* {@code /{viewerKind}/{viewerName}/layer/{appLayerId}/extract/{clientId}}.
7373
*/
7474
@GetMapping(path = "/download/{downloadId}")
7575
@Counted(value = "tailormap_api_extract_download", description = "Count of layer extract downloads")
@@ -119,7 +119,9 @@ public ResponseEntity<?> formats(
119119
@ModelAttribute GeoService service,
120120
@ModelAttribute Application application,
121121
@ModelAttribute AppTreeLayerNode appTreeLayerNode) {
122-
return ResponseEntity.ok(allowedExtractOutputFormats);
122+
return ResponseEntity.ok(allowedExtractOutputFormats.stream()
123+
.map(ExtractOutputFormat::getValue)
124+
.toList());
123125
}
124126

125127
@Transactional

src/main/java/org/tailormap/api/service/CreateLayerExtractService.java

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,9 @@ public void validateClientId(@NonNull String clientId) throws IllegalArgumentExc
159159
/**
160160
* Create a validated filename for an extract. The naming follows the pattern
161161
* {@code "%s_%s_%s.%s".formatted(sourceFT.getName(), clientId, UUIDv7.randomV7(), outputFormat.getExtension()) }
162-
* where the first part is the source feature type name, the second part is the SSE client id, the third part is a
163-
* random UUIDv7 and the fourth part is the file extension based on the requested output format.
162+
* where the first part is the source feature type name (this is cleaned from some characters), the second part is
163+
* the SSE client id, the third part is a random UUIDv7 and the fourth part is the file extension based on the
164+
* requested output format.
164165
*
165166
* @param clientId the SSE client id
166167
* @param sourceFT the source featuretype for the extract
@@ -180,6 +181,9 @@ public String createExtractFilename(
180181
if (cleanFTName.contains(":")) {
181182
// clip off the WFS namespace part
182183
cleanFTName = cleanFTName.substring(cleanFTName.lastIndexOf(":") + 1);
184+
// remove: . _ which are used as separators in the filename and could cause issues when parsing the filename
185+
// later on
186+
cleanFTName = cleanFTName.replaceAll("[._]", "");
183187
}
184188
return "%s_%s_%s.%s".formatted(cleanFTName, clientId, UUIDv7.randomV7(), outputFormat.getExtension());
185189
}
@@ -197,13 +201,16 @@ public void createLayerExtract(
197201
@NonNull String outputFileName) {
198202
SimpleFeatureSource inputFeatureSource = null;
199203

200-
this.emitProgress(clientId, outputFileName, 0, false, null);
204+
this.emitProgress(clientId, outputFileName, 0, false, "Starting extract");
201205

202206
try (Transaction outputTransaction = new DefaultTransaction("tailormap-extract-output")) {
203207
inputFeatureSource = featureSourceFactoryHelper.openGeoToolsFeatureSource(inputTmFeatureType);
204208

205209
Query q = new Query(inputFeatureSource.getName().toString());
206-
q.setPropertyNames(attributes.toArray(new String[0]));
210+
if (!attributes.isEmpty()) {
211+
q.setPropertyNames(attributes.toArray(new String[0]));
212+
}
213+
207214
if (!StringUtils.isBlank(filterCQL)) {
208215
Filter filter = ECQL.toFilter(filterCQL);
209216
q.setFilter(filter);
@@ -213,8 +220,8 @@ public void createLayerExtract(
213220
}
214221

215222
final int featCount = inputFeatureSource.getCount(q);
216-
AtomicInteger featsAdded = new AtomicInteger();
217223
logger.debug("Filtered source counts {}", featCount);
224+
final AtomicInteger featsAdded = new AtomicInteger();
218225

219226
FileDataStore outputDataStore = getExtractDataStore(extractOutputFormat, outputFileName, clientId);
220227
SimpleFeatureType fType =
@@ -245,7 +252,7 @@ public void createLayerExtract(
245252
logger.error("Output datastore is not a SimpleFeatureStore, cannot write features");
246253
}
247254
outputDataStore.dispose();
248-
this.emitProgress(clientId, outputFileName, 100, true, null);
255+
this.emitProgress(clientId, outputFileName, 100, true, "Extract completed successfully");
249256
} catch (IOException | CQLException | SchemaException e) {
250257
emitError(clientId, e.getMessage());
251258
logger.error("Creating extract failed", e);

src/main/resources/application.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ tailormap-api.feature.info.maxitems=30
3232
# deprecated
3333
tailormap-api.export.allowed-outputformats=csv,text/csv,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,excel2007,application/vnd.shp,application/x-zipped-shp,SHAPE-ZIP,application/geopackage+sqlite3,application/x-gpkg,geopackage,geopkg,gpkg,application/geo+json,application/geojson,application/json,json,DXF-ZIP
3434

35-
# see org.tailormap.api.controller.LayerExtractController.ExtractOutputFormattFormat for valid values
35+
# see org.tailormap.api.controller.LayerExtractController.ExtractOutputFormat for valid values
3636
tailormap-api.extract.allowed-outputformats=csv,geojson,xlsx,shape
3737
# any files older than this (in minutes) in the extract output directory will be deleted by a scheduled job, to prevent filling up the disk
3838
# tailormap-api.extract.cleanup-minutes=120

src/main/resources/openapi/status-responses.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,14 +81,14 @@ components:
8181
type: object
8282
nullable: true
8383
id:
84-
description: 'optional event identifier, can be used for event ordering and deduplication'
84+
description: 'optional event identifier (UUIDv7), can be used for event ordering and deduplication'
8585
type: string
8686
format: uuid
8787
nullable: true
8888
example:
89-
eventType: 'extract-started'
89+
eventType: 'extract-progress'
9090
details:
91-
status: 'started'
9291
message: 'Extracting data'
9392
progress: 17
93+
downloadId: '123e4567-e89b-12d3-a456-426614174001.csv'
9494
id: '123e4567-e89b-12d3-a456-426614174000'

src/main/resources/openapi/viewer-api.yaml

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1820,7 +1820,7 @@ paths:
18201820
in: path
18211821
required: true
18221822
description: 'A client-generated identifier;
1823-
this must be the same as the clientId used in eg. the `/extract` request to correlate the events with the extract request.
1823+
this must be the same as the clientId used in eg. the `/{viewerKind}/{viewerName}/layer/{appLayerId}/extract/{clientId}` request to correlate the events with the extract request.
18241824
The format should use the "Nano ID" format, for example `V1StGXR8_Z5jdHi6B-myT`.'
18251825
schema:
18261826
type: string
@@ -1871,7 +1871,7 @@ paths:
18711871
type: array
18721872
items:
18731873
type: string
1874-
example: '["csv","shape.zip"]'
1874+
example: '["csv","shape"]'
18751875

18761876
/{viewerKind}/{viewerName}/layer/{appLayerId}/extract/{clientId}:
18771877
description: 'Export the attributes as shown in the attribute list for a layer.'
@@ -1944,8 +1944,8 @@ paths:
19441944
responses:
19451945
'202':
19461946
description: 'Export started/queued. The client should listen to the `/events/` stream and wait for an
1947-
`extract-completed` event with `status: completed` to know when the export is finished and the file is ready
1948-
to be downloaded. If the connection is closed before that, the export may be cancelled.'
1947+
`extract-completed` event with `message: Extract task completed` to know when the export is finished and the
1948+
file is ready to be downloaded. If the connection is closed before that, the export may be cancelled.'
19491949
content:
19501950
application/json:
19511951
schema:
@@ -1957,12 +1957,7 @@ paths:
19571957
required:
19581958
- message
19591959
example:
1960-
eventType: 'extract-started'
1961-
id: '123e4567-e89b-12d3-a456-426614174000'
1962-
details:
1963-
status: started
1964-
message: 'Extracting data'
1965-
progress: 50
1960+
message: 'Extract request accepted'
19661961
downloadId: '123e4567-e89b-12d3-a456-426614174001.csv'
19671962
'400':
19681963
description: 'Bad Request. May be returned for some combination of parameters that can not be processed or are incomplete.'

src/test/java/org/tailormap/api/controller/LayerExtractControllerIntegrationTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ void should_export_wfs_to_csv_with_authentication() throws Exception {
228228
final String extractedDownloadId = getDownloadId(lastCompletedEventJson);
229229
assertThat(extractedDownloadId, containsString(".csv"));
230230

231-
final String downloadUrl = apiBasePath + layerBegroeidTerreindeelPostgis + downloadPath + extractedDownloadId;
231+
final String downloadUrl = apiBasePath + layerProxiedWithAuthInPublicApp + downloadPath + extractedDownloadId;
232232
MvcResult download = mockMvc.perform(get(downloadUrl).with(setServletPath(downloadUrl)))
233233
.andExpect(status().isOk())
234234
.andExpect(result -> {

src/test/java/org/tailormap/api/controller/LayerExtractControllerRestrictedFormatsIntegrationTest.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
1313
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
1414
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
15+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request;
1516
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
1617
import static org.tailormap.api.TestRequestProcessor.setServletPath;
1718
import static org.tailormap.api.controller.TestUrls.layerBegroeidTerreindeelPostgis;
@@ -52,7 +53,15 @@ void list_supported_formats() throws Exception {
5253

5354
@Test
5455
void invalid_output_format_should_return_bad_request_on_extract() throws Exception {
55-
final String validClientId = "format-test-" + System.nanoTime();
56+
final String validClientId = "invalid_output_format-" + System.nanoTime();
57+
final String sseUrl = apiBasePath + "/events/" + validClientId;
58+
mockMvc.perform(get(sseUrl)
59+
.accept(MediaType.TEXT_EVENT_STREAM)
60+
.with(setServletPath(sseUrl))
61+
.acceptCharset(StandardCharsets.UTF_8))
62+
.andExpect(request().asyncStarted())
63+
.andReturn();
64+
5665
final String extractUrl = apiBasePath + layerBegroeidTerreindeelPostgis + extractPath + validClientId;
5766
mockMvc.perform(post(extractUrl)
5867
.accept(MediaType.APPLICATION_JSON)

src/test/resources/application.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ management.endpoints.web.base-path=/api/actuator
44
tailormap-api.new-admin-username=tm-admin
55
# deprecated
66
tailormap-api.export.allowed-outputformats=application/geopackage+sqlite3,application/json
7-
# see org.tailormap.api.controller.LayerExtractController.ExtractOutputFormattFormat for valid values
7+
# see org.tailormap.api.controller.LayerExtractController.ExtractOutputFormat for valid values
88
tailormap-api.extract.allowed-outputformats=csv,geojson,xlsx,shape
99
# any files older than this (in minutes) in the extract output directory will be deleted by a scheduled job, to prevent filling up the disk
1010
# tailormap-api.extract.cleanup-minutes=120

0 commit comments

Comments
 (0)