View Javadoc
1   /*
2    * Copyright 2012-2025 CodeLibs Project and the Others.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13   * either express or implied. See the License for the specific language
14   * governing permissions and limitations under the License.
15   */
16  package org.codelibs.fess.mylasta.direction;
17  
18  import static org.codelibs.core.stream.StreamUtil.split;
19  import static org.codelibs.core.stream.StreamUtil.stream;
20  
21  import java.net.Authenticator;
22  import java.net.InetSocketAddress;
23  import java.net.PasswordAuthentication;
24  import java.net.Proxy;
25  import java.net.Proxy.Type;
26  import java.net.SocketAddress;
27  import java.util.ArrayList;
28  import java.util.Arrays;
29  import java.util.Collections;
30  import java.util.Date;
31  import java.util.Enumeration;
32  import java.util.HashMap;
33  import java.util.HashSet;
34  import java.util.List;
35  import java.util.Locale;
36  import java.util.Map;
37  import java.util.Set;
38  import java.util.concurrent.ConcurrentHashMap;
39  import java.util.regex.Pattern;
40  import java.util.stream.Collectors;
41  import java.util.stream.Stream;
42  
43  import javax.naming.directory.Attribute;
44  import javax.naming.directory.BasicAttribute;
45  
46  import org.codelibs.core.exception.ClassNotFoundRuntimeException;
47  import org.codelibs.core.lang.StringUtil;
48  import org.codelibs.core.misc.Pair;
49  import org.codelibs.core.misc.Tuple3;
50  import org.codelibs.core.misc.Tuple4;
51  import org.codelibs.fess.Constants;
52  import org.codelibs.fess.helper.PermissionHelper;
53  import org.codelibs.fess.ldap.LdapUtil;
54  import org.codelibs.fess.mylasta.action.FessUserBean;
55  import org.codelibs.fess.taglib.FessFunctions;
56  import org.codelibs.fess.util.ComponentUtil;
57  import org.codelibs.fess.util.JvmUtil;
58  import org.codelibs.fess.util.PrunedTag;
59  import org.dbflute.optional.OptionalThing;
60  import org.dbflute.util.DfTypeUtil;
61  import org.lastaflute.job.LaJob;
62  import org.lastaflute.job.subsidiary.JobConcurrentExec;
63  import org.lastaflute.web.validation.RequiredValidator;
64  import org.lastaflute.web.validation.theme.typed.DoubleTypeValidator;
65  import org.lastaflute.web.validation.theme.typed.FloatTypeValidator;
66  import org.lastaflute.web.validation.theme.typed.IntegerTypeValidator;
67  import org.lastaflute.web.validation.theme.typed.LongTypeValidator;
68  import org.opensearch.search.sort.SortBuilder;
69  import org.opensearch.search.sort.SortBuilders;
70  import org.opensearch.search.sort.SortOrder;
71  
72  public interface FessProp {
73  
74      String API_PING_SEARCH_ENGINE_FIELD_SET = "apiPingSearchEngineFieldSet";
75  
76      String QUERY_HIGHLIGHT_TERMINAL_CHARS = "queryHighlightTerminalChars";
77  
78      String QUERY_HIGHLIGHT_BOUNDARY_CHARS = "queryHighlightBoundaryChars";
79  
80      String QUERY_TRACK_TOTAL_HITS_VALUE = "queryTrackTotalHitsValue";
81  
82      String CORS_ALLOW_ORIGIN = "CorsAllowOrigin";
83  
84      String API_DASHBOARD_RESPONSE_HEADER_LIST = "apiDashboardResponseHeaderList";
85  
86      String API_JSON_RESPONSE_HEADER_LIST = "apiJsonResponseHeaderList";
87  
88      String API_GSA_RESPONSE_HEADER_LIST = "apiGsaResponseHeaderList";
89  
90      String SMB_AVAILABLE_SID_TYPES = "smbAvailableSidTypes";
91  
92      String LOGGING_SEARCH_DOCS_FIELDS = "loggingSearchDocsFields";
93  
94      String API_SEARCH_ACCEPT_REFERERS = "apiSearchAcceptReferers";
95  
96      String QUERY_GSA_RESPONSE_FIELDS = "queryGsaResponseFields";
97  
98      String THUMBNAIL_HTML_IMAGE_EXCLUDE_EXTENSIONS = "ThumbnailHtmlImageExcludeExtensions";
99  
100     String VIRTUAL_HOST_VALUE = "VirtualHostValue";
101 
102     String QUERY_DEFAULT_LANGUAGES = "queryDefaultLanguages";
103 
104     String HTML_PROXY = "httpProxy";
105 
106     String CRAWLER_FAILURE_URL_STATUS_CODES = "crawlerFailureUrlStatusCodes";
107 
108     String VIRTUAL_HOST_HEADERS = "virtualHostHeaders";
109 
110     String QUERY_COLLAPSE_INNER_HITS_SORTS = "queryCollapseInnerHitsSorts";
111 
112     String USER_CODE_PATTERN = "userCodePattern";
113 
114     String API_ADMIN_ACCESS_PERMISSION_SET = "apiAdminAccessPermissionSet";
115 
116     String CRAWLER_DOCUMENT_SPACE_CHARS = "crawlerDocumentSpaceChars";
117 
118     String CRAWLER_DOCUMENT_SPACES = "crawlerDocumentSpaces";
119 
120     String CRAWLER_DOCUMENT_FULLSTOP_CHARS = "crawlerDocumentFullstopChars";
121 
122     String INDEX_ADMIN_ARRAY_FIELD_SET = "indexAdminArrayFieldSet";
123 
124     String INDEX_ADMIN_DATE_FIELD_SET = "indexAdminDateFieldSet";
125 
126     String INDEX_ADMIN_INTEGER_FIELD_SET = "indexAdminIntegerFieldSet";
127 
128     String INDEX_ADMIN_LONG_FIELD_SET = "indexAdminLongFieldSet";
129 
130     String INDEX_ADMIN_FLOAT_FIELD_SET = "indexAdminFloatFieldSet";
131 
132     String INDEX_ADMIN_DOUBLE_FIELD_SET = "indexAdminDoubleFieldSet";
133 
134     String AUTHENTICATION_ADMIN_ROLES = "authenticationAdminRoles";
135 
136     String SEARCH_GUEST_ROLE_LIST = "searchGuestPermissionList";
137 
138     String SUGGEST_SEARCH_LOG_PERMISSIONS = "suggestSearchLogPermissions";
139 
140     String GROUP_VALUE_PREFIX = "group:";
141 
142     String ROLE_VALUE_PREFIX = "role:";
143 
144     String APP_VALUES = "appValues";
145 
146     String DEFAULT_SORT_VALUES = "defaultSortValues";
147 
148     String DEFAULT_LABEL_VALUES = "defaultLabelValues";
149 
150     String VIRTUAL_HOST_VALUES = "virtualHostValues";
151 
152     String QUERY_LANGUAGE_MAPPING = "queryLanguageMapping";
153 
154     String CRAWLER_METADATA_NAME_MAPPING = "crawlerMetadataNameMapping";
155 
156     String CRAWLER_METADATA_CONTENT_EXCLUDES = "crawlerMetadataContentExcludes";
157 
158     Map<String, Object> propMap = new ConcurrentHashMap<>();
159 
160     String getOrDefault(String propertyKey, String defaultValue);
161 
162     //
163     // system.properties
164     //
165 
166     default void storeSystemProperties() {
167         ComponentUtil.getSystemProperties().store();
168     }
169 
170     default String getSystemProperty(final String key) {
171         return ComponentUtil.getSystemProperties().getProperty(key, System.getProperty(Constants.SYSTEM_PROP_PREFIX + key));
172     }
173 
174     default String getSystemProperty(final String key, final String defaultValue) {
175         return ComponentUtil.getSystemProperties().getProperty(key, System.getProperty(Constants.SYSTEM_PROP_PREFIX + key, defaultValue));
176     }
177 
178     default void setSystemProperty(final String key, final String value) {
179         if (value != null) {
180             ComponentUtil.getSystemProperties().setProperty(key, value);
181         } else {
182             ComponentUtil.getSystemProperties().remove(key);
183         }
184     }
185 
186     default boolean getSystemPropertyAsBoolean(final String key, final boolean defaultValue) {
187         return Constants.TRUE.equalsIgnoreCase(getSystemProperty(key, defaultValue ? Constants.TRUE : Constants.FALSE));
188     }
189 
190     default void setSystemPropertyAsBoolean(final String key, final boolean value) {
191         setSystemProperty(key, value ? Constants.TRUE : Constants.FALSE);
192     }
193 
194     default int getSystemPropertyAsInt(final String key, final int defaultValue) {
195         final String value = getSystemProperty(key);
196         if (value != null) {
197             try {
198                 return Integer.parseInt(value);
199             } catch (final NumberFormatException e) {
200                 // ignore
201             }
202         }
203         return defaultValue;
204     }
205 
206     default void setSystemPropertyAsInt(final String key, final int value) {
207         setSystemProperty(key, Integer.toString(value));
208     }
209 
210     default void setSearchFileProxy(final boolean value) {
211         setSystemPropertyAsBoolean(Constants.SEARCH_FILE_PROXY_PROPERTY, value);
212     }
213 
214     default boolean isSearchFileProxyEnabled() {
215         return getSystemPropertyAsBoolean(Constants.SEARCH_FILE_PROXY_PROPERTY, true);
216     }
217 
218     default void setUseBrowserLocaleForSearch(final boolean value) {
219         setSystemPropertyAsBoolean(Constants.USE_BROWSER_LOCALE_FOR_SEARCH_PROPERTY, value);
220     }
221 
222     default boolean isBrowserLocaleForSearchUsed() {
223         return getSystemPropertyAsBoolean(Constants.USE_BROWSER_LOCALE_FOR_SEARCH_PROPERTY, false);
224     }
225 
226     default String[] getDefaultSortValues(final OptionalThing<FessUserBean> userBean) {
227         @SuppressWarnings("unchecked")
228         List<Pair<String, String>> list = (List<Pair<String, String>>) propMap.get(DEFAULT_SORT_VALUES);
229         if (list == null) {
230             final String value = getSystemProperty(Constants.DEFAULT_SORT_VALUE_PROPERTY);
231             if (StringUtil.isBlank(value)) {
232                 list = Collections.emptyList();
233             } else {
234                 final Set<String> keySet = new HashSet<>();
235                 list = split(value, "\n").get(stream -> stream.filter(StringUtil::isNotBlank).map(s -> {
236                     final String[] pair = s.split("=");
237                     if (pair.length == 1) {
238                         return new Pair<>(StringUtil.EMPTY, pair[0].trim());
239                     }
240                     if (pair.length == 2) {
241                         String sortValue = pair[1].trim();
242                         if (StringUtil.isBlank(sortValue) || "score".equals(sortValue)) {
243                             sortValue = "score.desc";
244                         }
245                         return new Pair<>(pair[0].trim(), sortValue);
246                     }
247                     return null;
248                 }).filter(o -> o != null && keySet.add(o.getFirst())).collect(Collectors.toList()));
249             }
250             propMap.put(DEFAULT_SORT_VALUES, list);
251         }
252         return list.stream().map(p -> {
253             final String key = p.getFirst();
254             if (StringUtil.isEmpty(key) || userBean
255                     .map(user -> stream(user.getRoles()).get(stream -> stream.anyMatch(s -> (ROLE_VALUE_PREFIX + s).equals(key)))
256                             || stream(user.getGroups()).get(stream -> stream.anyMatch(s -> (GROUP_VALUE_PREFIX + s).equals(key))))
257                     .orElse(false)) {
258                 return p.getSecond();
259             }
260             return null;
261         }).filter(StringUtil::isNotBlank).toArray(n -> new String[n]);
262     }
263 
264     default void setDefaultSortValue(final String value) {
265         setSystemProperty(Constants.DEFAULT_SORT_VALUE_PROPERTY, value);
266         propMap.remove(DEFAULT_SORT_VALUES);
267     }
268 
269     default String getDefaultSortValue() {
270         return getSystemProperty(Constants.DEFAULT_SORT_VALUE_PROPERTY, StringUtil.EMPTY);
271     }
272 
273     default String[] getDefaultLabelValues(final OptionalThing<FessUserBean> userBean) {
274         @SuppressWarnings("unchecked")
275         Map<String, List<String>> map = (Map<String, List<String>>) propMap.get(DEFAULT_LABEL_VALUES);
276         if (map == null) {
277             final String value = getSystemProperty(Constants.DEFAULT_LABEL_VALUE_PROPERTY);
278             if (StringUtil.isBlank(value)) {
279                 map = Collections.emptyMap();
280             } else {
281                 final Set<String> keySet = new HashSet<>();
282                 map = split(value, "\n").get(stream -> stream.filter(StringUtil::isNotBlank).map(s -> {
283                     final String[] pair = s.split("=");
284                     if (pair.length == 1) {
285                         return new Pair<>(StringUtil.EMPTY, pair[0].trim());
286                     }
287                     if (pair.length == 2) {
288                         return new Pair<>(pair[0].trim(), pair[1].trim());
289                     }
290                     return null;
291                 })
292                         .filter(o -> o != null && keySet.add(o.getFirst()))
293                         .collect(HashMap<String, List<String>>::new, (m, d) -> m.put(d.getFirst(), Arrays.asList(d.getSecond().split(","))),
294                                 HashMap::putAll));
295             }
296             propMap.put(DEFAULT_LABEL_VALUES, map);
297         }
298         return map.entrySet().stream().flatMap(e -> {
299             final String key = e.getKey();
300             if (StringUtil.isEmpty(key) || userBean
301                     .map(user -> stream(user.getRoles()).get(stream -> stream.anyMatch(s -> (ROLE_VALUE_PREFIX + s).equals(key)))
302                             || stream(user.getGroups()).get(stream -> stream.anyMatch(s -> (GROUP_VALUE_PREFIX + s).equals(key))))
303                     .orElse(false)) {
304                 return e.getValue().stream();
305             }
306             return Stream.empty();
307         }).filter(StringUtil::isNotBlank).toArray(n -> new String[n]);
308     }
309 
310     default void setAppValue(final String value) {
311         setSystemProperty(Constants.APP_VALUE_PROPERTY, value);
312         propMap.remove(APP_VALUES);
313     }
314 
315     default String getAppValue() {
316         return getSystemProperty(Constants.APP_VALUE_PROPERTY, StringUtil.EMPTY);
317     }
318 
319     default void setDefaultLabelValue(final String value) {
320         setSystemProperty(Constants.DEFAULT_LABEL_VALUE_PROPERTY, value);
321         propMap.remove(DEFAULT_LABEL_VALUES);
322     }
323 
324     default String getDefaultLabelValue() {
325         return getSystemProperty(Constants.DEFAULT_LABEL_VALUE_PROPERTY, StringUtil.EMPTY);
326     }
327 
328     default void setVirtualHostValue(final String value) {
329         setSystemProperty(Constants.VIRTUAL_HOST_VALUE_PROPERTY, value);
330         propMap.remove(VIRTUAL_HOST_HEADERS);
331     }
332 
333     default String getVirtualHostValue() {
334         return getSystemProperty(Constants.VIRTUAL_HOST_VALUE_PROPERTY, getVirtualHostHeaders());
335     }
336 
337     default void setLoginRequired(final boolean value) {
338         setSystemPropertyAsBoolean(Constants.LOGIN_REQUIRED_PROPERTY, value);
339     }
340 
341     default boolean isLoginRequired() {
342         return getSystemPropertyAsBoolean(Constants.LOGIN_REQUIRED_PROPERTY, false);
343     }
344 
345     default void setResultCollapsed(final boolean value) {
346         setSystemPropertyAsBoolean(Constants.RESULT_COLLAPSED_PROPERTY, value);
347     }
348 
349     default boolean isResultCollapsed() {
350         return switch (getFesenType()) {
351         case Constants.FESEN_TYPE_CLOUD, Constants.FESEN_TYPE_AWS -> false;
352         default -> getSystemPropertyAsBoolean(Constants.RESULT_COLLAPSED_PROPERTY, false);
353         };
354     }
355 
356     default void setLoginLinkEnabled(final boolean value) {
357         setSystemPropertyAsBoolean(Constants.LOGIN_LINK_ENALBED_PROPERTY, value);
358     }
359 
360     default boolean isLoginLinkEnabled() {
361         return getSystemPropertyAsBoolean(Constants.LOGIN_LINK_ENALBED_PROPERTY, true);
362     }
363 
364     default void setThumbnailEnabled(final boolean value) {
365         setSystemPropertyAsBoolean(Constants.THUMBNAIL_ENALBED_PROPERTY, value);
366     }
367 
368     default boolean isThumbnailEnabled() {
369         return getSystemPropertyAsBoolean(Constants.THUMBNAIL_ENALBED_PROPERTY, false);
370     }
371 
372     default void setIncrementalCrawling(final boolean value) {
373         setSystemPropertyAsBoolean(Constants.INCREMENTAL_CRAWLING_PROPERTY, value);
374     }
375 
376     default boolean isIncrementalCrawling() {
377         return getSystemPropertyAsBoolean(Constants.INCREMENTAL_CRAWLING_PROPERTY, true);
378     }
379 
380     default void setDayForCleanup(final int value) {
381         setSystemPropertyAsInt(Constants.DAY_FOR_CLEANUP_PROPERTY, value);
382     }
383 
384     default int getDayForCleanup() {
385         return getSystemPropertyAsInt(Constants.DAY_FOR_CLEANUP_PROPERTY, Constants.DEFAULT_DAY_FOR_CLEANUP);
386     }
387 
388     default void setCrawlingThreadCount(final int value) {
389         setSystemPropertyAsInt(Constants.CRAWLING_THREAD_COUNT_PROPERTY, value);
390     }
391 
392     default int getCrawlingThreadCount() {
393         return getSystemPropertyAsInt(Constants.CRAWLING_THREAD_COUNT_PROPERTY, 5);
394     }
395 
396     default void setSearchLog(final boolean value) {
397         setSystemPropertyAsBoolean(Constants.SEARCH_LOG_PROPERTY, value);
398     }
399 
400     default boolean isSearchLog() {
401         return getSystemPropertyAsBoolean(Constants.SEARCH_LOG_PROPERTY, true);
402     }
403 
404     default void setUserInfo(final boolean value) {
405         setSystemPropertyAsBoolean(Constants.USER_INFO_PROPERTY, value);
406     }
407 
408     default boolean isUserInfo() {
409         return getSystemPropertyAsBoolean(Constants.USER_INFO_PROPERTY, true);
410     }
411 
412     default void setUserFavorite(final boolean value) {
413         setSystemPropertyAsBoolean(Constants.USER_FAVORITE_PROPERTY, value);
414     }
415 
416     default boolean isUserFavorite() {
417         return getSystemPropertyAsBoolean(Constants.USER_FAVORITE_PROPERTY, false);
418     }
419 
420     default void setWebApiJson(final boolean value) {
421         setSystemPropertyAsBoolean(Constants.WEB_API_JSON_PROPERTY, value);
422     }
423 
424     default boolean isWebApiJson() {
425         return getSystemPropertyAsBoolean(Constants.WEB_API_JSON_PROPERTY, true);
426     }
427 
428     default boolean isWebApiGsa() {
429         return getSystemPropertyAsBoolean(Constants.WEB_API_GSA_PROPERTY, false);
430     }
431 
432     default void setAppendQueryParameter(final boolean value) {
433         setSystemPropertyAsBoolean(Constants.APPEND_QUERY_PARAMETER_PROPERTY, value);
434     }
435 
436     default boolean isAppendQueryParameter() {
437         return getSystemPropertyAsBoolean(Constants.APPEND_QUERY_PARAMETER_PROPERTY, false);
438     }
439 
440     default void setIgnoreFailureType(final String value) {
441         setSystemProperty(Constants.IGNORE_FAILURE_TYPE_PROPERTY, value);
442     }
443 
444     default String getIgnoreFailureType() {
445         return getSystemProperty(Constants.IGNORE_FAILURE_TYPE_PROPERTY, Constants.DEFAULT_IGNORE_FAILURE_TYPE);
446     }
447 
448     default void setFailureCountThreshold(final int value) {
449         setSystemPropertyAsInt(Constants.FAILURE_COUNT_THRESHOLD_PROPERTY, value);
450     }
451 
452     default int getFailureCountThreshold() {
453         return getSystemPropertyAsInt(Constants.FAILURE_COUNT_THRESHOLD_PROPERTY, Constants.DEFAULT_FAILURE_COUNT);
454     }
455 
456     default void setWebApiPopularWord(final boolean value) {
457         setSystemPropertyAsBoolean(Constants.WEB_API_POPULAR_WORD_PROPERTY, value);
458     }
459 
460     default boolean isWebApiPopularWord() {
461         return getSystemPropertyAsBoolean(Constants.WEB_API_POPULAR_WORD_PROPERTY, true);
462     }
463 
464     default void setCsvFileEncoding(final String value) {
465         setSystemProperty(Constants.CSV_FILE_ENCODING_PROPERTY, value);
466     }
467 
468     default String getCsvFileEncoding() {
469         return getSystemProperty(Constants.CSV_FILE_ENCODING_PROPERTY, Constants.UTF_8);
470     }
471 
472     default void setPurgeSearchLogDay(final int value) {
473         setSystemPropertyAsInt(Constants.PURGE_SEARCH_LOG_DAY_PROPERTY, value);
474     }
475 
476     default int getPurgeSearchLogDay() {
477         return getSystemPropertyAsInt(Constants.PURGE_SEARCH_LOG_DAY_PROPERTY, DfTypeUtil.toInteger(Constants.DEFAULT_PURGE_DAY));
478     }
479 
480     default void setPurgeJobLogDay(final int value) {
481         setSystemPropertyAsInt(Constants.PURGE_JOB_LOG_DAY_PROPERTY, value);
482     }
483 
484     default int getPurgeJobLogDay() {
485         return getSystemPropertyAsInt(Constants.PURGE_JOB_LOG_DAY_PROPERTY, DfTypeUtil.toInteger(Constants.DEFAULT_PURGE_DAY));
486     }
487 
488     default void setPurgeUserInfoDay(final int value) {
489         setSystemPropertyAsInt(Constants.PURGE_USER_INFO_DAY_PROPERTY, value);
490     }
491 
492     default int getPurgeUserInfoDay() {
493         return getSystemPropertyAsInt(Constants.PURGE_USER_INFO_DAY_PROPERTY, DfTypeUtil.toInteger(Constants.DEFAULT_PURGE_DAY));
494     }
495 
496     default void setPurgeByBots(final String value) {
497         setSystemProperty(Constants.PURGE_BY_BOTS_PROPERTY, value);
498     }
499 
500     default String getPurgeByBots() {
501         return getSystemProperty(Constants.PURGE_BY_BOTS_PROPERTY, Constants.DEFAULT_PURGE_BY_BOTS);
502     }
503 
504     default boolean hasNotification() {
505         return StringUtil.isNotBlank(getNotificationTo()) || StringUtil.isNotBlank(getSlackWebhookUrls())
506                 || StringUtil.isNotBlank(getGoogleChatWebhookUrls());
507     }
508 
509     default void setSlackWebhookUrls(final String value) {
510         setSystemProperty(Constants.SLACK_WEBHOOK_URLS_PROPERTY, value);
511     }
512 
513     default String getSlackWebhookUrls() {
514         return getSystemProperty(Constants.SLACK_WEBHOOK_URLS_PROPERTY, StringUtil.EMPTY);
515     }
516 
517     default void setGoogleChatWebhookUrls(final String value) {
518         setSystemProperty(Constants.GOOGLE_CHAT_WEBHOOK_URLS_PROPERTY, value);
519     }
520 
521     default String getGoogleChatWebhookUrls() {
522         return getSystemProperty(Constants.GOOGLE_CHAT_WEBHOOK_URLS_PROPERTY, StringUtil.EMPTY);
523     }
524 
525     default void setNotificationTo(final String value) {
526         setSystemProperty(Constants.NOTIFICATION_TO_PROPERTY, value);
527     }
528 
529     default String getNotificationTo() {
530         return getSystemProperty(Constants.NOTIFICATION_TO_PROPERTY, StringUtil.EMPTY);
531     }
532 
533     default void setLogNotificationEnabled(final boolean value) {
534         setSystemPropertyAsBoolean(Constants.LOG_NOTIFICATION_ENABLED_PROPERTY, value);
535     }
536 
537     default boolean isLogNotificationEnabled() {
538         return getSystemPropertyAsBoolean(Constants.LOG_NOTIFICATION_ENABLED_PROPERTY, false);
539     }
540 
541     default void setLogNotificationLevel(final String value) {
542         setSystemProperty(Constants.LOG_NOTIFICATION_LEVEL_PROPERTY, value);
543     }
544 
545     default String getLogNotificationLevel() {
546         return getSystemProperty(Constants.LOG_NOTIFICATION_LEVEL_PROPERTY, "ERROR");
547     }
548 
549     default void setSuggestSearchLog(final boolean value) {
550         setSystemPropertyAsBoolean(Constants.SUGGEST_SEARCH_LOG_PROPERTY, value);
551     }
552 
553     default boolean isSuggestSearchLog() {
554         return getSystemPropertyAsBoolean(Constants.SUGGEST_SEARCH_LOG_PROPERTY, true);
555     }
556 
557     default void setSuggestDocuments(final boolean value) {
558         setSystemPropertyAsBoolean(Constants.SUGGEST_DOCUMENTS_PROPERTY, value);
559     }
560 
561     default boolean isSuggestDocuments() {
562         return getSystemPropertyAsBoolean(Constants.SUGGEST_DOCUMENTS_PROPERTY, true);
563     }
564 
565     default void setPurgeSuggestSearchLogDay(final int value) {
566         setSystemPropertyAsInt(Constants.PURGE_SUGGEST_SEARCH_LOG_DAY_PROPERTY, value);
567     }
568 
569     default int getPurgeSuggestSearchLogDay() {
570         return getSystemPropertyAsInt(Constants.PURGE_SUGGEST_SEARCH_LOG_DAY_PROPERTY,
571                 DfTypeUtil.toInteger(Constants.DEFAULT_SUGGEST_PURGE_DAY));
572     }
573 
574     default void setLdapInitialContextFactory(final String value) {
575         setSystemProperty(Constants.LDAP_INITIAL_CONTEXT_FACTORY, value);
576     }
577 
578     default String getLdapInitialContextFactory() {
579         return getSystemProperty(Constants.LDAP_INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
580     }
581 
582     default void setLdapSecurityAuthentication(final String value) {
583         setSystemProperty(Constants.LDAP_SECURITY_AUTHENTICATION, value);
584     }
585 
586     default String getLdapSecurityAuthentication() {
587         return getSystemProperty(Constants.LDAP_SECURITY_AUTHENTICATION, "simple");
588     }
589 
590     default void setLdapProviderUrl(final String value) {
591         setSystemProperty(Constants.LDAP_PROVIDER_URL, value);
592     }
593 
594     default String getLdapProviderUrl() {
595         return getSystemProperty(Constants.LDAP_PROVIDER_URL);
596     }
597 
598     default void setLdapSecurityPrincipal(final String value) {
599         setSystemProperty(Constants.LDAP_SECURITY_PRINCIPAL, value);
600     }
601 
602     default String getLdapMemberofAttribute() {
603         return getSystemProperty(Constants.LDAP_MEMBEROF_ATTRIBUTE, "memberOf");
604     }
605 
606     default void setLdapMemberofAttribute(final String value) {
607         setSystemProperty(Constants.LDAP_MEMBEROF_ATTRIBUTE, value);
608     }
609 
610     default void setStorageEndpoint(final String value) {
611         setSystemProperty(Constants.STORAGE_ENDPOINT, value);
612     }
613 
614     default String getStorageEndpoint() {
615         return getSystemProperty(Constants.STORAGE_ENDPOINT, StringUtil.EMPTY);
616     }
617 
618     default void setStorageAccessKey(final String value) {
619         setSystemProperty(Constants.STORAGE_ACCESS_KEY, value);
620     }
621 
622     default String getStorageAccessKey() {
623         return getSystemProperty(Constants.STORAGE_ACCESS_KEY, StringUtil.EMPTY);
624     }
625 
626     default void setStorageSecretKey(final String value) {
627         setSystemProperty(Constants.STORAGE_SECRET_KEY, value);
628     }
629 
630     default String getStorageSecretKey() {
631         return getSystemProperty(Constants.STORAGE_SECRET_KEY, StringUtil.EMPTY);
632     }
633 
634     default void setStorageBucket(final String value) {
635         setSystemProperty(Constants.STORAGE_BUCKET, value);
636     }
637 
638     default String getStorageBucket() {
639         return getSystemProperty(Constants.STORAGE_BUCKET, StringUtil.EMPTY);
640     }
641 
642     default void setStorageType(final String value) {
643         setSystemProperty(Constants.STORAGE_TYPE, value);
644     }
645 
646     default String getStorageType() {
647         return getSystemProperty(Constants.STORAGE_TYPE, "auto");
648     }
649 
650     default void setStorageRegion(final String value) {
651         setSystemProperty(Constants.STORAGE_REGION, value);
652     }
653 
654     default String getStorageRegion() {
655         return getSystemProperty(Constants.STORAGE_REGION, "us-east-1");
656     }
657 
658     default void setStorageProjectId(final String value) {
659         setSystemProperty(Constants.STORAGE_PROJECT_ID, value);
660     }
661 
662     default String getStorageProjectId() {
663         return getSystemProperty(Constants.STORAGE_PROJECT_ID, StringUtil.EMPTY);
664     }
665 
666     default void setStorageCredentialsPath(final String value) {
667         setSystemProperty(Constants.STORAGE_CREDENTIALS_PATH, value);
668     }
669 
670     default String getStorageCredentialsPath() {
671         return getSystemProperty(Constants.STORAGE_CREDENTIALS_PATH, StringUtil.EMPTY);
672     }
673 
674     default void setRagLlmName(final String value) {
675         setSystemProperty(Constants.RAG_LLM_NAME, value);
676     }
677 
678     default String getRagLlmName() {
679         return getSystemProperty(Constants.RAG_LLM_NAME, "ollama");
680     }
681 
682     Integer getLdapMaxUsernameLengthAsInteger();
683 
684     default String getLdapSecurityPrincipal(final String username) {
685         final String value;
686         final int maxLength = getLdapMaxUsernameLengthAsInteger();
687         if (username == null) {
688             value = StringUtil.EMPTY;
689         } else if (maxLength >= 0 && username.length() > maxLength) {
690             value = LdapUtil.escapeValue(username.substring(0, maxLength));
691         } else {
692             value = LdapUtil.escapeValue(username);
693         }
694         return String.format(getSystemProperty(Constants.LDAP_SECURITY_PRINCIPAL, StringUtil.EMPTY), value);
695     }
696 
697     default String getLdapSecurityPrincipal() {
698         return getSystemProperty(Constants.LDAP_SECURITY_PRINCIPAL);
699     }
700 
701     default void setLdapAdminSecurityPrincipal(final String value) {
702         setSystemProperty(Constants.LDAP_ADMIN_SECURITY_PRINCIPAL, value);
703     }
704 
705     default String getLdapAdminSecurityPrincipal() {
706         return getSystemProperty(Constants.LDAP_ADMIN_SECURITY_PRINCIPAL);
707     }
708 
709     default void setLdapAdminSecurityCredentials(final String value) {
710         setSystemProperty(Constants.LDAP_ADMIN_SECURITY_CREDENTIALS,
711                 Constants.CIPHER_PREFIX + ComponentUtil.getPrimaryCipher().encrypt(value));
712     }
713 
714     default String getLdapAdminSecurityCredentials() {
715         final String value = getSystemProperty(Constants.LDAP_ADMIN_SECURITY_CREDENTIALS);
716         if (StringUtil.isNotBlank(value) && value.startsWith(Constants.CIPHER_PREFIX)) {
717             return ComponentUtil.getPrimaryCipher().decrypt(value.substring(Constants.CIPHER_PREFIX.length()));
718         }
719         return value;
720     }
721 
722     default void setLdapBaseDn(final String value) {
723         setSystemProperty(Constants.LDAP_BASE_DN, value);
724     }
725 
726     default String getLdapBaseDn() {
727         return getSystemProperty(Constants.LDAP_BASE_DN);
728     }
729 
730     default void setLdapAccountFilter(final String value) {
731         setSystemProperty(Constants.LDAP_ACCOUNT_FILTER, value);
732     }
733 
734     default String getLdapAccountFilter() {
735         return getSystemProperty(Constants.LDAP_ACCOUNT_FILTER);
736     }
737 
738     default void setLdapGroupFilter(final String value) {
739         setSystemProperty(Constants.LDAP_GROUP_FILTER, value);
740     }
741 
742     default String getLdapGroupFilter() {
743         return getSystemProperty(Constants.LDAP_GROUP_FILTER, StringUtil.EMPTY);
744     }
745 
746     default void setNotificationLogin(final String value) {
747         setSystemProperty(Constants.NOTIFICATION_LOGIN, value);
748     }
749 
750     default String getNotificationLogin() {
751         return getSystemProperty(Constants.NOTIFICATION_LOGIN, StringUtil.EMPTY);
752     }
753 
754     default void setNotificationAdvanceSearch(final String value) {
755         setSystemProperty(Constants.NOTIFICATION_ADVANCE_SEARCH, value);
756     }
757 
758     default String getNotificationAdvanceSearch() {
759         return getSystemProperty(Constants.NOTIFICATION_ADVANCE_SEARCH, StringUtil.EMPTY);
760     }
761 
762     default void setNotificationSearchTop(final String value) {
763         setSystemProperty(Constants.NOTIFICATION_SEARCH_TOP, value);
764     }
765 
766     default String getNotificationSearchTop() {
767         return getSystemProperty(Constants.NOTIFICATION_SEARCH_TOP, StringUtil.EMPTY);
768     }
769 
770     default void setUserAgentName(final String value) {
771         setSystemProperty(Constants.CRAWLING_USER_AGENT_PROPERTY, value);
772     }
773 
774     default String getUserAgentName() {
775         return getSystemProperty(Constants.CRAWLING_USER_AGENT_PROPERTY, Constants.CRAWLING_USER_AGENT_PREFIX
776                 + ComponentUtil.getSystemHelper().getProductVersion() + Constants.CRAWLING_USER_AGENT_SUFFIX);
777     }
778 
779     default void setLtrModelName(final String value) {
780         setSystemProperty(Constants.LTR_MODEL_NAME_PROPERTY, value);
781     }
782 
783     default String getLtrModelName() {
784         return getSystemProperty(Constants.LTR_MODEL_NAME_PROPERTY, StringUtil.EMPTY);
785     }
786 
787     default void setLtrWindowSize(final int value) {
788         setSystemPropertyAsInt(Constants.LTR_WINDOW_SIZE_PROPERTY, value);
789     }
790 
791     default int getLtrWindowSize() {
792         return getSystemPropertyAsInt(Constants.LTR_WINDOW_SIZE_PROPERTY, 100);
793     }
794 
795     /**
796      * Gets the permission fields for Entra ID authentication.
797      * Uses new entraid.permission.fields key with fallback to legacy aad.permission.fields.
798      * @return Array of permission field names.
799      */
800     default String[] getEntraIdPermissionFields() {
801         String value = getSystemProperty("entraid.permission.fields", null);
802         if (StringUtil.isBlank(value)) {
803             value = getSystemProperty("aad.permission.fields", "mail");
804         }
805         return split(value, ",").get(stream -> stream.filter(StringUtil::isNotBlank).map(String::trim).toArray(n -> new String[n]));
806     }
807 
808     /**
809      * Checks if domain services are enabled for Entra ID authentication.
810      * Uses new entraid.use.ds key with fallback to legacy aad.use.ds.
811      * @return true if domain services are enabled, false otherwise.
812      */
813     default boolean isEntraIdUseDomainServices() {
814         String value = getSystemProperty("entraid.use.ds", null);
815         if (StringUtil.isBlank(value)) {
816             value = getSystemProperty("aad.use.ds", "true");
817         }
818         return Constants.TRUE.equalsIgnoreCase(value);
819     }
820 
821     default void setSsoType(final String value) {
822         setSystemProperty(Constants.SSO_TYPE_PROPERTY, value);
823     }
824 
825     default String getSsoType() {
826         return getSystemProperty(Constants.SSO_TYPE_PROPERTY, Constants.NONE);
827     }
828 
829     //
830     // fess_*.properties
831     //
832 
833     String getAuthenticationAdminRoles();
834 
835     default String[] getAuthenticationAdminRolesAsArray() {
836         String[] roles = (String[]) propMap.get(AUTHENTICATION_ADMIN_ROLES);
837         if (roles == null) {
838             roles = getAuthenticationAdminRoles().split(",");
839             propMap.put(AUTHENTICATION_ADMIN_ROLES, roles);
840         }
841         return roles;
842     }
843 
844     String getJvmCrawlerOptions();
845 
846     default String[] getJvmCrawlerOptionsAsArray() {
847         return JvmUtil.filterJvmOptions(getJvmCrawlerOptions().split("\n"));
848     }
849 
850     String getJvmSuggestOptions();
851 
852     default String[] getJvmSuggestOptionsAsArray() {
853         return JvmUtil.filterJvmOptions(getJvmSuggestOptions().split("\n"));
854     }
855 
856     String getJvmThumbnailOptions();
857 
858     default String[] getJvmThumbnailOptionsAsArray() {
859         return JvmUtil.filterJvmOptions(getJvmThumbnailOptions().split("\n"));
860     }
861 
862     String getCrawlerDocumentHtmlPrunedTags();
863 
864     default PrunedTag[] getCrawlerDocumentHtmlPrunedTagsAsArray() {
865         PrunedTag[] tags = (PrunedTag[]) propMap.get("crawlerDocumentHtmlPrunedTags");
866         if (tags == null) {
867             tags = PrunedTag.parse(getCrawlerDocumentHtmlPrunedTags());
868             propMap.put("crawlerDocumentHtmlPrunedTags", tags);
869         }
870         return tags;
871     }
872 
873     String getCrawlerDocumentCacheHtmlMimetypes();
874 
875     default boolean isHtmlMimetypeForCache(final String mimetype) {
876         final String[] mimetypes = getCrawlerDocumentCacheHtmlMimetypes().split(",");
877         if (mimetypes.length == 1 && StringUtil.isBlank(mimetypes[0])) {
878             return true;
879         }
880         return stream(mimetypes).get(stream -> stream.anyMatch(s -> s.equalsIgnoreCase(mimetype)));
881     }
882 
883     String getCrawlerDocumentCacheSupportedMimetypes();
884 
885     default boolean isSupportedDocumentCacheMimetypes(final String mimetype) {
886         final String[] mimetypes = getCrawlerDocumentCacheSupportedMimetypes().split(",");
887         if (mimetypes.length == 1 && StringUtil.isBlank(mimetypes[0])) {
888             return true;
889         }
890         return stream(mimetypes).get(stream -> stream.anyMatch(s -> s.equalsIgnoreCase(mimetype)));
891     }
892 
893     String getIndexerClickCountEnabled();
894 
895     default boolean getIndexerClickCountEnabledAsBoolean() {
896         return Constants.TRUE.equalsIgnoreCase(getIndexerClickCountEnabled());
897     }
898 
899     String getIndexerFavoriteCountEnabled();
900 
901     default boolean getIndexerFavoriteCountEnabledAsBoolean() {
902         return Constants.TRUE.equalsIgnoreCase(getIndexerFavoriteCountEnabled());
903     }
904 
905     String getApiAccessTokenRequired();
906 
907     default boolean getApiAccessTokenRequiredAsBoolean() {
908         return Constants.TRUE.equalsIgnoreCase(getApiAccessTokenRequired());
909     }
910 
911     String getIndexerThreadDumpEnabled();
912 
913     default boolean getIndexerThreadDumpEnabledAsBoolean() {
914         return Constants.TRUE.equalsIgnoreCase(getIndexerThreadDumpEnabled());
915     }
916 
917     String getIndexBackupTargets();
918 
919     String getIndexBackupLogTargets();
920 
921     default String[] getIndexBackupAllTargets() {
922         return split(getIndexBackupTargets() + "," + getIndexBackupLogTargets(), ",")
923                 .get(stream -> stream.filter(StringUtil::isNotBlank).map(String::trim).toArray(n -> new String[n]));
924     }
925 
926     String getJobSystemJobIds();
927 
928     default boolean isSystemJobId(final String id) {
929         if (StringUtil.isBlank(getJobSystemJobIds())) {
930             return false;
931         }
932         return split(getJobSystemJobIds(), ",").get(stream -> stream.anyMatch(s -> s.equals(id)));
933     }
934 
935     String getSmbAvailableSidTypes();
936 
937     default Integer getAvailableSmbSidType(final int sidType) {
938         @SuppressWarnings("unchecked")
939         Map<Integer, Integer> params = (Map<Integer, Integer>) propMap.get(SMB_AVAILABLE_SID_TYPES);
940         if (params == null) {
941             params = split(getSmbAvailableSidTypes(), ",").get(stream -> stream.map(s -> {
942                 final String[] v = s.split(":");
943                 if (v.length == 1) {
944                     final int x = DfTypeUtil.toInteger(v[0].trim());
945                     return new Pair<>(x, x);
946                 }
947                 if (v.length == 2) {
948                     final int x = DfTypeUtil.toInteger(v[0].trim());
949                     final int y = DfTypeUtil.toInteger(v[1].trim());
950                     return new Pair<>(x, y);
951                 }
952                 return null;
953             }).filter(v -> v != null).collect(Collectors.toMap(Pair::getFirst, Pair::getSecond)));
954             propMap.put(SMB_AVAILABLE_SID_TYPES, params);
955         }
956         return params.get(sidType);
957     }
958 
959     String getSupportedLanguages();
960 
961     default String[] getSupportedLanguagesAsArray() {
962         return split(getSupportedLanguages(), ",").get(stream -> stream.filter(StringUtil::isNotBlank).toArray(n -> new String[n]));
963     }
964 
965     String getOnlineHelpSupportedLangs();
966 
967     default boolean isOnlineHelpSupportedLang(final String lang) {
968         if (StringUtil.isBlank(getOnlineHelpSupportedLangs())) {
969             return false;
970         }
971         return split(getOnlineHelpSupportedLangs(), ",").get(stream -> stream.filter(StringUtil::isNotBlank).anyMatch(s -> s.equals(lang)));
972     }
973 
974     String getForumSupportedLangs();
975 
976     default boolean isForumSupportedLang(final String lang) {
977         if (StringUtil.isBlank(getForumSupportedLangs())) {
978             return false;
979         }
980         return split(getForumSupportedLangs(), ",").get(stream -> stream.filter(StringUtil::isNotBlank).anyMatch(s -> s.equals(lang)));
981     }
982 
983     String getSupportedUploadedJsExtentions();
984 
985     default String[] getSupportedUploadedJsExtentionsAsArray() {
986         return split(getSupportedUploadedJsExtentions(), ",")
987                 .get(stream -> stream.filter(StringUtil::isNotBlank).toArray(n -> new String[n]));
988     }
989 
990     String getSupportedUploadedCssExtentions();
991 
992     default String[] getSupportedUploadedCssExtentionsAsArray() {
993         return split(getSupportedUploadedCssExtentions(), ",")
994                 .get(stream -> stream.filter(StringUtil::isNotBlank).toArray(n -> new String[n]));
995     }
996 
997     String getSupportedUploadedMediaExtentions();
998 
999     default String[] getSupportedUploadedMediaExtentionsAsArray() {
1000         return split(getSupportedUploadedMediaExtentions(), ",")
1001                 .get(stream -> stream.filter(StringUtil::isNotBlank).toArray(n -> new String[n]));
1002     }
1003 
1004     String getJobTemplateTitleWeb();
1005 
1006     String getJobTemplateTitleFile();
1007 
1008     String getJobTemplateTitleData();
1009 
1010     default String getJobTemplateTitle(final String type) {
1011         if (Constants.WEB_CRAWLER_TYPE.equals(type)) {
1012             return getJobTemplateTitleWeb();
1013         }
1014         if (Constants.FILE_CRAWLER_TYPE.equals(type)) {
1015             return getJobTemplateTitleFile();
1016         }
1017         if (Constants.DATA_CRAWLER_TYPE.equals(type)) {
1018             return getJobTemplateTitleData();
1019         }
1020         return "None";
1021     }
1022 
1023     String getSchedulerJobClass();
1024 
1025     default Class<? extends LaJob> getSchedulerJobClassAsClass() {
1026         try {
1027             return (Class<? extends LaJob>) Class.forName(getSchedulerJobClass());
1028         } catch (final ClassNotFoundException e) {
1029             throw new ClassNotFoundRuntimeException(e);
1030         }
1031     }
1032 
1033     String getSchedulerConcurrentExecMode();
1034 
1035     default JobConcurrentExec getSchedulerConcurrentExecModeAsEnum() {
1036         return JobConcurrentExec.valueOf(getSchedulerConcurrentExecMode());
1037     }
1038 
1039     String getCrawlerMetadataContentExcludes();
1040 
1041     default boolean isCrawlerMetadataContentIncluded(final String name) {
1042         Pattern[] patterns = (Pattern[]) propMap.get(CRAWLER_METADATA_CONTENT_EXCLUDES);
1043         if (patterns == null) {
1044             patterns = split(getCrawlerMetadataContentExcludes(), ",")
1045                     .get(stream -> stream.filter(StringUtil::isNotBlank).map(Pattern::compile).toArray(n -> new Pattern[n]));
1046             propMap.put(CRAWLER_METADATA_CONTENT_EXCLUDES, patterns);
1047         }
1048         return !stream(patterns).get(stream -> stream.anyMatch(p -> p.matcher(name).matches()));
1049     }
1050 
1051     String getCrawlerMetadataNameMapping();
1052 
1053     default Tuple3<String, String, String> getCrawlerMetadataNameMapping(final String name) {
1054         @SuppressWarnings("unchecked")
1055         Map<String, Tuple3<String, String, String>> params =
1056                 (Map<String, Tuple3<String, String, String>>) propMap.get(CRAWLER_METADATA_NAME_MAPPING);
1057         if (params == null) {
1058             params = split(getCrawlerMetadataNameMapping(), "\n").get(stream -> stream.filter(StringUtil::isNotBlank).map(v -> {
1059                 final String[] values = v.split("=");
1060                 if (values.length == 2) {
1061                     final String[] subValues = values[1].split(":", 3);
1062                     if (subValues.length == 3) {
1063                         return new Tuple4<>(values[0], subValues[0], subValues[1], subValues[2]);
1064                     }
1065                     if (subValues.length == 2) {
1066                         return new Tuple4<>(values[0], subValues[0], subValues[1], StringUtil.EMPTY);
1067                     }
1068                     return new Tuple4<>(values[0], values[1], Constants.MAPPING_TYPE_ARRAY, StringUtil.EMPTY);
1069                 }
1070                 return null;
1071             }).collect(Collectors.toMap(Tuple4::getValue1, d -> new Tuple3<>(d.getValue2(), d.getValue3(), d.getValue4()))));
1072             propMap.put(CRAWLER_METADATA_NAME_MAPPING, params);
1073         }
1074         return params.get(name);
1075     }
1076 
1077     default void addCrawlerMetadataNameMapping(final String name, final String fieldName, final String mappingType,
1078             final String dateFormat) {
1079         if (getCrawlerMetadataNameMapping(name) != null) {
1080             return;
1081         }
1082 
1083         @SuppressWarnings("unchecked")
1084         final Map<String, Tuple3<String, String, String>> params =
1085                 (Map<String, Tuple3<String, String, String>>) propMap.get(CRAWLER_METADATA_NAME_MAPPING);
1086         params.put(name, new Tuple3<>(fieldName, mappingType, dateFormat));
1087     }
1088 
1089     String getSuggestPopularWordFields();
1090 
1091     default String[] getSuggestPopularWordFieldsAsArray() {
1092         return split(getSuggestPopularWordFields(), "\n").get(stream -> stream.filter(StringUtil::isNotBlank).toArray(n -> new String[n]));
1093     }
1094 
1095     String getSuggestPopularWordTags();
1096 
1097     default String[] getSuggestPopularWordTagsAsArray() {
1098         return split(getSuggestPopularWordTags(), "\n").get(stream -> stream.filter(StringUtil::isNotBlank).toArray(n -> new String[n]));
1099     }
1100 
1101     String getSuggestPopularWordExcludes();
1102 
1103     default String[] getSuggestPopularWordExcludesAsArray() {
1104         return split(getSuggestPopularWordExcludes(), "\n")
1105                 .get(stream -> stream.filter(StringUtil::isNotBlank).toArray(n -> new String[n]));
1106     }
1107 
1108     String getQueryReplaceTermWithPrefixQuery();
1109 
1110     default boolean getQueryReplaceTermWithPrefixQueryAsBoolean() {
1111         return Boolean.parseBoolean(getQueryReplaceTermWithPrefixQuery());
1112     }
1113 
1114     String getQueryDefaultLanguages();
1115 
1116     String getQueryLanguageMapping();
1117 
1118     default Map<String, String> getQueryLanguageMappingMap() {
1119         @SuppressWarnings("unchecked")
1120         Map<String, String> params = (Map<String, String>) propMap.get(QUERY_LANGUAGE_MAPPING);
1121         if (params == null) {
1122             params = stream(getQueryLanguageMapping().split("\n")).get(stream -> stream.filter(StringUtil::isNotBlank).map(v -> {
1123                 final String[] values = v.split("=");
1124                 if (values.length == 2) {
1125                     return new Pair<>(values[0], values[1]);
1126                 }
1127                 return null;
1128             }).collect(Collectors.toMap(Pair::getFirst, Pair::getSecond)));
1129             propMap.put(QUERY_LANGUAGE_MAPPING, params);
1130         }
1131         return params;
1132     }
1133 
1134     default String[] normalizeQueryLanguages(final String[] langs) {
1135         final Map<String, String> mapping = getQueryLanguageMappingMap();
1136         return stream(langs).get(stream -> stream.map(s -> {
1137             if (StringUtil.isBlank(s)) {
1138                 return null;
1139             }
1140             final String lang1 = mapping.get(s);
1141             if (lang1 != null) {
1142                 return lang1;
1143             }
1144             return mapping.get(s.split("[\\-_]")[0]);
1145         }).filter(StringUtil::isNotBlank).distinct().toArray(n -> new String[n]));
1146     }
1147 
1148     default String[] getQueryLanguages(final Enumeration<Locale> locales, final String[] requestLangs) {
1149         // requestLangs > default > browser
1150         if (StringUtil.isNotBlank(getQueryDefaultLanguages())) {
1151             String[] langs = (String[]) propMap.get(QUERY_DEFAULT_LANGUAGES);
1152             if (langs == null) {
1153                 langs = split(getQueryDefaultLanguages(), ",").get(stream -> stream.map(String::trim).toArray(n -> new String[n]));
1154                 propMap.put(QUERY_DEFAULT_LANGUAGES, langs);
1155 
1156             }
1157             return normalizeQueryLanguages(langs);
1158         }
1159 
1160         if (requestLangs != null && requestLangs.length != 0) {
1161             return normalizeQueryLanguages(requestLangs);
1162         }
1163 
1164         if (locales == null) {
1165             return StringUtil.EMPTY_STRINGS;
1166         }
1167 
1168         return normalizeQueryLanguages(Collections.list(locales).stream().map(locale -> {
1169             final String language = locale.getLanguage();
1170             final String country = locale.getCountry();
1171             if (StringUtil.isNotBlank(language)) {
1172                 if (StringUtil.isNotBlank(country)) {
1173                     return language.toLowerCase(Locale.ROOT) + "-" + country.toLowerCase(Locale.ROOT);
1174                 }
1175                 return language.toLowerCase(Locale.ROOT);
1176             }
1177             return null;
1178         }).toArray(n -> new String[n]));
1179     }
1180 
1181     default Locale getQueryLocaleFromName(final String name) {
1182         if (name == null) {
1183             return Locale.ROOT;
1184         }
1185         final String value = name.toLowerCase(Locale.ROOT);
1186         final Map<String, String> mapping = getQueryLanguageMappingMap();
1187         for (final String key : mapping.keySet()) {
1188             if (value.endsWith("_" + key.toLowerCase(Locale.ROOT))) {
1189                 final String[] values = key.split("_");
1190                 switch (values.length) {
1191                 case 1:
1192                     return new Locale(values[0]);
1193                 case 2:
1194                     return new Locale(values[0], values[1]);
1195                 case 3:
1196                     return new Locale(values[0], values[1], values[2]);
1197                 default:
1198                     break;
1199                 }
1200             }
1201         }
1202         return Locale.ROOT;
1203     }
1204 
1205     String getSupportedUploadedFiles();
1206 
1207     default boolean isSupportedUploadedFile(final String name) {
1208         return split(getSuggestPopularWordExcludes(), ",")
1209                 .get(stream -> stream.filter(StringUtil::isNotBlank).anyMatch(s -> s.equals(name)));
1210     }
1211 
1212     String getLdapAdminUserObjectClasses();
1213 
1214     default Attribute getLdapAdminUserObjectClassAttribute() {
1215         final Attribute oc = new BasicAttribute("objectClass");
1216         split(getLdapAdminUserObjectClasses(), ",").of(stream -> stream.filter(StringUtil::isNotBlank).forEach(s -> oc.add(s.trim())));
1217         return oc;
1218     }
1219 
1220     String getLdapAdminUserFilter();
1221 
1222     default String getLdapAdminUserFilter(final String name) {
1223         return String.format(getLdapAdminUserFilter(), LdapUtil.escapeValue(name));
1224     }
1225 
1226     String getLdapAdminUserBaseDn();
1227 
1228     default String getLdapAdminUserSecurityPrincipal(final String name) {
1229         final StringBuilder buf = new StringBuilder(100);
1230         buf.append(String.format(getLdapAdminUserFilter(), LdapUtil.escapeValue(name)));
1231         if (StringUtil.isNotBlank(getLdapAdminUserBaseDn())) {
1232             buf.append(',').append(getLdapAdminUserBaseDn());
1233         }
1234         return buf.toString();
1235     }
1236 
1237     String getLdapAdminRoleObjectClasses();
1238 
1239     default Attribute getLdapAdminRoleObjectClassAttribute() {
1240         final Attribute oc = new BasicAttribute("objectClass");
1241         split(getLdapAdminRoleObjectClasses(), ",").of(stream -> stream.filter(StringUtil::isNotBlank).forEach(s -> oc.add(s.trim())));
1242         return oc;
1243     }
1244 
1245     String getLdapAdminRoleFilter();
1246 
1247     default String getLdapAdminRoleFilter(final String name) {
1248         return String.format(getLdapAdminRoleFilter(), LdapUtil.escapeValue(name));
1249     }
1250 
1251     String getLdapAdminRoleBaseDn();
1252 
1253     default String getLdapAdminRoleSecurityPrincipal(final String name) {
1254         final StringBuilder buf = new StringBuilder(100);
1255         buf.append(String.format(getLdapAdminRoleFilter(), LdapUtil.escapeValue(name)));
1256         if (StringUtil.isNotBlank(getLdapAdminRoleBaseDn())) {
1257             buf.append(',').append(getLdapAdminRoleBaseDn());
1258         }
1259         return buf.toString();
1260     }
1261 
1262     String getLdapAdminGroupObjectClasses();
1263 
1264     default Attribute getLdapAdminGroupObjectClassAttribute() {
1265         final Attribute oc = new BasicAttribute("objectClass");
1266         split(getLdapAdminGroupObjectClasses(), ",").of(stream -> stream.filter(StringUtil::isNotBlank).forEach(s -> oc.add(s.trim())));
1267         return oc;
1268     }
1269 
1270     String getLdapAdminGroupFilter();
1271 
1272     default String getLdapAdminGroupFilter(final String name) {
1273         return String.format(getLdapAdminGroupFilter(), LdapUtil.escapeValue(name));
1274     }
1275 
1276     String getLdapAdminGroupBaseDn();
1277 
1278     default String getLdapAdminGroupSecurityPrincipal(final String name) {
1279         final StringBuilder buf = new StringBuilder(100);
1280         buf.append(String.format(getLdapAdminGroupFilter(), LdapUtil.escapeValue(name)));
1281         if (StringUtil.isNotBlank(getLdapAdminGroupBaseDn())) {
1282             buf.append(',').append(getLdapAdminGroupBaseDn());
1283         }
1284         return buf.toString();
1285     }
1286 
1287     String getAuthenticationAdminUsers();
1288 
1289     default boolean isAdminUser(final String username) {
1290         return split(getAuthenticationAdminUsers(), ",").get(stream -> stream.anyMatch(s -> s.equals(username)));
1291     }
1292 
1293     boolean isLdapAdminEnabled();
1294 
1295     default boolean isLdapAdminEnabled(final String username) {
1296         if (isAdminUser(username)) {
1297             return false;
1298         }
1299         return isLdapAdminEnabled();
1300     }
1301 
1302     String getRoleSearchDefaultPermissions();
1303 
1304     default String[] getSearchDefaultPermissionsAsArray() {
1305         final PermissionHelper permissionHelper = ComponentUtil.getPermissionHelper();
1306         return split(getRoleSearchDefaultPermissions(), ",").get(stream -> stream.map(p -> permissionHelper.encode(p))
1307                 .filter(StringUtil::isNotBlank)
1308                 .distinct()
1309                 .toArray(n -> new String[n]));
1310     }
1311 
1312     String getRoleSearchDefaultDisplayPermissions();
1313 
1314     default String[] getSearchDefaultDisplayEncodedPermissions() {
1315         final PermissionHelper permissionHelper = ComponentUtil.getPermissionHelper();
1316         return split(getRoleSearchDefaultDisplayPermissions(), ",").get(stream -> stream.map(p -> permissionHelper.encode(p))
1317                 .filter(StringUtil::isNotBlank)
1318                 .distinct()
1319                 .toArray(n -> new String[n]));
1320     }
1321 
1322     default String getSearchDefaultDisplayPermission() {
1323         return split(getRoleSearchDefaultDisplayPermissions(), ",")
1324                 .get(stream -> stream.filter(StringUtil::isNotBlank).distinct().collect(Collectors.joining("\n")));
1325     }
1326 
1327     String getQueryGeoFields();
1328 
1329     default String[] getQueryGeoFieldsAsArray() {
1330         return split(getQueryGeoFields(), ",")
1331                 .get(stream -> stream.map(String::trim).filter(StringUtil::isNotBlank).toArray(n -> new String[n]));
1332     }
1333 
1334     String getSuggestSearchLogPermissions();
1335 
1336     default boolean isValidSearchLogPermissions(final String[] permissions) {
1337         if (permissions == null) {
1338             return false;
1339         }
1340         @SuppressWarnings("unchecked")
1341         List<String> validPermissionList = (List<String>) propMap.get(SUGGEST_SEARCH_LOG_PERMISSIONS);
1342         if (validPermissionList == null) {
1343             final PermissionHelper permissionHelper = ComponentUtil.getPermissionHelper();
1344             validPermissionList = split(getSuggestSearchLogPermissions(), ",")
1345                     .get(stream -> stream.map(s -> permissionHelper.encode(s)).filter(StringUtil::isNotBlank).collect(Collectors.toList()));
1346             propMap.put(SUGGEST_SEARCH_LOG_PERMISSIONS, validPermissionList);
1347         }
1348         final List<String> list = validPermissionList;
1349         return stream(permissions).get(stream -> stream.allMatch(v -> list.contains(v)));
1350     }
1351 
1352     String getRoleSearchUserPrefix();
1353 
1354     String getRoleSearchGuestPermissions();
1355 
1356     default List<String> getSearchGuestRoleList() {
1357         @SuppressWarnings("unchecked")
1358         List<String> list = (List<String>) propMap.get(SEARCH_GUEST_ROLE_LIST);
1359         if (list == null) {
1360             final PermissionHelper permissionHelper = ComponentUtil.getPermissionHelper();
1361             list = split(getRoleSearchGuestPermissions(), ",")
1362                     .get(stream -> stream.map(s -> permissionHelper.encode(s)).filter(StringUtil::isNotBlank).collect(Collectors.toList()));
1363             list.add(getRoleSearchUserPrefix() + Constants.GUEST_USER);
1364             propMap.put(SEARCH_GUEST_ROLE_LIST, list);
1365         }
1366         return list;
1367     }
1368 
1369     String getIndexAdminArrayFields();
1370 
1371     default Set<String> getIndexAdminArrayFieldSet() {
1372         @SuppressWarnings("unchecked")
1373         Set<String> fieldSet = (Set<String>) propMap.get(INDEX_ADMIN_ARRAY_FIELD_SET);
1374         if (fieldSet == null) {
1375             fieldSet = split(getIndexAdminArrayFields(), ",")
1376                     .get(stream -> stream.filter(StringUtil::isNotBlank).map(String::trim).collect(Collectors.toSet()));
1377             propMap.put(INDEX_ADMIN_ARRAY_FIELD_SET, fieldSet);
1378         }
1379         return fieldSet;
1380     }
1381 
1382     default boolean validateIndexArrayFields(final Map<String, Object> source) {
1383         return invalidIndexArrayFields(source).isEmpty();
1384     }
1385 
1386     default List<String> invalidIndexArrayFields(final Map<String, Object> source) {
1387         // TODO always returns empty list
1388         return split(getIndexAdminArrayFields(), ",").get(stream -> stream.filter(StringUtil::isNotBlank)
1389                 .map(String::trim)
1390                 .filter(s -> isNonEmptyValue(source.get(s)))
1391                 .filter(s -> false) // TODO
1392                 .collect(Collectors.toList()));
1393     }
1394 
1395     String getIndexAdminDateFields();
1396 
1397     default Set<String> getIndexAdminDateFieldSet() {
1398         @SuppressWarnings("unchecked")
1399         Set<String> fieldSet = (Set<String>) propMap.get(INDEX_ADMIN_DATE_FIELD_SET);
1400         if (fieldSet == null) {
1401             fieldSet = split(getIndexAdminDateFields(), ",")
1402                     .get(stream -> stream.filter(StringUtil::isNotBlank).map(String::trim).collect(Collectors.toSet()));
1403             propMap.put(INDEX_ADMIN_DATE_FIELD_SET, fieldSet);
1404         }
1405         return fieldSet;
1406     }
1407 
1408     default boolean validateIndexDateFields(final Map<String, Object> source) {
1409         return invalidIndexDateFields(source).isEmpty();
1410     }
1411 
1412     default List<String> invalidIndexDateFields(final Map<String, Object> source) {
1413         return split(getIndexAdminDateFields(), ",").get(
1414                 stream -> stream.filter(StringUtil::isNotBlank).map(String::trim).filter(s -> isNonEmptyValue(source.get(s))).filter(s -> {
1415                     final Object obj = source.get(s);
1416                     if (obj instanceof Date) {
1417                         return false;
1418                     }
1419                     return !validateDateTimeString(source.get(s));
1420                 }).collect(Collectors.toList()));
1421     }
1422 
1423     default boolean validateDateTimeString(final Object obj) {
1424         if (FessFunctions.parseDate(obj.toString()) != null) {
1425             return true;
1426         }
1427         return false;
1428     }
1429 
1430     String getIndexAdminIntegerFields();
1431 
1432     default Set<String> getIndexAdminIntegerFieldSet() {
1433         @SuppressWarnings("unchecked")
1434         Set<String> fieldSet = (Set<String>) propMap.get(INDEX_ADMIN_INTEGER_FIELD_SET);
1435         if (fieldSet == null) {
1436             fieldSet = split(getIndexAdminIntegerFields(), ",")
1437                     .get(stream -> stream.filter(StringUtil::isNotBlank).map(String::trim).collect(Collectors.toSet()));
1438             propMap.put(INDEX_ADMIN_INTEGER_FIELD_SET, fieldSet);
1439         }
1440         return fieldSet;
1441     }
1442 
1443     default boolean validateIndexIntegerFields(final Map<String, Object> source) {
1444         return invalidIndexIntegerFields(source).isEmpty();
1445     }
1446 
1447     default List<String> invalidIndexIntegerFields(final Map<String, Object> source) {
1448         final IntegerTypeValidator integerValidator = new IntegerTypeValidator();
1449         return split(getIndexAdminIntegerFields(), ",").get(
1450                 stream -> stream.filter(StringUtil::isNotBlank).map(String::trim).filter(s -> isNonEmptyValue(source.get(s))).filter(s -> {
1451                     final Object obj = source.get(s);
1452                     if (obj instanceof Number) {
1453                         return false;
1454                     }
1455                     return !integerValidator.isValid(obj.toString(), null);
1456                 }).collect(Collectors.toList()));
1457     }
1458 
1459     String getIndexAdminLongFields();
1460 
1461     default Set<String> getIndexAdminLongFieldSet() {
1462         @SuppressWarnings("unchecked")
1463         Set<String> fieldSet = (Set<String>) propMap.get(INDEX_ADMIN_LONG_FIELD_SET);
1464         if (fieldSet == null) {
1465             fieldSet = split(getIndexAdminLongFields(), ",")
1466                     .get(stream -> stream.filter(StringUtil::isNotBlank).map(String::trim).collect(Collectors.toSet()));
1467             propMap.put(INDEX_ADMIN_LONG_FIELD_SET, fieldSet);
1468         }
1469         return fieldSet;
1470     }
1471 
1472     default boolean validateIndexLongFields(final Map<String, Object> source) {
1473         return invalidIndexLongFields(source).isEmpty();
1474     }
1475 
1476     default List<String> invalidIndexLongFields(final Map<String, Object> source) {
1477         final LongTypeValidator longValidator = new LongTypeValidator();
1478         return split(getIndexAdminLongFields(), ",").get(
1479                 stream -> stream.filter(StringUtil::isNotBlank).map(String::trim).filter(s -> isNonEmptyValue(source.get(s))).filter(s -> {
1480                     final Object obj = source.get(s);
1481                     if (obj instanceof Number) {
1482                         return false;
1483                     }
1484                     return !longValidator.isValid(obj.toString(), null);
1485                 }).collect(Collectors.toList()));
1486     }
1487 
1488     String getIndexAdminFloatFields();
1489 
1490     default Set<String> getIndexAdminFloatFieldSet() {
1491         @SuppressWarnings("unchecked")
1492         Set<String> fieldSet = (Set<String>) propMap.get(INDEX_ADMIN_FLOAT_FIELD_SET);
1493         if (fieldSet == null) {
1494             fieldSet = split(getIndexAdminFloatFields(), ",")
1495                     .get(stream -> stream.filter(StringUtil::isNotBlank).map(String::trim).collect(Collectors.toSet()));
1496             propMap.put(INDEX_ADMIN_FLOAT_FIELD_SET, fieldSet);
1497         }
1498         return fieldSet;
1499     }
1500 
1501     default boolean validateIndexFloatFields(final Map<String, Object> source) {
1502         return invalidIndexFloatFields(source).isEmpty();
1503     }
1504 
1505     default List<String> invalidIndexFloatFields(final Map<String, Object> source) {
1506         final FloatTypeValidator floatValidator = new FloatTypeValidator();
1507         return split(getIndexAdminFloatFields(), ",").get(stream -> stream.filter(StringUtil::isNotBlank).map(String::trim).filter(s -> {
1508             final Object obj = source.get(s);
1509             if (obj instanceof Number) {
1510                 return false;
1511             }
1512             return !floatValidator.isValid(obj.toString(), null);
1513         }).collect(Collectors.toList()));
1514     }
1515 
1516     String getIndexAdminDoubleFields();
1517 
1518     default Set<String> getIndexAdminDoubleFieldSet() {
1519         @SuppressWarnings("unchecked")
1520         Set<String> fieldSet = (Set<String>) propMap.get(INDEX_ADMIN_DOUBLE_FIELD_SET);
1521         if (fieldSet == null) {
1522             fieldSet = split(getIndexAdminDoubleFields(), ",")
1523                     .get(stream -> stream.filter(StringUtil::isNotBlank).map(String::trim).collect(Collectors.toSet()));
1524             propMap.put(INDEX_ADMIN_DOUBLE_FIELD_SET, fieldSet);
1525         }
1526         return fieldSet;
1527     }
1528 
1529     default boolean validateIndexDoubleFields(final Map<String, Object> source) {
1530         return invalidIndexDoubleFields(source).isEmpty();
1531     }
1532 
1533     default List<String> invalidIndexDoubleFields(final Map<String, Object> source) {
1534         final DoubleTypeValidator doubleValidator = new DoubleTypeValidator();
1535         return split(getIndexAdminDoubleFields(), ",").get(stream -> stream.filter(StringUtil::isNotBlank).map(String::trim).filter(s -> {
1536             final Object obj = source.get(s);
1537             if (obj instanceof Number) {
1538                 return false;
1539             }
1540             return !doubleValidator.isValid(obj.toString(), null);
1541         }).collect(Collectors.toList()));
1542     }
1543 
1544     default Map<String, Object> convertToEditableDoc(final Map<String, Object> source) {
1545 
1546         final Set<String> arrayFieldSet = getIndexAdminArrayFieldSet();
1547         final Set<String> dateFieldSet = getIndexAdminDateFieldSet();
1548         final Set<String> integerFieldSet = getIndexAdminIntegerFieldSet();
1549         final Set<String> longFieldSet = getIndexAdminLongFieldSet();
1550         final Set<String> floatFieldSet = getIndexAdminFloatFieldSet();
1551         final Set<String> doubleFieldSet = getIndexAdminDoubleFieldSet();
1552 
1553         return source.entrySet().stream().map(e -> {
1554             final String key = e.getKey();
1555             Object value = e.getValue();
1556             if (value == null) {
1557                 value = StringUtil.EMPTY;
1558             }
1559             if (value instanceof String || value == null) {
1560                 return new Pair<>(key, value);
1561             }
1562             if (arrayFieldSet.contains(key)) {
1563                 if (value instanceof String[]) {
1564                     value = stream((String[]) value).get(stream -> stream.collect(Collectors.joining("\n")));
1565                 } else if (value instanceof List) {
1566                     @SuppressWarnings("unchecked")
1567                     final List<String> list = (List<String>) value;
1568                     value = list.stream().collect(Collectors.joining("\n"));
1569                 }
1570             } else if (dateFieldSet.contains(key)) {
1571                 value = FessFunctions.formatDate((Date) value);
1572             } else if (integerFieldSet.contains(key) || longFieldSet.contains(key) || floatFieldSet.contains(key)
1573                     || doubleFieldSet.contains(key)) {
1574                 value = value.toString();
1575             }
1576             return new Pair<>(key, value);
1577         }).collect(Collectors.toMap(Pair::getFirst, Pair::getSecond));
1578     }
1579 
1580     default Map<String, Object> convertToStorableDoc(final Map<String, Object> source) {
1581 
1582         final Set<String> arrayFieldSet = getIndexAdminArrayFieldSet();
1583         final Set<String> dateFieldSet = getIndexAdminDateFieldSet();
1584         final Set<String> integerFieldSet = getIndexAdminIntegerFieldSet();
1585         final Set<String> longFieldSet = getIndexAdminLongFieldSet();
1586         final Set<String> floatFieldSet = getIndexAdminFloatFieldSet();
1587         final Set<String> doubleFieldSet = getIndexAdminDoubleFieldSet();
1588 
1589         return source.entrySet().stream().filter(e -> isNonEmptyValue(e.getValue())).map(e -> {
1590             final String key = e.getKey();
1591             Object value = e.getValue();
1592             if (arrayFieldSet.contains(key)) {
1593                 if (value instanceof String[]) {
1594                     value = Arrays.stream((String[]) value).toList();
1595                 } else if (value instanceof List<?>) {
1596                     // nothing
1597                 } else {
1598                     value = split(value.toString(), "\n")
1599                             .get(stream -> stream.filter(StringUtil::isNotBlank).map(String::trim).collect(Collectors.toList()));
1600                 }
1601             } else if (dateFieldSet.contains(key)) {
1602                 // TODO time zone
1603                 if (value instanceof Date) {
1604                     // nothing
1605                 } else {
1606                     value = FessFunctions.parseDate(value.toString());
1607                 }
1608             } else if (integerFieldSet.contains(key)) {
1609                 if (value instanceof final Number num) {
1610                     value = num.intValue();
1611                 } else {
1612                     value = DfTypeUtil.toInteger(value.toString());
1613                 }
1614             } else if (longFieldSet.contains(key)) {
1615                 if (value instanceof final Number num) {
1616                     value = num.longValue();
1617                 } else {
1618                     value = DfTypeUtil.toLong(value.toString());
1619                 }
1620             } else if (floatFieldSet.contains(key)) {
1621                 if (value instanceof final Number num) {
1622                     value = num.floatValue();
1623                 } else {
1624                     value = DfTypeUtil.toFloat(value.toString());
1625                 }
1626             } else if (doubleFieldSet.contains(key)) {
1627                 if (value instanceof final Number num) {
1628                     value = num.doubleValue();
1629                 } else {
1630                     value = DfTypeUtil.toDouble(value.toString());
1631                 }
1632             }
1633             return new Pair<>(key, value);
1634         }).collect(Collectors.toMap(Pair::getFirst, Pair::getSecond));
1635     }
1636 
1637     String getIndexAdminRequiredFields();
1638 
1639     default boolean validateIndexRequiredFields(final Map<String, Object> source) {
1640         return invalidIndexRequiredFields(source).isEmpty();
1641     }
1642 
1643     default List<String> invalidIndexRequiredFields(final Map<String, Object> source) {
1644         final RequiredValidator requiredValidator = new RequiredValidator();
1645         return split(getIndexAdminRequiredFields(), ",").get(stream -> stream.filter(StringUtil::isNotBlank)
1646                 .map(String::trim)
1647                 .filter(s -> !requiredValidator.isValid(source.get(s), null))
1648                 .collect(Collectors.toList()));
1649     }
1650 
1651     default boolean isNonEmptyValue(final Object value) {
1652         final RequiredValidator requiredValidator = new RequiredValidator();
1653         return requiredValidator.isValid(value, null);
1654     }
1655 
1656     String getCrawlerDocumentSpaceChars();
1657 
1658     default int[] getCrawlerDocumentSpaceCharsAsArray() {
1659         return getCrawlerDocumentCharsAsArray(CRAWLER_DOCUMENT_SPACE_CHARS, getCrawlerDocumentSpaceChars());
1660     }
1661 
1662     default String[] getCrawlerDocumentSpaces() {
1663         String[] spaces = (String[]) propMap.get(CRAWLER_DOCUMENT_SPACES);
1664         if (spaces == null) {
1665             spaces = Arrays.stream(getCrawlerDocumentSpaceCharsAsArray()).mapToObj(Character::toString).toArray(n -> new String[n]);
1666             propMap.put(CRAWLER_DOCUMENT_SPACES, spaces);
1667         }
1668         return spaces;
1669     }
1670 
1671     default int[] getCrawlerDocumentCharsAsArray(final String key, final String spaceStr) {
1672         int[] spaceChars = (int[]) propMap.get(key);
1673         if (spaceChars == null) {
1674             if (spaceStr.startsWith("u")) {
1675                 spaceChars = split(spaceStr, "u")
1676                         .get(stream -> stream.filter(StringUtil::isNotBlank).mapToInt(s -> Integer.parseInt(s, 16)).toArray());
1677             } else {
1678                 // backward compatibility
1679                 final int length = spaceStr.length();
1680                 spaceChars = new int[length];
1681                 for (int i = 0; i < length; i++) {
1682                     spaceChars[i] = spaceStr.codePointAt(i);
1683                 }
1684             }
1685             propMap.put(key, spaceChars);
1686         }
1687         return spaceChars;
1688     }
1689 
1690     String getCrawlerDocumentFullstopChars();
1691 
1692     default boolean endsWithFullstop(final String s) {
1693         if (StringUtil.isBlank(s)) {
1694             return false;
1695         }
1696         for (final int i : getCrawlerDocumentFullstopCharsAsArray()) {
1697             if (s.endsWith(String.valueOf(i))) {
1698                 return true;
1699             }
1700         }
1701         return false;
1702     }
1703 
1704     default int[] getCrawlerDocumentFullstopCharsAsArray() {
1705         return getCrawlerDocumentCharsAsArray(CRAWLER_DOCUMENT_FULLSTOP_CHARS, getCrawlerDocumentFullstopChars());
1706     }
1707 
1708     String getQueryAdditionalResponseFields();
1709 
1710     default String[] getQueryAdditionalResponseFields(final String... fields) {
1711         final List<String> list = new ArrayList<>(fields.length + 10);
1712         stream(fields).of(stream -> stream.forEach(list::add));
1713         split(getQueryAdditionalResponseFields(), ",")
1714                 .of(stream -> stream.filter(StringUtil::isNotBlank).map(String::trim).forEach(list::add));
1715         return list.toArray(new String[list.size()]);
1716     }
1717 
1718     String getQueryAdditionalScrollResponseFields();
1719 
1720     default String[] getQueryAdditionalScrollResponseFields(final String... fields) {
1721         final List<String> list = new ArrayList<>(fields.length + 10);
1722         stream(fields).of(stream -> stream.forEach(list::add));
1723         split(getQueryAdditionalScrollResponseFields(), ",")
1724                 .of(stream -> stream.filter(StringUtil::isNotBlank).map(String::trim).forEach(list::add));
1725         return list.toArray(new String[list.size()]);
1726     }
1727 
1728     String getQueryAdditionalCacheResponseFields();
1729 
1730     default String[] getQueryAdditionalCacheResponseFields(final String... fields) {
1731         final List<String> list = new ArrayList<>(fields.length + 10);
1732         stream(fields).of(stream -> stream.forEach(list::add));
1733         split(getQueryAdditionalCacheResponseFields(), ",")
1734                 .of(stream -> stream.filter(StringUtil::isNotBlank).map(String::trim).forEach(list::add));
1735         return list.toArray(new String[list.size()]);
1736     }
1737 
1738     String getQueryAdditionalHighlightedFields();
1739 
1740     default String[] getQueryAdditionalHighlightedFields(final String... fields) {
1741         final List<String> list = new ArrayList<>(fields.length + 10);
1742         stream(fields).of(stream -> stream.forEach(list::add));
1743         split(getQueryAdditionalHighlightedFields(), ",")
1744                 .of(stream -> stream.filter(StringUtil::isNotBlank).map(String::trim).forEach(list::add));
1745         return list.toArray(new String[list.size()]);
1746     }
1747 
1748     String getQueryAdditionalSearchFields();
1749 
1750     default String[] getQueryAdditionalSearchFields(final String... fields) {
1751         final List<String> list = new ArrayList<>(fields.length + 10);
1752         stream(fields).of(stream -> stream.forEach(list::add));
1753         split(getQueryAdditionalSearchFields(), ",")
1754                 .of(stream -> stream.filter(StringUtil::isNotBlank).map(String::trim).forEach(list::add));
1755         return list.toArray(new String[list.size()]);
1756     }
1757 
1758     String getQueryAdditionalFacetFields();
1759 
1760     default String[] getQueryAdditionalFacetFields(final String... fields) {
1761         final List<String> list = new ArrayList<>(fields.length + 10);
1762         stream(fields).of(stream -> stream.forEach(list::add));
1763         split(getQueryAdditionalFacetFields(), ",")
1764                 .of(stream -> stream.filter(StringUtil::isNotBlank).map(String::trim).forEach(list::add));
1765         return list.toArray(new String[list.size()]);
1766     }
1767 
1768     String getQueryAdditionalSortFields();
1769 
1770     default String[] getQueryAdditionalSortFields(final String... fields) {
1771         final List<String> list = new ArrayList<>(fields.length + 10);
1772         stream(fields).of(stream -> stream.forEach(list::add));
1773         split(getQueryAdditionalSortFields(), ",").of(stream -> stream.filter(StringUtil::isNotBlank).map(String::trim).forEach(list::add));
1774         return list.toArray(new String[list.size()]);
1775     }
1776 
1777     String getQueryAdditionalApiResponseFields();
1778 
1779     default String[] getQueryAdditionalApiResponseFields(final String... fields) {
1780         final List<String> list = new ArrayList<>(fields.length + 10);
1781         stream(fields).of(stream -> stream.forEach(list::add));
1782         split(getQueryAdditionalApiResponseFields(), ",")
1783                 .of(stream -> stream.filter(StringUtil::isNotBlank).map(String::trim).forEach(list::add));
1784         return list.toArray(new String[list.size()]);
1785     }
1786 
1787     String getQueryAdditionalNotAnalyzedFields();
1788 
1789     default String[] getQueryAdditionalNotAnalyzedFields(final String... fields) {
1790         final List<String> list = new ArrayList<>(fields.length + 10);
1791         stream(fields).of(stream -> stream.forEach(list::add));
1792         split(getQueryAdditionalNotAnalyzedFields(), ",")
1793                 .of(stream -> stream.filter(StringUtil::isNotBlank).map(String::trim).forEach(list::add));
1794         return list.toArray(new String[list.size()]);
1795     }
1796 
1797     String getThumbnailGeneratorTargets();
1798 
1799     default String[] getThumbnailGeneratorTargetsAsArray() {
1800         return getThumbnailGeneratorTargets().split(",");
1801 
1802     }
1803 
1804     String getApiAdminAccessPermissions();
1805 
1806     default Set<String> getApiAdminAccessPermissionSet() {
1807         @SuppressWarnings("unchecked")
1808         Set<String> fieldSet = (Set<String>) propMap.get(API_ADMIN_ACCESS_PERMISSION_SET);
1809         if (fieldSet == null) {
1810             fieldSet = split(getApiAdminAccessPermissions(), ",")
1811                     .get(stream -> stream.filter(StringUtil::isNotBlank).map(String::trim).collect(Collectors.toSet()));
1812             propMap.put(API_ADMIN_ACCESS_PERMISSION_SET, fieldSet);
1813         }
1814         return fieldSet;
1815     }
1816 
1817     default boolean isApiAdminAccessAllowed(final Set<String> accessPermissions) {
1818         return getApiAdminAccessPermissionSet().stream().anyMatch(s -> accessPermissions.contains(s));
1819     }
1820 
1821     String getUserCodePattern();
1822 
1823     Integer getUserCodeMinLengthAsInteger();
1824 
1825     Integer getUserCodeMaxLengthAsInteger();
1826 
1827     default boolean isValidUserCode(final String userCode) {
1828         if (userCode == null) {
1829             return false;
1830         }
1831 
1832         final int length = userCode.length();
1833         if (getUserCodeMinLengthAsInteger().intValue() > length || getUserCodeMaxLengthAsInteger().intValue() < length) {
1834             return false;
1835         }
1836 
1837         Pattern pattern = (Pattern) propMap.get(USER_CODE_PATTERN);
1838         if (pattern == null) {
1839             pattern = Pattern.compile(getUserCodePattern());
1840             propMap.put(USER_CODE_PATTERN, pattern);
1841         }
1842         return pattern.matcher(userCode).matches();
1843     }
1844 
1845     String getQueryCollapseInnerHitsSorts();
1846 
1847     @SuppressWarnings("rawtypes")
1848     default OptionalThing<SortBuilder[]> getQueryCollapseInnerHitsSortBuilders() {
1849         @SuppressWarnings("unchecked")
1850         OptionalThing<SortBuilder[]> ot = (OptionalThing<SortBuilder[]>) propMap.get(QUERY_COLLAPSE_INNER_HITS_SORTS);
1851         if (ot == null) {
1852             final String sorts = getQueryCollapseInnerHitsSorts();
1853             if (StringUtil.isBlank(sorts)) {
1854                 ot = OptionalThing.empty();
1855             } else {
1856                 final SortBuilder[] sortBuilders = split(sorts, ",").get(stream -> stream.filter(StringUtil::isNotBlank).map(s -> {
1857                     final String[] values = s.split(":");
1858                     if (values.length > 1) {
1859                         return SortBuilders.fieldSort(values[0]).order("desc".equalsIgnoreCase(values[0]) ? SortOrder.DESC : SortOrder.ASC);
1860                     }
1861                     return SortBuilders.fieldSort(values[0]).order(SortOrder.ASC);
1862                 }).toArray(n -> new SortBuilder[n]));
1863                 ot = OptionalThing.of(sortBuilders);
1864             }
1865             propMap.put(QUERY_COLLAPSE_INNER_HITS_SORTS, ot);
1866         }
1867         return ot;
1868     }
1869 
1870     String getVirtualHostHeaders();
1871 
1872     default String getVirtualHostHeaderValue() {
1873         final String value = getVirtualHostValue();
1874         if (StringUtil.isNotBlank(value)) {
1875             return value;
1876         }
1877         return getVirtualHostHeaders();
1878     }
1879 
1880     @SuppressWarnings("unchecked")
1881     default Tuple3<String, String, String>[] getVirtualHosts() {
1882         Tuple3<String, String, String>[] hosts = (Tuple3<String, String, String>[]) propMap.get(VIRTUAL_HOST_HEADERS);
1883         if (hosts == null) {
1884             hosts = split(getVirtualHostHeaderValue(), "\n").get(stream -> stream.map(s -> {
1885                 final String[] v1 = s.split("=");
1886                 if (v1.length == 2) {
1887                     final String[] v2 = v1[0].split(":", 2);
1888                     if (v2.length == 2) {
1889                         return new Tuple3<>(v2[0].trim(), v2[1].trim(), v1[1].replaceAll("[^a-zA-Z0-9_]", StringUtil.EMPTY).trim());
1890                     }
1891                 }
1892                 return null;
1893             }).filter(v -> {
1894                 if (v == null) {
1895                     return false;
1896                 }
1897                 if ("admin".equalsIgnoreCase(v.getValue3()) || "common".equalsIgnoreCase(v.getValue3())
1898                         || "error".equalsIgnoreCase(v.getValue3()) || "login".equalsIgnoreCase(v.getValue3())
1899                         || "profile".equalsIgnoreCase(v.getValue3())) {
1900                     return false;
1901                 }
1902                 return true;
1903             }).toArray(n -> new Tuple3[n]));
1904             propMap.put(VIRTUAL_HOST_HEADERS, hosts);
1905         }
1906         return hosts;
1907     }
1908 
1909     String getCrawlerFailureUrlStatusCodes();
1910 
1911     default boolean isCrawlerFailureUrlStatusCodes(final int code) {
1912         int[] codes = (int[]) propMap.get(CRAWLER_FAILURE_URL_STATUS_CODES);
1913         if (codes == null) {
1914             codes = split(getCrawlerFailureUrlStatusCodes(), ",")
1915                     .get(stream -> stream.filter(StringUtil::isNotBlank).mapToInt(Integer::parseInt).toArray());
1916             propMap.put(CRAWLER_FAILURE_URL_STATUS_CODES, codes);
1917         }
1918         for (final int v : codes) {
1919             if (v == code) {
1920                 return true;
1921             }
1922         }
1923         return false;
1924     }
1925 
1926     Integer getThumbnailHtmlImageMinWidthAsInteger();
1927 
1928     Integer getThumbnailHtmlImageMinHeightAsInteger();
1929 
1930     java.math.BigDecimal getThumbnailHtmlImageMaxAspectRatioAsDecimal();
1931 
1932     default boolean validateThumbnailSize(final int width, final int height) {
1933         if (width <= 0 || height <= 0 || width < getThumbnailHtmlImageMinWidthAsInteger().intValue()
1934                 || height < getThumbnailHtmlImageMinHeightAsInteger().intValue()) {
1935             return false;
1936         }
1937 
1938         final float ratio = getThumbnailHtmlImageMaxAspectRatioAsDecimal().floatValue();
1939         if ((float) width / (float) height > ratio || (float) height / (float) width > ratio) {
1940             return false;
1941         }
1942 
1943         return true;
1944     }
1945 
1946     String getHttpProxyHost();
1947 
1948     Integer getHttpProxyPortAsInteger();
1949 
1950     String getHttpProxyUsername();
1951 
1952     String getHttpProxyPassword();
1953 
1954     default Proxy getHttpProxy() {
1955         Proxy proxy = (Proxy) propMap.get(HTML_PROXY);
1956         if (proxy == null) {
1957             if (StringUtil.isNotBlank(getHttpProxyHost()) && getHttpProxyPortAsInteger() != null) {
1958                 final SocketAddress addr = new InetSocketAddress(getHttpProxyHost(), getHttpProxyPortAsInteger());
1959                 proxy = new Proxy(Type.HTTP, addr);
1960                 if (StringUtil.isNotBlank(getHttpProxyUsername())) {
1961                     Authenticator.setDefault(new Authenticator() {
1962                         @Override
1963                         protected PasswordAuthentication getPasswordAuthentication() {
1964                             return new PasswordAuthentication(getHttpProxyUsername(), getHttpProxyPassword().toCharArray());
1965                         }
1966                     });
1967                 }
1968             } else {
1969                 proxy = Proxy.NO_PROXY;
1970             }
1971             propMap.put(HTML_PROXY, proxy);
1972         }
1973         return proxy;
1974     }
1975 
1976     String getThumbnailHtmlImageExcludeExtensions();
1977 
1978     default boolean isThumbnailHtmlImageUrl(final String url) {
1979         if (StringUtil.isBlank(url)) {
1980             return false;
1981         }
1982 
1983         String[] excludeExtensions = (String[]) propMap.get(THUMBNAIL_HTML_IMAGE_EXCLUDE_EXTENSIONS);
1984         if (excludeExtensions == null) {
1985             excludeExtensions =
1986                     split(getThumbnailHtmlImageExcludeExtensions(), ",").get(stream -> stream.map(s -> s.toLowerCase(Locale.ROOT).trim())
1987                             .filter(StringUtil::isNotBlank)
1988                             .toArray(n -> new String[n]));
1989             propMap.put(THUMBNAIL_HTML_IMAGE_EXCLUDE_EXTENSIONS, excludeExtensions);
1990         }
1991 
1992         final String u = url.toLowerCase(Locale.ROOT);
1993         return !stream(excludeExtensions).get(stream -> stream.anyMatch(s -> u.endsWith(s)));
1994     }
1995 
1996     String getQueryGsaResponseFields();
1997 
1998     default boolean isGsaResponseFields(final String name) {
1999         @SuppressWarnings("unchecked")
2000         Set<String> gsaResponseFieldSet = (Set<String>) propMap.get(QUERY_GSA_RESPONSE_FIELDS);
2001         if (gsaResponseFieldSet == null) {
2002             gsaResponseFieldSet = split(getQueryGsaResponseFields(), ",").get(stream -> stream.map(s -> s.toLowerCase(Locale.ROOT).trim())
2003                     .filter(StringUtil::isNotBlank)
2004                     .collect(Collectors.toSet()));
2005             propMap.put(QUERY_GSA_RESPONSE_FIELDS, gsaResponseFieldSet);
2006         }
2007         return gsaResponseFieldSet.contains(name.toLowerCase(Locale.ROOT));
2008     }
2009 
2010     String getApiSearchAcceptReferers();
2011 
2012     default boolean isAcceptedSearchReferer(final String referer) {
2013         Pattern[] patterns = (Pattern[]) propMap.get(API_SEARCH_ACCEPT_REFERERS);
2014         if (patterns == null) {
2015             final String refs = getApiSearchAcceptReferers();
2016             if (StringUtil.isBlank(refs)) {
2017                 patterns = new Pattern[0];
2018             } else {
2019                 patterns = split(refs, "\n").get(
2020                         stream -> stream.filter(StringUtil::isNotBlank).map(s -> Pattern.compile(s.trim())).toArray(n -> new Pattern[n]));
2021             }
2022             propMap.put(API_SEARCH_ACCEPT_REFERERS, patterns);
2023         }
2024         if (patterns.length == 0) {
2025             return true;
2026         }
2027 
2028         if (referer == null) {
2029             return false;
2030         }
2031         return Arrays.stream(patterns).anyMatch(p -> p.matcher(referer).matches());
2032     }
2033 
2034     String getQueryHighlightContentDescriptionFields();
2035 
2036     default String[] getQueryHighlightContentDescriptionFieldsAsArray() {
2037         return split(getQueryHighlightContentDescriptionFields(), ",")
2038                 .get(stream -> stream.filter(StringUtil::isNotBlank).map(String::trim).toArray(n -> new String[n]));
2039     }
2040 
2041     boolean isLdapIgnoreNetbiosName();
2042 
2043     default String getCanonicalLdapName(final String name) {
2044         if (isLdapIgnoreNetbiosName()) {
2045             final String[] values = name.split("\\\\");
2046             if (values.length == 0) {
2047                 return null;
2048             }
2049             if (values.length == 1) {
2050                 return values[0];
2051             }
2052             return String.join("\\", Arrays.copyOfRange(values, 1, values.length));
2053         }
2054         return name;
2055     }
2056 
2057     String getLoggingSearchDocsFields();
2058 
2059     default String[] getLoggingSearchDocsFieldsAsArray() {
2060         String[] fields = (String[]) propMap.get(LOGGING_SEARCH_DOCS_FIELDS);
2061         if (fields == null) {
2062             fields = split(getLoggingSearchDocsFields(), ",").get(stream -> stream.map(String::trim).toArray(n -> new String[n]));
2063             propMap.put(LOGGING_SEARCH_DOCS_FIELDS, fields);
2064         }
2065         return fields;
2066     }
2067 
2068     String getSchedulerTargetName();
2069 
2070     default boolean isSchedulerTarget(final String target) {
2071         if (StringUtil.isBlank(target)) {
2072             return true;
2073         }
2074 
2075         final String myName = getSchedulerTargetName();
2076 
2077         final String[] targets = target.split(",");
2078         for (String name : targets) {
2079             name = name.trim();
2080             if (Constants.DEFAULT_JOB_TARGET.equalsIgnoreCase(name) || StringUtil.isNotBlank(myName) && myName.equalsIgnoreCase(name)) {
2081                 return true;
2082             }
2083         }
2084         return false;
2085     }
2086 
2087     String getApiGsaResponseHeaders();
2088 
2089     default List<Pair<String, String>> getApiGsaResponseHeaderList() {
2090         @SuppressWarnings("unchecked")
2091         List<Pair<String, String>> list = (List<Pair<String, String>>) propMap.get(API_GSA_RESPONSE_HEADER_LIST);
2092         if (list == null) {
2093             list = split(getApiGsaResponseHeaders(), "\n").get(stream -> stream.filter(StringUtil::isNotBlank).map(s -> {
2094                 final String[] values = s.split(":", 2);
2095                 if (values.length == 2) {
2096                     return new Pair<>(values[0], values[1]);
2097                 }
2098                 return new Pair<>(values[0], StringUtil.EMPTY);
2099             }).collect(Collectors.toList()));
2100             propMap.put(API_GSA_RESPONSE_HEADER_LIST, list);
2101         }
2102         return list;
2103     }
2104 
2105     String getApiJsonResponseHeaders();
2106 
2107     default List<Pair<String, String>> getApiJsonResponseHeaderList() {
2108         @SuppressWarnings("unchecked")
2109         List<Pair<String, String>> list = (List<Pair<String, String>>) propMap.get(API_JSON_RESPONSE_HEADER_LIST);
2110         if (list == null) {
2111             list = split(getApiJsonResponseHeaders(), "\n").get(stream -> stream.filter(StringUtil::isNotBlank).map(s -> {
2112                 final String[] values = s.split(":", 2);
2113                 if (values.length == 2) {
2114                     return new Pair<>(values[0], values[1]);
2115                 }
2116                 return new Pair<>(values[0], StringUtil.EMPTY);
2117             }).collect(Collectors.toList()));
2118             propMap.put(API_JSON_RESPONSE_HEADER_LIST, list);
2119         }
2120         return list;
2121     }
2122 
2123     String getApiDashboardResponseHeaders();
2124 
2125     default List<Pair<String, String>> getApiDashboardResponseHeaderList() {
2126         @SuppressWarnings("unchecked")
2127         List<Pair<String, String>> list = (List<Pair<String, String>>) propMap.get(API_DASHBOARD_RESPONSE_HEADER_LIST);
2128         if (list == null) {
2129             list = split(getApiDashboardResponseHeaders(), "\n").get(stream -> stream.filter(StringUtil::isNotBlank).map(s -> {
2130                 final String[] values = s.split(":", 2);
2131                 if (values.length == 2) {
2132                     return new Pair<>(values[0], values[1]);
2133                 }
2134                 return new Pair<>(values[0], StringUtil.EMPTY);
2135             }).collect(Collectors.toList()));
2136             propMap.put(API_DASHBOARD_RESPONSE_HEADER_LIST, list);
2137         }
2138         return list;
2139     }
2140 
2141     String getApiCorsAllowOrigin();
2142 
2143     default List<String> getApiCorsAllowOriginList() {
2144         @SuppressWarnings("unchecked")
2145         List<String> list = (List<String>) propMap.get(CORS_ALLOW_ORIGIN);
2146         if (list == null) {
2147             list = split(getApiCorsAllowOrigin(), "\n")
2148                     .get(stream -> stream.map(String::trim).filter(StringUtil::isNotEmpty).collect(Collectors.toList()));
2149             propMap.put(CORS_ALLOW_ORIGIN, list);
2150         }
2151         return list;
2152     }
2153 
2154     String getIndexerLanguageFields();
2155 
2156     default String[] getIndexerLanguageFieldsAsArray() {
2157         return split(getIndexerLanguageFields(), ",").get(stream -> stream.map(String::trim).toArray(n -> new String[n]));
2158 
2159     }
2160 
2161     String getSessionTrackingModes();
2162 
2163     default Set<String> getSessionTrackingModesAsSet() {
2164         return split(getSessionTrackingModes(), ",")
2165                 .get(stream -> stream.map(s -> s.trim().toUpperCase(Locale.ENGLISH)).collect(Collectors.toSet()));
2166     }
2167 
2168     String getQueryTrackTotalHits();
2169 
2170     default Object getQueryTrackTotalHitsValue() {
2171         Object value = propMap.get(QUERY_TRACK_TOTAL_HITS_VALUE);
2172         if (value == null) {
2173             final String v = getQueryTrackTotalHits();
2174             if (Constants.TRUE.equalsIgnoreCase(v)) {
2175                 value = Boolean.TRUE;
2176             } else if (Constants.FALSE.equalsIgnoreCase(v)) {
2177                 value = Boolean.FALSE;
2178             } else {
2179                 try {
2180                     value = DfTypeUtil.toInteger(v);
2181                 } catch (final NumberFormatException e) {
2182                     value = StringUtil.EMPTY;
2183                 }
2184             }
2185             propMap.put(QUERY_TRACK_TOTAL_HITS_VALUE, value);
2186         }
2187         return value;
2188     }
2189 
2190     Integer getProcessorsAsInteger();
2191 
2192     default int availableProcessors() {
2193         final int num = getProcessorsAsInteger();
2194         if (num > 0) {
2195             return num;
2196         }
2197         return Runtime.getRuntime().availableProcessors();
2198     }
2199 
2200     Integer getCrawlerHttpThreadPoolSizeAsInteger();
2201 
2202     default int getCrawlerHttpProcessors() {
2203         final int num = getCrawlerHttpThreadPoolSizeAsInteger();
2204         if (num > 0) {
2205             return num;
2206         }
2207         return Runtime.getRuntime().availableProcessors();
2208     }
2209 
2210     String getPluginVersionFilter();
2211 
2212     default boolean isTargetPluginVersion(final String version) {
2213         final Pattern pattern;
2214         if (StringUtil.isBlank(getPluginVersionFilter())) {
2215             pattern = Pattern.compile("^" + Pattern.quote(ComponentUtil.getSystemHelper().getProductVersion()) + ".*");
2216         } else {
2217             pattern = Pattern.compile(getPluginVersionFilter());
2218         }
2219         return pattern.matcher(version).matches();
2220     }
2221 
2222     String getQueryHighlightBoundaryChars();
2223 
2224     default char[] getQueryHighlightBoundaryCharsAsArray() {
2225         final int[] values = getCrawlerDocumentCharsAsArray(QUERY_HIGHLIGHT_BOUNDARY_CHARS, getQueryHighlightBoundaryChars());
2226         final char[] chars = new char[values.length];
2227         for (int i = 0; i < values.length; i++) {
2228             chars[i] = (char) values[i];
2229         }
2230         return chars;
2231     }
2232 
2233     String getQueryHighlightTerminalChars();
2234 
2235     default int[] getQueryHighlightTerminalCharsAsArray() {
2236         return getCrawlerDocumentCharsAsArray(QUERY_HIGHLIGHT_TERMINAL_CHARS, getQueryHighlightTerminalChars());
2237     }
2238 
2239     String getHttpFileuploadMaxSize();
2240 
2241     default Long getHttpFileuploadMaxSizeAsLong() {
2242         final String value = getHttpFileuploadMaxSize();
2243         return value != null ? DfTypeUtil.toLong(value) : null;
2244     }
2245 
2246     String getHttpFileuploadThresholdSize();
2247 
2248     default Long getHttpFileuploadThresholdSizeAsLong() {
2249         final String value = getHttpFileuploadThresholdSize();
2250         return value != null ? DfTypeUtil.toLong(value) : null;
2251     }
2252 
2253     String getPasswordInvalidAdminPasswords();
2254 
2255     default boolean isValidAdminPassword(final String password) {
2256         return !split(getPasswordInvalidAdminPasswords(), "\n")
2257                 .get(stream -> stream.map(String::trim).filter(StringUtil::isNotEmpty).anyMatch(s -> s.equals(password)));
2258     }
2259 
2260     Integer getPasswordMinLengthAsInteger();
2261 
2262     boolean isPasswordRequireUppercase();
2263 
2264     boolean isPasswordRequireLowercase();
2265 
2266     boolean isPasswordRequireDigit();
2267 
2268     boolean isPasswordRequireSpecialChar();
2269 
2270     String getSearchlogRequestHeaders();
2271 
2272     default String[] getSearchlogRequestHeadersAsArray() {
2273         return split(getSearchlogRequestHeaders(), ",")
2274                 .get(stream -> stream.filter(StringUtil::isNotBlank).map(String::trim).toArray(n -> new String[n]));
2275     }
2276 
2277     String getApiPingSearchEngineFields();
2278 
2279     default Set<String> getApiPingEsFieldSet() {
2280         Set<String> value = (Set<String>) propMap.get(API_PING_SEARCH_ENGINE_FIELD_SET);
2281         if (value == null) {
2282             value = split(getApiPingSearchEngineFields(), ",")
2283                     .get(stream -> stream.filter(StringUtil::isNotBlank).map(String::trim).collect(Collectors.toSet()));
2284             propMap.put(API_PING_SEARCH_ENGINE_FIELD_SET, value);
2285         }
2286         return value;
2287     }
2288 
2289     String get(String propertyKey);
2290 
2291     String getSearchEngineType();
2292 
2293     default String getFesenType() {
2294         final String value = getSearchEngineType();
2295         if (value != null) {
2296             return value;
2297         }
2298         return get("elasticsearch.type");
2299     }
2300 
2301     String getSearchEngineHttpUrl();
2302 
2303     default String getFesenHttpUrl() {
2304         final String value = getSearchEngineHttpUrl();
2305         if (value != null) {
2306             return value;
2307         }
2308         return get("elasticsearch.http.url");
2309     }
2310 
2311     String getSearchEngineHttpSslCertificateAuthorities();
2312 
2313     default String getFesenHttpSslCertificateAuthorities() {
2314         final String value = getSearchEngineHttpSslCertificateAuthorities();
2315         if (value != null) {
2316             return value;
2317         }
2318         return get("elasticsearch.http.ssl.certificate_authorities");
2319     }
2320 
2321     String getSearchEngineUsername();
2322 
2323     default String getFesenUsername() {
2324         final String value = getSearchEngineUsername();
2325         if (value != null) {
2326             return value;
2327         }
2328         return get("elasticsearch.username");
2329     }
2330 
2331     String getSearchEnginePassword();
2332 
2333     default String getFesenPassword() {
2334         final String value = getSearchEnginePassword();
2335         if (value != null) {
2336             return value;
2337         }
2338         return get("elasticsearch.password");
2339     }
2340 
2341     Integer getAsInteger(String propertyKey);
2342 
2343     Integer getSearchEngineHeartbeatIntervalAsInteger();
2344 
2345     default long getFesenHeartbeatInterval() {
2346         Integer value = getSearchEngineHeartbeatIntervalAsInteger();
2347         if (value != null) {
2348             return value.longValue();
2349         }
2350         value = getAsInteger("elasticsearch.heartbeat_interval");
2351         if (value != null) {
2352             return value.longValue();
2353         }
2354         return 10000L;
2355     }
2356 
2357     // ===================================================================================
2358     //                                                                         Rate Limit
2359     //                                                                         ==========
2360     String RATE_LIMIT_WHITELIST_IPS_SET = "rateLimitWhitelistIpsSet";
2361 
2362     String RATE_LIMIT_BLOCKED_IPS_SET = "rateLimitBlockedIpsSet";
2363 
2364     String getRateLimitWhitelistIps();
2365 
2366     default Set<String> getRateLimitWhitelistIpsAsSet() {
2367         @SuppressWarnings("unchecked")
2368         Set<String> set = (Set<String>) propMap.get(RATE_LIMIT_WHITELIST_IPS_SET);
2369         if (set == null) {
2370             set = split(getRateLimitWhitelistIps(), ",")
2371                     .get(stream -> stream.map(String::trim).filter(StringUtil::isNotEmpty).collect(Collectors.toSet()));
2372             propMap.put(RATE_LIMIT_WHITELIST_IPS_SET, set);
2373         }
2374         return set;
2375     }
2376 
2377     String getRateLimitBlockedIps();
2378 
2379     default Set<String> getRateLimitBlockedIpsAsSet() {
2380         @SuppressWarnings("unchecked")
2381         Set<String> set = (Set<String>) propMap.get(RATE_LIMIT_BLOCKED_IPS_SET);
2382         if (set == null) {
2383             set = split(getRateLimitBlockedIps(), ",")
2384                     .get(stream -> stream.map(String::trim).filter(StringUtil::isNotEmpty).collect(Collectors.toSet()));
2385             propMap.put(RATE_LIMIT_BLOCKED_IPS_SET, set);
2386         }
2387         return set;
2388     }
2389 
2390     String RATE_LIMIT_TRUSTED_PROXIES_SET = "rateLimitTrustedProxiesSet";
2391 
2392     String getRateLimitTrustedProxies();
2393 
2394     default Set<String> getRateLimitTrustedProxiesAsSet() {
2395         @SuppressWarnings("unchecked")
2396         Set<String> set = (Set<String>) propMap.get(RATE_LIMIT_TRUSTED_PROXIES_SET);
2397         if (set == null) {
2398             set = split(getRateLimitTrustedProxies(), ",")
2399                     .get(stream -> stream.map(String::trim).filter(StringUtil::isNotEmpty).collect(Collectors.toSet()));
2400             propMap.put(RATE_LIMIT_TRUSTED_PROXIES_SET, set);
2401         }
2402         return set;
2403     }
2404 }