فهرست منبع

fix #2637 add fess_query.xml

Shinsuke Sugaya 3 سال پیش
والد
کامیت
c1b721cfa3
26فایلهای تغییر یافته به همراه1443 افزوده شده و 796 حذف شده
  1. 1 1
      src/main/java/org/codelibs/fess/api/json/JsonApiManager.java
  2. 2 1
      src/main/java/org/codelibs/fess/app/web/admin/AdminAction.java
  3. 4 0
      src/main/java/org/codelibs/fess/app/web/base/FessSearchAction.java
  4. 1 1
      src/main/java/org/codelibs/fess/app/web/cache/CacheAction.java
  5. 1 1
      src/main/java/org/codelibs/fess/app/web/thumbnail/ThumbnailAction.java
  6. 16 12
      src/main/java/org/codelibs/fess/es/client/SearchEngineClient.java
  7. 3 734
      src/main/java/org/codelibs/fess/helper/QueryHelper.java
  8. 5 2
      src/main/java/org/codelibs/fess/helper/SearchHelper.java
  9. 69 0
      src/main/java/org/codelibs/fess/query/BooleanQueryCommand.java
  10. 41 0
      src/main/java/org/codelibs/fess/query/BoostQueryCommand.java
  11. 75 0
      src/main/java/org/codelibs/fess/query/FuzzyQueryCommand.java
  12. 36 0
      src/main/java/org/codelibs/fess/query/MatchAllQueryCommand.java
  13. 58 0
      src/main/java/org/codelibs/fess/query/PhraseQueryCommand.java
  14. 83 0
      src/main/java/org/codelibs/fess/query/PrefixQueryCommand.java
  15. 126 0
      src/main/java/org/codelibs/fess/query/QueryCommand.java
  16. 403 0
      src/main/java/org/codelibs/fess/query/QueryFieldConfig.java
  17. 52 0
      src/main/java/org/codelibs/fess/query/QueryProcessor.java
  18. 144 0
      src/main/java/org/codelibs/fess/query/TermQueryCommand.java
  19. 75 0
      src/main/java/org/codelibs/fess/query/TermRangeQueryCommand.java
  20. 74 0
      src/main/java/org/codelibs/fess/query/WildcardQueryCommand.java
  21. 2 1
      src/main/java/org/codelibs/fess/sso/spnego/SpnegoAuthenticator.java
  22. 15 0
      src/main/java/org/codelibs/fess/util/ComponentUtil.java
  23. 1 7
      src/main/resources/app.xml
  24. 51 0
      src/main/resources/fess_query.xml
  25. 26 36
      src/test/java/org/codelibs/fess/helper/QueryHelperTest.java
  26. 79 0
      src/test/java/org/codelibs/fess/query/QueryCommandTest.java

+ 1 - 1
src/main/java/org/codelibs/fess/api/json/JsonApiManager.java

