View Javadoc
1   /*
2    * Copyright (C) 2025 B3Partners B.V.
3    *
4    * SPDX-License-Identifier: MIT
5    */
6   package org.tailormap.api.controller.admin;
7   
8   import java.io.Serializable;
9   import org.apache.commons.lang3.StringUtils;
10  import org.geotools.api.filter.FilterFactory;
11  import org.geotools.factory.CommonFactoryFinder;
12  import org.geotools.util.factory.GeoTools;
13  import org.springframework.beans.factory.annotation.Value;
14  import org.springframework.http.HttpStatus;
15  import org.springframework.http.MediaType;
16  import org.springframework.http.ResponseEntity;
17  import org.springframework.web.bind.annotation.ExceptionHandler;
18  import org.springframework.web.bind.annotation.GetMapping;
19  import org.springframework.web.bind.annotation.PathVariable;
20  import org.springframework.web.bind.annotation.RequestParam;
21  import org.springframework.web.bind.annotation.RestController;
22  import org.springframework.web.server.ResponseStatusException;
23  import org.tailormap.api.geotools.featuresources.FeatureSourceFactoryHelper;
24  import org.tailormap.api.persistence.TMFeatureType;
25  import org.tailormap.api.persistence.helper.UniqueValuesHelper;
26  import org.tailormap.api.persistence.json.TMAttributeDescriptor;
27  import org.tailormap.api.repository.FeatureTypeRepository;
28  import org.tailormap.api.viewer.model.ErrorResponse;
29  import org.tailormap.api.viewer.model.UniqueValuesResponse;
30  
31  @RestController
32  public class UniqueValuesAdminController {
33    private final FeatureTypeRepository featureTypeRepository;
34  
35    private final FeatureSourceFactoryHelper featureSourceFactoryHelper;
36  
37    @Value("${tailormap-api.unique.use_geotools_unique_function:true}")
38    private boolean useGeotoolsUniqueFunction;
39  
40    private final FilterFactory ff = CommonFactoryFinder.getFilterFactory(GeoTools.getDefaultHints());
41  
42    public UniqueValuesAdminController(
43        FeatureTypeRepository featureTypeRepository, FeatureSourceFactoryHelper featureSourceFactoryHelper) {
44      this.featureTypeRepository = featureTypeRepository;
45      this.featureSourceFactoryHelper = featureSourceFactoryHelper;
46    }
47  
48    @ExceptionHandler({ResponseStatusException.class})
49    public ResponseEntity<?> handleException(ResponseStatusException ex) {
50      // wrap the exception in a proper json response
51      return ResponseEntity.status(ex.getStatusCode())
52          .contentType(MediaType.APPLICATION_JSON)
53          .body(new ErrorResponse()
54              .message(
55                  ex.getReason() != null
56                      ? ex.getReason()
57                      : ex.getBody().getTitle())
58              .code(ex.getStatusCode().value()));
59    }
60  
61    @GetMapping(
62        path = "${tailormap-api.admin.base-path}/unique-values/{featureTypeId}/{attributeName}",
63        produces = MediaType.APPLICATION_JSON_VALUE)
64    public ResponseEntity<Serializable> getUniqueValues(
65        @PathVariable Long featureTypeId,
66        @PathVariable String attributeName,
67        @RequestParam(required = false) String filter)
68        throws ResponseStatusException {
69      if (StringUtils.isBlank(attributeName)) {
70        throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Attribute name is required");
71      }
72  
73      TMFeatureType tmft = this.featureTypeRepository
74          .findById(featureTypeId)
75          .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Feature type not found"));
76  
77      if (tmft.getAttributeByName(attributeName).isEmpty()) {
78        throw new ResponseStatusException(
79            HttpStatus.BAD_REQUEST,
80            "Attribute does not exist, available attributes: "
81                + tmft.getAttributes().stream()
82                    .map(TMAttributeDescriptor::getName)
83                    .toList());
84      }
85  
86      UniqueValuesResponse response = UniqueValuesHelper.getUniqueValues(
87          tmft, attributeName, filter, ff, featureSourceFactoryHelper, useGeotoolsUniqueFunction);
88      return ResponseEntity.status(HttpStatus.OK).body(response);
89    }
90  }