View Javadoc
1   /*
2    * Copyright 2012-2025 CodeLibs Project and the Others.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13   * either express or implied. See the License for the specific language
14   * governing permissions and limitations under the License.
15   */
16  package org.codelibs.fess.entity;
17  
18  import java.util.ArrayList;
19  import java.util.HashMap;
20  import java.util.List;
21  import java.util.Map;
22  
23  import org.codelibs.core.lang.StringUtil;
24  import org.codelibs.core.stream.StreamUtil;
25  import org.codelibs.fess.exception.InvalidQueryException;
26  import org.codelibs.fess.mylasta.direction.FessConfig;
27  import org.codelibs.fess.util.ComponentUtil;
28  import org.lastaflute.core.message.UserMessages;
29  import org.opensearch.index.query.BoolQueryBuilder;
30  import org.opensearch.index.query.QueryBuilder;
31  import org.opensearch.index.query.QueryBuilders;
32  
33  import jakarta.servlet.http.HttpServletRequest;
34  
35  /**
36   * Entity class that handles geographic information for search queries.
37   * This class processes geographic query parameters from HTTP requests and converts them
38   * into OpenSearch geo-distance queries for location-based search functionality.
39   *
40   */
41  public class GeoInfo {
42  
43      /** The OpenSearch query builder for geographic queries. */
44      private QueryBuilder builder;
45  
46      /**
47       * Constructs a GeoInfo instance by parsing geographic parameters from the HTTP request.
48       * Extracts geo.{field}.point and geo.{field}.distance parameters to create
49       * geo-distance queries for each configured geographic field.
50       *
51       * @param request the HTTP servlet request containing geographic parameters
52       * @throws InvalidQueryException if geo point format is invalid or parsing fails
53       */
54      public GeoInfo(final HttpServletRequest request) {
55  
56          final FessConfig fessConfig = ComponentUtil.getFessConfig();
57          final String[] geoFields = fessConfig.getQueryGeoFieldsAsArray();
58          final Map<String, List<QueryBuilder>> geoMap = new HashMap<>();
59  
60          StreamUtil.stream(request.getParameterMap())
61                  .of(stream -> stream.filter(e -> e.getKey().startsWith("geo.") && e.getKey().endsWith(".point")).forEach(e -> {
62                      final String key = e.getKey();
63                      for (final String geoField : geoFields) {
64                          if (key.startsWith("geo." + geoField + ".")) {
65                              final String distanceKey = key.replaceFirst(".point$", ".distance");
66                              final String distance = request.getParameter(distanceKey);
67                              if (StringUtil.isNotBlank(distance)) {
68                                  StreamUtil.stream(e.getValue()).of(s -> s.forEach(pt -> {
69                                      List<QueryBuilder> list = geoMap.get(geoField);
70                                      if (list == null) {
71                                          list = new ArrayList<>();
72                                          geoMap.put(geoField, list);
73                                      }
74                                      final String[] values = pt.split(",");
75                                      if (values.length != 2) {
76                                          throw new InvalidQueryException(
77                                                  messages -> messages.addErrorsInvalidQueryUnknown(UserMessages.GLOBAL_PROPERTY_KEY),
78                                                  "Invalid geo point: " + pt);
79                                      }
80                                      try {
81                                          final double lat = Double.parseDouble(values[0]);
82                                          final double lon = Double.parseDouble(values[1]);
83                                          list.add(QueryBuilders.geoDistanceQuery(geoField).distance(distance).point(lat, lon));
84                                      } catch (final Exception ex) {
85                                          throw new InvalidQueryException(
86                                                  messages -> messages.addErrorsInvalidQueryUnknown(UserMessages.GLOBAL_PROPERTY_KEY),
87                                                  ex.getLocalizedMessage(), ex);
88                                      }
89                                  }));
90                              }
91                              break;
92                          }
93                      }
94                  }));
95  
96          final QueryBuilder[] queryBuilders = geoMap.values().stream().map(list -> {
97              if (list.size() == 1) {
98                  return list.get(0);
99              }
100             if (list.size() > 1) {
101                 final BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
102                 list.forEach(boolQuery::should);
103                 return boolQuery;
104             }
105             return null;
106         }).filter(q -> q != null).toArray(n -> new QueryBuilder[n]);
107 
108         if (queryBuilders.length == 1) {
109             builder = queryBuilders[0];
110         } else if (queryBuilders.length > 1) {
111             final BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
112             StreamUtil.stream(queryBuilders).of(stream -> stream.forEach(boolQuery::must));
113             builder = boolQuery;
114         }
115 
116     }
117 
118     /**
119      * Returns the OpenSearch QueryBuilder for geographic queries.
120      *
121      * @return the QueryBuilder containing geo-distance queries, or null if no valid geo parameters were found
122      */
123     public QueryBuilder toQueryBuilder() {
124         return builder;
125     }
126 
127 }