xref: /OpenGrok/opengrok-indexer/src/main/java/org/opengrok/indexer/configuration/Configuration.java (revision 61dce4cb338e604a2107e3407eacd2a8999c2eef)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * See LICENSE.txt included in this distribution for the specific
9  * language governing permissions and limitations under the License.
10  *
11  * When distributing Covered Code, include this CDDL HEADER in each
12  * file and include the License file at LICENSE.txt.
13  * If applicable, add the following below this CDDL HEADER, with the
14  * fields enclosed by brackets "[]" replaced with your own identifying
15  * information: Portions Copyright [yyyy] [name of copyright owner]
16  *
17  * CDDL HEADER END
18  */
19 
20 /*
21  * Copyright (c) 2007, 2021, Oracle and/or its affiliates. All rights reserved.
22  * Portions Copyright (c) 2017, 2020, Chris Fraire <cfraire@me.com>.
23  * Portions Copyright (c) 2020, Aleksandr Kirillov <alexkirillovsamara@gmail.com>.
24  */
25 package org.opengrok.indexer.configuration;
26 
27 import java.beans.ExceptionListener;
28 import java.beans.XMLDecoder;
29 import java.beans.XMLEncoder;
30 import java.io.BufferedInputStream;
31 import java.io.BufferedOutputStream;
32 import java.io.ByteArrayInputStream;
33 import java.io.ByteArrayOutputStream;
34 import java.io.File;
35 import java.io.FileInputStream;
36 import java.io.FileOutputStream;
37 import java.io.IOException;
38 import java.io.InputStream;
39 import java.io.OutputStream;
40 import java.nio.file.Path;
41 import java.nio.file.Paths;
42 import java.util.ArrayList;
43 import java.util.Collections;
44 import java.util.HashMap;
45 import java.util.HashSet;
46 import java.util.LinkedList;
47 import java.util.List;
48 import java.util.Map;
49 import java.util.Set;
50 import java.util.TreeSet;
51 import java.util.concurrent.ConcurrentHashMap;
52 import java.util.logging.Level;
53 import java.util.logging.Logger;
54 import java.util.regex.PatternSyntaxException;
55 import java.util.stream.Collectors;
56 
57 import org.opengrok.indexer.authorization.AuthControlFlag;
58 import org.opengrok.indexer.authorization.AuthorizationStack;
59 import org.opengrok.indexer.history.RepositoryInfo;
60 import org.opengrok.indexer.logger.LoggerFactory;
61 
62 import static org.opengrok.indexer.configuration.PatternUtil.compilePattern;
63 
64 
65 /**
66  * Placeholder class for all configuration variables. Due to the multi-threaded
67  * nature of the web application, each thread will use the same instance of the
68  * configuration object for each page request. Class and methods should have
69  * package scope, but that didn't work with the XMLDecoder/XMLEncoder.
70  * <p>
71  * This should be as close to a
72  * <a href="https://en.wikipedia.org/wiki/Plain_old_Java_object">POJO</a> as
73  * possible.
74  */
75 public final class Configuration {
76 
77     private static final Logger LOGGER = LoggerFactory.getLogger(Configuration.class);
78     public static final String PLUGIN_DIRECTORY_DEFAULT = "plugins";
79 
80     /**
81      * Error string for negative numbers (could be int, double, long, ...).
82      * First argument is the name of the property, second argument is the actual
83      * value.
84      */
85     private static final String NEGATIVE_NUMBER_ERROR = "Invalid value for \"%s\" - \"%s\". Expected value greater or equal than 0";
86     /**
87      * Error string for non-positive numbers (could be int, double, long, ...).
88      * First argument is the name of the property, second argument is the actual
89      * value.
90      */
91     private static final String NONPOSITIVE_NUMBER_ERROR =
92         "Invalid value for \"%s\" - \"%s\". Expected value greater than 0";
93 
94     /**
95      * Path to {@code ctags} binary.
96      */
97     private String ctags;
98     private boolean webappCtags;
99 
100     /**
101      * A defined value to specify the mandoc binary or else null so that mandoc
102      * will be cross-referenced using {@code PlainXref}.
103      */
104     private String mandoc;
105 
106     /**
107      * Should the history log be cached?
108      */
109     private boolean historyCache;
110     /**
111      * flag to generate history. This is bigger hammer than @{code historyCache}
112      * above. If set to false, no history query will be ever made and the webapp
113      * will not display any history related links/allow any history queries.
114      */
115     private boolean historyEnabled;
116     /**
117      * maximum number of messages in webapp.
118      */
119     private int messageLimit;
120     /**
121      * Directory with authorization plug-ins. Default value is
122      * {@code dataRoot/../plugins} (can be {@code /var/opengrok/plugins} if dataRoot is
123      * {@code /var/opengrok/data}).
124      */
125     private String pluginDirectory;
126     /**
127      * Enable watching the plug-in directory for changes in real time. Suitable
128      * for development.
129      */
130     private boolean authorizationWatchdogEnabled;
131     private AuthorizationStack pluginStack;
132     private Map<String, Project> projects; // project name -> Project
133     private Set<Group> groups;
134     private String sourceRoot;
135     private String dataRoot;
136     /**
137      * Directory with include files for web application (header, footer, etc.).
138      */
139     private String includeRoot;
140     private List<RepositoryInfo> repositories;
141 
142     private boolean generateHtml;
143     /**
144      * Default projects will be used, when no project is selected and no project
145      * is in cookie, so basically only the first time a page is opened,
146      * or when web cookies are cleared.
147      */
148     private Set<Project> defaultProjects;
149     /**
150      * Default size of memory to be used for flushing of Lucene docs per thread.
151      * Lucene 4.x uses 16MB and 8 threads, so below is a nice tunable.
152      */
153     private double ramBufferSize;
154     /**
155      * If below is set, then we count how many files per project we need to
156      * process and print percentage of completion per project.
157      */
158     private boolean printProgress;
159     private boolean allowLeadingWildcard;
160     private IgnoredNames ignoredNames;
161     private Filter includedNames;
162     private String userPage;
163     private String userPageSuffix;
164     private String bugPage;
165     private String bugPattern;
166     private String reviewPage;
167     private String reviewPattern;
168     private String webappLAF;
169     private RemoteSCM remoteScmSupported;
170     private boolean optimizeDatabase;
171     private boolean quickContextScan;
172 
173     private LuceneLockName luceneLocking = LuceneLockName.OFF;
174     private boolean compressXref;
175     private boolean indexVersionedFilesOnly;
176     private int indexingParallelism;
177     private int repositoryInvalidationParallelism;
178     private int historyParallelism;
179     private int historyFileParallelism;
180     private boolean tagsEnabled;
181     private int hitsPerPage;
182     private int cachePages;
183     private short contextLimit; // initialized non-zero in ctor
184     private short contextSurround;
185     private boolean lastEditedDisplayMode;
186     private String CTagsExtraOptionsFile;
187     private int scanningDepth;
188     private int nestingMaximum;
189     private Set<String> allowedSymlinks;
190     private Set<String> canonicalRoots;
191     private boolean obfuscatingEMailAddresses;
192     private boolean chattyStatusPage;
193     private final Map<String, String> cmds;  // repository type -> command
194     private int tabSize;
195     private int indexerCommandTimeout; // in seconds
196     private int interactiveCommandTimeout; // in seconds
197     private int webappStartCommandTimeout; // in seconds
198     private int restfulCommandTimeout; // in seconds
199     private long ctagsTimeout; // in seconds
200     private long xrefTimeout; // in seconds
201     private boolean scopesEnabled;
202     private boolean projectsEnabled;
203     private boolean foldingEnabled;
204     /*
205      * Set to false if we want to disable fetching history of individual files
206      * (by running appropriate SCM command) when the history is not found
207      * in history cache for repositories capable of fetching history for
208      * directories.
209      */
210     private boolean fetchHistoryWhenNotInCache;
211     /*
212      * Set to false to disable extended handling of history of files across
213      * renames, i.e. support getting diffs of revisions across renames
214      * for capable repositories.
215      */
216     private boolean handleHistoryOfRenamedFiles;
217 
218     private boolean mergeCommitsEnabled;
219 
220     public static final double defaultRamBufferSize = 16;
221 
222     /**
223      * The directory hierarchy depth to limit the scanning for repositories.
224      * E.g. if the /mercurial/ directory (relative to source root) is a repository
225      * and /mercurial/usr/closed/ is sub-repository, the latter will be discovered
226      * only if the depth is set to 2 or greater.
227      */
228     public static final int defaultScanningDepth = 2;
229 
230     /**
231      * The name of the eftar file relative to the <var>DATA_ROOT</var>, which
232      * contains definition tags.
233      */
234     public static final String EFTAR_DTAGS_NAME = "dtags.eftar";
235 
236     /**
237      * Revision messages will be collapsible if they exceed this many number of
238      * characters. Front end enforces an appropriate minimum.
239      */
240     private int revisionMessageCollapseThreshold;
241 
242     /**
243      * Groups are collapsed if number of repositories is greater than this
244      * threshold. This applies only for non-favorite groups - groups which don't
245      * contain a project which is considered as a favorite project for the user.
246      * Favorite projects are the projects which the user browses and searches
247      * and are stored in a cookie. Favorite groups are always expanded.
248      */
249     private int groupsCollapseThreshold;
250 
251     /**
252      * Current indexed message will be collapsible if they exceed this many
253      * number of characters. Front end enforces an appropriate minimum.
254      */
255     private int currentIndexedCollapseThreshold;
256 
257     /**
258      * Upper bound for number of threads used for performing multi-project
259      * searches. This is total for the whole webapp.
260      */
261     private int MaxSearchThreadCount;
262 
263     /**
264      * Upper bound for number of threads used for getting revision contents.
265      * This is total for the whole webapp.
266      */
267     private int MaxRevisionThreadCount;
268 
269     /**
270      * If false, do not display listing or projects/repositories on the index page.
271      */
272     private boolean displayRepositories;
273 
274     /**
275      * If true, list directories first in xref directory listing.
276      */
277     private boolean listDirsFirst = true;
278 
279     /**
280      * A flag if the navigate window should be opened by default when browsing
281      * the source code of projects.
282      */
283     private boolean navigateWindowEnabled;
284 
285     private SuggesterConfig suggesterConfig = new SuggesterConfig();
286 
287     private StatsdConfig statsdConfig = new StatsdConfig();
288 
289     private Set<String> disabledRepositories;
290 
291     private Set<String> authenticationTokens; // for non-localhost API access
292     private String indexerAuthenticationToken;
293     private boolean allowInsecureTokens;
294 
295     private int historyChunkCount;
296     private boolean historyCachePerPartesEnabled = true;
297 
298     private String serverName;  // for reverse proxy environment
299 
300     private int connectTimeout = -1;    // connect timeout in seconds
301     private int apiTimeout = -1;    // API timeout in seconds
302 
303     private boolean historyBasedReindex;
304 
305     /*
306      * types of handling history for remote SCM repositories:
307      *  ON - index history and display it in webapp
308      *  OFF - do not index or display history in webapp
309      *  DIRBASED - index history only for repositories capable
310      *             of getting history for directories
311      *  UIONLY - display history only in webapp (do not index it)
312      */
313     public enum RemoteSCM {
314         ON, OFF, DIRBASED, UIONLY
315     }
316 
317     /**
318      * Get the default tab size (number of space characters per tab character)
319      * to use for each project. If {@code <= 0} tabs are read/write as is.
320      *
321      * @return current tab size set.
322      * @see Project#getTabSize()
323      * @see org.opengrok.indexer.analysis.ExpandTabsReader
324      */
getTabSize()325     public int getTabSize() {
326         return tabSize;
327     }
328 
329     /**
330      * Set the default tab size (number of space characters per tab character)
331      * to use for each project. If {@code <= 0} tabs are read/write as is.
332      *
333      * @param tabSize tabsize to set.
334      * @see Project#setTabSize(int)
335      * @see org.opengrok.indexer.analysis.ExpandTabsReader
336      */
setTabSize(int tabSize)337     public void setTabSize(int tabSize) {
338         this.tabSize = tabSize;
339     }
340 
getScanningDepth()341     public int getScanningDepth() {
342         return scanningDepth;
343     }
344 
345     /**
346      * Set the scanning depth to a new value.
347      *
348      * @param scanningDepth the new value
349      * @throws IllegalArgumentException when the scanningDepth is negative
350      */
setScanningDepth(int scanningDepth)351     public void setScanningDepth(int scanningDepth) throws IllegalArgumentException {
352         if (scanningDepth < 0) {
353             throw new IllegalArgumentException(
354                     String.format(NEGATIVE_NUMBER_ERROR, "scanningDepth", scanningDepth));
355         }
356         this.scanningDepth = scanningDepth;
357     }
358 
359     /**
360      * Gets the nesting maximum of repositories. Default is 1.
361      */
getNestingMaximum()362     public int getNestingMaximum() {
363         return nestingMaximum;
364     }
365 
366     /**
367      * Sets the nesting maximum of repositories to a specified value.
368      * @param nestingMaximum the new value
369      * @throws IllegalArgumentException if {@code nestingMaximum} is negative
370      */
setNestingMaximum(int nestingMaximum)371     public void setNestingMaximum(int nestingMaximum) throws IllegalArgumentException {
372         if (nestingMaximum < 0) {
373             throw new IllegalArgumentException(
374                     String.format(NEGATIVE_NUMBER_ERROR, "nestingMaximum", nestingMaximum));
375         }
376         this.nestingMaximum = nestingMaximum;
377     }
378 
getIndexerCommandTimeout()379     public int getIndexerCommandTimeout() {
380         return indexerCommandTimeout;
381     }
382 
383     /**
384      * Set the command timeout to a new value.
385      *
386      * @param timeout the new value
387      * @throws IllegalArgumentException when the timeout is negative
388      */
setIndexerCommandTimeout(int timeout)389     public void setIndexerCommandTimeout(int timeout) throws IllegalArgumentException {
390         if (timeout < 0) {
391             throw new IllegalArgumentException(
392                     String.format(NEGATIVE_NUMBER_ERROR, "commandTimeout", timeout));
393         }
394         this.indexerCommandTimeout = timeout;
395     }
396 
getRestfulCommandTimeout()397     public int getRestfulCommandTimeout() {
398         return restfulCommandTimeout;
399     }
400 
401     /**
402      * Set the command timeout to a new value.
403      *
404      * @param timeout the new value
405      * @throws IllegalArgumentException when the timeout is negative
406      */
setRestfulCommandTimeout(int timeout)407     public void setRestfulCommandTimeout(int timeout) throws IllegalArgumentException {
408         if (timeout < 0) {
409             throw new IllegalArgumentException(
410                     String.format(NEGATIVE_NUMBER_ERROR, "restfulCommandTimeout", timeout));
411         }
412         this.restfulCommandTimeout = timeout;
413     }
414 
getWebappStartCommandTimeout()415     public int getWebappStartCommandTimeout() {
416         return webappStartCommandTimeout;
417     }
418 
419     /**
420      * Set the command timeout to a new value.
421      *
422      * @param timeout the new value
423      * @throws IllegalArgumentException when the timeout is negative
424      */
setWebappStartCommandTimeout(int timeout)425     public void setWebappStartCommandTimeout(int timeout) throws IllegalArgumentException {
426         if (timeout < 0) {
427             throw new IllegalArgumentException(
428                     String.format(NEGATIVE_NUMBER_ERROR, "webappStartCommandTimeout", timeout));
429         }
430         this.webappStartCommandTimeout = timeout;
431     }
432 
getInteractiveCommandTimeout()433     public int getInteractiveCommandTimeout() {
434         return interactiveCommandTimeout;
435     }
436 
437     /**
438      * Set the interactive command timeout to a new value.
439      *
440      * @param timeout the new value
441      * @throws IllegalArgumentException when the timeout is negative
442      */
setInteractiveCommandTimeout(int timeout)443     public void setInteractiveCommandTimeout(int timeout) throws IllegalArgumentException {
444         if (timeout < 0) {
445             throw new IllegalArgumentException(
446                     String.format(NEGATIVE_NUMBER_ERROR, "interactiveCommandTimeout", timeout));
447         }
448         this.interactiveCommandTimeout = timeout;
449     }
450 
getCtagsTimeout()451     public long getCtagsTimeout() {
452         return ctagsTimeout;
453     }
454 
455     /**
456      * Set the ctags timeout to a new value.
457      *
458      * @param timeout the new value
459      * @throws IllegalArgumentException when the timeout is negative
460      */
setCtagsTimeout(long timeout)461     public void setCtagsTimeout(long timeout) throws IllegalArgumentException {
462         if (timeout < 0) {
463             throw new IllegalArgumentException(
464                     String.format(NEGATIVE_NUMBER_ERROR, "ctagsTimeout", timeout));
465         }
466         this.ctagsTimeout = timeout;
467     }
468 
getXrefTimeout()469     public long getXrefTimeout() {
470         return xrefTimeout;
471     }
472 
473     /**
474      * Set the timeout for generating xrefs to a new value.
475      *
476      * @param timeout the new value
477      * @throws IllegalArgumentException when the timeout is negative
478      */
setXrefTimeout(long timeout)479     public void setXrefTimeout(long timeout) throws IllegalArgumentException {
480         if (timeout < 0) {
481             throw new IllegalArgumentException(
482                     String.format(NEGATIVE_NUMBER_ERROR, "xrefTimeout", timeout));
483         }
484         this.xrefTimeout = timeout;
485     }
486 
isLastEditedDisplayMode()487     public boolean isLastEditedDisplayMode() {
488         return lastEditedDisplayMode;
489     }
490 
setLastEditedDisplayMode(boolean lastEditedDisplayMode)491     public void setLastEditedDisplayMode(boolean lastEditedDisplayMode) {
492         this.lastEditedDisplayMode = lastEditedDisplayMode;
493     }
494 
getGroupsCollapseThreshold()495     public int getGroupsCollapseThreshold() {
496         return groupsCollapseThreshold;
497     }
498 
499     /**
500      * Set the groups collapse threshold to a new value.
501      *
502      * @param groupsCollapseThreshold the new value
503      * @throws IllegalArgumentException when the timeout is negative
504      */
setGroupsCollapseThreshold(int groupsCollapseThreshold)505     public void setGroupsCollapseThreshold(int groupsCollapseThreshold) throws IllegalArgumentException {
506         if (groupsCollapseThreshold < 0) {
507             throw new IllegalArgumentException(
508                     String.format(NEGATIVE_NUMBER_ERROR, "groupsCollapseThreshold", groupsCollapseThreshold));
509         }
510         this.groupsCollapseThreshold = groupsCollapseThreshold;
511     }
512 
513     /**
514      * Creates a new instance of Configuration with default values.
515      */
Configuration()516     public Configuration() {
517         // This list of calls is sorted alphabetically so please keep it.
518         cmds = new HashMap<>();
519         setAllowLeadingWildcard(true);
520         setAllowedSymlinks(new HashSet<>());
521         setAuthenticationTokens(new HashSet<>());
522         setAuthorizationWatchdogEnabled(false);
523         //setBugPage("http://bugs.myserver.org/bugdatabase/view_bug.do?bug_id=");
524         setBugPattern("\\b([12456789][0-9]{6})\\b");
525         setCachePages(5);
526         setCanonicalRoots(new HashSet<>());
527         setIndexerCommandTimeout(600); // 10 minutes
528         setRestfulCommandTimeout(60);
529         setInteractiveCommandTimeout(30);
530         setWebappStartCommandTimeout(5);
531         setCompressXref(true);
532         setContextLimit((short) 10);
533         //contextSurround is default(short)
534         //ctags is default(String)
535         setCtagsTimeout(10);
536         setCurrentIndexedCollapseThreshold(27);
537         setDataRoot(null);
538         setDisplayRepositories(true);
539         setFetchHistoryWhenNotInCache(true);
540         setFoldingEnabled(true);
541         setGenerateHtml(true);
542         setGroups(new TreeSet<>());
543         setGroupsCollapseThreshold(4);
544         setHandleHistoryOfRenamedFiles(false);
545         setHistoryCache(true);
546         setHistoryEnabled(true);
547         setHitsPerPage(25);
548         setIgnoredNames(new IgnoredNames());
549         setIncludedNames(new Filter());
550         setIndexVersionedFilesOnly(false);
551         setLastEditedDisplayMode(true);
552         //luceneLocking default is OFF
553         //mandoc is default(String)
554         setMaxSearchThreadCount(2 * Runtime.getRuntime().availableProcessors());
555         setMaxRevisionThreadCount(Runtime.getRuntime().availableProcessors());
556         setMergeCommitsEnabled(false);
557         setMessageLimit(500);
558         setNavigateWindowEnabled(false);
559         setNestingMaximum(1);
560         setOptimizeDatabase(true);
561         setPluginDirectory(null);
562         setPluginStack(new AuthorizationStack(AuthControlFlag.REQUIRED, "default stack"));
563         setPrintProgress(false);
564         setDisabledRepositories(new HashSet<>());
565         setProjects(new ConcurrentHashMap<>());
566         setQuickContextScan(true);
567         //below can cause an outofmemory error, since it is defaulting to NO LIMIT
568         setRamBufferSize(defaultRamBufferSize); //MB
569         setRemoteScmSupported(RemoteSCM.OFF);
570         setRepositories(new ArrayList<>());
571         //setReviewPage("http://arc.myserver.org/caselog/PSARC/");
572         setReviewPattern("\\b(\\d{4}/\\d{3})\\b"); // in form e.g. PSARC 2008/305
573         setRevisionMessageCollapseThreshold(200);
574         setScanningDepth(defaultScanningDepth); // default depth of scanning for repositories
575         setScopesEnabled(true);
576         setSourceRoot(null);
577         //setTabSize(4);
578         setTagsEnabled(false);
579         //setUserPage("http://www.myserver.org/viewProfile.jspa?username=");
580         // Set to empty string so we can append it to the URL unconditionally later.
581         setHistoryBasedReindex(true);
582         setUserPageSuffix("");
583         setWebappLAF("default");
584         // webappCtags is default(boolean)
585         setXrefTimeout(30);
586         setApiTimeout(300); // 5 minutes
587         setConnectTimeout(3);
588     }
589 
getRepoCmd(String clazzName)590     public String getRepoCmd(String clazzName) {
591         return cmds.get(clazzName);
592     }
593 
setRepoCmd(String clazzName, String cmd)594     public String setRepoCmd(String clazzName, String cmd) {
595         if (clazzName == null) {
596             return null;
597         }
598         if (cmd == null || cmd.length() == 0) {
599             return cmds.remove(clazzName);
600         }
601         return cmds.put(clazzName, cmd);
602     }
603 
604     // just to satisfy bean/de|encoder stuff
getCmds()605     public Map<String, String> getCmds() {
606         return Collections.unmodifiableMap(cmds);
607     }
608 
609     /**
610      * @see org.opengrok.indexer.web.messages.MessagesContainer
611      *
612      * @return int the current message limit
613      */
getMessageLimit()614     public int getMessageLimit() {
615         return messageLimit;
616     }
617 
618     /**
619      * @see org.opengrok.indexer.web.messages.MessagesContainer
620      *
621      * @param messageLimit the limit
622      * @throws IllegalArgumentException when the limit is negative
623      */
setMessageLimit(int messageLimit)624     public void setMessageLimit(int messageLimit) throws IllegalArgumentException {
625         if (messageLimit < 0) {
626             throw new IllegalArgumentException(
627                     String.format(NEGATIVE_NUMBER_ERROR, "messageLimit", messageLimit));
628         }
629         this.messageLimit = messageLimit;
630     }
631 
getPluginDirectory()632     public String getPluginDirectory() {
633         return pluginDirectory;
634     }
635 
setPluginDirectory(String pluginDirectory)636     public void setPluginDirectory(String pluginDirectory) {
637         this.pluginDirectory = pluginDirectory;
638     }
639 
isAuthorizationWatchdogEnabled()640     public boolean isAuthorizationWatchdogEnabled() {
641         return authorizationWatchdogEnabled;
642     }
643 
setAuthorizationWatchdogEnabled(boolean authorizationWatchdogEnabled)644     public void setAuthorizationWatchdogEnabled(boolean authorizationWatchdogEnabled) {
645         this.authorizationWatchdogEnabled = authorizationWatchdogEnabled;
646     }
647 
getPluginStack()648     public AuthorizationStack getPluginStack() {
649         return pluginStack;
650     }
651 
setPluginStack(AuthorizationStack pluginStack)652     public void setPluginStack(AuthorizationStack pluginStack) {
653         this.pluginStack = pluginStack;
654     }
655 
setCmds(Map<String, String> cmds)656     public void setCmds(Map<String, String> cmds) {
657         this.cmds.clear();
658         this.cmds.putAll(cmds);
659     }
660 
661     /**
662      * Gets the configuration's ctags command. Default is null.
663      * @return the configured value
664      */
getCtags()665     public String getCtags() {
666         return ctags;
667     }
668 
669     /**
670      * Sets the configuration's ctags command.
671      * @param ctags A program name (full-path if necessary) or {@code null}
672      */
setCtags(String ctags)673     public void setCtags(String ctags) {
674         this.ctags = ctags;
675     }
676 
677     /**
678      * Gets the configuration's mandoc command. Default is {@code null}.
679      * @return the configured value
680      */
getMandoc()681     public String getMandoc() {
682         return mandoc;
683     }
684 
685     /**
686      * Sets the configuration's mandoc command.
687      * @param value A program name (full-path if necessary) or {@code null}
688      */
setMandoc(String value)689     public void setMandoc(String value) {
690         this.mandoc = value;
691     }
692 
693     /**
694      * Gets the total number of context lines per file to show in cases where it
695      * is limited. Default is 10.
696      * @return a value greater than zero
697      */
getContextLimit()698     public short getContextLimit() {
699         return contextLimit;
700     }
701 
702     /**
703      * Sets the total number of context lines per file to show in cases where it
704      * is limited.
705      * @param value a value greater than zero
706      * @throws IllegalArgumentException if {@code value} is not positive
707      */
setContextLimit(short value)708     public void setContextLimit(short value) throws IllegalArgumentException {
709         if (value < 1) {
710             throw new IllegalArgumentException(
711                 String.format(NONPOSITIVE_NUMBER_ERROR, "contextLimit", value));
712         }
713         this.contextLimit = value;
714     }
715 
716     /**
717      * Gets the number of context lines to show before or after any match.
718      * Default is zero.
719      * @return a value greater than or equal to zero
720      */
getContextSurround()721     public short getContextSurround() {
722         return contextSurround;
723     }
724 
725     /**
726      * Sets the number of context lines to show before or after any match.
727      * @param value a value greater than or equal to zero
728      * @throws IllegalArgumentException if {@code value} is negative
729      */
setContextSurround(short value)730     public void setContextSurround(short value)
731             throws IllegalArgumentException {
732         if (value < 0) {
733             throw new IllegalArgumentException(
734                 String.format(NEGATIVE_NUMBER_ERROR, "contextSurround", value));
735         }
736         this.contextSurround = value;
737     }
738 
getCachePages()739     public int getCachePages() {
740         return cachePages;
741     }
742 
743     /**
744      * Set the cache pages to a new value.
745      *
746      * @param cachePages the new value
747      * @throws IllegalArgumentException when the cachePages is negative
748      */
setCachePages(int cachePages)749     public void setCachePages(int cachePages) throws IllegalArgumentException {
750         if (cachePages < 0) {
751             throw new IllegalArgumentException(
752                     String.format(NEGATIVE_NUMBER_ERROR, "cachePages", cachePages));
753         }
754         this.cachePages = cachePages;
755     }
756 
getHitsPerPage()757     public int getHitsPerPage() {
758         return hitsPerPage;
759     }
760 
761     /**
762      * Set the hits per page to a new value.
763      *
764      * @param hitsPerPage the new value
765      * @throws IllegalArgumentException when the hitsPerPage is negative
766      */
setHitsPerPage(int hitsPerPage)767     public void setHitsPerPage(int hitsPerPage) throws IllegalArgumentException {
768         if (hitsPerPage < 0) {
769             throw new IllegalArgumentException(
770                     String.format(NEGATIVE_NUMBER_ERROR, "hitsPerPage", hitsPerPage));
771         }
772         this.hitsPerPage = hitsPerPage;
773     }
774 
775     /**
776      * Should the history be enabled ?
777      *
778      * @return {@code true} if history is enabled, {@code false} otherwise
779      */
isHistoryEnabled()780     public boolean isHistoryEnabled() {
781         return historyEnabled;
782     }
783 
784     /**
785      * Set whether history should be enabled.
786      *
787      * @param flag if {@code true} enable history
788      */
setHistoryEnabled(boolean flag)789     public void setHistoryEnabled(boolean flag) {
790         this.historyEnabled = flag;
791     }
792 
793     /**
794      * Should the history log be cached?
795      *
796      * @return {@code true} if a {@code HistoryCache} implementation should be
797      * used, {@code false} otherwise
798      */
isHistoryCache()799     public boolean isHistoryCache() {
800         return historyCache;
801     }
802 
803     /**
804      * Set whether history should be cached.
805      *
806      * @param historyCache if {@code true} enable history cache
807      */
setHistoryCache(boolean historyCache)808     public void setHistoryCache(boolean historyCache) {
809         this.historyCache = historyCache;
810     }
811 
isFetchHistoryWhenNotInCache()812     public boolean isFetchHistoryWhenNotInCache() {
813         return fetchHistoryWhenNotInCache;
814     }
815 
setFetchHistoryWhenNotInCache(boolean nofetch)816     public void setFetchHistoryWhenNotInCache(boolean nofetch) {
817         this.fetchHistoryWhenNotInCache = nofetch;
818     }
819 
isHandleHistoryOfRenamedFiles()820     public boolean isHandleHistoryOfRenamedFiles() {
821         return handleHistoryOfRenamedFiles;
822     }
823 
setHandleHistoryOfRenamedFiles(boolean enable)824     public void setHandleHistoryOfRenamedFiles(boolean enable) {
825         this.handleHistoryOfRenamedFiles = enable;
826     }
827 
setMergeCommitsEnabled(boolean flag)828     public void setMergeCommitsEnabled(boolean flag) {
829         this.mergeCommitsEnabled = flag;
830     }
831 
isMergeCommitsEnabled()832     public boolean isMergeCommitsEnabled() {
833         return mergeCommitsEnabled;
834     }
835 
isNavigateWindowEnabled()836     public boolean isNavigateWindowEnabled() {
837         return navigateWindowEnabled;
838     }
839 
setNavigateWindowEnabled(boolean navigateWindowEnabled)840     public void setNavigateWindowEnabled(boolean navigateWindowEnabled) {
841         this.navigateWindowEnabled = navigateWindowEnabled;
842     }
843 
getProjects()844     public Map<String, Project> getProjects() {
845         return projects;
846     }
847 
setProjects(Map<String, Project> projects)848     public void setProjects(Map<String, Project> projects) {
849         this.projects = projects;
850     }
851 
852     /**
853      * Adds a group to the set. This is performed upon configuration parsing
854      *
855      * @param group group
856      * @throws IOException when group is not unique across the set
857      */
addGroup(Group group)858     public void addGroup(Group group) throws IOException {
859         if (!groups.add(group)) {
860             throw new IOException(
861                     String.format("Duplicate group name '%s' in configuration.",
862                             group.getName()));
863         }
864     }
865 
getGroups()866     public Set<Group> getGroups() {
867         return groups;
868     }
869 
setGroups(Set<Group> groups)870     public void setGroups(Set<Group> groups) {
871         this.groups = groups;
872     }
873 
getSourceRoot()874     public String getSourceRoot() {
875         return sourceRoot;
876     }
877 
setSourceRoot(String sourceRoot)878     public void setSourceRoot(String sourceRoot) {
879         this.sourceRoot = sourceRoot;
880     }
881 
getDataRoot()882     public String getDataRoot() {
883         return dataRoot;
884     }
885 
886     /**
887      * Sets data root.
888      *
889      * This method also sets the pluginDirectory if it is not already set.
890      *
891      * @see #setPluginDirectory(java.lang.String)
892      *
893      * @param dataRoot data root path
894      */
setDataRoot(String dataRoot)895     public void setDataRoot(String dataRoot) {
896         if (dataRoot != null && getPluginDirectory() == null) {
897             setPluginDirectory(dataRoot + "/../" + PLUGIN_DIRECTORY_DEFAULT);
898         }
899         this.dataRoot = dataRoot;
900     }
901 
902     /**
903      * If {@link #includeRoot} is not set, {@link #dataRoot} will be returned.
904      * @return web include root directory
905      */
getIncludeRoot()906     public String getIncludeRoot() {
907         return includeRoot != null ? includeRoot : dataRoot;
908     }
909 
setIncludeRoot(String newRoot)910     public void setIncludeRoot(String newRoot) {
911         this.includeRoot = newRoot;
912     }
913 
getRepositories()914     public List<RepositoryInfo> getRepositories() {
915         return repositories;
916     }
917 
setRepositories(List<RepositoryInfo> repositories)918     public void setRepositories(List<RepositoryInfo> repositories) {
919         this.repositories = repositories;
920     }
921 
addRepositories(List<RepositoryInfo> repositories)922     public void addRepositories(List<RepositoryInfo> repositories) {
923         this.repositories.addAll(repositories);
924     }
925 
setGenerateHtml(boolean generateHtml)926     public void setGenerateHtml(boolean generateHtml) {
927         this.generateHtml = generateHtml;
928     }
929 
isGenerateHtml()930     public boolean isGenerateHtml() {
931         return generateHtml;
932     }
933 
setDefaultProjects(Set<Project> defaultProjects)934     public void setDefaultProjects(Set<Project> defaultProjects) {
935         this.defaultProjects = defaultProjects;
936     }
937 
getDefaultProjects()938     public Set<Project> getDefaultProjects() {
939         return defaultProjects;
940     }
941 
getRamBufferSize()942     public double getRamBufferSize() {
943         return ramBufferSize;
944     }
945 
946     /**
947      * Set size of memory to be used for flushing docs (default 16 MB) (this can
948      * improve index speed a LOT) note that this is per thread (lucene uses 8
949      * threads by default in 4.x).
950      *
951      * @param ramBufferSize new size in MB
952      */
setRamBufferSize(double ramBufferSize)953     public void setRamBufferSize(double ramBufferSize) {
954         this.ramBufferSize = ramBufferSize;
955     }
956 
isPrintProgress()957     public boolean isPrintProgress() {
958         return printProgress;
959     }
960 
setPrintProgress(boolean printProgress)961     public void setPrintProgress(boolean printProgress) {
962         this.printProgress = printProgress;
963     }
964 
setAllowLeadingWildcard(boolean allowLeadingWildcard)965     public void setAllowLeadingWildcard(boolean allowLeadingWildcard) {
966         this.allowLeadingWildcard = allowLeadingWildcard;
967     }
968 
isAllowLeadingWildcard()969     public boolean isAllowLeadingWildcard() {
970         return allowLeadingWildcard;
971     }
972 
isQuickContextScan()973     public boolean isQuickContextScan() {
974         return quickContextScan;
975     }
976 
setQuickContextScan(boolean quickContextScan)977     public void setQuickContextScan(boolean quickContextScan) {
978         this.quickContextScan = quickContextScan;
979     }
980 
setIgnoredNames(IgnoredNames ignoredNames)981     public void setIgnoredNames(IgnoredNames ignoredNames) {
982         this.ignoredNames = ignoredNames;
983     }
984 
getIgnoredNames()985     public IgnoredNames getIgnoredNames() {
986         return ignoredNames;
987     }
988 
setIncludedNames(Filter includedNames)989     public void setIncludedNames(Filter includedNames) {
990         this.includedNames = includedNames;
991     }
992 
getIncludedNames()993     public Filter getIncludedNames() {
994         return includedNames;
995     }
996 
setUserPage(String userPage)997     public void setUserPage(String userPage) {
998         this.userPage = userPage;
999     }
1000 
getUserPage()1001     public String getUserPage() {
1002         return userPage;
1003     }
1004 
setUserPageSuffix(String userPageSuffix)1005     public void setUserPageSuffix(String userPageSuffix) {
1006         this.userPageSuffix = userPageSuffix;
1007     }
1008 
getUserPageSuffix()1009     public String getUserPageSuffix() {
1010         return userPageSuffix;
1011     }
1012 
setBugPage(String bugPage)1013     public void setBugPage(String bugPage) {
1014         this.bugPage = bugPage;
1015     }
1016 
getBugPage()1017     public String getBugPage() {
1018         return bugPage;
1019     }
1020 
1021     /**
1022      * Set the bug pattern to a new value.
1023      *
1024      * @param bugPattern the new pattern
1025      * @throws PatternSyntaxException when the pattern is not a valid regexp or
1026      * does not contain at least one capture group and the group does not
1027      * contain a single character
1028      */
setBugPattern(String bugPattern)1029     public void setBugPattern(String bugPattern) throws PatternSyntaxException {
1030         this.bugPattern = compilePattern(bugPattern);
1031     }
1032 
getBugPattern()1033     public String getBugPattern() {
1034         return bugPattern;
1035     }
1036 
getReviewPage()1037     public String getReviewPage() {
1038         return reviewPage;
1039     }
1040 
setReviewPage(String reviewPage)1041     public void setReviewPage(String reviewPage) {
1042         this.reviewPage = reviewPage;
1043     }
1044 
getReviewPattern()1045     public String getReviewPattern() {
1046         return reviewPattern;
1047     }
1048 
1049     /**
1050      * Set the review pattern to a new value.
1051      *
1052      * @param reviewPattern the new pattern
1053      * @throws PatternSyntaxException when the pattern is not a valid regexp or
1054      * does not contain at least one capture group and the group does not
1055      * contain a single character
1056      */
setReviewPattern(String reviewPattern)1057     public void setReviewPattern(String reviewPattern) throws PatternSyntaxException {
1058         this.reviewPattern = compilePattern(reviewPattern);
1059     }
1060 
getWebappLAF()1061     public String getWebappLAF() {
1062         return webappLAF;
1063     }
1064 
setWebappLAF(String webappLAF)1065     public void setWebappLAF(String webappLAF) {
1066         this.webappLAF = webappLAF;
1067     }
1068 
1069     /**
1070      * Gets a value indicating if the web app should run ctags as necessary.
1071      */
isWebappCtags()1072     public boolean isWebappCtags() {
1073         return webappCtags;
1074     }
1075 
1076     /**
1077      * Sets a value indicating if the web app should run ctags as necessary.
1078      */
setWebappCtags(boolean value)1079     public void setWebappCtags(boolean value) {
1080         this.webappCtags = value;
1081     }
1082 
getRemoteScmSupported()1083     public RemoteSCM getRemoteScmSupported() {
1084         return remoteScmSupported;
1085     }
1086 
setRemoteScmSupported(RemoteSCM remoteScmSupported)1087     public void setRemoteScmSupported(RemoteSCM remoteScmSupported) {
1088         this.remoteScmSupported = remoteScmSupported;
1089     }
1090 
isOptimizeDatabase()1091     public boolean isOptimizeDatabase() {
1092         return optimizeDatabase;
1093     }
1094 
setOptimizeDatabase(boolean optimizeDatabase)1095     public void setOptimizeDatabase(boolean optimizeDatabase) {
1096         this.optimizeDatabase = optimizeDatabase;
1097     }
1098 
getLuceneLocking()1099     public LuceneLockName getLuceneLocking() {
1100         return luceneLocking;
1101     }
1102 
1103     /**
1104      * @param value off|on|simple|native where "on" is an alias for "simple".
1105      * Any other value is a fallback alias for "off" (with a logged warning).
1106      */
setLuceneLocking(LuceneLockName value)1107     public void setLuceneLocking(LuceneLockName value) {
1108         this.luceneLocking = value;
1109     }
1110 
setCompressXref(boolean compressXref)1111     public void setCompressXref(boolean compressXref) {
1112         this.compressXref = compressXref;
1113     }
1114 
isCompressXref()1115     public boolean isCompressXref() {
1116         return compressXref;
1117     }
1118 
isIndexVersionedFilesOnly()1119     public boolean isIndexVersionedFilesOnly() {
1120         return indexVersionedFilesOnly;
1121     }
1122 
setIndexVersionedFilesOnly(boolean indexVersionedFilesOnly)1123     public void setIndexVersionedFilesOnly(boolean indexVersionedFilesOnly) {
1124         this.indexVersionedFilesOnly = indexVersionedFilesOnly;
1125     }
1126 
getIndexingParallelism()1127     public int getIndexingParallelism() {
1128         return indexingParallelism;
1129     }
1130 
setIndexingParallelism(int value)1131     public void setIndexingParallelism(int value) {
1132         this.indexingParallelism = Math.max(value, 0);
1133     }
1134 
getRepositoryInvalidationParallelism()1135     public int getRepositoryInvalidationParallelism() {
1136         return repositoryInvalidationParallelism;
1137     }
1138 
setRepositoryInvalidationParallelism(int value)1139     public void setRepositoryInvalidationParallelism(int value) {
1140         this.repositoryInvalidationParallelism = Math.max(value, 0);
1141     }
1142 
getHistoryParallelism()1143     public int getHistoryParallelism() {
1144         return historyParallelism;
1145     }
1146 
setHistoryParallelism(int value)1147     public void setHistoryParallelism(int value) {
1148         this.historyParallelism = Math.max(value, 0);
1149     }
1150 
getHistoryFileParallelism()1151     public int getHistoryFileParallelism() {
1152         return historyFileParallelism;
1153     }
1154 
setHistoryFileParallelism(int value)1155     public void setHistoryFileParallelism(int value) {
1156         this.historyFileParallelism = Math.max(value, 0);
1157     }
1158 
isTagsEnabled()1159     public boolean isTagsEnabled() {
1160         return this.tagsEnabled;
1161     }
1162 
setTagsEnabled(boolean tagsEnabled)1163     public void setTagsEnabled(boolean tagsEnabled) {
1164         this.tagsEnabled = tagsEnabled;
1165     }
1166 
setRevisionMessageCollapseThreshold(int threshold)1167     public void setRevisionMessageCollapseThreshold(int threshold) {
1168         this.revisionMessageCollapseThreshold = threshold;
1169     }
1170 
getRevisionMessageCollapseThreshold()1171     public int getRevisionMessageCollapseThreshold() {
1172         return this.revisionMessageCollapseThreshold;
1173     }
1174 
getCurrentIndexedCollapseThreshold()1175     public int getCurrentIndexedCollapseThreshold() {
1176         return currentIndexedCollapseThreshold;
1177     }
1178 
setCurrentIndexedCollapseThreshold(int currentIndexedCollapseThreshold)1179     public void setCurrentIndexedCollapseThreshold(int currentIndexedCollapseThreshold) {
1180         this.currentIndexedCollapseThreshold = currentIndexedCollapseThreshold;
1181     }
1182 
isDisplayRepositories()1183     public boolean isDisplayRepositories() {
1184         return this.displayRepositories;
1185     }
1186 
setDisplayRepositories(boolean flag)1187     public void setDisplayRepositories(boolean flag) {
1188         this.displayRepositories = flag;
1189     }
1190 
getListDirsFirst()1191     public boolean getListDirsFirst() {
1192         return listDirsFirst;
1193     }
1194 
setListDirsFirst(boolean flag)1195     public void setListDirsFirst(boolean flag) {
1196         listDirsFirst = flag;
1197     }
1198 
1199     /**
1200      * The name of the file relative to the <var>DATA_ROOT</var>, which should
1201      * be included into the footer of generated web pages.
1202      */
1203     public static final String FOOTER_INCLUDE_FILE = "footer_include";
1204 
1205     /**
1206      * The name of the file relative to the <var>DATA_ROOT</var>, which should
1207      * be included into the header of generated web pages.
1208      */
1209     public static final String HEADER_INCLUDE_FILE = "header_include";
1210 
1211     /**
1212      * The name of the file relative to the <var>DATA_ROOT</var>, which should
1213      * be included into the body of web app's "Home" page.
1214      */
1215     public static final String BODY_INCLUDE_FILE = "body_include";
1216 
1217     /**
1218      * The name of the file relative to the <var>DATA_ROOT</var>, which should
1219      * be included into the error page handling access forbidden errors - HTTP
1220      * code 403 Forbidden.
1221      */
1222     public static final String E_FORBIDDEN_INCLUDE_FILE = "error_forbidden_include";
1223 
1224 
1225     /**
1226      * The name of the file relative to the <var>DATA_ROOT</var>, which should
1227      * be included into the HTTP header of generated web pages.
1228      */
1229     public static final String HTTP_HEADER_INCLUDE_FILE = "http_header_include";
1230 
1231     /**
1232      * @return path to the file holding compiled path descriptions for the web application
1233      */
getDtagsEftarPath()1234     public Path getDtagsEftarPath() {
1235         return Paths.get(getDataRoot(), EFTAR_DTAGS_NAME);
1236     }
1237 
getCTagsExtraOptionsFile()1238     public String getCTagsExtraOptionsFile() {
1239         return CTagsExtraOptionsFile;
1240     }
1241 
setCTagsExtraOptionsFile(String filename)1242     public void setCTagsExtraOptionsFile(String filename) {
1243         this.CTagsExtraOptionsFile = filename;
1244     }
1245 
getAllowedSymlinks()1246     public Set<String> getAllowedSymlinks() {
1247         return allowedSymlinks;
1248     }
1249 
setAllowedSymlinks(Set<String> allowedSymlinks)1250     public void setAllowedSymlinks(Set<String> allowedSymlinks) {
1251         this.allowedSymlinks = allowedSymlinks;
1252     }
1253 
getCanonicalRoots()1254     public Set<String> getCanonicalRoots() {
1255         return canonicalRoots;
1256     }
1257 
setCanonicalRoots(Set<String> canonicalRoots)1258     public void setCanonicalRoots(Set<String> canonicalRoots) {
1259         this.canonicalRoots = canonicalRoots;
1260     }
1261 
isObfuscatingEMailAddresses()1262     public boolean isObfuscatingEMailAddresses() {
1263         return obfuscatingEMailAddresses;
1264     }
1265 
setObfuscatingEMailAddresses(boolean obfuscate)1266     public void setObfuscatingEMailAddresses(boolean obfuscate) {
1267         this.obfuscatingEMailAddresses = obfuscate;
1268     }
1269 
isChattyStatusPage()1270     public boolean isChattyStatusPage() {
1271         return chattyStatusPage;
1272     }
1273 
setChattyStatusPage(boolean chattyStatusPage)1274     public void setChattyStatusPage(boolean chattyStatusPage) {
1275         this.chattyStatusPage = chattyStatusPage;
1276     }
1277 
isScopesEnabled()1278     public boolean isScopesEnabled() {
1279         return scopesEnabled;
1280     }
1281 
setScopesEnabled(boolean scopesEnabled)1282     public void setScopesEnabled(boolean scopesEnabled) {
1283         this.scopesEnabled = scopesEnabled;
1284     }
1285 
isFoldingEnabled()1286     public boolean isFoldingEnabled() {
1287         return foldingEnabled;
1288     }
1289 
setFoldingEnabled(boolean foldingEnabled)1290     public void setFoldingEnabled(boolean foldingEnabled) {
1291         this.foldingEnabled = foldingEnabled;
1292     }
1293 
getMaxSearchThreadCount()1294     public int getMaxSearchThreadCount() {
1295         return MaxSearchThreadCount;
1296     }
1297 
setMaxSearchThreadCount(int count)1298     public void setMaxSearchThreadCount(int count) {
1299         this.MaxSearchThreadCount = count;
1300     }
1301 
getMaxRevisionThreadCount()1302     public int getMaxRevisionThreadCount() {
1303         return MaxRevisionThreadCount;
1304     }
1305 
setMaxRevisionThreadCount(int count)1306     public void setMaxRevisionThreadCount(int count) {
1307         this.MaxRevisionThreadCount = count;
1308     }
1309 
isProjectsEnabled()1310     public boolean isProjectsEnabled() {
1311         return projectsEnabled;
1312     }
1313 
setProjectsEnabled(boolean flag)1314     public void setProjectsEnabled(boolean flag) {
1315         this.projectsEnabled = flag;
1316     }
1317 
getSuggesterConfig()1318     public SuggesterConfig getSuggesterConfig() {
1319         return suggesterConfig;
1320     }
1321 
setSuggesterConfig(final SuggesterConfig config)1322     public void setSuggesterConfig(final SuggesterConfig config) {
1323         if (config == null) {
1324             throw new IllegalArgumentException("Cannot set Suggester configuration to null");
1325         }
1326         this.suggesterConfig = config;
1327     }
1328 
getStatsdConfig()1329     public StatsdConfig getStatsdConfig() {
1330         return statsdConfig;
1331     }
1332 
setStatsdConfig(final StatsdConfig config)1333     public void setStatsdConfig(final StatsdConfig config) {
1334         if (config == null) {
1335             throw new IllegalArgumentException("Cannot set Statsd configuration to null");
1336         }
1337         this.statsdConfig = config;
1338     }
1339 
getDisabledRepositories()1340     public Set<String> getDisabledRepositories() {
1341         return disabledRepositories;
1342     }
1343 
setDisabledRepositories(Set<String> disabledRepositories)1344     public void setDisabledRepositories(Set<String> disabledRepositories) {
1345         this.disabledRepositories = disabledRepositories;
1346     }
1347 
getAuthenticationTokens()1348     public Set<String> getAuthenticationTokens() {
1349         return authenticationTokens;
1350     }
1351 
setAuthenticationTokens(Set<String> tokens)1352     public void setAuthenticationTokens(Set<String> tokens) {
1353         this.authenticationTokens = tokens;
1354     }
1355 
getIndexerAuthenticationToken()1356     public String getIndexerAuthenticationToken() {
1357         return indexerAuthenticationToken;
1358     }
1359 
setIndexerAuthenticationToken(String token)1360     public void setIndexerAuthenticationToken(String token) {
1361         this.indexerAuthenticationToken = token;
1362     }
1363 
isAllowInsecureTokens()1364     public boolean isAllowInsecureTokens() {
1365         return this.allowInsecureTokens;
1366     }
1367 
setAllowInsecureTokens(boolean value)1368     public void setAllowInsecureTokens(boolean value) {
1369         this.allowInsecureTokens = value;
1370     }
1371 
getHistoryChunkCount()1372     public int getHistoryChunkCount() {
1373         return historyChunkCount;
1374     }
1375 
setHistoryChunkCount(int historyChunkCount)1376     public void setHistoryChunkCount(int historyChunkCount) {
1377         this.historyChunkCount = historyChunkCount;
1378     }
1379 
isHistoryCachePerPartesEnabled()1380     public boolean isHistoryCachePerPartesEnabled() {
1381         return historyCachePerPartesEnabled;
1382     }
1383 
setHistoryCachePerPartesEnabled(boolean historyCachePerPartesEnabled)1384     public void setHistoryCachePerPartesEnabled(boolean historyCachePerPartesEnabled) {
1385         this.historyCachePerPartesEnabled = historyCachePerPartesEnabled;
1386     }
1387 
getServerName()1388     public String getServerName() {
1389         return serverName;
1390     }
1391 
setServerName(String serverName)1392     public void setServerName(String serverName) {
1393         this.serverName = serverName;
1394     }
1395 
getConnectTimeout()1396     public int getConnectTimeout() {
1397         return connectTimeout;
1398     }
1399 
setConnectTimeout(int connectTimeout)1400     public void setConnectTimeout(int connectTimeout) {
1401         if (connectTimeout < 0) {
1402             throw new IllegalArgumentException(String.format(NEGATIVE_NUMBER_ERROR, "connectTimeout", connectTimeout));
1403         }
1404         this.connectTimeout = connectTimeout;
1405     }
1406 
getApiTimeout()1407     public int getApiTimeout() {
1408         return apiTimeout;
1409     }
1410 
setApiTimeout(int apiTimeout)1411     public void setApiTimeout(int apiTimeout) {
1412         if (apiTimeout < 0) {
1413             throw new IllegalArgumentException(String.format(NEGATIVE_NUMBER_ERROR, "apiTimeout", apiTimeout));
1414         }
1415         this.apiTimeout = apiTimeout;
1416     }
1417 
isHistoryBasedReindex()1418     public boolean isHistoryBasedReindex() {
1419         return historyBasedReindex;
1420     }
1421 
setHistoryBasedReindex(boolean flag)1422     public void setHistoryBasedReindex(boolean flag) {
1423         historyBasedReindex = flag;
1424     }
1425 
1426     /**
1427      * Write the current configuration to a file.
1428      *
1429      * @param file the file to write the configuration into
1430      * @throws IOException if an error occurs
1431      */
write(File file)1432     public void write(File file) throws IOException {
1433         try (FileOutputStream out = new FileOutputStream(file)) {
1434             this.encodeObject(out);
1435         }
1436     }
1437 
getXMLRepresentationAsString()1438     public String getXMLRepresentationAsString() {
1439         ByteArrayOutputStream bos = new ByteArrayOutputStream();
1440         this.encodeObject(bos);
1441         return bos.toString();
1442     }
1443 
encodeObject(OutputStream out)1444     public void encodeObject(OutputStream out) {
1445         try (XMLEncoder e = new XMLEncoder(new BufferedOutputStream(out))) {
1446             e.writeObject(this);
1447         }
1448     }
1449 
read(File file)1450     public static Configuration read(File file) throws IOException {
1451         LOGGER.log(Level.INFO, "Reading configuration from {0}", file.getCanonicalPath());
1452         try (FileInputStream in = new FileInputStream(file)) {
1453             return decodeObject(in);
1454         }
1455     }
1456 
makeXMLStringAsConfiguration(String xmlconfig)1457     public static Configuration makeXMLStringAsConfiguration(String xmlconfig) throws IOException {
1458         final Configuration ret;
1459         final ByteArrayInputStream in = new ByteArrayInputStream(xmlconfig.getBytes());
1460         ret = decodeObject(in);
1461         return ret;
1462     }
1463 
1464     @SuppressWarnings("lgtm[java/unsafe-deserialization]")
decodeObject(InputStream in)1465     private static Configuration decodeObject(InputStream in) throws IOException {
1466         final Object ret;
1467         final LinkedList<Exception> exceptions = new LinkedList<>();
1468         ExceptionListener listener = exceptions::addLast;
1469 
1470         try (XMLDecoder d = new XMLDecoder(new BufferedInputStream(in), null, listener,
1471                 new ConfigurationClassLoader())) {
1472             ret = d.readObject();
1473         }
1474 
1475         if (!(ret instanceof Configuration)) {
1476             throw new IOException("Not a valid config file");
1477         }
1478 
1479         if (!exceptions.isEmpty()) {
1480             // There was an exception during parsing.
1481             // see addGroup()
1482             if (exceptions.getFirst() instanceof IOException) {
1483                 throw (IOException) exceptions.getFirst();
1484             }
1485             throw new IOException(exceptions.getFirst());
1486         }
1487 
1488         Configuration conf = ((Configuration) ret);
1489 
1490         // Removes all non-root groups.
1491         // This ensures that when the configuration is reloaded then the set
1492         // contains only root groups. Subgroups are discovered again
1493         // as follows below
1494         conf.groups.removeIf(g -> g.getParent() != null);
1495 
1496         // Traversing subgroups and checking for duplicates,
1497         // effectively transforms the group tree to a structure (Set)
1498         // supporting an iterator.
1499         TreeSet<Group> copy = new TreeSet<>();
1500         LinkedList<Group> stack = new LinkedList<>(conf.groups);
1501         while (!stack.isEmpty()) {
1502             Group group = stack.pollFirst();
1503             stack.addAll(group.getSubgroups());
1504 
1505             if (!copy.add(group)) {
1506                 throw new IOException(
1507                         String.format("Duplicate group name '%s' in configuration.",
1508                                 group.getName()));
1509             }
1510 
1511             // populate groups where the current group in in their subtree
1512             Group tmp = group.getParent();
1513             while (tmp != null) {
1514                 tmp.addDescendant(group);
1515                 tmp = tmp.getParent();
1516             }
1517         }
1518         conf.setGroups(copy);
1519 
1520         /*
1521          * Validate any defined canonicalRoot entries, and only include where
1522          * validation succeeds.
1523          */
1524         if (conf.canonicalRoots != null) {
1525             conf.canonicalRoots = conf.canonicalRoots.stream().filter(s -> {
1526                 String problem = CanonicalRootValidator.validate(s, "canonicalRoot element");
1527                 if (problem == null) {
1528                     return true;
1529                 } else {
1530                     LOGGER.warning(problem);
1531                     return false;
1532                 }
1533             }).collect(Collectors.toCollection(HashSet::new));
1534         }
1535 
1536         return conf;
1537     }
1538 
1539     public static class ConfigurationException extends Exception {
1540         static final long serialVersionUID = -1;
1541 
ConfigurationException(String message)1542         public ConfigurationException(String message) {
1543             super(message);
1544         }
1545     }
1546 
1547     /**
1548      * Check if configuration is populated and self-consistent.
1549      * @throws ConfigurationException on error
1550      */
checkConfiguration()1551     public void checkConfiguration() throws ConfigurationException {
1552 
1553         if (getSourceRoot() == null) {
1554             throw new ConfigurationException("Source root is not specified.");
1555         }
1556 
1557         if (getDataRoot() == null) {
1558             throw new ConfigurationException("Data root is not specified.");
1559         }
1560 
1561         if (!new File(getSourceRoot()).canRead()) {
1562             throw new ConfigurationException("Source root directory '" + getSourceRoot() + "' must be readable.");
1563         }
1564 
1565         if (!new File(getDataRoot()).canWrite()) {
1566             throw new ConfigurationException("Data root directory '" + getDataRoot() + "' must be writable.");
1567         }
1568 
1569         if (!isHistoryEnabled() && isHistoryBasedReindex()) {
1570             LOGGER.log(Level.INFO, "History based reindex is on, however history is off. " +
1571                     "History has to be enabled for history based reindex.");
1572         }
1573 
1574         if (!isHistoryCache() && isHistoryBasedReindex()) {
1575             LOGGER.log(Level.INFO, "History based reindex is on, however history cache is off. " +
1576                     "History cache has to be enabled for history based reindex.");
1577         }
1578     }
1579 }
1580