1
2
3
4
5
6 package org.tailormap.api.controller.admin;
7
8 import io.swagger.v3.oas.annotations.Operation;
9 import io.swagger.v3.oas.annotations.media.Content;
10 import io.swagger.v3.oas.annotations.media.Schema;
11 import io.swagger.v3.oas.annotations.responses.ApiResponse;
12 import java.io.IOException;
13 import org.apache.commons.lang3.StringUtils;
14 import org.geotools.api.data.SimpleFeatureSource;
15 import org.springframework.http.HttpStatus;
16 import org.springframework.http.MediaType;
17 import org.springframework.http.ResponseEntity;
18 import org.springframework.web.bind.annotation.ExceptionHandler;
19 import org.springframework.web.bind.annotation.GetMapping;
20 import org.springframework.web.bind.annotation.PathVariable;
21 import org.springframework.web.bind.annotation.RequestParam;
22 import org.springframework.web.bind.annotation.RestController;
23 import org.springframework.web.server.ResponseStatusException;
24 import org.tailormap.api.geotools.featuresources.FeatureSourceFactoryHelper;
25 import org.tailormap.api.geotools.featuresources.FeatureSourceStatistics;
26 import org.tailormap.api.persistence.TMFeatureType;
27 import org.tailormap.api.repository.FeatureTypeRepository;
28 import org.tailormap.api.viewer.model.AttributeStatisticsResponse;
29 import org.tailormap.api.viewer.model.ErrorResponse;
30
31 @RestController
32 public class AttributeStatisticsAdminController {
33 private final FeatureTypeRepository featureTypeRepository;
34 private final FeatureSourceFactoryHelper featureSourceFactoryHelper;
35
36 public AttributeStatisticsAdminController(
37 FeatureTypeRepository featureTypeRepository, FeatureSourceFactoryHelper featureSourceFactoryHelper) {
38 this.featureTypeRepository = featureTypeRepository;
39 this.featureSourceFactoryHelper = featureSourceFactoryHelper;
40 }
41
42 @ExceptionHandler({ResponseStatusException.class})
43 public ResponseEntity<?> handleException(ResponseStatusException ex) {
44
45 return ResponseEntity.status(ex.getStatusCode())
46 .contentType(MediaType.APPLICATION_JSON)
47 .body(new ErrorResponse()
48 .message(
49 ex.getReason() != null
50 ? ex.getReason()
51 : ex.getBody().getTitle())
52 .code(ex.getStatusCode().value()));
53 }
54
55
56
57
58
59
60
61
62
63
64
65 @Operation(
66 summary = "Retrieve attribute statistics for a feature type",
67 description = "Retrieve statistics (min, max, average, sum, count) for a given attribute of a feature type."
68 + " Optionally, a CQL filter can be applied to filter the features before calculating the"
69 + " statistics.")
70 @GetMapping(
71 path = "${tailormap-api.admin.base-path}/statistics/{featureTypeId}/{attributeName}",
72 produces = MediaType.APPLICATION_JSON_VALUE)
73 @ApiResponse(
74 responseCode = "200",
75 description = "Statistics retrieved successfully",
76 content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(example = """
77 {"min":85746,"max":100000,"average":92873,"sum":371492,"count":4}
78 """)))
79 public ResponseEntity<AttributeStatisticsResponse> getStatistics(
80 @PathVariable Long featureTypeId,
81 @PathVariable String attributeName,
82 @RequestParam(required = false) String filter) {
83
84 if (StringUtils.isBlank(attributeName)) {
85 throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Attribute name is required");
86 }
87
88 TMFeatureType tmft = this.featureTypeRepository
89 .findById(featureTypeId)
90 .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Feature type not found"));
91
92 if (tmft.getAttributeByName(attributeName).isEmpty()) {
93 throw new ResponseStatusException(
94 HttpStatus.BAD_REQUEST, "Attribute does not exist in feature type: " + attributeName);
95 }
96 SimpleFeatureSource featureSource = null;
97 try {
98 featureSource = featureSourceFactoryHelper.openGeoToolsFeatureSource(tmft);
99 return ResponseEntity.ok()
100 .body(FeatureSourceStatistics.getFeatureSourceStatistics(
101 featureSource, attributeName, filter, 10, null));
102 } catch (IllegalArgumentException e) {
103 throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage(), e);
104 } catch (IOException e) {
105 throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Error accessing feature source", e);
106 } finally {
107 if (featureSource != null) {
108 featureSource.getDataStore().dispose();
109 }
110 }
111 }
112 }