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.query;
17  
18  import static org.codelibs.core.stream.StreamUtil.split;
19  import static org.codelibs.core.stream.StreamUtil.stream;
20  
21  import java.util.ArrayList;
22  import java.util.Collections;
23  import java.util.HashSet;
24  import java.util.List;
25  import java.util.Set;
26  import java.util.function.Consumer;
27  import java.util.stream.Stream;
28  
29  import org.apache.logging.log4j.LogManager;
30  import org.apache.logging.log4j.Logger;
31  import org.codelibs.core.lang.StringUtil;
32  import org.codelibs.core.misc.Pair;
33  import org.codelibs.fess.mylasta.direction.FessConfig;
34  import org.codelibs.fess.util.ComponentUtil;
35  
36  import jakarta.annotation.PostConstruct;
37  
38  /**
39   * Configuration class for query field mappings in the Fess search engine.
40   * This class manages field configurations for various query operations including
41   * response fields, search fields, facet fields, sort fields, and highlighting.
42   * It initializes field mappings from the FessConfig and provides methods to
43   * query field properties and capabilities.
44   */
45  public class QueryFieldConfig {
46  
47      /**
48       * Default constructor.
49       */
50      public QueryFieldConfig() {
51          // Default constructor
52      }
53  
54      /** Logger instance for this class */
55      private static final Logger logger = LogManager.getLogger(QueryFieldConfig.class);
56  
57      /** Field name for document score in search results */
58      public static final String SCORE_FIELD = "score";
59  
60      /** Field name for OpenSearch document score */
61      public static final String DOC_SCORE_FIELD = "_score";
62  
63      /** Field name for site information in search results */
64      public static final String SITE_FIELD = "site";
65  
66      /** Field name for URL-based search queries */
67      public static final String INURL_FIELD = "inurl";
68  
69      /** Sort value for score-based sorting */
70      protected static final String SCORE_SORT_VALUE = "score";
71  
72      /** Array of fields to be included in standard search response */
73      protected String[] responseFields;
74  
75      /** Array of fields to be included in scroll search response */
76      protected String[] scrollResponseFields;
77  
78      /** Array of fields to be included in cache search response */
79      protected String[] cacheResponseFields;
80  
81      /** Array of fields that can be highlighted in search results */
82      protected String[] highlightedFields;
83  
84      /** Array of fields that can be searched against */
85      protected String[] searchFields;
86  
87      /** Set of fields that can be searched against for O(1) lookup */
88      protected Set<String> searchFieldSet;
89  
90      /** Array of fields that can be used for faceted search */
91      protected String[] facetFields;
92  
93      /** Set of fields that can be used for faceted search for O(1) lookup */
94      protected Set<String> facetFieldSet;
95  
96      /** Array of fields that can be used for sorting search results */
97      protected String[] sortFields;
98  
99      /** Set of fields that can be used for sorting for O(1) lookup */
100     protected Set<String> sortFieldSet;
101 
102     /** Set of fields that are allowed in API responses */
103     protected Set<String> apiResponseFieldSet;
104 
105     /** Set of fields that are not analyzed during indexing */
106     protected Set<String> notAnalyzedFieldSet;
107 
108     /** List of additional default fields with their boost values */
109     protected List<Pair<String, Float>> additionalDefaultList = new ArrayList<>();
110 
111     /**
112      * Initializes the query field configuration by loading field mappings from FessConfig.
113      * This method is called after dependency injection is complete.
114      * It sets up response fields, search fields, facet fields, sort fields, and other
115      * field configurations based on the application configuration.
116      */
117     @PostConstruct
118     public void init() {
119         if (logger.isDebugEnabled()) {
120             logger.debug("Initializing {}", this.getClass().getSimpleName());
121         }
122         final FessConfig fessConfig = ComponentUtil.getFessConfig();
123         if (responseFields == null) {
124             responseFields = fessConfig.getQueryAdditionalResponseFields(//
125                     SCORE_FIELD, //
126                     fessConfig.getIndexFieldId(), //
127                     fessConfig.getIndexFieldDocId(), //
128                     fessConfig.getIndexFieldBoost(), //
129                     fessConfig.getIndexFieldContentLength(), //
130                     fessConfig.getIndexFieldHost(), //
131                     fessConfig.getIndexFieldSite(), //
132                     fessConfig.getIndexFieldLastModified(), //
133                     fessConfig.getIndexFieldTimestamp(), //
134                     fessConfig.getIndexFieldMimetype(), //
135                     fessConfig.getIndexFieldFiletype(), //
136                     fessConfig.getIndexFieldFilename(), //
137                     fessConfig.getIndexFieldCreated(), //
138                     fessConfig.getIndexFieldTitle(), //
139                     fessConfig.getIndexFieldDigest(), //
140                     fessConfig.getIndexFieldUrl(), //
141                     fessConfig.getIndexFieldThumbnail(), //
142                     fessConfig.getIndexFieldClickCount(), //
143                     fessConfig.getIndexFieldFavoriteCount(), //
144                     fessConfig.getIndexFieldConfigId(), //
145                     fessConfig.getIndexFieldLang(), //
146                     fessConfig.getIndexFieldHasCache());
147         }
148         if (scrollResponseFields == null) {
149             scrollResponseFields = fessConfig.getQueryAdditionalScrollResponseFields(//
150                     SCORE_FIELD, //
151                     fessConfig.getIndexFieldId(), //
152                     fessConfig.getIndexFieldDocId(), //
153                     fessConfig.getIndexFieldBoost(), //
154                     fessConfig.getIndexFieldContentLength(), //
155                     fessConfig.getIndexFieldHost(), //
156                     fessConfig.getIndexFieldSite(), //
157                     fessConfig.getIndexFieldLastModified(), //
158                     fessConfig.getIndexFieldTimestamp(), //
159                     fessConfig.getIndexFieldMimetype(), //
160                     fessConfig.getIndexFieldFiletype(), //
161                     fessConfig.getIndexFieldFilename(), //
162                     fessConfig.getIndexFieldCreated(), //
163                     fessConfig.getIndexFieldTitle(), //
164                     fessConfig.getIndexFieldDigest(), //
165                     fessConfig.getIndexFieldUrl(), //
166                     fessConfig.getIndexFieldThumbnail(), //
167                     fessConfig.getIndexFieldClickCount(), //
168                     fessConfig.getIndexFieldFavoriteCount(), //
169                     fessConfig.getIndexFieldConfigId(), //
170                     fessConfig.getIndexFieldLang(), //
171                     fessConfig.getIndexFieldHasCache());
172         }
173         if (cacheResponseFields == null) {
174             cacheResponseFields = fessConfig.getQueryAdditionalCacheResponseFields(//
175                     SCORE_FIELD, //
176                     fessConfig.getIndexFieldId(), //
177                     fessConfig.getIndexFieldDocId(), //
178                     fessConfig.getIndexFieldBoost(), //
179                     fessConfig.getIndexFieldContentLength(), //
180                     fessConfig.getIndexFieldHost(), //
181                     fessConfig.getIndexFieldSite(), //
182                     fessConfig.getIndexFieldLastModified(), //
183                     fessConfig.getIndexFieldTimestamp(), //
184                     fessConfig.getIndexFieldMimetype(), //
185                     fessConfig.getIndexFieldFiletype(), //
186                     fessConfig.getIndexFieldFilename(), //
187                     fessConfig.getIndexFieldCreated(), //
188                     fessConfig.getIndexFieldTitle(), //
189                     fessConfig.getIndexFieldDigest(), //
190                     fessConfig.getIndexFieldUrl(), //
191                     fessConfig.getIndexFieldClickCount(), //
192                     fessConfig.getIndexFieldFavoriteCount(), //
193                     fessConfig.getIndexFieldConfigId(), //
194                     fessConfig.getIndexFieldLang(), //
195                     fessConfig.getIndexFieldCache());
196         }
197         if (highlightedFields == null) {
198             highlightedFields = fessConfig.getQueryAdditionalHighlightedFields( //
199                     fessConfig.getIndexFieldContent());
200         }
201         if (searchFields == null) {
202             searchFields = fessConfig.getQueryAdditionalSearchFields(//
203                     INURL_FIELD, //
204                     fessConfig.getIndexFieldUrl(), //
205                     fessConfig.getIndexFieldDocId(), //
206                     fessConfig.getIndexFieldHost(), //
207                     fessConfig.getIndexFieldSite(), //
208                     fessConfig.getIndexFieldTitle(), //
209                     fessConfig.getIndexFieldContent(), //
210                     fessConfig.getIndexFieldContentLength(), //
211                     fessConfig.getIndexFieldLastModified(), //
212                     fessConfig.getIndexFieldTimestamp(), //
213                     fessConfig.getIndexFieldMimetype(), //
214                     fessConfig.getIndexFieldFiletype(), //
215                     fessConfig.getIndexFieldFilename(), //
216                     fessConfig.getIndexFieldLabel(), //
217                     fessConfig.getIndexFieldSegment(), //
218                     fessConfig.getIndexFieldAnchor(), //
219                     fessConfig.getIndexFieldClickCount(), //
220                     fessConfig.getIndexFieldFavoriteCount(), //
221                     fessConfig.getIndexFieldLang());
222             // Initialize Set for O(1) lookup performance
223             searchFieldSet = new HashSet<>();
224             Collections.addAll(searchFieldSet, searchFields);
225         }
226         if (facetFields == null) {
227             facetFields = fessConfig.getQueryAdditionalFacetFields(//
228                     fessConfig.getIndexFieldUrl(), //
229                     fessConfig.getIndexFieldHost(), //
230                     fessConfig.getIndexFieldTitle(), //
231                     fessConfig.getIndexFieldContent(), //
232                     fessConfig.getIndexFieldContentLength(), //
233                     fessConfig.getIndexFieldLastModified(), //
234                     fessConfig.getIndexFieldTimestamp(), //
235                     fessConfig.getIndexFieldMimetype(), //
236                     fessConfig.getIndexFieldFiletype(), //
237                     fessConfig.getIndexFieldLabel(), //
238                     fessConfig.getIndexFieldSegment());
239             // Initialize Set for O(1) lookup performance
240             facetFieldSet = new HashSet<>();
241             Collections.addAll(facetFieldSet, facetFields);
242         }
243         if (sortFields == null) {
244             sortFields = fessConfig.getQueryAdditionalSortFields(//
245                     SCORE_SORT_VALUE, //
246                     fessConfig.getIndexFieldFilename(), //
247                     fessConfig.getIndexFieldCreated(), //
248                     fessConfig.getIndexFieldContentLength(), //
249                     fessConfig.getIndexFieldLastModified(), //
250                     fessConfig.getIndexFieldTimestamp(), //
251                     fessConfig.getIndexFieldClickCount(), //
252                     fessConfig.getIndexFieldFavoriteCount());
253             // Initialize Set for O(1) lookup performance
254             sortFieldSet = new HashSet<>();
255             Collections.addAll(sortFieldSet, sortFields);
256         }
257         if (apiResponseFieldSet == null) {
258             setApiResponseFields(fessConfig.getQueryAdditionalApiResponseFields(//
259                     SCORE_SORT_VALUE, //
260                     fessConfig.getResponseFieldContentDescription(), //
261                     fessConfig.getResponseFieldContentTitle(), //
262                     fessConfig.getResponseFieldSitePath(), //
263                     fessConfig.getResponseFieldUrlLink(), //
264                     fessConfig.getIndexFieldId(), //
265                     fessConfig.getIndexFieldDocId(), //
266                     fessConfig.getIndexFieldBoost(), //
267                     fessConfig.getIndexFieldContentLength(), //
268                     fessConfig.getIndexFieldHost(), //
269                     fessConfig.getIndexFieldSite(), //
270                     fessConfig.getIndexFieldLastModified(), //
271                     fessConfig.getIndexFieldTimestamp(), //
272                     fessConfig.getIndexFieldMimetype(), //
273                     fessConfig.getIndexFieldFiletype(), //
274                     fessConfig.getIndexFieldFilename(), //
275                     fessConfig.getIndexFieldCreated(), //
276                     fessConfig.getIndexFieldTitle(), //
277                     fessConfig.getIndexFieldDigest(), //
278                     fessConfig.getIndexFieldUrl()));
279         }
280         if (notAnalyzedFieldSet == null) {
281             setNotAnalyzedFields(fessConfig.getQueryAdditionalNotAnalyzedFields(//
282                     fessConfig.getIndexFieldAnchor(), //
283                     fessConfig.getIndexFieldBoost(), //
284                     fessConfig.getIndexFieldClickCount(), //
285                     fessConfig.getIndexFieldConfigId(), //
286                     fessConfig.getIndexFieldContentLength(), //
287                     fessConfig.getIndexFieldCreated(), //
288                     fessConfig.getIndexFieldDocId(), //
289                     fessConfig.getIndexFieldExpires(), //
290                     fessConfig.getIndexFieldFavoriteCount(), //
291                     fessConfig.getIndexFieldFiletype(), //
292                     fessConfig.getIndexFieldFilename(), //
293                     fessConfig.getIndexFieldHasCache(), //
294                     fessConfig.getIndexFieldHost(), //
295                     fessConfig.getIndexFieldId(), //
296                     fessConfig.getIndexFieldLabel(), //
297                     fessConfig.getIndexFieldLang(), //
298                     fessConfig.getIndexFieldLastModified(), //
299                     fessConfig.getIndexFieldMimetype(), //
300                     fessConfig.getIndexFieldParentId(), //
301                     fessConfig.getIndexFieldPrimaryTerm(), //
302                     fessConfig.getIndexFieldRole(), //
303                     fessConfig.getIndexFieldSegment(), //
304                     fessConfig.getIndexFieldSeqNo(), //
305                     fessConfig.getIndexFieldSite(), //
306                     fessConfig.getIndexFieldTimestamp(), //
307                     fessConfig.getIndexFieldUrl(), //
308                     fessConfig.getIndexFieldVersion()));
309         }
310         split(fessConfig.getQueryAdditionalAnalyzedFields(), ",")
311                 .of(stream -> stream.map(String::trim).filter(StringUtil::isNotBlank).forEach(s -> notAnalyzedFieldSet.remove(s)));
312         split(fessConfig.getQueryAdditionalDefaultFields(), ",").of(stream -> stream.filter(StringUtil::isNotBlank).map(s -> {
313             final Pair<String, Float> pair = new Pair<>();
314             final String[] values = s.split(":");
315             if (values.length == 1) {
316                 pair.setFirst(values[0].trim());
317                 pair.setSecond(1.0f);
318             } else if (values.length > 1) {
319                 pair.setFirst(values[0]);
320                 pair.setSecond(Float.parseFloat(values[1]));
321             } else {
322                 return null;
323             }
324             return pair;
325         }).forEach(additionalDefaultList::add));
326     }
327 
328     /**
329      * Sets the fields that should not be analyzed during indexing.
330      *
331      * @param fields array of field names that should not be analyzed
332      */
333     public void setNotAnalyzedFields(final String[] fields) {
334         notAnalyzedFieldSet = new HashSet<>();
335         Collections.addAll(notAnalyzedFieldSet, fields);
336     }
337 
338     /**
339      * Checks if the specified field can be used for sorting.
340      * Uses O(1) Set lookup for improved performance.
341      *
342      * @param field the field name to check
343      * @return true if the field can be used for sorting, false otherwise
344      */
345     protected boolean isSortField(final String field) {
346         return sortFieldSet != null && sortFieldSet.contains(field);
347     }
348 
349     /**
350      * Checks if the specified field can be used for faceted search.
351      * Uses O(1) Set lookup for improved performance.
352      *
353      * @param field the field name to check
354      * @return true if the field can be used for faceted search, false otherwise
355      */
356     public boolean isFacetField(final String field) {
357         if (StringUtil.isBlank(field)) {
358             return false;
359         }
360         return facetFieldSet != null && facetFieldSet.contains(field);
361     }
362 
363     /**
364      * Checks if the specified sort value is valid for facet sorting.
365      *
366      * @param sort the sort value to check
367      * @return true if the sort value is valid for facets ("count" or "index"), false otherwise
368      */
369     public boolean isFacetSortValue(final String sort) {
370         return "count".equals(sort) || "index".equals(sort);
371     }
372 
373     /**
374      * Sets the fields that are allowed in API responses.
375      *
376      * @param fields array of field names that are allowed in API responses
377      */
378     public void setApiResponseFields(final String[] fields) {
379         apiResponseFieldSet = new HashSet<>();
380         Collections.addAll(apiResponseFieldSet, fields);
381     }
382 
383     /**
384      * Checks if the specified field is allowed in API responses.
385      *
386      * @param field the field name to check
387      * @return true if the field is allowed in API responses, false otherwise
388      */
389     public boolean isApiResponseField(final String field) {
390         return apiResponseFieldSet.contains(field);
391     }
392 
393     /**
394      * Gets the fields that are included in standard search responses.
395      *
396      * @return array of field names for standard search responses
397      */
398     public String[] getResponseFields() {
399         return responseFields;
400     }
401 
402     /**
403      * Sets the fields that are included in standard search responses.
404      *
405      * @param responseFields array of field names for standard search responses
406      */
407     public void setResponseFields(final String[] responseFields) {
408         this.responseFields = responseFields;
409     }
410 
411     /**
412      * Gets the fields that are included in scroll search responses.
413      *
414      * @return array of field names for scroll search responses
415      */
416     public String[] getScrollResponseFields() {
417         return scrollResponseFields;
418     }
419 
420     /**
421      * Sets the fields that are included in scroll search responses.
422      *
423      * @param scrollResponseFields array of field names for scroll search responses
424      */
425     public void setScrollResponseFields(final String[] scrollResponseFields) {
426         this.scrollResponseFields = scrollResponseFields;
427     }
428 
429     /**
430      * Gets the fields that are included in cache search responses.
431      *
432      * @return array of field names for cache search responses
433      */
434     public String[] getCacheResponseFields() {
435         return cacheResponseFields;
436     }
437 
438     /**
439      * Sets the fields that are included in cache search responses.
440      *
441      * @param cacheResponseFields array of field names for cache search responses
442      */
443     public void setCacheResponseFields(final String[] cacheResponseFields) {
444         this.cacheResponseFields = cacheResponseFields;
445     }
446 
447     /**
448      * Gets the fields that can be highlighted in search results.
449      *
450      * @return array of field names that can be highlighted
451      */
452     public String[] getHighlightedFields() {
453         return highlightedFields;
454     }
455 
456     /**
457      * Sets the fields that can be highlighted in search results.
458      *
459      * @param highlightedFields array of field names that can be highlighted
460      */
461     public void setHighlightedFields(final String[] highlightedFields) {
462         this.highlightedFields = highlightedFields;
463     }
464 
465     /**
466      * Processes the highlighted fields using the provided stream consumer.
467      *
468      * @param stream consumer that processes the stream of highlighted field names
469      */
470     public void highlightedFields(final Consumer<Stream<String>> stream) {
471         stream(highlightedFields).of(stream);
472     }
473 
474     /**
475      * Gets the fields that can be searched against.
476      *
477      * @return array of field names that can be searched
478      */
479     public String[] getSearchFields() {
480         return searchFields;
481     }
482 
483     /**
484      * Sets the fields that can be searched against.
485      * Also updates the searchFieldSet for O(1) lookup performance.
486      *
487      * @param supportedFields array of field names that can be searched
488      */
489     public void setSearchFields(final String[] supportedFields) {
490         searchFields = supportedFields;
491         searchFieldSet = new HashSet<>();
492         Collections.addAll(searchFieldSet, supportedFields);
493     }
494 
495     /**
496      * Gets the fields that can be used for faceted search.
497      *
498      * @return array of field names that can be used for faceted search
499      */
500     public String[] getFacetFields() {
501         return facetFields;
502     }
503 
504     /**
505      * Sets the fields that can be used for faceted search.
506      * Also updates the facetFieldSet for O(1) lookup performance.
507      *
508      * @param facetFields array of field names that can be used for faceted search
509      */
510     public void setFacetFields(final String[] facetFields) {
511         this.facetFields = facetFields;
512         facetFieldSet = new HashSet<>();
513         Collections.addAll(facetFieldSet, facetFields);
514     }
515 
516     /**
517      * Gets the fields that can be used for sorting search results.
518      *
519      * @return array of field names that can be used for sorting
520      */
521     public String[] getSortFields() {
522         return sortFields;
523     }
524 
525     /**
526      * Sets the fields that can be used for sorting search results.
527      * Also updates the sortFieldSet for O(1) lookup performance.
528      *
529      * @param sortFields array of field names that can be used for sorting
530      */
531     public void setSortFields(final String[] sortFields) {
532         this.sortFields = sortFields;
533         sortFieldSet = new HashSet<>();
534         Collections.addAll(sortFieldSet, sortFields);
535     }
536 
537 }