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 org.apache.logging.log4j.LogManager;
19  import org.apache.logging.log4j.Logger;
20  import org.apache.lucene.index.Term;
21  import org.apache.lucene.search.FuzzyQuery;
22  import org.apache.lucene.search.Query;
23  import org.codelibs.fess.Constants;
24  import org.codelibs.fess.entity.QueryContext;
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.common.unit.Fuzziness;
30  import org.opensearch.index.query.QueryBuilder;
31  import org.opensearch.index.query.QueryBuilders;
32  
33  /**
34   * Query command implementation for handling fuzzy search queries.
35   * This class converts Lucene FuzzyQuery objects into OpenSearch fuzzy query builders,
36   * supporting configurable fuzzy matching parameters like edit distance and expansions.
37   *
38   */
39  public class FuzzyQueryCommand extends QueryCommand {
40      private static final Logger logger = LogManager.getLogger(FuzzyQueryCommand.class);
41  
42      /**
43       * Default constructor.
44       */
45      public FuzzyQueryCommand() {
46          super();
47      }
48  
49      @Override
50      protected String getQueryClassName() {
51          return FuzzyQuery.class.getSimpleName();
52      }
53  
54      /**
55       * Executes the fuzzy query command to convert a Lucene FuzzyQuery into an OpenSearch QueryBuilder.
56       *
57       * @param context the query context containing search configuration
58       * @param query the Lucene query to convert (must be a FuzzyQuery)
59       * @param boost the boost factor to apply to the query
60       * @return OpenSearch QueryBuilder for fuzzy matching
61       * @throws InvalidQueryException if the query is not a FuzzyQuery
62       */
63      @Override
64      public QueryBuilder execute(final QueryContext context, final Query query, final float boost) {
65          if (query instanceof final FuzzyQuery fuzzyQuery) {
66              if (logger.isDebugEnabled()) {
67                  logger.debug("FuzzyQuery: query={}, boost={}", query, boost);
68              }
69              return convertFuzzyQuery(context, fuzzyQuery, boost);
70          }
71          throw new InvalidQueryException(messages -> messages.addErrorsInvalidQueryUnknown(UserMessages.GLOBAL_PROPERTY_KEY),
72                  "Unknown q: " + query.getClass() + " => " + query);
73      }
74  
75      /**
76       * Converts a Lucene FuzzyQuery into an OpenSearch fuzzy query builder.
77       * Applies fuzzy matching configuration including edit distance, expansions, and prefix length.
78       *
79       * @param context the query context containing field mappings
80       * @param fuzzyQuery the Lucene FuzzyQuery to convert
81       * @param boost the boost factor to apply
82       * @return OpenSearch QueryBuilder configured for fuzzy matching
83       */
84      protected QueryBuilder convertFuzzyQuery(final QueryContext context, final FuzzyQuery fuzzyQuery, final float boost) {
85          final FessConfig fessConfig = ComponentUtil.getFessConfig();
86          final Term term = fuzzyQuery.getTerm();
87          final String field = getSearchField(context.getDefaultField(), term.field());
88  
89          if (Constants.DEFAULT_FIELD.equals(field)) {
90              final String text = term.text();
91              context.addFieldLog(field, text);
92              context.addHighlightedQuery(text);
93              return buildDefaultQueryBuilder(fessConfig, context,
94                      (f, b) -> QueryBuilders.fuzzyQuery(f, text)
95                              .fuzziness(Fuzziness.fromEdits(fuzzyQuery.getMaxEdits()))
96                              .boost(b * boost)
97                              .maxExpansions(fessConfig.getQueryFuzzyExpansionsAsInteger())
98                              .prefixLength(fessConfig.getQueryFuzzyPrefixLengthAsInteger())
99                              .transpositions(Constants.TRUE.equalsIgnoreCase(fessConfig.getQueryFuzzyTranspositions())));
100         }
101 
102         if (isSearchField(field)) {
103             final String text = term.text();
104             context.addFieldLog(field, text);
105             context.addHighlightedQuery(text);
106             return QueryBuilders.fuzzyQuery(field, text)
107                     .boost(boost)
108                     .fuzziness(Fuzziness.fromEdits(fuzzyQuery.getMaxEdits()))
109                     .maxExpansions(fessConfig.getQueryFuzzyExpansionsAsInteger())
110                     .prefixLength(fessConfig.getQueryFuzzyPrefixLengthAsInteger())
111                     .transpositions(Constants.TRUE.equalsIgnoreCase(fessConfig.getQueryFuzzyTranspositions()));
112         }
113 
114         final String origQuery = term.toString();
115         context.addFieldLog(Constants.DEFAULT_FIELD, origQuery);
116         context.addHighlightedQuery(origQuery);
117         return buildDefaultQueryBuilder(fessConfig, context,
118                 (f, b) -> QueryBuilders.fuzzyQuery(f, origQuery)
119                         .fuzziness(Fuzziness.fromEdits(fuzzyQuery.getMaxEdits()))
120                         .boost(b * boost)
121                         .maxExpansions(fessConfig.getQueryFuzzyExpansionsAsInteger())
122                         .prefixLength(fessConfig.getQueryFuzzyPrefixLengthAsInteger())
123                         .transpositions(Constants.TRUE.equalsIgnoreCase(fessConfig.getQueryFuzzyTranspositions())));
124     }
125 }