@@ -302,7 +302,7 @@ public class JsonApiManager extends BaseJsonApiManager {
                     for (final Map.Entry<String, Object> entry : document.entrySet()) {
                         final String name = entry.getKey();
                         if (StringUtil.isNotBlank(name) && entry.getValue() != null
-                                && ComponentUtil.getQueryHelper().isApiResponseField(name)) {
+                                && ComponentUtil.getQueryFieldConfig().isApiResponseField(name)) {
                             if (!first2) {
                                 buf.append(',');
                             } else {

+ 2 - 1
src/main/java/org/codelibs/fess/app/web/admin/AdminAction.java

@@ -331,7 +331,8 @@ public class AdminAction extends FessAdminAction {
         }
         if (user.hasRoles(getActionRoles(AdminStorageAction.ROLE))) {
             return AdminStorageAction.class;
-        } else if (user.hasRoles(getActionRoles(AdminWebconfigAction.ROLE))) {
+        }
+        if (user.hasRoles(getActionRoles(AdminWebconfigAction.ROLE))) {
             return AdminWebconfigAction.class;
         } else if (user.hasRoles(getActionRoles(AdminFileconfigAction.ROLE))) {
             return AdminFileconfigAction.class;

+ 4 - 0
src/main/java/org/codelibs/fess/app/web/base/FessSearchAction.java

@@ -41,6 +41,7 @@ import org.codelibs.fess.helper.RoleQueryHelper;
 import org.codelibs.fess.helper.SearchHelper;
 import org.codelibs.fess.helper.UserInfoHelper;
 import org.codelibs.fess.mylasta.action.FessUserBean;
+import org.codelibs.fess.query.QueryFieldConfig;
 import org.codelibs.fess.thumbnail.ThumbnailManager;
 import org.codelibs.fess.util.ComponentUtil;
 import org.dbflute.optional.OptionalThing;
@@ -66,6 +67,9 @@ public abstract class FessSearchAction extends FessBaseAction {
     @Resource
     protected QueryHelper queryHelper;
 
+    @Resource
+    protected QueryFieldConfig queryFieldConfig;
+
     @Resource
     protected RoleQueryHelper roleQueryHelper;
 

+ 1 - 1
src/main/java/org/codelibs/fess/app/web/cache/CacheAction.java

@@ -54,7 +54,7 @@ public class CacheAction extends FessSearchAction {
 
         Map<String, Object> doc = null;
         try {
-            doc = searchHelper.getDocumentByDocId(form.docId, queryHelper.getCacheResponseFields(), getUserBean()).orElse(null);
+            doc = searchHelper.getDocumentByDocId(form.docId, queryFieldConfig.getCacheResponseFields(), getUserBean()).orElse(null);
         } catch (final Exception e) {
             logger.warn("Failed to request: {}", form.docId, e);
         }

+ 1 - 1
src/main/java/org/codelibs/fess/app/web/thumbnail/ThumbnailAction.java

@@ -52,7 +52,7 @@ public class ThumbnailAction extends FessSearchAction {
         }
 
         final Map<String, Object> doc =
-                searchHelper.getDocumentByDocId(form.docId, queryHelper.getResponseFields(), getUserBean()).orElse(null);
+                searchHelper.getDocumentByDocId(form.docId, queryFieldConfig.getResponseFields(), getUserBean()).orElse(null);
         final String url = DocumentUtil.getValue(doc, fessConfig.getIndexFieldThumbnail(), String.class);
         if (StringUtil.isBlank(form.queryId) || StringUtil.isBlank(url) || !thumbnailSupport) {
             // 404

+ 16 - 12
src/main/java/org/codelibs/fess/es/client/SearchEngineClient.java

@@ -67,6 +67,7 @@ import org.codelibs.fess.exception.SearchQueryException;
 import org.codelibs.fess.helper.DocumentHelper;
 import org.codelibs.fess.helper.QueryHelper;
 import org.codelibs.fess.mylasta.direction.FessConfig;
+import org.codelibs.fess.query.QueryFieldConfig;
 import org.codelibs.fess.util.BooleanFunction;
 import org.codelibs.fess.util.ComponentUtil;
 import org.codelibs.fess.util.DocMap;
@@ -1191,13 +1192,14 @@ public class SearchEngineClient implements Client {
             }
 
             final QueryHelper queryHelper = ComponentUtil.getQueryHelper();
+            final QueryFieldConfig queryFieldConfig = ComponentUtil.getQueryFieldConfig();
             final FessConfig fessConfig = ComponentUtil.getFessConfig();
 
             if (offset > fessConfig.getQueryMaxSearchResultOffsetAsInteger()) {
                 throw new ResultOffsetExceededException("The number of result size is exceeded.");
             }
 
-            final QueryContext queryContext = buildQueryContext(queryHelper, fessConfig);
+            final QueryContext queryContext = buildQueryContext(queryHelper, queryFieldConfig, fessConfig);
 
             searchRequestBuilder.setFrom(offset).setSize(size);
 
@@ -1208,19 +1210,19 @@ public class SearchEngineClient implements Client {
             }
 
             // rescorer
-            buildRescorer(queryHelper, fessConfig);
+            buildRescorer(queryHelper, queryFieldConfig, fessConfig);
 
             // sort
-            buildSort(queryContext, fessConfig);
+            buildSort(queryContext, queryFieldConfig, fessConfig);
 
             // highlighting
             if (highlightInfo != null) {
-                buildHighlighter(queryHelper, fessConfig);
+                buildHighlighter(queryHelper, queryFieldConfig, fessConfig);
             }
 
             // facets
             if (facetInfo != null) {
-                buildFacet(queryHelper, fessConfig);
+                buildFacet(queryHelper, queryFieldConfig, fessConfig);
             }
 
             if (!SearchRequestType.ADMIN_SEARCH.equals(searchRequestType) && !isScroll && fessConfig.isResultCollapsed()
@@ -1253,9 +1255,9 @@ public class SearchEngineClient implements Client {
             }
         }
 
-        protected void buildFacet(final QueryHelper queryHelper, final FessConfig fessConfig) {
+        protected void buildFacet(final QueryHelper queryHelper, final QueryFieldConfig queryFieldConfig, final FessConfig fessConfig) {
             stream(facetInfo.field).of(stream -> stream.forEach(f -> {
-                if (!queryHelper.isFacetField(f)) {
+                if (!queryFieldConfig.isFacetField(f)) {
                     throw new SearchQueryException("Invalid facet field: " + f);
                 }
                 final String encodedField = BaseEncoding.base64().encode(f.getBytes(StandardCharsets.UTF_8));
@@ -1283,7 +1285,8 @@ public class SearchEngineClient implements Client {
             }));
         }
 
-        protected void buildHighlighter(final QueryHelper queryHelper, final FessConfig fessConfig) {
+        protected void buildHighlighter(final QueryHelper queryHelper, final QueryFieldConfig queryFieldConfig,
+                final FessConfig fessConfig) {
             final String highlighterType = highlightInfo.getType();
             final int fragmentSize = highlightInfo.getFragmentSize();
             final int numOfFragments = highlightInfo.getNumOfFragments();
@@ -1298,7 +1301,7 @@ public class SearchEngineClient implements Client {
             final int phraseLimit = fessConfig.getQueryHighlightPhraseLimitAsInteger();
             final String encoder = fessConfig.getQueryHighlightEncoder();
             final HighlightBuilder highlightBuilder = new HighlightBuilder();
-            queryHelper.highlightedFields(stream -> stream.forEach(hf -> highlightBuilder
+            queryFieldConfig.highlightedFields(stream -> stream.forEach(hf -> highlightBuilder
                     .field(new HighlightBuilder.Field(hf).highlighterType(highlighterType).fragmentSize(fragmentSize)
                             .numOfFragments(numOfFragments).boundaryChars(boundaryChars).boundaryMaxScan(boundaryMaxScan)
                             .boundaryScannerType(boundaryScannerType).forceSource(forceSource).fragmenter(fragmenter)
@@ -1307,15 +1310,16 @@ public class SearchEngineClient implements Client {
             searchRequestBuilder.highlighter(highlightBuilder);
         }
 
-        protected void buildSort(final QueryContext queryContext, final FessConfig fessConfig) {
+        protected void buildSort(final QueryContext queryContext, final QueryFieldConfig queryFieldConfig, final FessConfig fessConfig) {
             queryContext.sortBuilders().forEach(sortBuilder -> searchRequestBuilder.addSort(sortBuilder));
         }
 
-        protected void buildRescorer(final QueryHelper queryHelper, final FessConfig fessConfig) {
+        protected void buildRescorer(final QueryHelper queryHelper, final QueryFieldConfig queryFieldConfig, final FessConfig fessConfig) {
             stream(queryHelper.getRescorers(condition())).of(stream -> stream.forEach(searchRequestBuilder::addRescorer));
         }
 
-        protected QueryContext buildQueryContext(final QueryHelper queryHelper, final FessConfig fessConfig) {
+        protected QueryContext buildQueryContext(final QueryHelper queryHelper, final QueryFieldConfig queryFieldConfig,
+                final FessConfig fessConfig) {
             return queryHelper.build(searchRequestType, query, context -> {
                 if (SearchRequestType.ADMIN_SEARCH.equals(searchRequestType)) {
                     context.skipRoleQuery();

+ 3 - 734
src/main/java/org/codelibs/fess/helper/QueryHelper.java

@@ -15,45 +15,25 @@
  */
 package org.codelibs.fess.helper;
 
-import static org.codelibs.core.stream.StreamUtil.split;
 import static org.codelibs.core.stream.StreamUtil.stream;
 
-import java.lang.Character.UnicodeBlock;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
 import java.util.function.Consumer;
-import java.util.stream.Stream;
 
-import javax.annotation.PostConstruct;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpSession;
 
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
-import org.apache.lucene.index.Term;
 import org.apache.lucene.queryparser.classic.ParseException;
 import org.apache.lucene.queryparser.classic.QueryParser;
-import org.apache.lucene.search.BooleanClause;
-import org.apache.lucene.search.BooleanQuery;
-import org.apache.lucene.search.BoostQuery;
-import org.apache.lucene.search.FuzzyQuery;
-import org.apache.lucene.search.MatchAllDocsQuery;
-import org.apache.lucene.search.PhraseQuery;
-import org.apache.lucene.search.PrefixQuery;
 import org.apache.lucene.search.Query;
-import org.apache.lucene.search.TermQuery;
-import org.apache.lucene.search.TermRangeQuery;
-import org.apache.lucene.search.WildcardQuery;
-import org.apache.lucene.util.BytesRef;
 import org.codelibs.core.lang.StringUtil;
-import org.codelibs.core.misc.Pair;
 import org.codelibs.fess.Constants;
 import org.codelibs.fess.entity.FacetInfo;
 import org.codelibs.fess.entity.GeoInfo;
@@ -62,17 +42,16 @@ import org.codelibs.fess.entity.SearchRequestParams.SearchRequestType;
 import org.codelibs.fess.exception.InvalidQueryException;
 import org.codelibs.fess.mylasta.action.FessUserBean;
 import org.codelibs.fess.mylasta.direction.FessConfig;
+import org.codelibs.fess.query.QueryFieldConfig;
 import org.codelibs.fess.score.QueryRescorer;
 import org.codelibs.fess.util.ComponentUtil;
 import org.dbflute.optional.OptionalThing;
 import org.lastaflute.core.message.UserMessages;
 import org.lastaflute.web.util.LaRequestUtil;
 import org.opensearch.action.search.SearchRequestBuilder;
-import org.opensearch.common.unit.Fuzziness;
 import org.opensearch.index.query.BoolQueryBuilder;
 import org.opensearch.index.query.QueryBuilder;
 import org.opensearch.index.query.QueryBuilders;
-import org.opensearch.index.query.RangeQueryBuilder;
 import org.opensearch.index.query.functionscore.FunctionScoreQueryBuilder.FilterFunctionBuilder;
 import org.opensearch.index.query.functionscore.ScoreFunctionBuilder;
 import org.opensearch.index.query.functionscore.ScoreFunctionBuilders;
@@ -86,42 +65,10 @@ public class QueryHelper {
 
     protected static final String PREFERENCE_QUERY = "_query";
 
-    protected static final String ES_SCORE_FIELD = "_score";
-
-    protected static final String SCORE_SORT_VALUE = "score";
-
-    protected static final String SCORE_FIELD = "score";
-
-    protected static final String INURL_FIELD = "inurl";
-
-    protected static final String SITE_FIELD = "site";
-
-    protected Set<String> apiResponseFieldSet;
-
-    protected Set<String> highlightFieldSet = new HashSet<>();
-
-    protected Set<String> notAnalyzedFieldSet;
-
-    protected String[] responseFields;
-
-    protected String[] scrollResponseFields;
-
-    protected String[] cacheResponseFields;
-
-    protected String[] highlightedFields;
-
-    protected String[] searchFields;
-
-    protected String[] facetFields;
-
     protected String sortPrefix = "sort:";
 
-    protected String[] sortFields;
-
     protected String additionalQuery;
 
-    protected boolean lowercaseWildcard = true;
-
     protected SortBuilder<?>[] defaultSortBuilders;
 
     protected String highlightPrefix = "hl_";
@@ -136,209 +83,6 @@ public class QueryHelper {
 
     protected List<QueryRescorer> queryRescorerList = new ArrayList<>();
 
-    protected List<Pair<String, Float>> additionalDefaultList = new ArrayList<>();
-
-    @PostConstruct
-    public void init() {
-        if (logger.isDebugEnabled()) {
-            logger.debug("Initialize {}", this.getClass().getSimpleName());
-        }
-        final FessConfig fessConfig = ComponentUtil.getFessConfig();
-        if (responseFields == null) {
-            responseFields = fessConfig.getQueryAdditionalResponseFields(//
-                    SCORE_FIELD, //
-                    fessConfig.getIndexFieldId(), //
-                    fessConfig.getIndexFieldDocId(), //
-                    fessConfig.getIndexFieldBoost(), //
-                    fessConfig.getIndexFieldContentLength(), //
-                    fessConfig.getIndexFieldHost(), //
-                    fessConfig.getIndexFieldSite(), //
-                    fessConfig.getIndexFieldLastModified(), //
-                    fessConfig.getIndexFieldTimestamp(), //
-                    fessConfig.getIndexFieldMimetype(), //
-                    fessConfig.getIndexFieldFiletype(), //
-                    fessConfig.getIndexFieldFilename(), //
-                    fessConfig.getIndexFieldCreated(), //
-                    fessConfig.getIndexFieldTitle(), //
-                    fessConfig.getIndexFieldDigest(), //
-                    fessConfig.getIndexFieldUrl(), //
-                    fessConfig.getIndexFieldThumbnail(), //
-                    fessConfig.getIndexFieldClickCount(), //
-                    fessConfig.getIndexFieldFavoriteCount(), //
-                    fessConfig.getIndexFieldConfigId(), //
-                    fessConfig.getIndexFieldLang(), //
-                    fessConfig.getIndexFieldHasCache());
-        }
-        if (scrollResponseFields == null) {
-            scrollResponseFields = fessConfig.getQueryAdditionalScrollResponseFields(//
-                    SCORE_FIELD, //
-                    fessConfig.getIndexFieldId(), //
-                    fessConfig.getIndexFieldDocId(), //
-                    fessConfig.getIndexFieldBoost(), //
-                    fessConfig.getIndexFieldContentLength(), //
-                    fessConfig.getIndexFieldHost(), //
-                    fessConfig.getIndexFieldSite(), //
-                    fessConfig.getIndexFieldLastModified(), //
-                    fessConfig.getIndexFieldTimestamp(), //
-                    fessConfig.getIndexFieldMimetype(), //
-                    fessConfig.getIndexFieldFiletype(), //
-                    fessConfig.getIndexFieldFilename(), //
-                    fessConfig.getIndexFieldCreated(), //
-                    fessConfig.getIndexFieldTitle(), //
-                    fessConfig.getIndexFieldDigest(), //
-                    fessConfig.getIndexFieldUrl(), //
-                    fessConfig.getIndexFieldThumbnail(), //
-                    fessConfig.getIndexFieldClickCount(), //
-                    fessConfig.getIndexFieldFavoriteCount(), //
-                    fessConfig.getIndexFieldConfigId(), //
-                    fessConfig.getIndexFieldLang(), //
-                    fessConfig.getIndexFieldHasCache());
-        }
-        if (cacheResponseFields == null) {
-            cacheResponseFields = fessConfig.getQueryAdditionalCacheResponseFields(//
-                    SCORE_FIELD, //
-                    fessConfig.getIndexFieldId(), //
-                    fessConfig.getIndexFieldDocId(), //
-                    fessConfig.getIndexFieldBoost(), //
-                    fessConfig.getIndexFieldContentLength(), //
-                    fessConfig.getIndexFieldHost(), //
-                    fessConfig.getIndexFieldSite(), //
-                    fessConfig.getIndexFieldLastModified(), //
-                    fessConfig.getIndexFieldTimestamp(), //
-                    fessConfig.getIndexFieldMimetype(), //
-                    fessConfig.getIndexFieldFiletype(), //
-                    fessConfig.getIndexFieldFilename(), //
-                    fessConfig.getIndexFieldCreated(), //
-                    fessConfig.getIndexFieldTitle(), //
-                    fessConfig.getIndexFieldDigest(), //
-                    fessConfig.getIndexFieldUrl(), //
-                    fessConfig.getIndexFieldClickCount(), //
-                    fessConfig.getIndexFieldFavoriteCount(), //
-                    fessConfig.getIndexFieldConfigId(), //
-                    fessConfig.getIndexFieldLang(), //
-                    fessConfig.getIndexFieldCache());
-        }
-        if (highlightedFields == null) {
-            highlightedFields = fessConfig.getQueryAdditionalHighlightedFields( //
-                    fessConfig.getIndexFieldContent());
-        }
-        if (searchFields == null) {
-            searchFields = fessConfig.getQueryAdditionalSearchFields(//
-                    INURL_FIELD, //
-                    fessConfig.getIndexFieldUrl(), //
-                    fessConfig.getIndexFieldDocId(), //
-                    fessConfig.getIndexFieldHost(), //
-                    fessConfig.getIndexFieldSite(), //
-                    fessConfig.getIndexFieldTitle(), //
-                    fessConfig.getIndexFieldContent(), //
-                    fessConfig.getIndexFieldContentLength(), //
-                    fessConfig.getIndexFieldLastModified(), //
-                    fessConfig.getIndexFieldTimestamp(), //
-                    fessConfig.getIndexFieldMimetype(), //
-                    fessConfig.getIndexFieldFiletype(), //
-                    fessConfig.getIndexFieldFilename(), //
-                    fessConfig.getIndexFieldLabel(), //
-                    fessConfig.getIndexFieldSegment(), //
-                    fessConfig.getIndexFieldAnchor(), //
-                    fessConfig.getIndexFieldClickCount(), //
-                    fessConfig.getIndexFieldFavoriteCount(), //
-                    fessConfig.getIndexFieldLang());
-        }
-        if (facetFields == null) {
-            facetFields = fessConfig.getQueryAdditionalFacetFields(//
-                    fessConfig.getIndexFieldUrl(), //
-                    fessConfig.getIndexFieldHost(), //
-                    fessConfig.getIndexFieldTitle(), //
-                    fessConfig.getIndexFieldContent(), //
-                    fessConfig.getIndexFieldContentLength(), //
-                    fessConfig.getIndexFieldLastModified(), //
-                    fessConfig.getIndexFieldTimestamp(), //
-                    fessConfig.getIndexFieldMimetype(), //
-                    fessConfig.getIndexFieldFiletype(), //
-                    fessConfig.getIndexFieldLabel(), //
-                    fessConfig.getIndexFieldSegment());
-        }
-        if (sortFields == null) {
-            sortFields = fessConfig.getQueryAdditionalSortFields(//
-                    SCORE_SORT_VALUE, //
-                    fessConfig.getIndexFieldFilename(), //
-                    fessConfig.getIndexFieldCreated(), //
-                    fessConfig.getIndexFieldContentLength(), //
-                    fessConfig.getIndexFieldLastModified(), //
-                    fessConfig.getIndexFieldTimestamp(), //
-                    fessConfig.getIndexFieldClickCount(), //
-                    fessConfig.getIndexFieldFavoriteCount());
-        }
-        if (apiResponseFieldSet == null) {
-            setApiResponseFields(fessConfig.getQueryAdditionalApiResponseFields(//
-                    fessConfig.getResponseFieldContentDescription(), //
-                    fessConfig.getResponseFieldContentTitle(), //
-                    fessConfig.getResponseFieldSitePath(), //
-                    fessConfig.getResponseFieldUrlLink(), //
-                    fessConfig.getIndexFieldId(), //
-                    fessConfig.getIndexFieldDocId(), //
-                    fessConfig.getIndexFieldBoost(), //
-                    fessConfig.getIndexFieldContentLength(), //
-                    fessConfig.getIndexFieldHost(), //
-                    fessConfig.getIndexFieldSite(), //
-                    fessConfig.getIndexFieldLastModified(), //
-                    fessConfig.getIndexFieldTimestamp(), //
-                    fessConfig.getIndexFieldMimetype(), //
-                    fessConfig.getIndexFieldFiletype(), //
-                    fessConfig.getIndexFieldFilename(), //
-                    fessConfig.getIndexFieldCreated(), //
-                    fessConfig.getIndexFieldTitle(), //
-                    fessConfig.getIndexFieldDigest(), //
-                    fessConfig.getIndexFieldUrl()));
-        }
-        if (notAnalyzedFieldSet == null) {
-            setNotAnalyzedFields(fessConfig.getQueryAdditionalNotAnalyzedFields(//
-                    fessConfig.getIndexFieldAnchor(), //
-                    fessConfig.getIndexFieldBoost(), //
-                    fessConfig.getIndexFieldClickCount(), //
-                    fessConfig.getIndexFieldConfigId(), //
-                    fessConfig.getIndexFieldContentLength(), //
-                    fessConfig.getIndexFieldCreated(), //
-                    fessConfig.getIndexFieldDocId(), //
-                    fessConfig.getIndexFieldExpires(), //
-                    fessConfig.getIndexFieldFavoriteCount(), //
-                    fessConfig.getIndexFieldFiletype(), //
-                    fessConfig.getIndexFieldFilename(), //
-                    fessConfig.getIndexFieldHasCache(), //
-                    fessConfig.getIndexFieldHost(), //
-                    fessConfig.getIndexFieldId(), //
-                    fessConfig.getIndexFieldLabel(), //
-                    fessConfig.getIndexFieldLang(), //
-                    fessConfig.getIndexFieldLastModified(), //
-                    fessConfig.getIndexFieldMimetype(), //
-                    fessConfig.getIndexFieldParentId(), //
-                    fessConfig.getIndexFieldPrimaryTerm(), //
-                    fessConfig.getIndexFieldRole(), //
-                    fessConfig.getIndexFieldSegment(), //
-                    fessConfig.getIndexFieldSeqNo(), //
-                    fessConfig.getIndexFieldSite(), //
-                    fessConfig.getIndexFieldTimestamp(), //
-                    fessConfig.getIndexFieldUrl(), //
-                    fessConfig.getIndexFieldVersion()));
-        }
-        split(fessConfig.getQueryAdditionalAnalyzedFields(), ",")
-                .of(stream -> stream.map(String::trim).filter(StringUtil::isNotBlank).forEach(s -> notAnalyzedFieldSet.remove(s)));
-        split(fessConfig.getQueryAdditionalDefaultFields(), ",").of(stream -> stream.filter(StringUtil::isNotBlank).map(s -> {
-            final Pair<String, Float> pair = new Pair<>();
-            final String[] values = s.split(":");
-            if (values.length == 1) {
-                pair.setFirst(values[0].trim());
-                pair.setSecond(1.0f);
-            } else if (values.length > 1) {
-                pair.setFirst(values[0]);
-                pair.setSecond(Float.parseFloat(values[1]));
-            } else {
-                return null;
-            }
-            return pair;
-        }).forEach(additionalDefaultList::add));
-    }
-
     public QueryContext build(final SearchRequestType searchRequestType, final String query, final Consumer<QueryContext> context) {
         String q;
         if (additionalQuery != null && StringUtil.isNotBlank(query)) {
@@ -406,7 +150,7 @@ public class QueryHelper {
     public void buildBaseQuery(final QueryContext queryContext, final Consumer<QueryContext> context) {
         try {
             final Query query = getQueryParser().parse(queryContext.getQueryString());
-            final QueryBuilder queryBuilder = convertQuery(queryContext, query, 1.0f);
+            final QueryBuilder queryBuilder = ComponentUtil.getQueryProcessor().execute(queryContext, query, 1.0f);
             if (queryBuilder != null) {
                 queryContext.setQueryBuilder(queryBuilder);
             } else {
@@ -424,383 +168,6 @@ public class QueryHelper {
         return ComponentUtil.getQueryParser();
     }
 
-    protected QueryBuilder convertQuery(final QueryContext context, final Query query, final float boost) {
-        if (query instanceof final TermQuery termQuery) {
-            return convertTermQuery(context, termQuery, boost);
-        }
-        if (query instanceof final TermRangeQuery termRangeQuery) {
-            return convertTermRangeQuery(context, termRangeQuery, boost);
-        }
-        if (query instanceof final PhraseQuery phraseQuery) {
-            return convertPhraseQuery(context, phraseQuery, boost);
-        }
-        if (query instanceof final FuzzyQuery fuzzyQuery) {
-            return convertFuzzyQuery(context, fuzzyQuery, boost);
-        }
-        if (query instanceof final PrefixQuery prefixQuery) {
-            return convertPrefixQuery(context, prefixQuery, boost);
-        }
-        if (query instanceof final WildcardQuery wildcardQuery) {
-            return convertWildcardQuery(context, wildcardQuery, boost);
-        }
-        if (query instanceof final BooleanQuery booleanQuery) {
-            return convertBooleanQuery(context, booleanQuery, boost);
-        }
-        if (query instanceof MatchAllDocsQuery) {
-            return QueryBuilders.matchAllQuery();
-        }
-        if (query instanceof final BoostQuery boostQuery) {
-            return convertQuery(context, boostQuery.getQuery(), boostQuery.getBoost());
-        }
-        throw new InvalidQueryException(messages -> messages.addErrorsInvalidQueryUnknown(UserMessages.GLOBAL_PROPERTY_KEY),
-                "Unknown q: " + query.getClass() + " => " + query);
-    }
-
-    protected QueryBuilder convertBooleanQuery(final QueryContext context, final BooleanQuery booleanQuery, final float boost) {
-        final BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
-        for (final BooleanClause clause : booleanQuery.clauses()) {
-            final QueryBuilder queryBuilder = convertQuery(context, clause.getQuery(), boost);
-            if (queryBuilder != null) {
-                switch (clause.getOccur()) {
-                case MUST:
-                    boolQuery.must(queryBuilder);
-                    break;
-                case SHOULD:
-                    boolQuery.should(queryBuilder);
-                    break;
-                case MUST_NOT:
-                    boolQuery.mustNot(queryBuilder);
-                    break;
-                default:
-                    break;
-                }
-            }
-        }
-        if (boolQuery.hasClauses()) {
-            return boolQuery;
-        }
-        return null;
-    }
-
-    protected String toLowercaseWildcard(final String value) {
-        if (lowercaseWildcard) {
-            return value.toLowerCase(Locale.ROOT);
-        }
-        return value;
-    }
-
-    protected String getSearchField(final QueryContext context, final String field) {
-        if (Constants.DEFAULT_FIELD.equals(field) && context.getDefaultField() != null) {
-            return context.getDefaultField();
-        }
-        return field;
-    }
-
-    protected QueryBuilder convertWildcardQuery(final QueryContext context, final WildcardQuery wildcardQuery, final float boost) {
-        final String field = getSearchField(context, wildcardQuery.getField());
-        if (Constants.DEFAULT_FIELD.equals(field)) {
-            context.addFieldLog(field, wildcardQuery.getTerm().text());
-            return buildDefaultQueryBuilder(
-                    (f, b) -> QueryBuilders.wildcardQuery(f, toLowercaseWildcard(wildcardQuery.getTerm().text())).boost(b * boost));
-        }
-        if (isSearchField(field)) {
-            context.addFieldLog(field, wildcardQuery.getTerm().text());
-            return QueryBuilders.wildcardQuery(field, toLowercaseWildcard(wildcardQuery.getTerm().text())).boost(boost);
-        }
-        final String query = wildcardQuery.getTerm().toString();
-        final String origQuery = toLowercaseWildcard(query);
-        context.addFieldLog(Constants.DEFAULT_FIELD, query);
-        context.addHighlightedQuery(origQuery);
-        return buildDefaultQueryBuilder((f, b) -> QueryBuilders.wildcardQuery(f, origQuery).boost(b * boost));
-    }
-
-    protected QueryBuilder convertPrefixQuery(final QueryContext context, final PrefixQuery prefixQuery, final float boost) {
-        final FessConfig fessConfig = ComponentUtil.getFessConfig();
-        final String field = getSearchField(context, prefixQuery.getField());
-        if (Constants.DEFAULT_FIELD.equals(field)) {
-            context.addFieldLog(field, prefixQuery.getPrefix().text());
-            return buildDefaultQueryBuilder(
-                    (f, b) -> QueryBuilders.matchPhrasePrefixQuery(f, toLowercaseWildcard(prefixQuery.getPrefix().text())).boost(b * boost)
-                            .maxExpansions(fessConfig.getQueryPrefixExpansionsAsInteger()).slop(fessConfig.getQueryPrefixSlopAsInteger()));
-        }
-        if (!isSearchField(field)) {
-            final String query = prefixQuery.getPrefix().toString();
-            final String origQuery = toLowercaseWildcard(query);
-            context.addFieldLog(Constants.DEFAULT_FIELD, query);
-            context.addHighlightedQuery(origQuery);
-            return buildDefaultQueryBuilder((f, b) -> QueryBuilders.matchPhrasePrefixQuery(f, origQuery).boost(b * boost)
-                    .maxExpansions(fessConfig.getQueryPrefixExpansionsAsInteger()).slop(fessConfig.getQueryPrefixSlopAsInteger()));
-        }
-        context.addFieldLog(field, prefixQuery.getPrefix().text());
-        if (notAnalyzedFieldSet.contains(field)) {
-            return QueryBuilders.prefixQuery(field, toLowercaseWildcard(prefixQuery.getPrefix().text())).boost(boost);
-        }
-        return QueryBuilders.matchPhrasePrefixQuery(field, toLowercaseWildcard(prefixQuery.getPrefix().text())).boost(boost)
-                .maxExpansions(fessConfig.getQueryPrefixExpansionsAsInteger()).slop(fessConfig.getQueryPrefixSlopAsInteger());
-    }
-
-    protected QueryBuilder convertFuzzyQuery(final QueryContext context, final FuzzyQuery fuzzyQuery, final float boost) {
-        final FessConfig fessConfig = ComponentUtil.getFessConfig();
-        final Term term = fuzzyQuery.getTerm();
-        final String field = getSearchField(context, term.field());
-        // TODO fuzzy value
-        if (Constants.DEFAULT_FIELD.equals(field)) {
-            context.addFieldLog(field, term.text());
-            return buildDefaultQueryBuilder(
-                    (f, b) -> QueryBuilders.fuzzyQuery(f, term.text()).fuzziness(Fuzziness.fromEdits(fuzzyQuery.getMaxEdits()))
-                            .boost(b * boost).maxExpansions(fessConfig.getQueryFuzzyExpansionsAsInteger())
-                            .prefixLength(fessConfig.getQueryFuzzyPrefixLengthAsInteger())
-                            .transpositions(Constants.TRUE.equalsIgnoreCase(fessConfig.getQueryFuzzyTranspositions())));
-        }
-        if (isSearchField(field)) {
-            context.addFieldLog(field, term.text());
-            return QueryBuilders.fuzzyQuery(field, term.text()).boost(boost).fuzziness(Fuzziness.fromEdits(fuzzyQuery.getMaxEdits()))
-                    .maxExpansions(fessConfig.getQueryFuzzyExpansionsAsInteger())
-                    .prefixLength(fessConfig.getQueryFuzzyPrefixLengthAsInteger())
-                    .transpositions(Constants.TRUE.equalsIgnoreCase(fessConfig.getQueryFuzzyTranspositions()));
-        }
-        final String origQuery = fuzzyQuery.toString();
-        context.addFieldLog(Constants.DEFAULT_FIELD, origQuery);
-        context.addHighlightedQuery(origQuery);
-        return buildDefaultQueryBuilder((f, b) -> QueryBuilders.fuzzyQuery(f, origQuery)
-                .fuzziness(Fuzziness.fromEdits(fuzzyQuery.getMaxEdits())).boost(b * boost)
-                .maxExpansions(fessConfig.getQueryFuzzyExpansionsAsInteger()).prefixLength(fessConfig.getQueryFuzzyPrefixLengthAsInteger())
-                .transpositions(Constants.TRUE.equalsIgnoreCase(fessConfig.getQueryFuzzyTranspositions())));
-    }
-
-    protected QueryBuilder convertTermRangeQuery(final QueryContext context, final TermRangeQuery termRangeQuery, final float boost) {
-        final String field = getSearchField(context, termRangeQuery.getField());
-        if (!isSearchField(field)) {
-            final String origQuery = termRangeQuery.toString();
-            context.addFieldLog(Constants.DEFAULT_FIELD, origQuery);
-            context.addHighlightedQuery(origQuery);
-            return buildDefaultQueryBuilder((f, b) -> QueryBuilders.matchPhraseQuery(f, origQuery).boost(b));
-        }
-        context.addFieldLog(field, termRangeQuery.toString(field));
-        final RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery(field);
-        final BytesRef min = termRangeQuery.getLowerTerm();
-        if (min != null) {
-            if (termRangeQuery.includesLower()) {
-                rangeQuery.gte(min.utf8ToString());
-            } else {
-                rangeQuery.gt(min.utf8ToString());
-            }
-        }
-        final BytesRef max = termRangeQuery.getUpperTerm();
-        if (max != null) {
-            if (termRangeQuery.includesUpper()) {
-                rangeQuery.lte(max.utf8ToString());
-            } else {
-                rangeQuery.lt(max.utf8ToString());
-            }
-        }
-        rangeQuery.boost(boost);
-        return rangeQuery;
-    }
-
-    protected QueryBuilder buildMatchPhraseQuery(final String f, final String text) {
-        final FessConfig fessConfig = ComponentUtil.getFessConfig();
-        if (text == null || text.length() != 1
-                || (!fessConfig.getIndexFieldTitle().equals(f) && !fessConfig.getIndexFieldContent().equals(f))) {
-            return QueryBuilders.matchPhraseQuery(f, text);
-        }
-
-        final UnicodeBlock block = UnicodeBlock.of(text.codePointAt(0));
-        if (block == UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS //
-                || block == UnicodeBlock.HIRAGANA //
-                || block == UnicodeBlock.KATAKANA //
-                || block == UnicodeBlock.HANGUL_SYLLABLES //
-        ) {
-            return QueryBuilders.prefixQuery(f, text);
-        }
-        return QueryBuilders.matchPhraseQuery(f, text);
-    }
-
-    protected QueryBuilder convertTermQuery(final QueryContext context, final TermQuery termQuery, final float boost) {
-        final String field = getSearchField(context, termQuery.getTerm().field());
-        final String text = termQuery.getTerm().text();
-        final FessConfig fessConfig = ComponentUtil.getFessConfig();
-        if (fessConfig.getQueryReplaceTermWithPrefixQueryAsBoolean() && text.length() > 1 && text.endsWith("*")) {
-            return convertPrefixQuery(context, new PrefixQuery(new Term(field, text.substring(0, text.length() - 1))), boost);
-        }
-        if (Constants.DEFAULT_FIELD.equals(field)) {
-            context.addFieldLog(field, text);
-            context.addHighlightedQuery(text);
-            return buildDefaultTermQueryBuilder(boost, text);
-        }
-        if ("sort".equals(field)) {
-            split(text, ",").of(stream -> stream.filter(StringUtil::isNotBlank).forEach(t -> {
-                final String[] values = t.split("\\.");
-                if (values.length > 2) {
-                    throw new InvalidQueryException(
-                            messages -> messages.addErrorsInvalidQuerySortValue(UserMessages.GLOBAL_PROPERTY_KEY, text),
-                            "Invalid sort field: " + termQuery);
-                }
-                final String sortField = values[0];
-                if (!isSortField(sortField)) {
-                    throw new InvalidQueryException(
-                            messages -> messages.addErrorsInvalidQueryUnsupportedSortField(UserMessages.GLOBAL_PROPERTY_KEY, sortField),
-                            "Unsupported sort field: " + termQuery);
-                }
-                SortOrder sortOrder;
-                if (values.length == 2) {
-                    sortOrder = SortOrder.DESC.toString().equalsIgnoreCase(values[1]) ? SortOrder.DESC : SortOrder.ASC;
-                    if (sortOrder == null) {
-                        throw new InvalidQueryException(
-                                messages -> messages.addErrorsInvalidQueryUnsupportedSortOrder(UserMessages.GLOBAL_PROPERTY_KEY, values[1]),
-                                "Invalid sort order: " + termQuery);
-                    }
-                } else {
-                    sortOrder = SortOrder.ASC;
-                }
-                context.addSorts(createFieldSortBuilder(sortField, sortOrder));
-            }));
-            return null;
-        }
-        if (INURL_FIELD.equals(field) || (StringUtil.equals(field, context.getDefaultField())
-                && fessConfig.getIndexFieldUrl().equals(context.getDefaultField()))) {
-            return QueryBuilders.wildcardQuery(fessConfig.getIndexFieldUrl(), "*" + text + "*").boost(boost);
-        }
-        if (SITE_FIELD.equals(field)) {
-            return convertSiteQuery(context, text, boost);
-        }
-        if (!isSearchField(field)) {
-            final String origQuery = termQuery.toString();
-            context.addFieldLog(Constants.DEFAULT_FIELD, origQuery);
-            context.addHighlightedQuery(origQuery);
-            return buildDefaultQueryBuilder((f, b) -> buildMatchPhraseQuery(f, origQuery).boost(b * boost));
-        }
-        context.addFieldLog(field, text);
-        context.addHighlightedQuery(text);
-        if (notAnalyzedFieldSet.contains(field)) {
-            return QueryBuilders.termQuery(field, text).boost(boost);
-        }
-        return buildMatchPhraseQuery(field, text).boost(boost);
-    }
-
-    protected QueryBuilder convertSiteQuery(final QueryContext context, final String text, final float boost) {
-        return QueryBuilders.prefixQuery(ComponentUtil.getFessConfig().getIndexFieldSite(), text).boost(boost);
-    }
-
-    protected QueryBuilder convertPhraseQuery(final QueryContext context, final PhraseQuery query, final float boost) {
-        final Term[] terms = query.getTerms();
-        if (terms.length == 0) {
-            throw new InvalidQueryException(messages -> messages.addErrorsInvalidQueryUnknown(UserMessages.GLOBAL_PROPERTY_KEY),
-                    "Unknown phrase query: " + query);
-        }
-        final String field = terms[0].field();
-        final String[] texts = stream(terms).get(stream -> stream.map(Term::text).toArray(n -> new String[n]));
-        final String text = String.join(" ", texts);
-        context.addFieldLog(field, text);
-        stream(texts).of(stream -> stream.forEach(t -> context.addHighlightedQuery(t)));
-        return buildDefaultQueryBuilder((f, b) -> buildMatchPhraseQuery(f, text).boost(b * boost));
-    }
-
-    protected boolean isSearchField(final String field) {
-        for (final String searchField : searchFields) {
-            if (searchField.equals(field)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    protected QueryBuilder buildDefaultTermQueryBuilder(final float boost, final String text) {
-        final BoolQueryBuilder boolQuery = buildDefaultQueryBuilder((f, b) -> buildMatchPhraseQuery(f, text).boost(b * boost));
-        final FessConfig fessConfig = ComponentUtil.getFessConfig();
-        final Integer fuzzyMinLength = fessConfig.getQueryBoostFuzzyMinLengthAsInteger();
-        if (fuzzyMinLength >= 0 && text.length() >= fuzzyMinLength) {
-            boolQuery.should(QueryBuilders.fuzzyQuery(fessConfig.getIndexFieldTitle(), text)
-                    .boost(fessConfig.getQueryBoostFuzzyTitleAsDecimal().floatValue())
-                    .prefixLength(fessConfig.getQueryBoostFuzzyTitlePrefixLengthAsInteger())
-                    .transpositions(Constants.TRUE.equalsIgnoreCase(fessConfig.getQueryBoostFuzzyTitleTranspositions()))
-                    .fuzziness(Fuzziness.build(fessConfig.getQueryBoostFuzzyTitleFuzziness()))
-                    .maxExpansions(fessConfig.getQueryBoostFuzzyTitleExpansionsAsInteger()));
-            boolQuery.should(QueryBuilders.fuzzyQuery(fessConfig.getIndexFieldContent(), text)
-                    .prefixLength(fessConfig.getQueryBoostFuzzyContentPrefixLengthAsInteger())
-                    .transpositions(Constants.TRUE.equalsIgnoreCase(fessConfig.getQueryBoostFuzzyContentTranspositions()))
-                    .boost(fessConfig.getQueryBoostFuzzyContentAsDecimal().floatValue())
-                    .fuzziness(Fuzziness.build(fessConfig.getQueryBoostFuzzyContentFuzziness()))
-                    .maxExpansions(fessConfig.getQueryBoostFuzzyContentExpansionsAsInteger()));
-        }
-        return boolQuery;
-    }
-
-    protected BoolQueryBuilder buildDefaultQueryBuilder(final DefaultQueryBuilderFunction builder) {
-        final BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
-        final FessConfig fessConfig = ComponentUtil.getFessConfig();
-        boolQuery.should(builder.apply(fessConfig.getIndexFieldTitle(), fessConfig.getQueryBoostTitleAsDecimal().floatValue()));
-        boolQuery.should(builder.apply(fessConfig.getIndexFieldContent(), fessConfig.getQueryBoostContentAsDecimal().floatValue()));
-        final float importantContentBoost = fessConfig.getQueryBoostImportantContentAsDecimal().floatValue();
-        if (importantContentBoost >= 0.0f) {
-            boolQuery.should(builder.apply(fessConfig.getIndexFieldImportantContent(), importantContentBoost));
-        }
-        final float importantContantLangBoost = fessConfig.getQueryBoostImportantContentLangAsDecimal().floatValue();
-        getQueryLanguages().ifPresent(langs -> stream(langs).of(stream -> stream.forEach(lang -> {
-            boolQuery.should(
-                    builder.apply(fessConfig.getIndexFieldTitle() + "_" + lang, fessConfig.getQueryBoostTitleLangAsDecimal().floatValue()));
-            boolQuery.should(builder.apply(fessConfig.getIndexFieldContent() + "_" + lang,
-                    fessConfig.getQueryBoostContentLangAsDecimal().floatValue()));
-            if (importantContantLangBoost >= 0.0f) {
-                boolQuery.should(builder.apply(fessConfig.getIndexFieldImportantContent() + "_" + lang, importantContantLangBoost));
-            }
-        })));
-        additionalDefaultList.stream().forEach(f -> {
-            final QueryBuilder query = builder.apply(f.getFirst(), f.getSecond());
-            boolQuery.should(query);
-        });
-        return boolQuery;
-    }
-
-    interface DefaultQueryBuilderFunction {
-        QueryBuilder apply(String field, float boost);
-    }
-
-    protected OptionalThing<String[]> getQueryLanguages() {
-        return LaRequestUtil.getOptionalRequest().map(request -> ComponentUtil.getFessConfig().getQueryLanguages(request.getLocales(),
-                (String[]) request.getAttribute(Constants.REQUEST_LANGUAGES)));
-    }
-
-    protected boolean isSortField(final String field) {
-        for (final String f : sortFields) {
-            if (f.equals(field)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    public boolean isFacetField(final String field) {
-        if (StringUtil.isBlank(field)) {
-            return false;
-        }
-        boolean flag = false;
-        for (final String f : facetFields) {
-            if (field.equals(f)) {
-                flag = true;
-            }
-        }
-        return flag;
-    }
-
-    public boolean isFacetSortValue(final String sort) {
-        return "count".equals(sort) || "index".equals(sort);
-    }
-
-    public void setApiResponseFields(final String[] fields) {
-        apiResponseFieldSet = new HashSet<>();
-        Collections.addAll(apiResponseFieldSet, fields);
-    }
-
-    public void setNotAnalyzedFields(final String[] fields) {
-        notAnalyzedFieldSet = new HashSet<>();
-        Collections.addAll(notAnalyzedFieldSet, fields);
-    }
-
-    public boolean isApiResponseField(final String field) {
-        return apiResponseFieldSet.contains(field);
-    }
-
     public void processSearchPreference(final SearchRequestBuilder searchRequestBuilder, final OptionalThing<FessUserBean> userBean,
             final String query) {
         userBean.map(user -> {
@@ -850,82 +217,6 @@ public class QueryHelper {
         return null;
     }
 
-    /**
-     * @return the responseFields
-     */
-    public String[] getResponseFields() {
-        return responseFields;
-    }
-
-    /**
-     * @param responseFields the responseFields to set
-     */
-    public void setResponseFields(final String[] responseFields) {
-        this.responseFields = responseFields;
-    }
-
-    public String[] getScrollResponseFields() {
-        return scrollResponseFields;
-    }
-
-    public void setScrollResponseFields(final String[] scrollResponseFields) {
-        this.scrollResponseFields = scrollResponseFields;
-    }
-
-    public String[] getCacheResponseFields() {
-        return cacheResponseFields;
-    }
-
-    public void setCacheResponseFields(final String[] cacheResponseFields) {
-        this.cacheResponseFields = cacheResponseFields;
-    }
-
-    /**
-     * @return the highlightedFields
-     */
-    public String[] getHighlightedFields() {
-        return highlightedFields;
-    }
-
-    /**
-     * @param highlightedFields the highlightedFields to set
-     */
-    public void setHighlightedFields(final String[] highlightedFields) {
-        this.highlightedFields = highlightedFields;
-    }
-
-    public void highlightedFields(final Consumer<Stream<String>> stream) {
-        stream(highlightedFields).of(stream);
-    }
-
-    /**
-     * @return the supportedFields
-     */
-    public String[] getSearchFields() {
-        return searchFields;
-    }
-
-    /**
-     * @param supportedFields the supportedFields to set
-     */
-    public void setSearchFields(final String[] supportedFields) {
-        searchFields = supportedFields;
-    }
-
-    /**
-     * @return the facetFields
-     */
-    public String[] getFacetFields() {
-        return facetFields;
-    }
-
-    /**
-     * @param facetFields the facetFields to set
-     */
-    public void setFacetFields(final String[] facetFields) {
-        this.facetFields = facetFields;
-    }
-
     /**
      * @return the sortPrefix
      */
@@ -940,24 +231,6 @@ public class QueryHelper {
         this.sortPrefix = sortPrefix;
     }
 
-    /**
-     * @return the sortFields
-     */
-    public String[] getSortFields() {
-        return sortFields;
-    }
-
-    /**
-     * @param sortFields the sortFields to set
-     */
-    public void setSortFields(final String[] sortFields) {
-        this.sortFields = sortFields;
-    }
-
-    public void addHighlightField(final String field) {
-        highlightFieldSet.add(field);
-    }
-
     /**
      * @return the additionalQuery
      */
@@ -982,7 +255,7 @@ public class QueryHelper {
     }
 
     protected SortBuilder<?> createFieldSortBuilder(final String field, final SortOrder order) {
-        if (SCORE_FIELD.equals(field) || ES_SCORE_FIELD.equals(field)) {
+        if (QueryFieldConfig.SCORE_FIELD.equals(field) || QueryFieldConfig.ES_SCORE_FIELD.equals(field)) {
             return SortBuilders.scoreSort().order(order);
         }
         return SortBuilders.fieldSort(field).order(order);
@@ -1016,10 +289,6 @@ public class QueryHelper {
         return UUID.randomUUID().toString().replace("-", StringUtil.EMPTY);
     }
 
-    public void setLowercaseWildcard(final boolean lowercaseWildcard) {
-        this.lowercaseWildcard = lowercaseWildcard;
-    }
-
     public void addBoostFunction(final ScoreFunctionBuilder<?> scoreFunction) {
         boostFunctionList.add(new FilterFunctionBuilder(scoreFunction));
     }

+ 5 - 2
src/main/java/org/codelibs/fess/helper/SearchHelper.java

@@ -43,6 +43,7 @@ import org.codelibs.fess.es.client.SearchEngineClientException;
 import org.codelibs.fess.exception.InvalidQueryException;
 import org.codelibs.fess.mylasta.action.FessUserBean;
 import org.codelibs.fess.mylasta.direction.FessConfig;
+import org.codelibs.fess.query.QueryFieldConfig;
 import org.codelibs.fess.util.BooleanFunction;
 import org.codelibs.fess.util.ComponentUtil;
 import org.codelibs.fess.util.QueryResponseList;
@@ -159,12 +160,13 @@ public class SearchHelper {
             final OptionalThing<FessUserBean> userBean) {
         final FessConfig fessConfig = ComponentUtil.getFessConfig();
         final QueryHelper queryHelper = ComponentUtil.getQueryHelper();
+        final QueryFieldConfig queryFieldConfig = ComponentUtil.getQueryFieldConfig();
         return ComponentUtil.getSearchEngineClient().search(fessConfig.getIndexDocumentSearchIndex(), searchRequestBuilder -> {
             queryHelper.processSearchPreference(searchRequestBuilder, userBean, query);
             return SearchConditionBuilder.builder(searchRequestBuilder).query(query).offset(params.getStartPosition())
                     .size(params.getPageSize()).facetInfo(params.getFacetInfo()).geoInfo(params.getGeoInfo())
                     .highlightInfo(params.getHighlightInfo()).similarDocHash(params.getSimilarDocHash())
-                    .responseFields(queryHelper.getResponseFields()).searchRequestType(params.getType())
+                    .responseFields(queryFieldConfig.getResponseFields()).searchRequestType(params.getType())
                     .trackTotalHits(params.getTrackTotalHits()).build();
         }, (searchRequestBuilder, execTime, searchResponse) -> {
             searchResponse.ifPresent(r -> {
@@ -196,9 +198,10 @@ public class SearchHelper {
         return ComponentUtil.getSearchEngineClient().<Map<String, Object>> scrollSearch(fessConfig.getIndexDocumentSearchIndex(),
                 searchRequestBuilder -> {
                     final QueryHelper queryHelper = ComponentUtil.getQueryHelper();
+                    final QueryFieldConfig queryFieldConfig = ComponentUtil.getQueryFieldConfig();
                     queryHelper.processSearchPreference(searchRequestBuilder, userBean, query);
                     return SearchConditionBuilder.builder(searchRequestBuilder).scroll().query(query).size(pageSize)
-                            .responseFields(queryHelper.getScrollResponseFields()).searchRequestType(params.getType()).build();
+                            .responseFields(queryFieldConfig.getScrollResponseFields()).searchRequestType(params.getType()).build();
                 }, (searchResponse, hit) -> {
                     final Map<String, Object> docMap = new HashMap<>();
                     final Map<String, Object> source = hit.getSourceAsMap();

+ 69 - 0
src/main/java/org/codelibs/fess/query/BooleanQueryCommand.java

@@ -0,0 +1,69 @@
+/*
+ * Copyright 2012-2022 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.query;
+
+import org.apache.lucene.search.BooleanClause;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.Query;
+import org.codelibs.fess.entity.QueryContext;
+import org.codelibs.fess.exception.InvalidQueryException;
+import org.lastaflute.core.message.UserMessages;
+import org.opensearch.index.query.BoolQueryBuilder;
+import org.opensearch.index.query.QueryBuilder;
+import org.opensearch.index.query.QueryBuilders;
+
+public class BooleanQueryCommand extends QueryCommand {
+
+    @Override
+    protected String getQueryClassName() {
+        return BooleanQuery.class.getSimpleName();
+    }
+
+    @Override
+    public QueryBuilder execute(final QueryContext context, final Query query, final float boost) {
+        if (query instanceof final BooleanQuery booleanQuery) {
+            return convertBooleanQuery(context, booleanQuery, boost);
+        }
+        throw new InvalidQueryException(messages -> messages.addErrorsInvalidQueryUnknown(UserMessages.GLOBAL_PROPERTY_KEY),
+                "Unknown q: " + query.getClass() + " => " + query);
+    }
+
+    protected QueryBuilder convertBooleanQuery(final QueryContext context, final BooleanQuery booleanQuery, final float boost) {
+        final BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
+        for (final BooleanClause clause : booleanQuery.clauses()) {
+            final QueryBuilder queryBuilder = getQueryProcessor().execute(context, clause.getQuery(), boost);
+            if (queryBuilder != null) {
+                switch (clause.getOccur()) {
+                case MUST:
+                    boolQuery.must(queryBuilder);
+                    break;
+                case SHOULD:
+                    boolQuery.should(queryBuilder);
+                    break;
+                case MUST_NOT:
+                    boolQuery.mustNot(queryBuilder);
+                    break;
+                default:
+                    break;
+                }
+            }
+        }
+        if (boolQuery.hasClauses()) {
+            return boolQuery;
+        }
+        return null;
+    }
+}

+ 41 - 0
src/main/java/org/codelibs/fess/query/BoostQueryCommand.java

@@ -0,0 +1,41 @@
+/*
+ * Copyright 2012-2022 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.query;
+
+import org.apache.lucene.search.BoostQuery;
+import org.apache.lucene.search.Query;
+import org.codelibs.fess.entity.QueryContext;
+import org.codelibs.fess.exception.InvalidQueryException;
+import org.lastaflute.core.message.UserMessages;
+import org.opensearch.index.query.QueryBuilder;
+
+public class BoostQueryCommand extends QueryCommand {
+
+    @Override
+    protected String getQueryClassName() {
+        return BoostQuery.class.getSimpleName();
+    }
+
+    @Override
+    public QueryBuilder execute(final QueryContext context, final Query query, final float boost) {
+        if (query instanceof final BoostQuery boostQuery) {
+            return getQueryProcessor().execute(context, boostQuery.getQuery(), boostQuery.getBoost());
+        }
+        throw new InvalidQueryException(messages -> messages.addErrorsInvalidQueryUnknown(UserMessages.GLOBAL_PROPERTY_KEY),
+                "Unknown q: " + query.getClass() + " => " + query);
+    }
+
+}

+ 75 - 0
src/main/java/org/codelibs/fess/query/FuzzyQueryCommand.java

@@ -0,0 +1,75 @@
+/*
+ * Copyright 2012-2022 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.query;
+
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.FuzzyQuery;
+import org.apache.lucene.search.Query;
+import org.codelibs.fess.Constants;
+import org.codelibs.fess.entity.QueryContext;
+import org.codelibs.fess.exception.InvalidQueryException;
+import org.codelibs.fess.mylasta.direction.FessConfig;
+import org.codelibs.fess.util.ComponentUtil;
+import org.lastaflute.core.message.UserMessages;
+import org.opensearch.common.unit.Fuzziness;
+import org.opensearch.index.query.QueryBuilder;
+import org.opensearch.index.query.QueryBuilders;
+
+public class FuzzyQueryCommand extends QueryCommand {
+
+    @Override
+    protected String getQueryClassName() {
+        return FuzzyQuery.class.getSimpleName();
+    }
+
+    @Override
+    public QueryBuilder execute(final QueryContext context, final Query query, final float boost) {
+        if (query instanceof final FuzzyQuery fuzzyQuery) {
+            return convertFuzzyQuery(context, fuzzyQuery, boost);
+        }
+        throw new InvalidQueryException(messages -> messages.addErrorsInvalidQueryUnknown(UserMessages.GLOBAL_PROPERTY_KEY),
+                "Unknown q: " + query.getClass() + " => " + query);
+    }
+
+    protected QueryBuilder convertFuzzyQuery(final QueryContext context, final FuzzyQuery fuzzyQuery, final float boost) {
+        final FessConfig fessConfig = ComponentUtil.getFessConfig();
+        final Term term = fuzzyQuery.getTerm();
+        final String field = getSearchField(context, term.field());
+        // TODO fuzzy value
+        if (Constants.DEFAULT_FIELD.equals(field)) {
+            context.addFieldLog(field, term.text());
+            return buildDefaultQueryBuilder(
+                    (f, b) -> QueryBuilders.fuzzyQuery(f, term.text()).fuzziness(Fuzziness.fromEdits(fuzzyQuery.getMaxEdits()))
+                            .boost(b * boost).maxExpansions(fessConfig.getQueryFuzzyExpansionsAsInteger())
+                            .prefixLength(fessConfig.getQueryFuzzyPrefixLengthAsInteger())
+                            .transpositions(Constants.TRUE.equalsIgnoreCase(fessConfig.getQueryFuzzyTranspositions())));
+        }
+        if (isSearchField(field)) {
+            context.addFieldLog(field, term.text());
+            return QueryBuilders.fuzzyQuery(field, term.text()).boost(boost).fuzziness(Fuzziness.fromEdits(fuzzyQuery.getMaxEdits()))
+                    .maxExpansions(fessConfig.getQueryFuzzyExpansionsAsInteger())
+                    .prefixLength(fessConfig.getQueryFuzzyPrefixLengthAsInteger())
+                    .transpositions(Constants.TRUE.equalsIgnoreCase(fessConfig.getQueryFuzzyTranspositions()));
+        }
+        final String origQuery = fuzzyQuery.toString();
+        context.addFieldLog(Constants.DEFAULT_FIELD, origQuery);
+        context.addHighlightedQuery(origQuery);
+        return buildDefaultQueryBuilder((f, b) -> QueryBuilders.fuzzyQuery(f, origQuery)
+                .fuzziness(Fuzziness.fromEdits(fuzzyQuery.getMaxEdits())).boost(b * boost)
+                .maxExpansions(fessConfig.getQueryFuzzyExpansionsAsInteger()).prefixLength(fessConfig.getQueryFuzzyPrefixLengthAsInteger())
+                .transpositions(Constants.TRUE.equalsIgnoreCase(fessConfig.getQueryFuzzyTranspositions())));
+    }
+}

+ 36 - 0
src/main/java/org/codelibs/fess/query/MatchAllQueryCommand.java

@@ -0,0 +1,36 @@
+/*
+ * Copyright 2012-2022 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.query;
+
+import org.apache.lucene.search.MatchAllDocsQuery;
+import org.apache.lucene.search.Query;
+import org.codelibs.fess.entity.QueryContext;
+import org.opensearch.index.query.QueryBuilder;
+import org.opensearch.index.query.QueryBuilders;
+
+public class MatchAllQueryCommand extends QueryCommand {
+
+    @Override
+    protected String getQueryClassName() {
+        return MatchAllDocsQuery.class.getSimpleName();
+    }
+
+    @Override
+    public QueryBuilder execute(final QueryContext context, final Query query, final float boost) {
+        return QueryBuilders.matchAllQuery();
+    }
+
+}

+ 58 - 0
src/main/java/org/codelibs/fess/query/PhraseQueryCommand.java

@@ -0,0 +1,58 @@
+/*
+ * Copyright 2012-2022 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.query;
+
+import static org.codelibs.core.stream.StreamUtil.stream;
+
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.PhraseQuery;
+import org.apache.lucene.search.Query;
+import org.codelibs.fess.entity.QueryContext;
+import org.codelibs.fess.exception.InvalidQueryException;
+import org.lastaflute.core.message.UserMessages;
+import org.opensearch.index.query.QueryBuilder;
+
+public class PhraseQueryCommand extends QueryCommand {
+
+    @Override
+    protected String getQueryClassName() {
+        return PhraseQuery.class.getSimpleName();
+    }
+
+    @Override
+    public QueryBuilder execute(final QueryContext context, final Query query, final float boost) {
+        if (query instanceof final PhraseQuery phraseQuery) {
+            return convertPhraseQuery(context, phraseQuery, boost);
+        }
+        throw new InvalidQueryException(messages -> messages.addErrorsInvalidQueryUnknown(UserMessages.GLOBAL_PROPERTY_KEY),
+                "Unknown q: " + query.getClass() + " => " + query);
+    }
+
+    protected QueryBuilder convertPhraseQuery(final QueryContext context, final PhraseQuery phraseQuery, final float boost) {
+        final Term[] terms = phraseQuery.getTerms();
+        if (terms.length == 0) {
+            throw new InvalidQueryException(messages -> messages.addErrorsInvalidQueryUnknown(UserMessages.GLOBAL_PROPERTY_KEY),
+                    "Unknown phrase query: " + phraseQuery);
+        }
+        final String field = terms[0].field();
+        final String[] texts = stream(terms).get(stream -> stream.map(Term::text).toArray(n -> new String[n]));
+        final String text = String.join(" ", texts);
+        context.addFieldLog(field, text);
+        stream(texts).of(stream -> stream.forEach(t -> context.addHighlightedQuery(t)));
+        return buildDefaultQueryBuilder((f, b) -> buildMatchPhraseQuery(f, text).boost(b * boost));
+    }
+
+}

+ 83 - 0
src/main/java/org/codelibs/fess/query/PrefixQueryCommand.java

@@ -0,0 +1,83 @@
+/*
+ * Copyright 2012-2022 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.query;
+
+import java.util.Locale;
+
+import org.apache.lucene.search.PrefixQuery;
+import org.apache.lucene.search.Query;
+import org.codelibs.fess.Constants;
+import org.codelibs.fess.entity.QueryContext;
+import org.codelibs.fess.exception.InvalidQueryException;
+import org.codelibs.fess.mylasta.direction.FessConfig;
+import org.codelibs.fess.util.ComponentUtil;
+import org.lastaflute.core.message.UserMessages;
+import org.opensearch.index.query.QueryBuilder;
+import org.opensearch.index.query.QueryBuilders;
+
+public class PrefixQueryCommand extends QueryCommand {
+    protected boolean lowercaseWildcard = true;
+
+    @Override
+    protected String getQueryClassName() {
+        return PrefixQuery.class.getSimpleName();
+    }
+
+    @Override
+    public QueryBuilder execute(final QueryContext context, final Query query, final float boost) {
+        if (query instanceof final PrefixQuery prefixQuery) {
+            return convertPrefixQuery(context, prefixQuery, boost);
+        }
+        throw new InvalidQueryException(messages -> messages.addErrorsInvalidQueryUnknown(UserMessages.GLOBAL_PROPERTY_KEY),
+                "Unknown q: " + query.getClass() + " => " + query);
+    }
+
+    protected QueryBuilder convertPrefixQuery(final QueryContext context, final PrefixQuery prefixQuery, final float boost) {
+        final FessConfig fessConfig = ComponentUtil.getFessConfig();
+        final String field = getSearchField(context, prefixQuery.getField());
+        if (Constants.DEFAULT_FIELD.equals(field)) {
+            context.addFieldLog(field, prefixQuery.getPrefix().text());
+            return buildDefaultQueryBuilder(
+                    (f, b) -> QueryBuilders.matchPhrasePrefixQuery(f, toLowercaseWildcard(prefixQuery.getPrefix().text())).boost(b * boost)
+                            .maxExpansions(fessConfig.getQueryPrefixExpansionsAsInteger()).slop(fessConfig.getQueryPrefixSlopAsInteger()));
+        }
+        if (!isSearchField(field)) {
+            final String query = prefixQuery.getPrefix().toString();
+            final String origQuery = toLowercaseWildcard(query);
+            context.addFieldLog(Constants.DEFAULT_FIELD, query);
+            context.addHighlightedQuery(origQuery);
+            return buildDefaultQueryBuilder((f, b) -> QueryBuilders.matchPhrasePrefixQuery(f, origQuery).boost(b * boost)
+                    .maxExpansions(fessConfig.getQueryPrefixExpansionsAsInteger()).slop(fessConfig.getQueryPrefixSlopAsInteger()));
+        }
+        context.addFieldLog(field, prefixQuery.getPrefix().text());
+        if (getQueryFieldConfig().notAnalyzedFieldSet.contains(field)) {
+            return QueryBuilders.prefixQuery(field, toLowercaseWildcard(prefixQuery.getPrefix().text())).boost(boost);
+        }
+        return QueryBuilders.matchPhrasePrefixQuery(field, toLowercaseWildcard(prefixQuery.getPrefix().text())).boost(boost)
+                .maxExpansions(fessConfig.getQueryPrefixExpansionsAsInteger()).slop(fessConfig.getQueryPrefixSlopAsInteger());
+    }
+
+    protected String toLowercaseWildcard(final String value) {
+        if (lowercaseWildcard) {
+            return value.toLowerCase(Locale.ROOT);
+        }
+        return value;
+    }
+
+    public void setLowercaseWildcard(boolean lowercaseWildcard) {
+        this.lowercaseWildcard = lowercaseWildcard;
+    }
+}

+ 126 - 0
src/main/java/org/codelibs/fess/query/QueryCommand.java

@@ -0,0 +1,126 @@
+/*
+ * Copyright 2012-2022 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.query;
+
+import static org.codelibs.core.stream.StreamUtil.stream;
+
+import java.lang.Character.UnicodeBlock;
+
+import org.apache.lucene.search.Query;
+import org.codelibs.fess.Constants;
+import org.codelibs.fess.entity.QueryContext;
+import org.codelibs.fess.mylasta.direction.FessConfig;
+import org.codelibs.fess.query.TermQueryCommand.DefaultQueryBuilderFunction;
+import org.codelibs.fess.util.ComponentUtil;
+import org.dbflute.optional.OptionalThing;
+import org.lastaflute.web.util.LaRequestUtil;
+import org.opensearch.index.query.BoolQueryBuilder;
+import org.opensearch.index.query.QueryBuilder;
+import org.opensearch.index.query.QueryBuilders;
+import org.opensearch.search.sort.SortBuilder;
+import org.opensearch.search.sort.SortBuilders;
+import org.opensearch.search.sort.SortOrder;
+
+public abstract class QueryCommand {
+
+    public abstract QueryBuilder execute(final QueryContext context, final Query query, final float boost);
+
+    protected abstract String getQueryClassName();
+
+    public void register() {
+        ComponentUtil.getQueryProcessor().add(getQueryClassName(), this);
+    }
+
+    protected QueryFieldConfig getQueryFieldConfig() {
+        return ComponentUtil.getQueryFieldConfig();
+    }
+
+    protected QueryProcessor getQueryProcessor() {
+        return ComponentUtil.getQueryProcessor();
+    }
+
+    protected SortBuilder<?> createFieldSortBuilder(final String field, final SortOrder order) {
+        if (QueryFieldConfig.SCORE_FIELD.equals(field) || QueryFieldConfig.ES_SCORE_FIELD.equals(field)) {
+            return SortBuilders.scoreSort().order(order);
+        }
+        return SortBuilders.fieldSort(field).order(order);
+    }
+
+    protected boolean isSearchField(final String field) {
+        for (final String searchField : getQueryFieldConfig().searchFields) {
+            if (searchField.equals(field)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    protected OptionalThing<String[]> getQueryLanguages() {
+        return LaRequestUtil.getOptionalRequest().map(request -> ComponentUtil.getFessConfig().getQueryLanguages(request.getLocales(),
+                (String[]) request.getAttribute(Constants.REQUEST_LANGUAGES)));
+    }
+
+    protected BoolQueryBuilder buildDefaultQueryBuilder(final DefaultQueryBuilderFunction builder) {
+        final BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
+        final FessConfig fessConfig = ComponentUtil.getFessConfig();
+        boolQuery.should(builder.apply(fessConfig.getIndexFieldTitle(), fessConfig.getQueryBoostTitleAsDecimal().floatValue()));
+        boolQuery.should(builder.apply(fessConfig.getIndexFieldContent(), fessConfig.getQueryBoostContentAsDecimal().floatValue()));
+        final float importantContentBoost = fessConfig.getQueryBoostImportantContentAsDecimal().floatValue();
+        if (importantContentBoost >= 0.0f) {
+            boolQuery.should(builder.apply(fessConfig.getIndexFieldImportantContent(), importantContentBoost));
+        }
+        final float importantContantLangBoost = fessConfig.getQueryBoostImportantContentLangAsDecimal().floatValue();
+        getQueryLanguages().ifPresent(langs -> stream(langs).of(stream -> stream.forEach(lang -> {
+            boolQuery.should(
+                    builder.apply(fessConfig.getIndexFieldTitle() + "_" + lang, fessConfig.getQueryBoostTitleLangAsDecimal().floatValue()));
+            boolQuery.should(builder.apply(fessConfig.getIndexFieldContent() + "_" + lang,
+                    fessConfig.getQueryBoostContentLangAsDecimal().floatValue()));
+            if (importantContantLangBoost >= 0.0f) {
+                boolQuery.should(builder.apply(fessConfig.getIndexFieldImportantContent() + "_" + lang, importantContantLangBoost));
+            }
+        })));
+        getQueryFieldConfig().additionalDefaultList.stream().forEach(f -> {
+            final QueryBuilder query = builder.apply(f.getFirst(), f.getSecond());
+            boolQuery.should(query);
+        });
+        return boolQuery;
+    }
+
+    protected QueryBuilder buildMatchPhraseQuery(final String f, final String text) {
+        final FessConfig fessConfig = ComponentUtil.getFessConfig();
+        if (text == null || text.length() != 1
+                || (!fessConfig.getIndexFieldTitle().equals(f) && !fessConfig.getIndexFieldContent().equals(f))) {
+            return QueryBuilders.matchPhraseQuery(f, text);
+        }
+
+        final UnicodeBlock block = UnicodeBlock.of(text.codePointAt(0));
+        if (block == UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS //
+                || block == UnicodeBlock.HIRAGANA //
+                || block == UnicodeBlock.KATAKANA //
+                || block == UnicodeBlock.HANGUL_SYLLABLES //
+        ) {
+            return QueryBuilders.prefixQuery(f, text);
+        }
+        return QueryBuilders.matchPhraseQuery(f, text);
+    }
+
+    protected String getSearchField(final QueryContext context, final String field) {
+        if (Constants.DEFAULT_FIELD.equals(field) && context.getDefaultField() != null) {
+            return context.getDefaultField();
+        }
+        return field;
+    }
+}

+ 403 - 0
src/main/java/org/codelibs/fess/query/QueryFieldConfig.java

@@ -0,0 +1,403 @@
+/*
+ * Copyright 2012-2022 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.query;
+
+import static org.codelibs.core.stream.StreamUtil.split;
+import static org.codelibs.core.stream.StreamUtil.stream;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+
+import javax.annotation.PostConstruct;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.codelibs.core.lang.StringUtil;
+import org.codelibs.core.misc.Pair;
+import org.codelibs.fess.mylasta.direction.FessConfig;
+import org.codelibs.fess.util.ComponentUtil;
+
+public class QueryFieldConfig {
+
+    private static final Logger logger = LogManager.getLogger(QueryFieldConfig.class);
+
+    public static final String SCORE_FIELD = "score";
+
+    public static final String ES_SCORE_FIELD = "_score";
+
+    public static final String SITE_FIELD = "site";
+
+    public static final String INURL_FIELD = "inurl";
+
+    protected static final String SCORE_SORT_VALUE = "score";
+
+    protected String[] responseFields;
+
+    protected String[] scrollResponseFields;
+
+    protected String[] cacheResponseFields;
+
+    protected String[] highlightedFields;
+
+    protected String[] searchFields;
+
+    protected String[] facetFields;
+
+    protected String[] sortFields;
+
+    protected Set<String> apiResponseFieldSet;
+
+    protected Set<String> notAnalyzedFieldSet;
+
+    protected List<Pair<String, Float>> additionalDefaultList = new ArrayList<>();
+
+    @PostConstruct
+    public void init() {
+        if (logger.isDebugEnabled()) {
+            logger.debug("Initialize {}", this.getClass().getSimpleName());
+        }
+        final FessConfig fessConfig = ComponentUtil.getFessConfig();
+        if (responseFields == null) {
+            responseFields = fessConfig.getQueryAdditionalResponseFields(//
+                    SCORE_FIELD, //
+                    fessConfig.getIndexFieldId(), //
+                    fessConfig.getIndexFieldDocId(), //
+                    fessConfig.getIndexFieldBoost(), //
+                    fessConfig.getIndexFieldContentLength(), //
+                    fessConfig.getIndexFieldHost(), //
+                    fessConfig.getIndexFieldSite(), //
+                    fessConfig.getIndexFieldLastModified(), //
+                    fessConfig.getIndexFieldTimestamp(), //
+                    fessConfig.getIndexFieldMimetype(), //
+                    fessConfig.getIndexFieldFiletype(), //
+                    fessConfig.getIndexFieldFilename(), //
+                    fessConfig.getIndexFieldCreated(), //
+                    fessConfig.getIndexFieldTitle(), //
+                    fessConfig.getIndexFieldDigest(), //
+                    fessConfig.getIndexFieldUrl(), //
+                    fessConfig.getIndexFieldThumbnail(), //
+                    fessConfig.getIndexFieldClickCount(), //
+                    fessConfig.getIndexFieldFavoriteCount(), //
+                    fessConfig.getIndexFieldConfigId(), //
+                    fessConfig.getIndexFieldLang(), //
+                    fessConfig.getIndexFieldHasCache());
+        }
+        if (scrollResponseFields == null) {
+            scrollResponseFields = fessConfig.getQueryAdditionalScrollResponseFields(//
+                    SCORE_FIELD, //
+                    fessConfig.getIndexFieldId(), //
+                    fessConfig.getIndexFieldDocId(), //
+                    fessConfig.getIndexFieldBoost(), //
+                    fessConfig.getIndexFieldContentLength(), //
+                    fessConfig.getIndexFieldHost(), //
+                    fessConfig.getIndexFieldSite(), //
+                    fessConfig.getIndexFieldLastModified(), //
+                    fessConfig.getIndexFieldTimestamp(), //
+                    fessConfig.getIndexFieldMimetype(), //
+                    fessConfig.getIndexFieldFiletype(), //
+                    fessConfig.getIndexFieldFilename(), //
+                    fessConfig.getIndexFieldCreated(), //
+                    fessConfig.getIndexFieldTitle(), //
+                    fessConfig.getIndexFieldDigest(), //
+                    fessConfig.getIndexFieldUrl(), //
+                    fessConfig.getIndexFieldThumbnail(), //
+                    fessConfig.getIndexFieldClickCount(), //
+                    fessConfig.getIndexFieldFavoriteCount(), //
+                    fessConfig.getIndexFieldConfigId(), //
+                    fessConfig.getIndexFieldLang(), //
+                    fessConfig.getIndexFieldHasCache());
+        }
+        if (cacheResponseFields == null) {
+            cacheResponseFields = fessConfig.getQueryAdditionalCacheResponseFields(//
+                    SCORE_FIELD, //
+                    fessConfig.getIndexFieldId(), //
+                    fessConfig.getIndexFieldDocId(), //
+                    fessConfig.getIndexFieldBoost(), //
+                    fessConfig.getIndexFieldContentLength(), //
+                    fessConfig.getIndexFieldHost(), //
+                    fessConfig.getIndexFieldSite(), //
+                    fessConfig.getIndexFieldLastModified(), //
+                    fessConfig.getIndexFieldTimestamp(), //
+                    fessConfig.getIndexFieldMimetype(), //
+                    fessConfig.getIndexFieldFiletype(), //
+                    fessConfig.getIndexFieldFilename(), //
+                    fessConfig.getIndexFieldCreated(), //
+                    fessConfig.getIndexFieldTitle(), //
+                    fessConfig.getIndexFieldDigest(), //
+                    fessConfig.getIndexFieldUrl(), //
+                    fessConfig.getIndexFieldClickCount(), //
+                    fessConfig.getIndexFieldFavoriteCount(), //
+                    fessConfig.getIndexFieldConfigId(), //
+                    fessConfig.getIndexFieldLang(), //
+                    fessConfig.getIndexFieldCache());
+        }
+        if (highlightedFields == null) {
+            highlightedFields = fessConfig.getQueryAdditionalHighlightedFields( //
+                    fessConfig.getIndexFieldContent());
+        }
+        if (searchFields == null) {
+            searchFields = fessConfig.getQueryAdditionalSearchFields(//
+                    INURL_FIELD, //
+                    fessConfig.getIndexFieldUrl(), //
+                    fessConfig.getIndexFieldDocId(), //
+                    fessConfig.getIndexFieldHost(), //
+                    fessConfig.getIndexFieldSite(), //
+                    fessConfig.getIndexFieldTitle(), //
+                    fessConfig.getIndexFieldContent(), //
+                    fessConfig.getIndexFieldContentLength(), //
+                    fessConfig.getIndexFieldLastModified(), //
+                    fessConfig.getIndexFieldTimestamp(), //
+                    fessConfig.getIndexFieldMimetype(), //
+                    fessConfig.getIndexFieldFiletype(), //
+                    fessConfig.getIndexFieldFilename(), //
+                    fessConfig.getIndexFieldLabel(), //
+                    fessConfig.getIndexFieldSegment(), //
+                    fessConfig.getIndexFieldAnchor(), //
+                    fessConfig.getIndexFieldClickCount(), //
+                    fessConfig.getIndexFieldFavoriteCount(), //
+                    fessConfig.getIndexFieldLang());
+        }
+        if (facetFields == null) {
+            facetFields = fessConfig.getQueryAdditionalFacetFields(//
+                    fessConfig.getIndexFieldUrl(), //
+                    fessConfig.getIndexFieldHost(), //
+                    fessConfig.getIndexFieldTitle(), //
+                    fessConfig.getIndexFieldContent(), //
+                    fessConfig.getIndexFieldContentLength(), //
+                    fessConfig.getIndexFieldLastModified(), //
+                    fessConfig.getIndexFieldTimestamp(), //
+                    fessConfig.getIndexFieldMimetype(), //
+                    fessConfig.getIndexFieldFiletype(), //
+                    fessConfig.getIndexFieldLabel(), //
+                    fessConfig.getIndexFieldSegment());
+        }
+        if (sortFields == null) {
+            sortFields = fessConfig.getQueryAdditionalSortFields(//
+                    SCORE_SORT_VALUE, //
+                    fessConfig.getIndexFieldFilename(), //
+                    fessConfig.getIndexFieldCreated(), //
+                    fessConfig.getIndexFieldContentLength(), //
+                    fessConfig.getIndexFieldLastModified(), //
+                    fessConfig.getIndexFieldTimestamp(), //
+                    fessConfig.getIndexFieldClickCount(), //
+                    fessConfig.getIndexFieldFavoriteCount());
+        }
+        if (apiResponseFieldSet == null) {
+            setApiResponseFields(fessConfig.getQueryAdditionalApiResponseFields(//
+                    fessConfig.getResponseFieldContentDescription(), //
+                    fessConfig.getResponseFieldContentTitle(), //
+                    fessConfig.getResponseFieldSitePath(), //
+                    fessConfig.getResponseFieldUrlLink(), //
+                    fessConfig.getIndexFieldId(), //
+                    fessConfig.getIndexFieldDocId(), //
+                    fessConfig.getIndexFieldBoost(), //
+                    fessConfig.getIndexFieldContentLength(), //
+                    fessConfig.getIndexFieldHost(), //
+                    fessConfig.getIndexFieldSite(), //
+                    fessConfig.getIndexFieldLastModified(), //
+                    fessConfig.getIndexFieldTimestamp(), //
+                    fessConfig.getIndexFieldMimetype(), //
+                    fessConfig.getIndexFieldFiletype(), //
+                    fessConfig.getIndexFieldFilename(), //
+                    fessConfig.getIndexFieldCreated(), //
+                    fessConfig.getIndexFieldTitle(), //
+                    fessConfig.getIndexFieldDigest(), //
+                    fessConfig.getIndexFieldUrl()));
+        }
+        if (notAnalyzedFieldSet == null) {
+            setNotAnalyzedFields(fessConfig.getQueryAdditionalNotAnalyzedFields(//
+                    fessConfig.getIndexFieldAnchor(), //
+                    fessConfig.getIndexFieldBoost(), //
+                    fessConfig.getIndexFieldClickCount(), //
+                    fessConfig.getIndexFieldConfigId(), //
+                    fessConfig.getIndexFieldContentLength(), //
+                    fessConfig.getIndexFieldCreated(), //
+                    fessConfig.getIndexFieldDocId(), //
+                    fessConfig.getIndexFieldExpires(), //
+                    fessConfig.getIndexFieldFavoriteCount(), //
+                    fessConfig.getIndexFieldFiletype(), //
+                    fessConfig.getIndexFieldFilename(), //
+                    fessConfig.getIndexFieldHasCache(), //
+                    fessConfig.getIndexFieldHost(), //
+                    fessConfig.getIndexFieldId(), //
+                    fessConfig.getIndexFieldLabel(), //
+                    fessConfig.getIndexFieldLang(), //
+                    fessConfig.getIndexFieldLastModified(), //
+                    fessConfig.getIndexFieldMimetype(), //
+                    fessConfig.getIndexFieldParentId(), //
+                    fessConfig.getIndexFieldPrimaryTerm(), //
+                    fessConfig.getIndexFieldRole(), //
+                    fessConfig.getIndexFieldSegment(), //
+                    fessConfig.getIndexFieldSeqNo(), //
+                    fessConfig.getIndexFieldSite(), //
+                    fessConfig.getIndexFieldTimestamp(), //
+                    fessConfig.getIndexFieldUrl(), //
+                    fessConfig.getIndexFieldVersion()));
+        }
+        split(fessConfig.getQueryAdditionalAnalyzedFields(), ",")
+                .of(stream -> stream.map(String::trim).filter(StringUtil::isNotBlank).forEach(s -> notAnalyzedFieldSet.remove(s)));
+        split(fessConfig.getQueryAdditionalDefaultFields(), ",").of(stream -> stream.filter(StringUtil::isNotBlank).map(s -> {
+            final Pair<String, Float> pair = new Pair<>();
+            final String[] values = s.split(":");
+            if (values.length == 1) {
+                pair.setFirst(values[0].trim());
+                pair.setSecond(1.0f);
+            } else if (values.length > 1) {
+                pair.setFirst(values[0]);
+                pair.setSecond(Float.parseFloat(values[1]));
+            } else {
+                return null;
+            }
+            return pair;
+        }).forEach(additionalDefaultList::add));
+    }
+
+    public void setNotAnalyzedFields(final String[] fields) {
+        notAnalyzedFieldSet = new HashSet<>();
+        Collections.addAll(notAnalyzedFieldSet, fields);
+    }
+
+    protected boolean isSortField(final String field) {
+        for (final String f : sortFields) {
+            if (f.equals(field)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public boolean isFacetField(final String field) {
+        if (StringUtil.isBlank(field)) {
+            return false;
+        }
+        boolean flag = false;
+        for (final String f : facetFields) {
+            if (field.equals(f)) {
+                flag = true;
+            }
+        }
+        return flag;
+    }
+
+    public boolean isFacetSortValue(final String sort) {
+        return "count".equals(sort) || "index".equals(sort);
+    }
+
+    public void setApiResponseFields(final String[] fields) {
+        apiResponseFieldSet = new HashSet<>();
+        Collections.addAll(apiResponseFieldSet, fields);
+    }
+
+    public boolean isApiResponseField(final String field) {
+        return apiResponseFieldSet.contains(field);
+    }
+
+    /**
+     * @return the responseFields
+     */
+    public String[] getResponseFields() {
+        return responseFields;
+    }
+
+    /**
+     * @param responseFields the responseFields to set
+     */
+    public void setResponseFields(final String[] responseFields) {
+        this.responseFields = responseFields;
+    }
+
+    public String[] getScrollResponseFields() {
+        return scrollResponseFields;
+    }
+
+    public void setScrollResponseFields(final String[] scrollResponseFields) {
+        this.scrollResponseFields = scrollResponseFields;
+    }
+
+    public String[] getCacheResponseFields() {
+        return cacheResponseFields;
+    }
+
+    public void setCacheResponseFields(final String[] cacheResponseFields) {
+        this.cacheResponseFields = cacheResponseFields;
+    }
+
+    /**
+     * @return the highlightedFields
+     */
+    public String[] getHighlightedFields() {
+        return highlightedFields;
+    }
+
+    /**
+     * @param highlightedFields the highlightedFields to set
+     */
+    public void setHighlightedFields(final String[] highlightedFields) {
+        this.highlightedFields = highlightedFields;
+    }
+
+    public void highlightedFields(final Consumer<Stream<String>> stream) {
+        stream(highlightedFields).of(stream);
+    }
+
+    /**
+     * @return the supportedFields
+     */
+    public String[] getSearchFields() {
+        return searchFields;
+    }
+
+    /**
+     * @param supportedFields the supportedFields to set
+     */
+    public void setSearchFields(final String[] supportedFields) {
+        searchFields = supportedFields;
+    }
+
+    /**
+     * @return the facetFields
+     */
+    public String[] getFacetFields() {
+        return facetFields;
+    }
+
+    /**
+     * @param facetFields the facetFields to set
+     */
+    public void setFacetFields(final String[] facetFields) {
+        this.facetFields = facetFields;
+    }
+
+    /**
+     * @return the sortFields
+     */
+    public String[] getSortFields() {
+        return sortFields;
+    }
+
+    /**
+     * @param sortFields the sortFields to set
+     */
+    public void setSortFields(final String[] sortFields) {
+        this.sortFields = sortFields;
+    }
+
+}

+ 52 - 0
src/main/java/org/codelibs/fess/query/QueryProcessor.java

@@ -0,0 +1,52 @@
+/*
+ * Copyright 2012-2022 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.query;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.lucene.search.Query;
+import org.codelibs.fess.entity.QueryContext;
+import org.codelibs.fess.exception.InvalidQueryException;
+import org.lastaflute.core.message.UserMessages;
+import org.opensearch.index.query.QueryBuilder;
+
+public class QueryProcessor {
+    private static final Logger logger = LogManager.getLogger(QueryProcessor.class);
+
+    protected Map<String, QueryCommand> queryCommandMap = new HashMap<>();
+
+    public QueryBuilder execute(final QueryContext context, final Query query, final float boost) {
+        final QueryCommand queryCommand = queryCommandMap.get(query.getClass().getSimpleName());
+        if (queryCommand != null) {
+            return queryCommand.execute(context, query, boost);
+        }
+        throw new InvalidQueryException(messages -> messages.addErrorsInvalidQueryUnknown(UserMessages.GLOBAL_PROPERTY_KEY),
+                "Unknown q: " + query.getClass() + " => " + query);
+    }
+
+    public void add(final String name, final QueryCommand queryCommand) {
+        if (name == null || queryCommand == null) {
+            throw new IllegalArgumentException("name or queryCommand is null.");
+        }
+        if (logger.isDebugEnabled()) {
+            logger.debug("Loaded {}", name);
+        }
+        queryCommandMap.put(name, queryCommand);
+    }
+}

+ 144 - 0
src/main/java/org/codelibs/fess/query/TermQueryCommand.java

@@ -0,0 +1,144 @@
+/*
+ * Copyright 2012-2022 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.query;
+
+import static org.codelibs.core.stream.StreamUtil.split;
+
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.PrefixQuery;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.TermQuery;
+import org.codelibs.core.lang.StringUtil;
+import org.codelibs.fess.Constants;
+import org.codelibs.fess.entity.QueryContext;
+import org.codelibs.fess.exception.InvalidQueryException;
+import org.codelibs.fess.mylasta.direction.FessConfig;
+import org.codelibs.fess.util.ComponentUtil;
+import org.lastaflute.core.message.UserMessages;
+import org.opensearch.common.unit.Fuzziness;
+import org.opensearch.index.query.BoolQueryBuilder;
+import org.opensearch.index.query.QueryBuilder;
+import org.opensearch.index.query.QueryBuilders;
+import org.opensearch.search.sort.SortOrder;
+
+public class TermQueryCommand extends QueryCommand {
+
+    @Override
+    protected String getQueryClassName() {
+        return TermQuery.class.getSimpleName();
+    }
+
+    @Override
+    public QueryBuilder execute(final QueryContext context, final Query query, final float boost) {
+        if (query instanceof final TermQuery termQuery) {
+            return convertTermQuery(context, termQuery, boost);
+        }
+        throw new InvalidQueryException(messages -> messages.addErrorsInvalidQueryUnknown(UserMessages.GLOBAL_PROPERTY_KEY),
+                "Unknown q: " + query.getClass() + " => " + query);
+    }
+
+    protected QueryBuilder convertTermQuery(final QueryContext context, final TermQuery termQuery, final float boost) {
+        final String field = getSearchField(context, termQuery.getTerm().field());
+        final String text = termQuery.getTerm().text();
+        final FessConfig fessConfig = ComponentUtil.getFessConfig();
+        if (fessConfig.getQueryReplaceTermWithPrefixQueryAsBoolean() && text.length() > 1 && text.endsWith("*")) {
+            return getQueryProcessor().execute(context, new PrefixQuery(new Term(field, text.substring(0, text.length() - 1))), boost);
+        }
+        if (Constants.DEFAULT_FIELD.equals(field)) {
+            context.addFieldLog(field, text);
+            context.addHighlightedQuery(text);
+            return buildDefaultTermQueryBuilder(boost, text);
+        }
+        if ("sort".equals(field)) {
+            split(text, ",").of(stream -> stream.filter(StringUtil::isNotBlank).forEach(t -> {
+                final String[] values = t.split("\\.");
+                if (values.length > 2) {
+                    throw new InvalidQueryException(
+                            messages -> messages.addErrorsInvalidQuerySortValue(UserMessages.GLOBAL_PROPERTY_KEY, text),
+                            "Invalid sort field: " + termQuery);
+                }
+                final String sortField = values[0];
+                if (!getQueryFieldConfig().isSortField(sortField)) {
+                    throw new InvalidQueryException(
+                            messages -> messages.addErrorsInvalidQueryUnsupportedSortField(UserMessages.GLOBAL_PROPERTY_KEY, sortField),
+                            "Unsupported sort field: " + termQuery);
+                }
+                SortOrder sortOrder;
+                if (values.length == 2) {
+                    sortOrder = SortOrder.DESC.toString().equalsIgnoreCase(values[1]) ? SortOrder.DESC : SortOrder.ASC;
+                    if (sortOrder == null) {
+                        throw new InvalidQueryException(
+                                messages -> messages.addErrorsInvalidQueryUnsupportedSortOrder(UserMessages.GLOBAL_PROPERTY_KEY, values[1]),
+                                "Invalid sort order: " + termQuery);
+                    }
+                } else {
+                    sortOrder = SortOrder.ASC;
+                }
+                context.addSorts(createFieldSortBuilder(sortField, sortOrder));
+            }));
+            return null;
+        }
+        if (QueryFieldConfig.INURL_FIELD.equals(field) || (StringUtil.equals(field, context.getDefaultField())
+                && fessConfig.getIndexFieldUrl().equals(context.getDefaultField()))) {
+            return QueryBuilders.wildcardQuery(fessConfig.getIndexFieldUrl(), "*" + text + "*").boost(boost);
+        }
+        if (QueryFieldConfig.SITE_FIELD.equals(field)) {
+            return convertSiteQuery(context, text, boost);
+        }
+        if (!isSearchField(field)) {
+            final String origQuery = termQuery.toString();
+            context.addFieldLog(Constants.DEFAULT_FIELD, origQuery);
+            context.addHighlightedQuery(origQuery);
+            return buildDefaultQueryBuilder((f, b) -> buildMatchPhraseQuery(f, origQuery).boost(b * boost));
+        }
+        context.addFieldLog(field, text);
+        context.addHighlightedQuery(text);
+        if (getQueryFieldConfig().notAnalyzedFieldSet.contains(field)) {
+            return QueryBuilders.termQuery(field, text).boost(boost);
+        }
+        return buildMatchPhraseQuery(field, text).boost(boost);
+    }
+
+    protected QueryBuilder buildDefaultTermQueryBuilder(final float boost, final String text) {
+        final BoolQueryBuilder boolQuery = buildDefaultQueryBuilder((f, b) -> buildMatchPhraseQuery(f, text).boost(b * boost));
+        final FessConfig fessConfig = ComponentUtil.getFessConfig();
+        final Integer fuzzyMinLength = fessConfig.getQueryBoostFuzzyMinLengthAsInteger();
+        if (fuzzyMinLength >= 0 && text.length() >= fuzzyMinLength) {
+            boolQuery.should(QueryBuilders.fuzzyQuery(fessConfig.getIndexFieldTitle(), text)
+                    .boost(fessConfig.getQueryBoostFuzzyTitleAsDecimal().floatValue())
+                    .prefixLength(fessConfig.getQueryBoostFuzzyTitlePrefixLengthAsInteger())
+                    .transpositions(Constants.TRUE.equalsIgnoreCase(fessConfig.getQueryBoostFuzzyTitleTranspositions()))
+                    .fuzziness(Fuzziness.build(fessConfig.getQueryBoostFuzzyTitleFuzziness()))
+                    .maxExpansions(fessConfig.getQueryBoostFuzzyTitleExpansionsAsInteger()));
+            boolQuery.should(QueryBuilders.fuzzyQuery(fessConfig.getIndexFieldContent(), text)
+                    .prefixLength(fessConfig.getQueryBoostFuzzyContentPrefixLengthAsInteger())
+                    .transpositions(Constants.TRUE.equalsIgnoreCase(fessConfig.getQueryBoostFuzzyContentTranspositions()))
+                    .boost(fessConfig.getQueryBoostFuzzyContentAsDecimal().floatValue())
+                    .fuzziness(Fuzziness.build(fessConfig.getQueryBoostFuzzyContentFuzziness()))
+                    .maxExpansions(fessConfig.getQueryBoostFuzzyContentExpansionsAsInteger()));
+        }
+        return boolQuery;
+    }
+
+    protected QueryBuilder convertSiteQuery(final QueryContext context, final String text, final float boost) {
+        return QueryBuilders.prefixQuery(ComponentUtil.getFessConfig().getIndexFieldSite(), text).boost(boost);
+    }
+
+    interface DefaultQueryBuilderFunction {
+        QueryBuilder apply(String field, float boost);
+    }
+
+}

+ 75 - 0
src/main/java/org/codelibs/fess/query/TermRangeQueryCommand.java

@@ -0,0 +1,75 @@
+/*
+ * Copyright 2012-2022 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.query;
+
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.TermRangeQuery;
+import org.apache.lucene.util.BytesRef;
+import org.codelibs.fess.Constants;
+import org.codelibs.fess.entity.QueryContext;
+import org.codelibs.fess.exception.InvalidQueryException;
+import org.lastaflute.core.message.UserMessages;
+import org.opensearch.index.query.QueryBuilder;
+import org.opensearch.index.query.QueryBuilders;
+import org.opensearch.index.query.RangeQueryBuilder;
+
+public class TermRangeQueryCommand extends QueryCommand {
+
+    @Override
+    protected String getQueryClassName() {
+        return TermRangeQuery.class.getSimpleName();
+    }
+
+    @Override
+    public QueryBuilder execute(final QueryContext context, final Query query, final float boost) {
+        if (query instanceof final TermRangeQuery termRangeQuery) {
+            return convertTermRangeQuery(context, termRangeQuery, boost);
+        }
+        throw new InvalidQueryException(messages -> messages.addErrorsInvalidQueryUnknown(UserMessages.GLOBAL_PROPERTY_KEY),
+                "Unknown q: " + query.getClass() + " => " + query);
+    }
+
+    protected QueryBuilder convertTermRangeQuery(final QueryContext context, final TermRangeQuery termRangeQuery, final float boost) {
+        final String field = getSearchField(context, termRangeQuery.getField());
+        if (!isSearchField(field)) {
+            final String origQuery = termRangeQuery.toString();
+            context.addFieldLog(Constants.DEFAULT_FIELD, origQuery);
+            context.addHighlightedQuery(origQuery);
+            return buildDefaultQueryBuilder((f, b) -> QueryBuilders.matchPhraseQuery(f, origQuery).boost(b));
+        }
+        context.addFieldLog(field, termRangeQuery.toString(field));
+        final RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery(field);
+        final BytesRef min = termRangeQuery.getLowerTerm();
+        if (min != null) {
+            if (termRangeQuery.includesLower()) {
+                rangeQuery.gte(min.utf8ToString());
+            } else {
+                rangeQuery.gt(min.utf8ToString());
+            }
+        }
+        final BytesRef max = termRangeQuery.getUpperTerm();
+        if (max != null) {
+            if (termRangeQuery.includesUpper()) {
+                rangeQuery.lte(max.utf8ToString());
+            } else {
+                rangeQuery.lt(max.utf8ToString());
+            }
+        }
+        rangeQuery.boost(boost);
+        return rangeQuery;
+    }
+
+}

+ 74 - 0
src/main/java/org/codelibs/fess/query/WildcardQueryCommand.java

@@ -0,0 +1,74 @@
+/*
+ * Copyright 2012-2022 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.query;
+
+import java.util.Locale;
+
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.WildcardQuery;
+import org.codelibs.fess.Constants;
+import org.codelibs.fess.entity.QueryContext;
+import org.codelibs.fess.exception.InvalidQueryException;
+import org.lastaflute.core.message.UserMessages;
+import org.opensearch.index.query.QueryBuilder;
+import org.opensearch.index.query.QueryBuilders;
+
+public class WildcardQueryCommand extends QueryCommand {
+    protected boolean lowercaseWildcard = true;
+
+    @Override
+    protected String getQueryClassName() {
+        return WildcardQuery.class.getSimpleName();
+    }
+
+    @Override
+    public QueryBuilder execute(final QueryContext context, final Query query, final float boost) {
+        if (query instanceof final WildcardQuery wildcardQuery) {
+            return convertWildcardQuery(context, wildcardQuery, boost);
+        }
+        throw new InvalidQueryException(messages -> messages.addErrorsInvalidQueryUnknown(UserMessages.GLOBAL_PROPERTY_KEY),
+                "Unknown q: " + query.getClass() + " => " + query);
+    }
+
+    protected QueryBuilder convertWildcardQuery(final QueryContext context, final WildcardQuery wildcardQuery, final float boost) {
+        final String field = getSearchField(context, wildcardQuery.getField());
+        if (Constants.DEFAULT_FIELD.equals(field)) {
+            context.addFieldLog(field, wildcardQuery.getTerm().text());
+            return buildDefaultQueryBuilder(
+                    (f, b) -> QueryBuilders.wildcardQuery(f, toLowercaseWildcard(wildcardQuery.getTerm().text())).boost(b * boost));
+        }
+        if (isSearchField(field)) {
+            context.addFieldLog(field, wildcardQuery.getTerm().text());
+            return QueryBuilders.wildcardQuery(field, toLowercaseWildcard(wildcardQuery.getTerm().text())).boost(boost);
+        }
+        final String query = wildcardQuery.getTerm().toString();
+        final String origQuery = toLowercaseWildcard(query);
+        context.addFieldLog(Constants.DEFAULT_FIELD, query);
+        context.addHighlightedQuery(origQuery);
+        return buildDefaultQueryBuilder((f, b) -> QueryBuilders.wildcardQuery(f, origQuery).boost(b * boost));
+    }
+
+    protected String toLowercaseWildcard(final String value) {
+        if (lowercaseWildcard) {
+            return value.toLowerCase(Locale.ROOT);
+        }
+        return value;
+    }
+
+    public void setLowercaseWildcard(boolean lowercaseWildcard) {
+        this.lowercaseWildcard = lowercaseWildcard;
+    }
+}

+ 2 - 1
src/main/java/org/codelibs/fess/sso/spnego/SpnegoAuthenticator.java

@@ -215,7 +215,8 @@ public class SpnegoAuthenticator implements SsoAuthenticator {
             }
             if (SpnegoHttpFilter.Constants.ALLOW_UNSEC_BASIC.equals(name)) {
                 return getProperty(SPNEGO_ALLOW_UNSECURE_BASIC, "true");
-            } else if (SpnegoHttpFilter.Constants.PROMPT_NTLM.equals(name)) {
+            }
+            if (SpnegoHttpFilter.Constants.PROMPT_NTLM.equals(name)) {
                 return getProperty(SPNEGO_PROMPT_NTLM, "true");
             } else if (SpnegoHttpFilter.Constants.ALLOW_LOCALHOST.equals(name)) {
                 return getProperty(SPNEGO_ALLOW_LOCALHOST, "true");

+ 15 - 0
src/main/java/org/codelibs/fess/util/ComponentUtil.java

@@ -77,6 +77,8 @@ import org.codelibs.fess.job.JobExecutor;
 import org.codelibs.fess.ldap.LdapManager;
 import org.codelibs.fess.mylasta.direction.FessConfig;
 import org.codelibs.fess.mylasta.direction.FessProp;
+import org.codelibs.fess.query.QueryFieldConfig;
+import org.codelibs.fess.query.QueryProcessor;
 import org.codelibs.fess.script.ScriptEngineFactory;
 import org.codelibs.fess.sso.SsoManager;
 import org.codelibs.fess.thumbnail.ThumbnailManager;
@@ -180,6 +182,10 @@ public final class ComponentUtil {
 
     private static final String QUERY_HELPER = "queryHelper";
 
+    private static final String QUERY_FIELD_CONFIG = "queryFieldConfig";
+
+    private static final String QUERY_PROCESSOR = "queryProcessor";
+
     private static final String SAMBA_HELPER = "sambaHelper";
 
     private static final String VIEW_HELPER = "viewHelper";
@@ -262,6 +268,14 @@ public final class ComponentUtil {
         return getComponent(QUERY_HELPER);
     }
 
+    public static QueryFieldConfig getQueryFieldConfig() {
+        return getComponent(QUERY_FIELD_CONFIG);
+    }
+
+    public static QueryProcessor getQueryProcessor() {
+        return getComponent(QUERY_PROCESSOR);
+    }
+
     public static LabelTypeHelper getLabelTypeHelper() {
         return getComponent(LABEL_TYPE_HELPER);
     }
@@ -502,6 +516,7 @@ public final class ComponentUtil {
         }
     }
 
+    @SuppressWarnings("unchecked")
     public static <T> T getComponent(final String componentName) {
         try {
             return SingletonLaContainer.getComponent(componentName);

+ 1 - 7
src/main/resources/app.xml

@@ -14,6 +14,7 @@
 	<include path="fess_thumbnail.xml"/>
 	<include path="fess_sso.xml"/>
 	<include path="fess_score.xml"/>
+	<include path="fess_query.xml"/>
 
 	<include path="crawler/client.xml" />
 	<include path="crawler/mimetype.xml" />
@@ -52,13 +53,6 @@
 	</component>
 	<component name="queryHelper" class="org.codelibs.fess.helper.QueryHelper">
 		<property name="defaultFacetInfo">facetInfo</property>
-		<property name="lowercaseWildcard">false</property>
-		<postConstruct name="addHighlightField">
-			<arg>"title"</arg>
-		</postConstruct>
-		<postConstruct name="addHighlightField">
-			<arg>"content"</arg>
-		</postConstruct>
 		<postConstruct name="addQueryRescorer">
 			<arg>
 				<component class="org.codelibs.fess.score.LtrQueryRescorer" />

+ 51 - 0
src/main/resources/fess_query.xml

@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE components PUBLIC "-//DBFLUTE//DTD LastaDi 1.0//EN"
+	"http://dbflute.org/meta/lastadi10.dtd">
+<components>
+	<component name="queryFieldConfig"
+		class="org.codelibs.fess.query.QueryFieldConfig">
+	</component>
+
+	<component name="queryProcessor"
+		class="org.codelibs.fess.query.QueryProcessor">
+	</component>
+
+	<component name="booleanQueryCommand"
+		class="org.codelibs.fess.query.BooleanQueryCommand">
+		<postConstruct name="register"></postConstruct>
+	</component>
+	<component name="boostQueryCommand"
+		class="org.codelibs.fess.query.BoostQueryCommand">
+		<postConstruct name="register"></postConstruct>
+	</component>
+	<component name="fuzzyQueryCommand"
+		class="org.codelibs.fess.query.FuzzyQueryCommand">
+		<postConstruct name="register"></postConstruct>
+	</component>
+	<component name="matchAllQueryCommand"
+		class="org.codelibs.fess.query.MatchAllQueryCommand">
+		<postConstruct name="register"></postConstruct>
+	</component>
+	<component name="phraseQueryCommand"
+		class="org.codelibs.fess.query.PhraseQueryCommand">
+		<postConstruct name="register"></postConstruct>
+	</component>
+	<component name="prefixQueryCommand"
+		class="org.codelibs.fess.query.PrefixQueryCommand">
+		<property name="lowercaseWildcard">false</property>
+		<postConstruct name="register"></postConstruct>
+	</component>
+	<component name="termQueryCommand"
+		class="org.codelibs.fess.query.TermQueryCommand">
+		<postConstruct name="register"></postConstruct>
+	</component>
+	<component name="termRangeQueryCommand"
+		class="org.codelibs.fess.query.TermRangeQueryCommand">
+		<postConstruct name="register"></postConstruct>
+	</component>
+	<component name="wildcardQueryCommand"
+		class="org.codelibs.fess.query.WildcardQueryCommand">
+		<property name="lowercaseWildcard">false</property>
+		<postConstruct name="register"></postConstruct>
+	</component>
+</components>

+ 26 - 36
src/test/java/org/codelibs/fess/helper/QueryHelperTest.java

@@ -24,11 +24,20 @@ import org.codelibs.core.io.FileUtil;
 import org.codelibs.core.misc.DynamicProperties;
 import org.codelibs.fess.Constants;
 import org.codelibs.fess.entity.SearchRequestParams.SearchRequestType;
+import org.codelibs.fess.query.BooleanQueryCommand;
+import org.codelibs.fess.query.BoostQueryCommand;
+import org.codelibs.fess.query.FuzzyQueryCommand;
+import org.codelibs.fess.query.MatchAllQueryCommand;
+import org.codelibs.fess.query.PhraseQueryCommand;
+import org.codelibs.fess.query.PrefixQueryCommand;
+import org.codelibs.fess.query.QueryFieldConfig;
+import org.codelibs.fess.query.QueryProcessor;
+import org.codelibs.fess.query.TermQueryCommand;
+import org.codelibs.fess.query.TermRangeQueryCommand;
+import org.codelibs.fess.query.WildcardQueryCommand;
 import org.codelibs.fess.unit.UnitFessTestCase;
 import org.codelibs.fess.util.ComponentUtil;
 import org.opensearch.index.query.BoolQueryBuilder;
-import org.opensearch.index.query.MatchPhraseQueryBuilder;
-import org.opensearch.index.query.PrefixQueryBuilder;
 import org.opensearch.index.query.QueryBuilder;
 import org.opensearch.index.query.QueryBuilders;
 import org.opensearch.index.query.functionscore.ScoreFunctionBuilders;
@@ -37,6 +46,8 @@ public class QueryHelperTest extends UnitFessTestCase {
 
     private QueryHelper queryHelper;
 
+    private QueryFieldConfig queryFieldConfig;
+
     @Override
     public void setUp() throws Exception {
         super.setUp();
@@ -57,7 +68,19 @@ public class QueryHelperTest extends UnitFessTestCase {
         ComponentUtil.register(new VirtualHostHelper(), "virtualHostHelper");
         ComponentUtil.register(new KeyMatchHelper(), "keyMatchHelper");
         inject(queryHelper);
-        queryHelper.init();
+        queryFieldConfig = new QueryFieldConfig();
+        ComponentUtil.register(queryFieldConfig, "queryFieldConfig");
+        queryFieldConfig.init();
+        ComponentUtil.register(new QueryProcessor(), "queryProcessor");
+        new BooleanQueryCommand().register();
+        new BoostQueryCommand().register();
+        new FuzzyQueryCommand().register();
+        new MatchAllQueryCommand().register();
+        new PhraseQueryCommand().register();
+        new PrefixQueryCommand().register();
+        new TermQueryCommand().register();
+        new TermRangeQueryCommand().register();
+        new WildcardQueryCommand().register();
     }
 
     public void test_build() {
@@ -82,34 +105,6 @@ public class QueryHelperTest extends UnitFessTestCase {
                         orQuery(simpleQuery("QUERY1", titleBoost, contentBoost), simpleQuery("QUERY2", titleBoost, contentBoost))),
                 buildQuery("QUERY1 OR QUERY2"));
 
-        assertQueryBuilder("test", "", MatchPhraseQueryBuilder.class);
-        assertQueryBuilder("test", "test", MatchPhraseQueryBuilder.class);
-        assertQueryBuilder("test", "a", MatchPhraseQueryBuilder.class);
-        assertQueryBuilder("test", "あ", MatchPhraseQueryBuilder.class);
-        assertQueryBuilder("test", "ア", MatchPhraseQueryBuilder.class);
-        assertQueryBuilder("test", "亜", MatchPhraseQueryBuilder.class);
-        assertQueryBuilder("test", "아", MatchPhraseQueryBuilder.class);
-        assertQueryBuilder("title", "test", MatchPhraseQueryBuilder.class);
-        assertQueryBuilder("title", "a", MatchPhraseQueryBuilder.class);
-        assertQueryBuilder("title", "あ", PrefixQueryBuilder.class);
-        assertQueryBuilder("title", "ああ", MatchPhraseQueryBuilder.class);
-        assertQueryBuilder("title", "ア", PrefixQueryBuilder.class);
-        assertQueryBuilder("title", "アア", MatchPhraseQueryBuilder.class);
-        assertQueryBuilder("title", "亜", PrefixQueryBuilder.class);
-        assertQueryBuilder("title", "亜亜", MatchPhraseQueryBuilder.class);
-        assertQueryBuilder("title", "아", PrefixQueryBuilder.class);
-        assertQueryBuilder("title", "아아", MatchPhraseQueryBuilder.class);
-        assertQueryBuilder("content", "test", MatchPhraseQueryBuilder.class);
-        assertQueryBuilder("content", "a", MatchPhraseQueryBuilder.class);
-        assertQueryBuilder("content", "あ", PrefixQueryBuilder.class);
-        assertQueryBuilder("content", "ああ", MatchPhraseQueryBuilder.class);
-        assertQueryBuilder("content", "ア", PrefixQueryBuilder.class);
-        assertQueryBuilder("content", "アア", MatchPhraseQueryBuilder.class);
-        assertQueryBuilder("content", "亜", PrefixQueryBuilder.class);
-        assertQueryBuilder("content", "亜亜", MatchPhraseQueryBuilder.class);
-        assertQueryBuilder("content", "아", PrefixQueryBuilder.class);
-        assertQueryBuilder("content", "아아", MatchPhraseQueryBuilder.class);
-
         assertEquals(
                 "{\"function_score\":{\"query\":{\"prefix\":{\"site\":{\"value\":\"fess.codelibs.org\",\"boost\":1.0}}},\"functions\":[{\"filter\":{\"match_all\":{\"boost\":1.0}},\"field_value_factor\":{\"field\":\"boost\",\"factor\":1.0,\"modifier\":\"none\"}}],\"score_mode\":\"multiply\",\"max_boost\":3.4028235E38,\"boost\":1.0}}",
                 buildQuery("site:fess.codelibs.org").toString().replaceAll("\\s", ""));
@@ -141,11 +136,6 @@ public class QueryHelperTest extends UnitFessTestCase {
                 buildQuery("allinurl: aaa bbb").toString().replaceAll("\\s", ""));
     }
 
-    private void assertQueryBuilder(String field, String value, Class<?> clazz) {
-        QueryBuilder queryBuilder = queryHelper.buildMatchPhraseQuery(field, value);
-        assertEquals(clazz, queryBuilder.getClass());
-    }
-
     private QueryBuilder andQuery(QueryBuilder... queries) {
         BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
         for (QueryBuilder query : queries) {

+ 79 - 0
src/test/java/org/codelibs/fess/query/QueryCommandTest.java

@@ -0,0 +1,79 @@
+/*
+ * Copyright 2012-2022 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.query;
+
+import org.apache.lucene.search.Query;
+import org.codelibs.fess.entity.QueryContext;
+import org.codelibs.fess.unit.UnitFessTestCase;
+import org.opensearch.index.query.MatchPhraseQueryBuilder;
+import org.opensearch.index.query.PrefixQueryBuilder;
+import org.opensearch.index.query.QueryBuilder;
+
+public class QueryCommandTest extends UnitFessTestCase {
+    private QueryCommand queryCommand;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        queryCommand = new QueryCommand() {
+            @Override
+            public QueryBuilder execute(QueryContext context, Query query, float boost) {
+                return null;
+            }
+
+            @Override
+            protected String getQueryClassName() {
+                return null;
+            }
+        };
+    }
+
+    public void test_buildMatchPhraseQuery() {
+        assertQueryBuilder("test", "", MatchPhraseQueryBuilder.class);
+        assertQueryBuilder("test", "test", MatchPhraseQueryBuilder.class);
+        assertQueryBuilder("test", "a", MatchPhraseQueryBuilder.class);
+        assertQueryBuilder("test", "あ", MatchPhraseQueryBuilder.class);
+        assertQueryBuilder("test", "ア", MatchPhraseQueryBuilder.class);
+        assertQueryBuilder("test", "亜", MatchPhraseQueryBuilder.class);
+        assertQueryBuilder("test", "아", MatchPhraseQueryBuilder.class);
+        assertQueryBuilder("title", "test", MatchPhraseQueryBuilder.class);
+        assertQueryBuilder("title", "a", MatchPhraseQueryBuilder.class);
+        assertQueryBuilder("title", "あ", PrefixQueryBuilder.class);
+        assertQueryBuilder("title", "ああ", MatchPhraseQueryBuilder.class);
+        assertQueryBuilder("title", "ア", PrefixQueryBuilder.class);
+        assertQueryBuilder("title", "アア", MatchPhraseQueryBuilder.class);
+        assertQueryBuilder("title", "亜", PrefixQueryBuilder.class);
+        assertQueryBuilder("title", "亜亜", MatchPhraseQueryBuilder.class);
+        assertQueryBuilder("title", "아", PrefixQueryBuilder.class);
+        assertQueryBuilder("title", "아아", MatchPhraseQueryBuilder.class);
+        assertQueryBuilder("content", "test", MatchPhraseQueryBuilder.class);
+        assertQueryBuilder("content", "a", MatchPhraseQueryBuilder.class);
+        assertQueryBuilder("content", "あ", PrefixQueryBuilder.class);
+        assertQueryBuilder("content", "ああ", MatchPhraseQueryBuilder.class);
+        assertQueryBuilder("content", "ア", PrefixQueryBuilder.class);
+        assertQueryBuilder("content", "アア", MatchPhraseQueryBuilder.class);
+        assertQueryBuilder("content", "亜", PrefixQueryBuilder.class);
+        assertQueryBuilder("content", "亜亜", MatchPhraseQueryBuilder.class);
+        assertQueryBuilder("content", "아", PrefixQueryBuilder.class);
+        assertQueryBuilder("content", "아아", MatchPhraseQueryBuilder.class);
+    }
+
+    private void assertQueryBuilder(String field, String value, Class<?> clazz) {
+        QueryBuilder queryBuilder = queryCommand.buildMatchPhraseQuery(field, value);
+        assertEquals(clazz, queryBuilder.getClass());
+    }
+
+}