Kaynağa Gözat

fix #2240 add highlight parameters

Shinsuke Sugaya 5 yıl önce
ebeveyn
işleme
c12d370a10

+ 11 - 0
src/main/java/org/codelibs/fess/entity/HighlightInfo.java

@@ -22,12 +22,14 @@ public class HighlightInfo {
     private String type;
     private int fragmentSize;
     private int numOfFragments;
+    private int fragmentOffset;
 
     public HighlightInfo() {
         final FessConfig fessConfig = ComponentUtil.getFessConfig();
         this.type = fessConfig.getQueryHighlightType();
         this.fragmentSize = fessConfig.getQueryHighlightFragmentSizeAsInteger();
         this.numOfFragments = fessConfig.getQueryHighlightNumberOfFragmentsAsInteger();
+        this.fragmentOffset = fessConfig.getQueryHighlightFragmentOffsetAsInteger();
     }
 
     public String getType() {
@@ -56,4 +58,13 @@ public class HighlightInfo {
         this.numOfFragments = numOfFragments;
         return this;
     }
+
+    public int getFragmentOffset() {
+        return fragmentOffset;
+    }
+
+    public HighlightInfo fragmentOffset(final int fragmentOffset) {
+        this.fragmentOffset = fragmentOffset;
+        return this;
+    }
 }

+ 93 - 56
src/main/java/org/codelibs/fess/es/client/FessEsClient.java

@@ -1108,24 +1108,7 @@ public class FessEsClient implements Client {
                 throw new ResultOffsetExceededException("The number of result size is exceeded.");
             }
 
-            final QueryContext queryContext =
-                    queryHelper.build(searchRequestType, query, context -> {
-                        if (SearchRequestType.ADMIN_SEARCH.equals(searchRequestType)) {
-                            context.skipRoleQuery();
-                        } else if (similarDocHash != null) {
-                            final DocumentHelper documentHelper = ComponentUtil.getDocumentHelper();
-                            context.addQuery(boolQuery -> {
-                                boolQuery.filter(QueryBuilders.termQuery(fessConfig.getIndexFieldContentMinhashBits(),
-                                        documentHelper.decodeSimilarDocHash(similarDocHash)));
-                            });
-                        }
-
-                        if (geoInfo != null && geoInfo.toQueryBuilder() != null) {
-                            context.addQuery(boolQuery -> {
-                                boolQuery.filter(geoInfo.toQueryBuilder());
-                            });
-                        }
-                    });
+            final QueryContext queryContext = buildQueryContext(queryHelper, fessConfig);
 
             searchRequestBuilder.setFrom(offset).setSize(size);
 
@@ -1141,53 +1124,19 @@ public class FessEsClient implements Client {
             }
 
             // rescorer
-            stream(queryHelper.getRescorers(condition())).of(stream -> stream.forEach(searchRequestBuilder::addRescorer));
+            buildRescorer(queryHelper, fessConfig);
 
             // sort
-            queryContext.sortBuilders().forEach(sortBuilder -> searchRequestBuilder.addSort(sortBuilder));
+            buildSort(queryContext, fessConfig);
 
             // highlighting
             if (highlightInfo != null) {
-                final HighlightBuilder highlightBuilder = new HighlightBuilder();
-                queryHelper.highlightedFields(stream -> stream.forEach(hf -> highlightBuilder.field(new HighlightBuilder.Field(hf)
-                        .highlighterType(highlightInfo.getType()).fragmentSize(highlightInfo.getFragmentSize())
-                        .numOfFragments(highlightInfo.getNumOfFragments()))));
-                searchRequestBuilder.highlighter(highlightBuilder);
+                buildHighlighter(queryHelper, fessConfig);
             }
 
             // facets
             if (facetInfo != null) {
-                stream(facetInfo.field).of(
-                        stream -> stream.forEach(f -> {
-                            if (queryHelper.isFacetField(f)) {
-                                final String encodedField = BaseEncoding.base64().encode(f.getBytes(StandardCharsets.UTF_8));
-                                final TermsAggregationBuilder termsBuilder =
-                                        AggregationBuilders.terms(Constants.FACET_FIELD_PREFIX + encodedField).field(f);
-                                termsBuilder.order(facetInfo.getBucketOrder());
-                                if (facetInfo.size != null) {
-                                    termsBuilder.size(facetInfo.size);
-                                }
-                                if (facetInfo.minDocCount != null) {
-                                    termsBuilder.minDocCount(facetInfo.minDocCount);
-                                }
-                                if (facetInfo.missing != null) {
-                                    termsBuilder.missing(facetInfo.missing);
-                                }
-                                searchRequestBuilder.addAggregation(termsBuilder);
-                            } else {
-                                throw new SearchQueryException("Invalid facet field: " + f);
-                            }
-                        }));
-                stream(facetInfo.query).of(
-                        stream -> stream.forEach(fq -> {
-                            final QueryContext facetContext = new QueryContext(fq, false);
-                            queryHelper.buildBaseQuery(facetContext, c -> {});
-                            final String encodedFacetQuery = BaseEncoding.base64().encode(fq.getBytes(StandardCharsets.UTF_8));
-                            final FilterAggregationBuilder filterBuilder =
-                                    AggregationBuilders.filter(Constants.FACET_QUERY_PREFIX + encodedFacetQuery,
-                                            facetContext.getQueryBuilder());
-                            searchRequestBuilder.addAggregation(filterBuilder);
-                        }));
+                buildFacet(queryHelper, fessConfig);
             }
 
             if (!SearchRequestType.ADMIN_SEARCH.equals(searchRequestType) && !isScroll && fessConfig.isResultCollapsed()
@@ -1199,6 +1148,94 @@ public class FessEsClient implements Client {
             return true;
         }
 
+        protected void buildFacet(final QueryHelper queryHelper, final FessConfig fessConfig) {
+            stream(facetInfo.field).of(
+                    stream -> stream.forEach(f -> {
+                        if (queryHelper.isFacetField(f)) {
+                            final String encodedField = BaseEncoding.base64().encode(f.getBytes(StandardCharsets.UTF_8));
+                            final TermsAggregationBuilder termsBuilder =
+                                    AggregationBuilders.terms(Constants.FACET_FIELD_PREFIX + encodedField).field(f);
+                            termsBuilder.order(facetInfo.getBucketOrder());
+                            if (facetInfo.size != null) {
+                                termsBuilder.size(facetInfo.size);
+                            }
+                            if (facetInfo.minDocCount != null) {
+                                termsBuilder.minDocCount(facetInfo.minDocCount);
+                            }
+                            if (facetInfo.missing != null) {
+                                termsBuilder.missing(facetInfo.missing);
+                            }
+                            searchRequestBuilder.addAggregation(termsBuilder);
+                        } else {
+                            throw new SearchQueryException("Invalid facet field: " + f);
+                        }
+                    }));
+            stream(facetInfo.query)
+                    .of(stream -> stream.forEach(fq -> {
+                        final QueryContext facetContext = new QueryContext(fq, false);
+                        queryHelper.buildBaseQuery(facetContext, c -> {});
+                        final String encodedFacetQuery = BaseEncoding.base64().encode(fq.getBytes(StandardCharsets.UTF_8));
+                        final FilterAggregationBuilder filterBuilder =
+                                AggregationBuilders.filter(Constants.FACET_QUERY_PREFIX + encodedFacetQuery, facetContext.getQueryBuilder());
+                        searchRequestBuilder.addAggregation(filterBuilder);
+                    }));
+        }
+
+        protected void buildHighlighter(final QueryHelper queryHelper, final FessConfig fessConfig) {
+            final String highlighterType = highlightInfo.getType();
+            final int fragmentSize = highlightInfo.getFragmentSize();
+            final int numOfFragments = highlightInfo.getNumOfFragments();
+            final int fragmentOffset = highlightInfo.getFragmentOffset();
+            final char[] boundaryChars = fessConfig.getQueryHighlightBoundaryCharsAsArray();
+            final int boundaryMaxScan = fessConfig.getQueryHighlightBoundaryMaxScanAsInteger();
+            final String boundaryScannerType = fessConfig.getQueryHighlightBoundaryScanner();
+            final boolean forceSource = fessConfig.isQueryHighlightForceSource();
+            final String fragmenter = fessConfig.getQueryHighlightFragmenter();
+            final int noMatchSize = fessConfig.getQueryHighlightNoMatchSizeAsInteger();
+            final String order = fessConfig.getQueryHighlightOrder();
+            final int phraseLimit = fessConfig.getQueryHighlightPhraseLimitAsInteger();
+            final String encoder = fessConfig.getQueryHighlightEncoder();
+            final HighlightBuilder highlightBuilder = new HighlightBuilder();
+            queryHelper.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)
+                            .fragmentOffset(fragmentOffset).noMatchSize(noMatchSize).order(order).phraseLimit(phraseLimit))
+                    .encoder(encoder)));
+            searchRequestBuilder.highlighter(highlightBuilder);
+        }
+
+        protected void buildSort(final QueryContext queryContext, final FessConfig fessConfig) {
+            queryContext.sortBuilders().forEach(sortBuilder -> searchRequestBuilder.addSort(sortBuilder));
+        }
+
+        protected void buildRescorer(final QueryHelper queryHelper, final FessConfig fessConfig) {
+            stream(queryHelper.getRescorers(condition())).of(stream -> stream.forEach(searchRequestBuilder::addRescorer));
+        }
+
+        protected QueryContext buildQueryContext(final QueryHelper queryHelper, final FessConfig fessConfig) {
+            return queryHelper.build(
+                    searchRequestType,
+                    query,
+                    context -> {
+                        if (SearchRequestType.ADMIN_SEARCH.equals(searchRequestType)) {
+                            context.skipRoleQuery();
+                        } else if (similarDocHash != null) {
+                            final DocumentHelper documentHelper = ComponentUtil.getDocumentHelper();
+                            context.addQuery(boolQuery -> {
+                                boolQuery.filter(QueryBuilders.termQuery(fessConfig.getIndexFieldContentMinhashBits(),
+                                        documentHelper.decodeSimilarDocHash(similarDocHash)));
+                            });
+                        }
+
+                        if (geoInfo != null && geoInfo.toQueryBuilder() != null) {
+                            context.addQuery(boolQuery -> {
+                                boolQuery.filter(geoInfo.toQueryBuilder());
+                            });
+                        }
+                    });
+        }
+
         protected CollapseBuilder getCollapseBuilder(final FessConfig fessConfig) {
             final InnerHitBuilder innerHitBuilder =
                     new InnerHitBuilder().setName(fessConfig.getQueryCollapseInnerHitsName()).setSize(

+ 1 - 1
src/main/java/org/codelibs/fess/es/config/exentity/CrawlingConfig.java

@@ -67,7 +67,7 @@ public interface CrawlingConfig {
     public enum ConfigType {
         WEB("W"), FILE("F"), DATA("D");
 
-        private String typePrefix;
+        private final String typePrefix;
 
         ConfigType(final String typePrefix) {
             this.typePrefix = typePrefix;

+ 227 - 6
src/main/java/org/codelibs/fess/mylasta/direction/FessConfig.java

@@ -83,7 +83,7 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
     -Djna.nosys=true
     -Djdk.io.permissionsUseCanonicalPath=true
     -server
-    -Xms256m
+    -Xms128m
     -Xmx256m
     -XX:MaxMetaspaceSize=128m
     -XX:CompressedClassSpaceSize=32m
@@ -114,7 +114,7 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
     -Djdk.io.permissionsUseCanonicalPath=true
     -server
     -Xms128m
-    -Xmx128m
+    -Xmx256m
     -XX:MaxMetaspaceSize=128m
     -XX:CompressedClassSpaceSize=32m
     -XX:-UseGCOverheadLimit
@@ -675,6 +675,36 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
     /** The key of the configuration. e.g. </strong> */
     String QUERY_HIGHLIGHT_TAG_POST = "query.highlight.tag.post";
 
+    /** The key of the configuration. e.g.  */
+    String QUERY_HIGHLIGHT_BOUNDARY_CHARS = "query.highlight.boundary.chars";
+
+    /** The key of the configuration. e.g. 20 */
+    String QUERY_HIGHLIGHT_BOUNDARY_MAX_SCAN = "query.highlight.boundary.max.scan";
+
+    /** The key of the configuration. e.g. sentence */
+    String QUERY_HIGHLIGHT_BOUNDARY_SCANNER = "query.highlight.boundary.scanner";
+
+    /** The key of the configuration. e.g. default */
+    String QUERY_HIGHLIGHT_ENCODER = "query.highlight.encoder";
+
+    /** The key of the configuration. e.g. false */
+    String QUERY_HIGHLIGHT_FORCE_SOURCE = "query.highlight.force.source";
+
+    /** The key of the configuration. e.g. span */
+    String QUERY_HIGHLIGHT_FRAGMENTER = "query.highlight.fragmenter";
+
+    /** The key of the configuration. e.g. -1 */
+    String QUERY_HIGHLIGHT_FRAGMENT_OFFSET = "query.highlight.fragment.offset";
+
+    /** The key of the configuration. e.g. 0 */
+    String QUERY_HIGHLIGHT_NO_MATCH_SIZE = "query.highlight.no.match.size";
+
+    /** The key of the configuration. e.g. score */
+    String QUERY_HIGHLIGHT_ORDER = "query.highlight.order";
+
+    /** The key of the configuration. e.g. 256 */
+    String QUERY_HIGHLIGHT_PHRASE_LIMIT = "query.highlight.phrase.limit";
+
     /** The key of the configuration. e.g. hl_content,digest */
     String QUERY_HIGHLIGHT_CONTENT_DESCRIPTION_FIELDS = "query.highlight.content.description.fields";
 
@@ -1543,7 +1573,7 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
     -Djna.nosys=true
     -Djdk.io.permissionsUseCanonicalPath=true
     -server
-    -Xms256m
+    -Xms128m
     -Xmx256m
     -XX:MaxMetaspaceSize=128m
     -XX:CompressedClassSpaceSize=32m
@@ -1578,7 +1608,7 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
     -Djdk.io.permissionsUseCanonicalPath=true
     -server
     -Xms128m
-    -Xmx128m
+    -Xmx256m
     -XX:MaxMetaspaceSize=128m
     -XX:CompressedClassSpaceSize=32m
     -XX:-UseGCOverheadLimit
@@ -3422,6 +3452,123 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
      */
     String getQueryHighlightTagPost();
 
+    /**
+     * Get the value for the key 'query.highlight.boundary.chars'. <br>
+     * The value is, e.g.  <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     */
+    String getQueryHighlightBoundaryChars();
+
+    /**
+     * Get the value for the key 'query.highlight.boundary.chars' as {@link Integer}. <br>
+     * The value is, e.g.  <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     * @throws NumberFormatException When the property is not integer.
+     */
+    Integer getQueryHighlightBoundaryCharsAsInteger();
+
+    /**
+     * Get the value for the key 'query.highlight.boundary.max.scan'. <br>
+     * The value is, e.g. 20 <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     */
+    String getQueryHighlightBoundaryMaxScan();
+
+    /**
+     * Get the value for the key 'query.highlight.boundary.max.scan' as {@link Integer}. <br>
+     * The value is, e.g. 20 <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     * @throws NumberFormatException When the property is not integer.
+     */
+    Integer getQueryHighlightBoundaryMaxScanAsInteger();
+
+    /**
+     * Get the value for the key 'query.highlight.boundary.scanner'. <br>
+     * The value is, e.g. sentence <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     */
+    String getQueryHighlightBoundaryScanner();
+
+    /**
+     * Get the value for the key 'query.highlight.encoder'. <br>
+     * The value is, e.g. default <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     */
+    String getQueryHighlightEncoder();
+
+    /**
+     * Get the value for the key 'query.highlight.force.source'. <br>
+     * The value is, e.g. false <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     */
+    String getQueryHighlightForceSource();
+
+    /**
+     * Is the property for the key 'query.highlight.force.source' true? <br>
+     * The value is, e.g. false <br>
+     * @return The determination, true or false. (if not found, exception but basically no way)
+     */
+    boolean isQueryHighlightForceSource();
+
+    /**
+     * Get the value for the key 'query.highlight.fragmenter'. <br>
+     * The value is, e.g. span <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     */
+    String getQueryHighlightFragmenter();
+
+    /**
+     * Get the value for the key 'query.highlight.fragment.offset'. <br>
+     * The value is, e.g. -1 <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     */
+    String getQueryHighlightFragmentOffset();
+
+    /**
+     * Get the value for the key 'query.highlight.fragment.offset' as {@link Integer}. <br>
+     * The value is, e.g. -1 <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     * @throws NumberFormatException When the property is not integer.
+     */
+    Integer getQueryHighlightFragmentOffsetAsInteger();
+
+    /**
+     * Get the value for the key 'query.highlight.no.match.size'. <br>
+     * The value is, e.g. 0 <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     */
+    String getQueryHighlightNoMatchSize();
+
+    /**
+     * Get the value for the key 'query.highlight.no.match.size' as {@link Integer}. <br>
+     * The value is, e.g. 0 <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     * @throws NumberFormatException When the property is not integer.
+     */
+    Integer getQueryHighlightNoMatchSizeAsInteger();
+
+    /**
+     * Get the value for the key 'query.highlight.order'. <br>
+     * The value is, e.g. score <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     */
+    String getQueryHighlightOrder();
+
+    /**
+     * Get the value for the key 'query.highlight.phrase.limit'. <br>
+     * The value is, e.g. 256 <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     */
+    String getQueryHighlightPhraseLimit();
+
+    /**
+     * Get the value for the key 'query.highlight.phrase.limit' as {@link Integer}. <br>
+     * The value is, e.g. 256 <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     * @throws NumberFormatException When the property is not integer.
+     */
+    Integer getQueryHighlightPhraseLimitAsInteger();
+
     /**
      * Get the value for the key 'query.highlight.content.description.fields'. <br>
      * The value is, e.g. hl_content,digest <br>
@@ -6992,6 +7139,70 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
             return get(FessConfig.QUERY_HIGHLIGHT_TAG_POST);
         }
 
+        public String getQueryHighlightBoundaryChars() {
+            return get(FessConfig.QUERY_HIGHLIGHT_BOUNDARY_CHARS);
+        }
+
+        public Integer getQueryHighlightBoundaryCharsAsInteger() {
+            return getAsInteger(FessConfig.QUERY_HIGHLIGHT_BOUNDARY_CHARS);
+        }
+
+        public String getQueryHighlightBoundaryMaxScan() {
+            return get(FessConfig.QUERY_HIGHLIGHT_BOUNDARY_MAX_SCAN);
+        }
+
+        public Integer getQueryHighlightBoundaryMaxScanAsInteger() {
+            return getAsInteger(FessConfig.QUERY_HIGHLIGHT_BOUNDARY_MAX_SCAN);
+        }
+
+        public String getQueryHighlightBoundaryScanner() {
+            return get(FessConfig.QUERY_HIGHLIGHT_BOUNDARY_SCANNER);
+        }
+
+        public String getQueryHighlightEncoder() {
+            return get(FessConfig.QUERY_HIGHLIGHT_ENCODER);
+        }
+
+        public String getQueryHighlightForceSource() {
+            return get(FessConfig.QUERY_HIGHLIGHT_FORCE_SOURCE);
+        }
+
+        public boolean isQueryHighlightForceSource() {
+            return is(FessConfig.QUERY_HIGHLIGHT_FORCE_SOURCE);
+        }
+
+        public String getQueryHighlightFragmenter() {
+            return get(FessConfig.QUERY_HIGHLIGHT_FRAGMENTER);
+        }
+
+        public String getQueryHighlightFragmentOffset() {
+            return get(FessConfig.QUERY_HIGHLIGHT_FRAGMENT_OFFSET);
+        }
+
+        public Integer getQueryHighlightFragmentOffsetAsInteger() {
+            return getAsInteger(FessConfig.QUERY_HIGHLIGHT_FRAGMENT_OFFSET);
+        }
+
+        public String getQueryHighlightNoMatchSize() {
+            return get(FessConfig.QUERY_HIGHLIGHT_NO_MATCH_SIZE);
+        }
+
+        public Integer getQueryHighlightNoMatchSizeAsInteger() {
+            return getAsInteger(FessConfig.QUERY_HIGHLIGHT_NO_MATCH_SIZE);
+        }
+
+        public String getQueryHighlightOrder() {
+            return get(FessConfig.QUERY_HIGHLIGHT_ORDER);
+        }
+
+        public String getQueryHighlightPhraseLimit() {
+            return get(FessConfig.QUERY_HIGHLIGHT_PHRASE_LIMIT);
+        }
+
+        public Integer getQueryHighlightPhraseLimitAsInteger() {
+            return getAsInteger(FessConfig.QUERY_HIGHLIGHT_PHRASE_LIMIT);
+        }
+
         public String getQueryHighlightContentDescriptionFields() {
             return get(FessConfig.QUERY_HIGHLIGHT_CONTENT_DESCRIPTION_FIELDS);
         }
@@ -8353,10 +8564,10 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
                             "-Djava.awt.headless=true\n-Dfile.encoding=UTF-8\n-Djna.nosys=true\n-Djdk.io.permissionsUseCanonicalPath=true\n-Dhttp.maxConnections=20\n-server\n-Xms512m\n-Xmx512m\n-XX:MaxMetaspaceSize=128m\n-XX:CompressedClassSpaceSize=32m\n-XX:-UseGCOverheadLimit\n-XX:+UseTLAB\n-XX:+DisableExplicitGC\n-XX:+HeapDumpOnOutOfMemoryError\n-XX:-OmitStackTraceInFastThrow\n-XX:+UnlockExperimentalVMOptions\n-XX:+UseG1GC\n-XX:InitiatingHeapOccupancyPercent=45\n-XX:G1HeapRegionSize=1m\n-XX:MaxGCPauseMillis=60000\n-XX:G1NewSizePercent=5\n-XX:G1MaxNewSizePercent=5\n-Djcifs.smb.client.responseTimeout=30000\n-Djcifs.smb.client.soTimeout=35000\n-Djcifs.smb.client.connTimeout=60000\n-Djcifs.smb.client.sessionTimeout=60000\n-Djcifs.smb1.smb.client.connTimeout=60000\n-Djcifs.smb1.smb.client.soTimeout=35000\n-Djcifs.smb1.smb.client.responseTimeout=30000\n-Dgroovy.use.classvalue=true\n-Dio.netty.noUnsafe=true\n-Dio.netty.noKeySetOptimization=true\n-Dio.netty.recycler.maxCapacityPerThread=0\n-Dlog4j.shutdownHookEnabled=false\n-Dlog4j2.disable.jmx=true\n-Dlog4j.skipJansi=true\n-Dsun.java2d.cmm=sun.java2d.cmm.kcms.KcmsServiceProvider\n-Dorg.apache.pdfbox.rendering.UsePureJavaCMYKConversion=true\n");
             defaultMap
                     .put(FessConfig.JVM_SUGGEST_OPTIONS,
-                            "-Djava.awt.headless=true\n-Dfile.encoding=UTF-8\n-Djna.nosys=true\n-Djdk.io.permissionsUseCanonicalPath=true\n-server\n-Xms256m\n-Xmx256m\n-XX:MaxMetaspaceSize=128m\n-XX:CompressedClassSpaceSize=32m\n-XX:-UseGCOverheadLimit\n-XX:+UseTLAB\n-XX:+DisableExplicitGC\n-XX:+HeapDumpOnOutOfMemoryError\n-XX:+UnlockExperimentalVMOptions\n-XX:+UseG1GC\n-XX:InitiatingHeapOccupancyPercent=45\n-XX:G1HeapRegionSize=1m\n-XX:MaxGCPauseMillis=60000\n-XX:G1NewSizePercent=5\n-XX:G1MaxNewSizePercent=30\n-Dgroovy.use.classvalue=true\n-Dio.netty.noUnsafe=true\n-Dio.netty.noKeySetOptimization=true\n-Dio.netty.recycler.maxCapacityPerThread=0\n-Dlog4j.shutdownHookEnabled=false\n-Dlog4j2.disable.jmx=true\n-Dlog4j.skipJansi=true\n");
+                            "-Djava.awt.headless=true\n-Dfile.encoding=UTF-8\n-Djna.nosys=true\n-Djdk.io.permissionsUseCanonicalPath=true\n-server\n-Xms128m\n-Xmx256m\n-XX:MaxMetaspaceSize=128m\n-XX:CompressedClassSpaceSize=32m\n-XX:-UseGCOverheadLimit\n-XX:+UseTLAB\n-XX:+DisableExplicitGC\n-XX:+HeapDumpOnOutOfMemoryError\n-XX:+UnlockExperimentalVMOptions\n-XX:+UseG1GC\n-XX:InitiatingHeapOccupancyPercent=45\n-XX:G1HeapRegionSize=1m\n-XX:MaxGCPauseMillis=60000\n-XX:G1NewSizePercent=5\n-XX:G1MaxNewSizePercent=30\n-Dgroovy.use.classvalue=true\n-Dio.netty.noUnsafe=true\n-Dio.netty.noKeySetOptimization=true\n-Dio.netty.recycler.maxCapacityPerThread=0\n-Dlog4j.shutdownHookEnabled=false\n-Dlog4j2.disable.jmx=true\n-Dlog4j.skipJansi=true\n");
             defaultMap
                     .put(FessConfig.JVM_THUMBNAIL_OPTIONS,
-                            "-Djava.awt.headless=true\n-Dfile.encoding=UTF-8\n-Djna.nosys=true\n-Djdk.io.permissionsUseCanonicalPath=true\n-server\n-Xms128m\n-Xmx128m\n-XX:MaxMetaspaceSize=128m\n-XX:CompressedClassSpaceSize=32m\n-XX:-UseGCOverheadLimit\n-XX:+UseTLAB\n-XX:+DisableExplicitGC\n-XX:+HeapDumpOnOutOfMemoryError\n-XX:-OmitStackTraceInFastThrow\n-XX:+UnlockExperimentalVMOptions\n-XX:+UseG1GC\n-XX:InitiatingHeapOccupancyPercent=45\n-XX:G1HeapRegionSize=4m\n-XX:MaxGCPauseMillis=60000\n-XX:G1NewSizePercent=5\n-XX:G1MaxNewSizePercent=50\n-Djcifs.smb.client.responseTimeout=30000\n-Djcifs.smb.client.soTimeout=35000\n-Djcifs.smb.client.connTimeout=60000\n-Djcifs.smb.client.sessionTimeout=60000\n-Djcifs.smb1.smb.client.connTimeout=60000\n-Djcifs.smb1.smb.client.soTimeout=35000\n-Djcifs.smb1.smb.client.responseTimeout=30000\n-Dgroovy.use.classvalue=true\n-Dio.netty.noUnsafe=true\n-Dio.netty.noKeySetOptimization=true\n-Dio.netty.recycler.maxCapacityPerThread=0\n-Dlog4j.shutdownHookEnabled=false\n-Dlog4j2.disable.jmx=true\n-Dlog4j.skipJansi=true\n-Dsun.java2d.cmm=sun.java2d.cmm.kcms.KcmsServiceProvider\n-Dorg.apache.pdfbox.rendering.UsePureJavaCMYKConversion=true\n");
+                            "-Djava.awt.headless=true\n-Dfile.encoding=UTF-8\n-Djna.nosys=true\n-Djdk.io.permissionsUseCanonicalPath=true\n-server\n-Xms128m\n-Xmx256m\n-XX:MaxMetaspaceSize=128m\n-XX:CompressedClassSpaceSize=32m\n-XX:-UseGCOverheadLimit\n-XX:+UseTLAB\n-XX:+DisableExplicitGC\n-XX:+HeapDumpOnOutOfMemoryError\n-XX:-OmitStackTraceInFastThrow\n-XX:+UnlockExperimentalVMOptions\n-XX:+UseG1GC\n-XX:InitiatingHeapOccupancyPercent=45\n-XX:G1HeapRegionSize=4m\n-XX:MaxGCPauseMillis=60000\n-XX:G1NewSizePercent=5\n-XX:G1MaxNewSizePercent=50\n-Djcifs.smb.client.responseTimeout=30000\n-Djcifs.smb.client.soTimeout=35000\n-Djcifs.smb.client.connTimeout=60000\n-Djcifs.smb.client.sessionTimeout=60000\n-Djcifs.smb1.smb.client.connTimeout=60000\n-Djcifs.smb1.smb.client.soTimeout=35000\n-Djcifs.smb1.smb.client.responseTimeout=30000\n-Dgroovy.use.classvalue=true\n-Dio.netty.noUnsafe=true\n-Dio.netty.noKeySetOptimization=true\n-Dio.netty.recycler.maxCapacityPerThread=0\n-Dlog4j.shutdownHookEnabled=false\n-Dlog4j2.disable.jmx=true\n-Dlog4j.skipJansi=true\n-Dsun.java2d.cmm=sun.java2d.cmm.kcms.KcmsServiceProvider\n-Dorg.apache.pdfbox.rendering.UsePureJavaCMYKConversion=true\n");
             defaultMap.put(FessConfig.JOB_SYSTEM_JOB_IDS, "default_crawler");
             defaultMap.put(FessConfig.JOB_TEMPLATE_TITLE_WEB, "Web Crawler - {0}");
             defaultMap.put(FessConfig.JOB_TEMPLATE_TITLE_FILE, "File Crawler - {0}");
@@ -8538,6 +8749,16 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
             defaultMap.put(FessConfig.QUERY_HIGHLIGHT_TYPE, "fvh");
             defaultMap.put(FessConfig.QUERY_HIGHLIGHT_TAG_PRE, "<strong>");
             defaultMap.put(FessConfig.QUERY_HIGHLIGHT_TAG_POST, "</strong>");
+            defaultMap.put(FessConfig.QUERY_HIGHLIGHT_BOUNDARY_CHARS, "\t\n ");
+            defaultMap.put(FessConfig.QUERY_HIGHLIGHT_BOUNDARY_MAX_SCAN, "20");
+            defaultMap.put(FessConfig.QUERY_HIGHLIGHT_BOUNDARY_SCANNER, "sentence");
+            defaultMap.put(FessConfig.QUERY_HIGHLIGHT_ENCODER, "default");
+            defaultMap.put(FessConfig.QUERY_HIGHLIGHT_FORCE_SOURCE, "false");
+            defaultMap.put(FessConfig.QUERY_HIGHLIGHT_FRAGMENTER, "span");
+            defaultMap.put(FessConfig.QUERY_HIGHLIGHT_FRAGMENT_OFFSET, "-1");
+            defaultMap.put(FessConfig.QUERY_HIGHLIGHT_NO_MATCH_SIZE, "0");
+            defaultMap.put(FessConfig.QUERY_HIGHLIGHT_ORDER, "score");
+            defaultMap.put(FessConfig.QUERY_HIGHLIGHT_PHRASE_LIMIT, "256");
             defaultMap.put(FessConfig.QUERY_HIGHLIGHT_CONTENT_DESCRIPTION_FIELDS, "hl_content,digest");
             defaultMap.put(FessConfig.QUERY_MAX_SEARCH_RESULT_OFFSET, "100000");
             defaultMap.put(FessConfig.QUERY_ADDITIONAL_DEFAULT_FIELDS, "");

+ 18 - 0
src/main/java/org/codelibs/fess/mylasta/direction/FessProp.java

@@ -71,6 +71,8 @@ import org.lastaflute.web.validation.theme.typed.LongTypeValidator;
 
 public interface FessProp {
 
+    String QUERY_HIGHLIGHT_BOUNDARY_CHARS = "queryHighlightBoundaryChars";
+
     String QUERY_TRACK_TOTAL_HITS_VALUE = "queryTrackTotalHitsValue";
 
     String CORS_ALLOW_ORIGIN = "CorsAllowOrigin";
@@ -2022,4 +2024,20 @@ public interface FessProp {
         }
         return pattern.matcher(version).matches();
     }
+
+    String getQueryHighlightBoundaryChars();
+
+    default char[] getQueryHighlightBoundaryCharsAsArray() {
+        char[] chars = (char[]) propMap.get(QUERY_HIGHLIGHT_BOUNDARY_CHARS);
+        if (chars == null) {
+            final String value = getQueryHighlightBoundaryChars();
+            chars = new char[value.length()];
+            for (int i = 0; i < value.length(); i++) {
+                chars[i] = value.charAt(i);
+            }
+            propMap.put(QUERY_HIGHLIGHT_BOUNDARY_CHARS, chars);
+        }
+        return chars;
+    }
+
 }

+ 10 - 0
src/main/resources/fess_config.properties

@@ -340,6 +340,16 @@ query.highlight.number.of.fragments=2
 query.highlight.type=fvh
 query.highlight.tag.pre=<strong>
 query.highlight.tag.post=</strong>
+query.highlight.boundary.chars=\u0009\u000A\u0013\u0020
+query.highlight.boundary.max.scan=20
+query.highlight.boundary.scanner=sentence
+query.highlight.encoder=default
+query.highlight.force.source=false
+query.highlight.fragmenter=span
+query.highlight.fragment.offset=-1
+query.highlight.no.match.size=0
+query.highlight.order=score
+query.highlight.phrase.limit=256
 query.highlight.content.description.fields=hl_content,digest
 query.max.search.result.offset=100000
 query.additional.default.fields=