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