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 java.util.Locale;
19  
20  import org.apache.logging.log4j.LogManager;
21  import org.apache.logging.log4j.Logger;
22  import org.apache.lucene.search.PrefixQuery;
23  import org.apache.lucene.search.Query;
24  import org.codelibs.fess.Constants;
25  import org.codelibs.fess.entity.QueryContext;
26  import org.codelibs.fess.exception.InvalidQueryException;
27  import org.codelibs.fess.mylasta.direction.FessConfig;
28  import org.codelibs.fess.util.ComponentUtil;
29  import org.lastaflute.core.message.UserMessages;
30  import org.opensearch.index.query.QueryBuilder;
31  import org.opensearch.index.query.QueryBuilders;
32  
33  /**
34   * Query command for prefix queries.
35   */
36  public class PrefixQueryCommand extends QueryCommand {
37  
38      /**
39       * Default constructor.
40       */
41      public PrefixQueryCommand() {
42          super();
43      }
44  
45      private static final Logger logger = LogManager.getLogger(PrefixQueryCommand.class);
46  
47      /** Flag to convert wildcard to lowercase. */
48      protected boolean lowercaseWildcard = true;
49  
50      @Override
51      protected String getQueryClassName() {
52          return PrefixQuery.class.getSimpleName();
53      }
54  
55      @Override
56      public QueryBuilder execute(final QueryContext context, final Query query, final float boost) {
57          if (query instanceof final PrefixQuery prefixQuery) {
58              if (logger.isDebugEnabled()) {
59                  logger.debug("PrefixQuery: query={}, boost={}", query, boost);
60              }
61              return convertPrefixQuery(context, prefixQuery, boost);
62          }
63          throw new InvalidQueryException(messages -> messages.addErrorsInvalidQueryUnknown(UserMessages.GLOBAL_PROPERTY_KEY),
64                  "Unknown q: " + query.getClass() + " => " + query);
65      }
66  
67      /**
68       * Converts a prefix query to a query builder.
69       *
70       * @param context the query context
71       * @param prefixQuery the prefix query
72       * @param boost the boost factor
73       * @return the query builder
74       */
75      protected QueryBuilder convertPrefixQuery(final QueryContext context, final PrefixQuery prefixQuery, final float boost) {
76          final FessConfig fessConfig = ComponentUtil.getFessConfig();
77          final String field = getSearchField(context.getDefaultField(), prefixQuery.getField());
78          final String text = prefixQuery.getPrefix().text();
79  
80          if (Constants.DEFAULT_FIELD.equals(field)) {
81              context.addFieldLog(field, text + "*");
82              context.addHighlightedQuery(text);
83              return buildDefaultQueryBuilder(fessConfig, context,
84                      (f, b) -> QueryBuilders.matchPhrasePrefixQuery(f, toLowercaseWildcard(text))
85                              .boost(b * boost)
86                              .maxExpansions(fessConfig.getQueryPrefixExpansionsAsInteger())
87                              .slop(fessConfig.getQueryPrefixSlopAsInteger()));
88          }
89  
90          if (!isSearchField(field)) {
91              final String query = prefixQuery.getPrefix().toString();
92              final String origQuery = toLowercaseWildcard(query);
93              context.addFieldLog(Constants.DEFAULT_FIELD, query + "*");
94              context.addHighlightedQuery(origQuery);
95              return buildDefaultQueryBuilder(fessConfig, context,
96                      (f, b) -> QueryBuilders.matchPhrasePrefixQuery(f, origQuery)
97                              .boost(b * boost)
98                              .maxExpansions(fessConfig.getQueryPrefixExpansionsAsInteger())
99                              .slop(fessConfig.getQueryPrefixSlopAsInteger()));
100         }
101 
102         context.addFieldLog(field, text + "*");
103         context.addHighlightedQuery(text);
104         if (getQueryFieldConfig().notAnalyzedFieldSet.contains(field)) {
105             return QueryBuilders.prefixQuery(field, toLowercaseWildcard(text)).boost(boost);
106         }
107         return QueryBuilders.matchPhrasePrefixQuery(field, toLowercaseWildcard(text))
108                 .boost(boost)
109                 .maxExpansions(fessConfig.getQueryPrefixExpansionsAsInteger())
110                 .slop(fessConfig.getQueryPrefixSlopAsInteger());
111     }
112 
113     /**
114      * Converts value to lowercase if lowercase wildcard is enabled.
115      *
116      * @param value the value to convert
117      * @return the converted value
118      */
119     protected String toLowercaseWildcard(final String value) {
120         if (lowercaseWildcard) {
121             return value.toLowerCase(Locale.ROOT);
122         }
123         return value;
124     }
125 
126     /**
127      * Sets the lowercase wildcard flag.
128      *
129      * @param lowercaseWildcard the lowercase wildcard flag
130      */
131     public void setLowercaseWildcard(final boolean lowercaseWildcard) {
132         this.lowercaseWildcard = lowercaseWildcard;
133     }
134 }