SearchController.java
/*
* Copyright (C) 2024 B3Partners B.V.
*
* SPDX-License-Identifier: MIT
*/
package org.tailormap.api.controller;
import static org.springframework.web.bind.annotation.RequestMethod.GET;
import io.micrometer.core.annotation.Counted;
import io.micrometer.core.annotation.Timed;
import java.io.IOException;
import java.io.Serializable;
import java.lang.invoke.MethodHandles;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.common.SolrException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.server.ResponseStatusException;
import org.tailormap.api.annotation.AppRestController;
import org.tailormap.api.persistence.Application;
import org.tailormap.api.persistence.GeoService;
import org.tailormap.api.persistence.SearchIndex;
import org.tailormap.api.persistence.json.AppLayerSettings;
import org.tailormap.api.persistence.json.AppTreeLayerNode;
import org.tailormap.api.repository.SearchIndexRepository;
import org.tailormap.api.solr.SolrHelper;
import org.tailormap.api.solr.SolrService;
import org.tailormap.api.viewer.model.SearchResponse;
@AppRestController
@Validated
@RequestMapping(
path = "${tailormap-api.base-path}/{viewerKind}/{viewerName}/layer/{appLayerId}/search",
produces = MediaType.APPLICATION_JSON_VALUE)
public class SearchController {
private static final Logger logger =
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private final SearchIndexRepository searchIndexRepository;
private final SolrService solrService;
@Value("${tailormap-api.solr-query-timeout-seconds:7}")
private int solrQueryTimeout;
@Value("${tailormap-api.pageSize:100}")
private int numResultsToReturn;
public SearchController(SearchIndexRepository searchIndexRepository, SolrService solrService) {
this.searchIndexRepository = searchIndexRepository;
this.solrService = solrService;
}
@Transactional(readOnly = true)
@RequestMapping(method = {GET})
@Timed(value = "search", description = "time spent to process search a request")
@Counted(value = "search", description = "number of search calls")
public ResponseEntity<Serializable> search(
@ModelAttribute AppTreeLayerNode appTreeLayerNode,
@ModelAttribute GeoService service,
@ModelAttribute Application application,
@RequestParam(required = false, name = "q") final String solrQuery,
@RequestParam(required = false, defaultValue = "0") Integer start,
@RequestParam(required = false, name = "fq") final String solrFilterQuery,
@RequestParam(required = false, name = "pt") final String solrPoint,
@RequestParam(required = false, name = "d") final Double solrDistance) {
AppLayerSettings appLayerSettings = application.getAppLayerSettings(appTreeLayerNode);
if (appLayerSettings.getSearchIndexId() == null) {
throw new ResponseStatusException(
HttpStatus.NOT_FOUND,
"Layer '%s' does not have a search index".formatted(appTreeLayerNode.getLayerName()));
}
final SearchIndex searchIndex = searchIndexRepository
.findById(appLayerSettings.getSearchIndexId())
.orElseThrow(() -> new ResponseStatusException(
HttpStatus.NOT_FOUND,
"Layer '%s' does not have a search index".formatted(appTreeLayerNode.getLayerName())));
try (SolrClient solrClient = solrService.getSolrClientForSearching();
SolrHelper solrHelper = new SolrHelper(solrClient).withQueryTimeout(solrQueryTimeout)) {
final SearchResponse searchResponse = solrHelper.findInIndex(
searchIndex, solrQuery, solrFilterQuery, solrPoint, solrDistance, start, numResultsToReturn);
return (null == searchResponse.getDocuments()
|| searchResponse.getDocuments().isEmpty())
? ResponseEntity.noContent().build()
: ResponseEntity.ok().body(searchResponse);
} catch (SolrServerException | IOException e) {
logger.error("Error while contacting Solr", e);
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Error while searching", e);
} catch (SolrException e) {
logger.error("Error while searching", e);
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Error while searching with given query", e);
}
}
}