xref: /OpenGrok/opengrok-indexer/src/main/java/org/opengrok/indexer/configuration/RuntimeEnvironment.java (revision 5650cf1579547c2f9158c047af5b8af27ab2e6d3)
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) 2006, 2022, Oracle and/or its affiliates. All rights reserved.
22  * Portions Copyright (c) 2017, 2020, Chris Fraire <cfraire@me.com>.
23  */
24 package org.opengrok.indexer.configuration;
25 
26 import static org.opengrok.indexer.configuration.Configuration.makeXMLStringAsConfiguration;
27 import static org.opengrok.indexer.index.IndexerUtil.getWebAppHeaders;
28 
29 import java.io.File;
30 import java.io.FileNotFoundException;
31 import java.io.IOException;
32 import java.nio.file.Path;
33 import java.nio.file.Paths;
34 import java.util.ArrayList;
35 import java.util.Arrays;
36 import java.util.Collection;
37 import java.util.Collections;
38 import java.util.Date;
39 import java.util.HashMap;
40 import java.util.HashSet;
41 import java.util.List;
42 import java.util.Map;
43 import java.util.Set;
44 import java.util.SortedSet;
45 import java.util.TreeSet;
46 import java.util.concurrent.ConcurrentHashMap;
47 import java.util.concurrent.CopyOnWriteArraySet;
48 import java.util.concurrent.ExecutorService;
49 import java.util.concurrent.Executors;
50 import java.util.concurrent.TimeUnit;
51 import java.util.function.Function;
52 import java.util.logging.Level;
53 import java.util.logging.Logger;
54 import java.util.stream.Collectors;
55 
56 import jakarta.ws.rs.client.ClientBuilder;
57 import jakarta.ws.rs.client.Entity;
58 import jakarta.ws.rs.core.Response;
59 import org.apache.lucene.index.IndexReader;
60 import org.apache.lucene.index.MultiReader;
61 import org.apache.lucene.search.SearcherManager;
62 import org.apache.lucene.store.AlreadyClosedException;
63 import org.apache.lucene.store.Directory;
64 import org.apache.lucene.store.FSDirectory;
65 import org.apache.lucene.util.NamedThreadFactory;
66 import org.jetbrains.annotations.VisibleForTesting;
67 import org.opengrok.indexer.authorization.AuthorizationFramework;
68 import org.opengrok.indexer.authorization.AuthorizationStack;
69 import org.opengrok.indexer.history.FileCollector;
70 import org.opengrok.indexer.history.HistoryGuru;
71 import org.opengrok.indexer.history.RepositoryInfo;
72 import org.opengrok.indexer.index.IndexDatabase;
73 import org.opengrok.indexer.index.IndexerParallelizer;
74 import org.opengrok.indexer.logger.LoggerFactory;
75 import org.opengrok.indexer.util.CloseableReentrantReadWriteLock;
76 import org.opengrok.indexer.util.CtagsUtil;
77 import org.opengrok.indexer.util.ForbiddenSymlinkException;
78 import org.opengrok.indexer.util.LazilyInstantiate;
79 import org.opengrok.indexer.util.PathUtils;
80 import org.opengrok.indexer.util.ResourceLock;
81 import org.opengrok.indexer.util.Statistics;
82 import org.opengrok.indexer.web.ApiUtils;
83 import org.opengrok.indexer.web.Prefix;
84 import org.opengrok.indexer.web.Util;
85 import org.opengrok.indexer.web.messages.Message;
86 import org.opengrok.indexer.web.messages.MessagesContainer;
87 import org.opengrok.indexer.web.messages.MessagesContainer.AcceptedMessage;
88 
89 /**
90  * The RuntimeEnvironment class is used as a placeholder for the current
91  * configuration this execution context (classloader) is using.
92  */
93 public final class RuntimeEnvironment {
94 
95     private static final Logger LOGGER = LoggerFactory.getLogger(RuntimeEnvironment.class);
96 
97     private static final String URL_PREFIX = "/source" + Prefix.SEARCH_R + "?";
98 
99     private Configuration configuration;
100     private final CloseableReentrantReadWriteLock configLock;
101     private final LazilyInstantiate<IndexerParallelizer> lzIndexerParallelizer;
102     private final LazilyInstantiate<ExecutorService> lzSearchExecutor;
103     private final LazilyInstantiate<ExecutorService> lzRevisionExecutor;
104     private static final RuntimeEnvironment instance = new RuntimeEnvironment();
105 
106     private final Map<Project, List<RepositoryInfo>> repository_map = new ConcurrentHashMap<>();
107     private final Map<String, SearcherManager> searcherManagerMap = new ConcurrentHashMap<>();
108 
109     private String configURI;
110     IncludeFiles includeFiles = new IncludeFiles();
111     private final MessagesContainer messagesContainer = new MessagesContainer();
112 
113     private static final IndexTimestamp indexTime = new IndexTimestamp();
114 
115     /**
116      * Stores a transient value when
117      * {@link #setCtags(java.lang.String)} is called -- i.e. the
118      * value is not mediated to {@link Configuration}.
119      */
120     private String ctags;
121     /**
122      * Stores a transient value when
123      * {@link #setMandoc(java.lang.String)} is called -- i.e. the
124      * value is not mediated to {@link Configuration}.
125      */
126     private String mandoc;
127 
128     private transient File dtagsEftar = null;
129 
130     private transient volatile Boolean ctagsFound;
131     private final transient Set<String> ctagsLanguages = new HashSet<>();
132 
133     private final WatchDogService watchDog;
134 
135     private final Set<ConfigurationChangedListener> listeners = new CopyOnWriteArraySet<>();
136 
getSubFiles()137     public List<String> getSubFiles() {
138         return subFiles;
139     }
140 
141     private final List<String> subFiles = new ArrayList<>();
142 
143     /**
144      * Maps project name to FileCollector object. This is used to pass the list of files acquired when
145      * generating history cache in the first phase of indexing to the second phase of indexing.
146      */
147     private final Map<String, FileCollector> fileCollectorMap = new HashMap<>();
148 
149     /**
150      * Creates a new instance of RuntimeEnvironment. Private to ensure a
151      * singleton anti-pattern.
152      */
RuntimeEnvironment()153     private RuntimeEnvironment() {
154         configuration = new Configuration();
155         configLock = new CloseableReentrantReadWriteLock();
156         watchDog = new WatchDogService();
157         lzIndexerParallelizer = LazilyInstantiate.using(() ->
158                 new IndexerParallelizer(this));
159         lzSearchExecutor = LazilyInstantiate.using(this::newSearchExecutor);
160         lzRevisionExecutor = LazilyInstantiate.using(this::newRevisionExecutor);
161     }
162 
163     // Instance of authorization framework and its lock.
164     private AuthorizationFramework authFramework;
165     private final Object authFrameworkLock = new Object();
166 
167     private boolean indexer;
168 
isIndexer()169     public boolean isIndexer() {
170         return indexer;
171     }
172 
setIndexer(boolean indexer)173     public void setIndexer(boolean indexer) {
174         this.indexer = indexer;
175     }
176 
177     /**
178      * @return {@code WatchDogService} instance
179      */
getWatchDog()180     public WatchDogService getWatchDog() {
181         return watchDog;
182     }
183 
184     /** Gets the thread pool used for multi-project searches. */
getSearchExecutor()185     public ExecutorService getSearchExecutor() {
186         return lzSearchExecutor.get();
187     }
188 
newSearchExecutor()189     private ExecutorService newSearchExecutor() {
190         return Executors.newFixedThreadPool(
191                 this.getMaxSearchThreadCount(),
192                 runnable -> {
193                     Thread thread = Executors.defaultThreadFactory().newThread(runnable);
194                     thread.setName("search-" + thread.getId());
195                     return thread;
196                 });
197     }
198 
getRevisionExecutor()199     public ExecutorService getRevisionExecutor() {
200         return lzRevisionExecutor.get();
201     }
202 
newRevisionExecutor()203     private ExecutorService newRevisionExecutor() {
204         return Executors.newFixedThreadPool(this.getMaxRevisionThreadCount(),
205                 new NamedThreadFactory("get-revision"));
206     }
207 
shutdownRevisionExecutor()208     public void shutdownRevisionExecutor() throws InterruptedException {
209         getRevisionExecutor().shutdownNow();
210         getRevisionExecutor().awaitTermination(getIndexerCommandTimeout(), TimeUnit.SECONDS);
211     }
212 
213     /**
214      * Get the one and only instance of the RuntimeEnvironment.
215      *
216      * @return the one and only instance of the RuntimeEnvironment
217      */
getInstance()218     public static RuntimeEnvironment getInstance() {
219         return instance;
220     }
221 
getIndexerParallelizer()222     public IndexerParallelizer getIndexerParallelizer() {
223         return lzIndexerParallelizer.get();
224     }
225 
226     /**
227      * Gets an instance associated to this environment.
228      */
getPathAccepter()229     public PathAccepter getPathAccepter() {
230         return new PathAccepter(getIgnoredNames(), getIncludedNames());
231     }
232 
getCanonicalPath(String s)233     private String getCanonicalPath(String s) {
234         if (s == null) {
235             return null;
236         }
237         try {
238             File file = new File(s);
239             if (!file.exists()) {
240                 return s;
241             }
242             return file.getCanonicalPath();
243         } catch (IOException ex) {
244             LOGGER.log(Level.SEVERE, "Failed to get canonical path", ex);
245             return s;
246         }
247     }
248 
getScanningDepth()249     public int getScanningDepth() {
250         return syncReadConfiguration(Configuration::getScanningDepth);
251     }
252 
setScanningDepth(int scanningDepth)253     public void setScanningDepth(int scanningDepth) {
254         syncWriteConfiguration(scanningDepth, Configuration::setScanningDepth);
255     }
256 
getNestingMaximum()257     public int getNestingMaximum() {
258         return syncReadConfiguration(Configuration::getNestingMaximum);
259     }
260 
setNestingMaximum(int nestingMaximum)261     public void setNestingMaximum(int nestingMaximum) {
262         syncWriteConfiguration(nestingMaximum, Configuration::setNestingMaximum);
263     }
264 
getCommandTimeout(CommandTimeoutType cmdType)265     public int getCommandTimeout(CommandTimeoutType cmdType) {
266         switch (cmdType) {
267             case INDEXER:
268                 return getIndexerCommandTimeout();
269             case INTERACTIVE:
270                 return getInteractiveCommandTimeout();
271             case WEBAPP_START:
272                 return getWebappStartCommandTimeout();
273             case RESTFUL:
274                 return getRestfulCommandTimeout();
275         }
276 
277         throw new IllegalArgumentException("invalid command timeout type");
278     }
279 
getRestfulCommandTimeout()280     public int getRestfulCommandTimeout() {
281         return syncReadConfiguration(Configuration::getRestfulCommandTimeout);
282     }
283 
setRestfulCommandTimeout(int timeout)284     public void setRestfulCommandTimeout(int timeout) {
285         syncWriteConfiguration(timeout, Configuration::setWebappStartCommandTimeout);
286     }
287 
getWebappStartCommandTimeout()288     public int getWebappStartCommandTimeout() {
289         return syncReadConfiguration(Configuration::getWebappStartCommandTimeout);
290     }
291 
setWebappStartCommandTimeout(int timeout)292     public void setWebappStartCommandTimeout(int timeout) {
293         syncWriteConfiguration(timeout, Configuration::setWebappStartCommandTimeout);
294     }
295 
getIndexerCommandTimeout()296     public int getIndexerCommandTimeout() {
297         return syncReadConfiguration(Configuration::getIndexerCommandTimeout);
298     }
299 
setIndexerCommandTimeout(int timeout)300     public void setIndexerCommandTimeout(int timeout) {
301         syncWriteConfiguration(timeout, Configuration::setIndexerCommandTimeout);
302     }
303 
getInteractiveCommandTimeout()304     public int getInteractiveCommandTimeout() {
305         return syncReadConfiguration(Configuration::getInteractiveCommandTimeout);
306     }
307 
setInteractiveCommandTimeout(int timeout)308     public void setInteractiveCommandTimeout(int timeout) {
309         syncWriteConfiguration(timeout, Configuration::setInteractiveCommandTimeout);
310     }
311 
getCtagsTimeout()312     public long getCtagsTimeout() {
313         return syncReadConfiguration(Configuration::getCtagsTimeout);
314     }
315 
setCtagsTimeout(long timeout)316     public void setCtagsTimeout(long timeout) {
317         syncWriteConfiguration(timeout, Configuration::setCtagsTimeout);
318     }
319 
getXrefTimeout()320     public long getXrefTimeout() {
321         return syncReadConfiguration(Configuration::getXrefTimeout);
322     }
323 
setXrefTimeout(long timeout)324     public void setXrefTimeout(long timeout) {
325         syncWriteConfiguration(timeout, Configuration::setXrefTimeout);
326     }
327 
setLastEditedDisplayMode(boolean lastEditedDisplayMode)328     public void setLastEditedDisplayMode(boolean lastEditedDisplayMode) {
329         syncWriteConfiguration(lastEditedDisplayMode, Configuration::setLastEditedDisplayMode);
330     }
331 
isLastEditedDisplayMode()332     public boolean isLastEditedDisplayMode() {
333         return syncReadConfiguration(Configuration::isLastEditedDisplayMode);
334     }
335 
336     /**
337      * Get the path to the where the web application includes are stored.
338      *
339      * @return the path to the web application include files
340      */
getIncludeRootPath()341     public String getIncludeRootPath() {
342         return syncReadConfiguration(Configuration::getIncludeRoot);
343     }
344 
345     /**
346      * Set include root path.
347      * @param includeRoot path
348      */
setIncludeRoot(String includeRoot)349     public void setIncludeRoot(String includeRoot) {
350         syncWriteConfiguration(getCanonicalPath(includeRoot), Configuration::setIncludeRoot);
351     }
352 
353     /**
354      * Get the path to the where the index database is stored.
355      *
356      * @return the path to the index database
357      */
getDataRootPath()358     public String getDataRootPath() {
359         return syncReadConfiguration(Configuration::getDataRoot);
360     }
361 
362     /**
363      * Get a file representing the index database.
364      *
365      * @return the index database
366      */
getDataRootFile()367     public File getDataRootFile() {
368         File ret = null;
369         String file = getDataRootPath();
370         if (file != null) {
371             ret = new File(file);
372         }
373 
374         return ret;
375     }
376 
377     /**
378      * Set the path to where the index database is stored.
379      *
380      * @param dataRoot the index database
381      */
setDataRoot(String dataRoot)382     public void setDataRoot(String dataRoot) {
383         syncWriteConfiguration(getCanonicalPath(dataRoot), Configuration::setDataRoot);
384     }
385 
386     /**
387      * Get the path to where the sources are located.
388      *
389      * @return path to where the sources are located
390      */
getSourceRootPath()391     public String getSourceRootPath() {
392         return syncReadConfiguration(Configuration::getSourceRoot);
393     }
394 
395     /**
396      * Get a file representing the directory where the sources are located.
397      *
398      * @return A file representing the directory where the sources are located
399      */
getSourceRootFile()400     public File getSourceRootFile() {
401         File ret = null;
402         String file = getSourceRootPath();
403         if (file != null) {
404             ret = new File(file);
405         }
406 
407         return ret;
408     }
409 
410     /**
411      * Specify the source root.
412      *
413      * @param sourceRoot the location of the sources
414      */
setSourceRoot(String sourceRoot)415     public void setSourceRoot(String sourceRoot) {
416         syncWriteConfiguration(getCanonicalPath(sourceRoot), Configuration::setSourceRoot);
417     }
418 
419     /**
420      * Returns a path relative to source root. This would just be a simple
421      * substring operation, except we need to support symlinks outside the
422      * source root.
423      *
424      * @param file A file to resolve
425      * @return Path relative to source root
426      * @throws IOException If an IO error occurs
427      * @throws FileNotFoundException if the file is not relative to source root
428      * or if {@code sourceRoot} is not defined
429      * @throws ForbiddenSymlinkException if symbolic-link checking encounters
430      * an ineligible link
431      */
getPathRelativeToSourceRoot(File file)432     public String getPathRelativeToSourceRoot(File file)
433             throws IOException, ForbiddenSymlinkException {
434         String sourceRoot = getSourceRootPath();
435         if (sourceRoot == null) {
436             throw new FileNotFoundException("sourceRoot is not defined");
437         }
438 
439         String maybeRelPath = PathUtils.getRelativeToCanonical(file.toPath(),
440                 Paths.get(sourceRoot), getAllowedSymlinks(), getCanonicalRoots());
441         File maybeRelFile = new File(maybeRelPath);
442         if (!maybeRelFile.isAbsolute()) {
443             /*
444              * N.b. OpenGrok has a weird convention that source-root "relative"
445              * paths must start with a '/' as they are elsewhere directly
446              * appended to getSourceRootPath() and also stored as such.
447              */
448             maybeRelPath = File.separator + maybeRelPath;
449             return maybeRelPath;
450         }
451 
452         throw new FileNotFoundException("Failed to resolve [" + file.getPath()
453                 + "] relative to source root [" + sourceRoot + "]");
454     }
455 
456     /**
457      * Do we have any projects ?
458      *
459      * @return true if we have projects
460      */
hasProjects()461     public boolean hasProjects() {
462         return (this.isProjectsEnabled() && getProjects().size() > 0);
463     }
464 
465     /**
466      * Get list of projects.
467      *
468      * @return a list containing all the projects
469      */
getProjectList()470     public List<Project> getProjectList() {
471         return new ArrayList<>(getProjects().values());
472     }
473 
474     /**
475      * Get project map.
476      *
477      * @return a Map with all the projects
478      */
getProjects()479     public Map<String, Project> getProjects() {
480         return syncReadConfiguration(Configuration::getProjects);
481     }
482 
483     /**
484      * Get names of all projects.
485      *
486      * @return a list containing names of all projects.
487      */
getProjectNames()488     public List<String> getProjectNames() {
489         return getProjectList().stream().map(Project::getName).collect(Collectors.toList());
490     }
491 
492     /**
493      * Set the list of the projects.
494      *
495      * @param projects the map of projects to use
496      */
setProjects(Map<String, Project> projects)497     public void setProjects(Map<String, Project> projects) {
498         syncWriteConfiguration(projects, (c, p) -> {
499             if (p != null) {
500                 populateGroups(getGroups(), new TreeSet<>(p.values()));
501             }
502             c.setProjects(p);
503         });
504     }
505 
506     /**
507      * Do we have groups?
508      *
509      * @return true if we have groups
510      */
hasGroups()511     public boolean hasGroups() {
512         return (getGroups() != null && !getGroups().isEmpty());
513     }
514 
515     /**
516      * Get all of the groups.
517      *
518      * @return a set containing all of the groups (may be null)
519      */
getGroups()520     public Set<Group> getGroups() {
521         return syncReadConfiguration(Configuration::getGroups);
522     }
523 
524     /**
525      * Set the list of the groups.
526      *
527      * @param groups the set of groups to use
528      */
setGroups(Set<Group> groups)529     public void setGroups(Set<Group> groups) {
530         syncWriteConfiguration(groups, (c, g) -> {
531             populateGroups(g, new TreeSet<>(getProjects().values()));
532             c.setGroups(g);
533         });
534     }
535 
536     /**
537      * Returns constructed project - repositories map.
538      *
539      * @return the map
540      * @see #generateProjectRepositoriesMap
541      */
getProjectRepositoriesMap()542     public Map<Project, List<RepositoryInfo>> getProjectRepositoriesMap() {
543         return repository_map;
544     }
545 
546     /**
547      * Gets a static placeholder for the web application context name that is
548      * translated to the true servlet {@code contextPath} on demand.
549      * @return {@code "/source"} + {@link Prefix#SEARCH_R} + {@code "?"}
550      */
getUrlPrefix()551     public String getUrlPrefix() {
552         return URL_PREFIX;
553     }
554 
555     /**
556      * Gets the name of the ctags program to use: either the last value passed
557      * successfully to {@link #setCtags(java.lang.String)}, or
558      * {@link Configuration#getCtags()}, or the system property for
559      * {@code "org.opengrok.indexer.analysis.Ctags"}, or "ctags" as a
560      * default.
561      * @return a defined value
562      */
getCtags()563     public String getCtags() {
564         if (ctags != null) {
565             return ctags;
566         }
567 
568         String value = syncReadConfiguration(Configuration::getCtags);
569         return value != null ? value :
570                 System.getProperty(CtagsUtil.SYSTEM_CTAGS_PROPERTY, "ctags");
571     }
572 
573     /**
574      * Sets the name of the ctags program to use, or resets to use the fallbacks
575      * documented for {@link #getCtags()}.
576      * <p>
577      * N.b. the value is not mediated to {@link Configuration}.
578      *
579      * @param ctags a defined value or {@code null} to reset to use the
580      * {@link Configuration#getCtags()} fallbacks
581      * @see #getCtags()
582      */
setCtags(String ctags)583     public void setCtags(String ctags) {
584         this.ctags = ctags;
585     }
586 
587     /**
588      * Gets the name of the mandoc program to use: either the last value passed
589      * successfully to {@link #setMandoc(java.lang.String)}, or
590      * {@link Configuration#getMandoc()}, or the system property for
591      * {@code "org.opengrok.indexer.analysis.Mandoc"}, or {@code null} as a
592      * default.
593      * @return a defined instance or {@code null}
594      */
getMandoc()595     public String getMandoc() {
596         if (mandoc != null) {
597             return mandoc;
598         }
599 
600         String value = syncReadConfiguration(Configuration::getMandoc);
601         return value != null ? value :
602                 System.getProperty("org.opengrok.indexer.analysis.Mandoc");
603     }
604 
605     /**
606      * Sets the name of the mandoc program to use, or resets to use the
607      * fallbacks documented for {@link #getMandoc()}.
608      * <p>
609      * N.b. the value is not mediated to {@link Configuration}.
610      *
611      * @param value a defined value or {@code null} to reset to use the
612      * {@link Configuration#getMandoc()} fallbacks
613      * @see #getMandoc()
614      */
setMandoc(String value)615     public void setMandoc(String value) {
616         this.mandoc = value;
617     }
618 
getCachePages()619     public int getCachePages() {
620         return syncReadConfiguration(Configuration::getCachePages);
621     }
622 
setCachePages(int cachePages)623     public void setCachePages(int cachePages) {
624         syncWriteConfiguration(cachePages, Configuration::setCachePages);
625     }
626 
getHitsPerPage()627     public int getHitsPerPage() {
628         return syncReadConfiguration(Configuration::getHitsPerPage);
629     }
630 
setHitsPerPage(int hitsPerPage)631     public void setHitsPerPage(int hitsPerPage) {
632         syncWriteConfiguration(hitsPerPage, Configuration::setHitsPerPage);
633     }
634 
635     /**
636      * Validate that there is a Universal ctags program.
637      *
638      * @return true if success, false otherwise
639      */
validateUniversalCtags()640     public boolean validateUniversalCtags() {
641         if (ctagsFound == null) {
642             String ctagsBinary = getCtags();
643             try (ResourceLock resourceLock = configLock.writeLockAsResource()) {
644                 //noinspection ConstantConditions to avoid warning of no reference to auto-closeable
645                 assert resourceLock != null;
646                 if (ctagsFound == null) {
647                     ctagsFound = CtagsUtil.validate(ctagsBinary);
648                     if (ctagsFound) {
649                         List<String> languages = CtagsUtil.getLanguages(ctagsBinary);
650                         if (languages != null) {
651                             ctagsLanguages.addAll(languages);
652                         }
653                     }
654                 }
655             }
656         }
657         return ctagsFound;
658     }
659 
660     /**
661      * Gets the base set of supported Ctags languages.
662      * @return a defined set which may be empty if
663      * {@link #validateUniversalCtags()} has not yet been called or if the call
664      * fails
665      */
getCtagsLanguages()666     public Set<String> getCtagsLanguages() {
667         return Collections.unmodifiableSet(ctagsLanguages);
668     }
669 
670     /**
671      * Is history cache currently enabled?
672      *
673      * @return true if history cache is enabled
674      */
useHistoryCache()675     public boolean useHistoryCache() {
676         return syncReadConfiguration(Configuration::isHistoryCache);
677     }
678 
679     /**
680      * Specify if we should use history cache or not.
681      *
682      * @param useHistoryCache set false if you do not want to use history cache
683      */
setUseHistoryCache(boolean useHistoryCache)684     public void setUseHistoryCache(boolean useHistoryCache) {
685         syncWriteConfiguration(useHistoryCache, Configuration::setHistoryCache);
686     }
687 
688     /**
689      * Should we generate HTML or not during the indexing phase.
690      *
691      * @return true if HTML should be generated during the indexing phase
692      */
isGenerateHtml()693     public boolean isGenerateHtml() {
694         return syncReadConfiguration(Configuration::isGenerateHtml);
695     }
696 
697     /**
698      * Specify if we should generate HTML or not during the indexing phase.
699      *
700      * @param generateHtml set this to true to pregenerate HTML
701      */
setGenerateHtml(boolean generateHtml)702     public void setGenerateHtml(boolean generateHtml) {
703         syncWriteConfiguration(generateHtml, Configuration::setGenerateHtml);
704     }
705 
706     /**
707      * Set if we should compress the xref files or not.
708      *
709      * @param compressXref set to true if the generated html files should be
710      * compressed
711      */
setCompressXref(boolean compressXref)712     public void setCompressXref(boolean compressXref) {
713         syncWriteConfiguration(compressXref, Configuration::setCompressXref);
714     }
715 
716     /**
717      * Are we using compressed HTML files?
718      *
719      * @return {@code true} if the html-files should be compressed.
720      */
isCompressXref()721     public boolean isCompressXref() {
722         return syncReadConfiguration(Configuration::isCompressXref);
723     }
724 
isQuickContextScan()725     public boolean isQuickContextScan() {
726         return syncReadConfiguration(Configuration::isQuickContextScan);
727     }
728 
setQuickContextScan(boolean quickContextScan)729     public void setQuickContextScan(boolean quickContextScan) {
730         syncWriteConfiguration(quickContextScan, Configuration::setQuickContextScan);
731     }
732 
getRepositories()733     public List<RepositoryInfo> getRepositories() {
734         return syncReadConfiguration(Configuration::getRepositories);
735     }
736 
737     /**
738      * Set the list of repositories.
739      *
740      * @param repositories the repositories to use
741      */
setRepositories(List<RepositoryInfo> repositories)742     public void setRepositories(List<RepositoryInfo> repositories) {
743         syncWriteConfiguration(repositories, Configuration::setRepositories);
744     }
745 
removeRepositories()746     public void removeRepositories() {
747         syncWriteConfiguration(null, Configuration::setRepositories);
748     }
749 
750     /**
751      * Search through the directory for repositories and use the result to replace
752      * the lists of repositories in both RuntimeEnvironment/Configuration and HistoryGuru.
753      *
754      * @param dir the directories to start the search in
755      */
setRepositories(String... dir)756     public void setRepositories(String... dir) {
757         List<RepositoryInfo> repos = new ArrayList<>(HistoryGuru.getInstance().
758                 addRepositories(Arrays.stream(dir).map(File::new).toArray(File[]::new)));
759         setRepositories(repos);
760     }
761 
762     /**
763      * Add repositories to the list.
764      * @param repositories list of repositories
765      */
addRepositories(List<RepositoryInfo> repositories)766     public void addRepositories(List<RepositoryInfo> repositories) {
767         syncWriteConfiguration(repositories, Configuration::addRepositories);
768     }
769 
770     /**
771      * Set the specified projects as default in the configuration.
772      * This method should be called only after projects were discovered and became part of the configuration,
773      * i.e. after {@link org.opengrok.indexer.index.Indexer#prepareIndexer} was called.
774      *
775      * @param defaultProjects The default project to use
776      * @see #setDefaultProjects
777      */
setDefaultProjectsFromNames(Set<String> defaultProjects)778     public void setDefaultProjectsFromNames(Set<String> defaultProjects) {
779         if (defaultProjects != null && !defaultProjects.isEmpty()) {
780             Set<Project> projects = new TreeSet<>();
781             for (String projectPath : defaultProjects) {
782                 if (projectPath.equals("__all__")) {
783                     projects.addAll(getProjects().values());
784                     break;
785                 }
786                 for (Project p : getProjectList()) {
787                     if (p.getPath().equals(Util.fixPathIfWindows(projectPath))) {
788                         projects.add(p);
789                         break;
790                     }
791                 }
792             }
793             if (!projects.isEmpty()) {
794                 setDefaultProjects(projects);
795             }
796         }
797     }
798 
799     /**
800      * Set the projects that are specified to be the default projects to use.
801      * The default projects are the projects you will search (from the web
802      * application) if the page request didn't contain the cookie..
803      *
804      * @param defaultProjects The default project to use
805      */
setDefaultProjects(Set<Project> defaultProjects)806     public void setDefaultProjects(Set<Project> defaultProjects) {
807         syncWriteConfiguration(defaultProjects, Configuration::setDefaultProjects);
808     }
809 
810     /**
811      * Get the projects that are specified to be the default projects to use.
812      * The default projects are the projects you will search (from the web
813      * application) if the page request didn't contain the cookie..
814      *
815      * @return the default projects (may be null if not specified)
816      */
getDefaultProjects()817     public Set<Project> getDefaultProjects() {
818         Set<Project> projects = syncReadConfiguration(Configuration::getDefaultProjects);
819         if (projects == null) {
820             return null;
821         }
822         return Collections.unmodifiableSet(projects);
823     }
824 
825     /**
826      *
827      * @return at what size (in MB) we should flush the buffer
828      */
getRamBufferSize()829     public double getRamBufferSize() {
830         return syncReadConfiguration(Configuration::getRamBufferSize);
831     }
832 
833     /**
834      * Set the size of buffer which will determine when the docs are flushed to
835      * disk. Specify size in MB please. 16MB is default note that this is per
836      * thread (lucene uses 8 threads by default in 4.x)
837      *
838      * @param ramBufferSize the size(in MB) when we should flush the docs
839      */
setRamBufferSize(double ramBufferSize)840     public void setRamBufferSize(double ramBufferSize) {
841         syncWriteConfiguration(ramBufferSize, Configuration::setRamBufferSize);
842     }
843 
setPluginDirectory(String pluginDirectory)844     public void setPluginDirectory(String pluginDirectory) {
845         syncWriteConfiguration(pluginDirectory, Configuration::setPluginDirectory);
846     }
847 
getPluginDirectory()848     public String getPluginDirectory() {
849         return syncReadConfiguration(Configuration::getPluginDirectory);
850     }
851 
isAuthorizationWatchdog()852     public boolean isAuthorizationWatchdog() {
853         return syncReadConfiguration(Configuration::isAuthorizationWatchdogEnabled);
854     }
855 
setAuthorizationWatchdog(boolean authorizationWatchdogEnabled)856     public void setAuthorizationWatchdog(boolean authorizationWatchdogEnabled) {
857         syncWriteConfiguration(authorizationWatchdogEnabled,
858                 Configuration::setAuthorizationWatchdogEnabled);
859     }
860 
getPluginStack()861     public AuthorizationStack getPluginStack() {
862         return syncReadConfiguration(Configuration::getPluginStack);
863     }
864 
setPluginStack(AuthorizationStack pluginStack)865     public void setPluginStack(AuthorizationStack pluginStack) {
866         syncWriteConfiguration(pluginStack, Configuration::setPluginStack);
867     }
868 
869     /**
870      * Is the progress print flag turned on?
871      *
872      * @return true if we can print per project progress %
873      */
isPrintProgress()874     public boolean isPrintProgress() {
875         return syncReadConfiguration(Configuration::isPrintProgress);
876     }
877 
878     /**
879      * Set the printing of progress % flag (user convenience).
880      *
881      * @param printProgress new value
882      */
setPrintProgress(boolean printProgress)883     public void setPrintProgress(boolean printProgress) {
884         syncWriteConfiguration(printProgress, Configuration::setPrintProgress);
885     }
886 
887     /**
888      * Specify if a search may start with a wildcard. Note that queries that
889      * start with a wildcard will give a significant impact on the search
890      * performance.
891      *
892      * @param allowLeadingWildcard set to true to activate (disabled by default)
893      */
setAllowLeadingWildcard(boolean allowLeadingWildcard)894     public void setAllowLeadingWildcard(boolean allowLeadingWildcard) {
895         syncWriteConfiguration(allowLeadingWildcard, Configuration::setAllowLeadingWildcard);
896     }
897 
898     /**
899      * Is leading wildcards allowed?
900      *
901      * @return true if a search may start with a wildcard
902      */
isAllowLeadingWildcard()903     public boolean isAllowLeadingWildcard() {
904         return syncReadConfiguration(Configuration::isAllowLeadingWildcard);
905     }
906 
getIgnoredNames()907     public IgnoredNames getIgnoredNames() {
908         return syncReadConfiguration(Configuration::getIgnoredNames);
909     }
910 
setIgnoredNames(IgnoredNames ignoredNames)911     public void setIgnoredNames(IgnoredNames ignoredNames) {
912         syncWriteConfiguration(ignoredNames, Configuration::setIgnoredNames);
913     }
914 
getIncludedNames()915     public Filter getIncludedNames() {
916         return syncReadConfiguration(Configuration::getIncludedNames);
917     }
918 
setIncludedNames(Filter includedNames)919     public void setIncludedNames(Filter includedNames) {
920         syncWriteConfiguration(includedNames, Configuration::setIncludedNames);
921     }
922 
923     /**
924      * Returns the user page for the history listing.
925      *
926      * @return the URL string fragment preceeding the username
927      */
getUserPage()928     public String getUserPage() {
929         return syncReadConfiguration(Configuration::getUserPage);
930     }
931 
932     /**
933      * Get the client command to use to access the repository for the given
934      * fully qualified classname.
935      *
936      * @param clazzName name of the targeting class
937      * @return {@code null} if not yet set, the client command otherwise.
938      */
getRepoCmd(String clazzName)939     public String getRepoCmd(String clazzName) {
940         return syncReadConfiguration(c -> c.getRepoCmd(clazzName));
941     }
942 
943     /**
944      * Set the client command to use to access the repository for the given
945      * fully qualified classname.
946      *
947      * @param clazzName name of the targeting class. If {@code null} this method
948      * does nothing.
949      * @param cmd the client command to use. If {@code null} the corresponding
950      * entry for the given clazzName get removed.
951      * @return the client command previously set, which might be {@code null}.
952      */
setRepoCmd(String clazzName, String cmd)953     public String setRepoCmd(String clazzName, String cmd) {
954         syncWriteConfiguration(null, (c, ignored) -> c.setRepoCmd(clazzName, cmd));
955         return cmd;
956     }
957 
setRepoCmds(Map<String, String> cmds)958     public void setRepoCmds(Map<String, String> cmds) {
959         syncWriteConfiguration(cmds, Configuration::setCmds);
960     }
961 
962     /**
963      * Sets the user page for the history listing.
964      *
965      * @param userPage the URL fragment preceeding the username from history
966      */
setUserPage(String userPage)967     public void setUserPage(String userPage) {
968         syncWriteConfiguration(userPage, Configuration::setUserPage);
969     }
970 
971     /**
972      * Returns the user page suffix for the history listing.
973      *
974      * @return the URL string fragment following the username
975      */
getUserPageSuffix()976     public String getUserPageSuffix() {
977         return syncReadConfiguration(Configuration::getUserPageSuffix);
978     }
979 
980     /**
981      * Sets the user page suffix for the history listing.
982      *
983      * @param userPageSuffix the URL fragment following the username from
984      * history
985      */
setUserPageSuffix(String userPageSuffix)986     public void setUserPageSuffix(String userPageSuffix) {
987         syncWriteConfiguration(userPageSuffix, Configuration::setUserPageSuffix);
988     }
989 
990     /**
991      * Returns the bug page for the history listing.
992      *
993      * @return the URL string fragment preceeding the bug ID
994      */
getBugPage()995     public String getBugPage() {
996         return syncReadConfiguration(Configuration::getBugPage);
997     }
998 
999     /**
1000      * Sets the bug page for the history listing.
1001      *
1002      * @param bugPage the URL fragment preceeding the bug ID
1003      */
setBugPage(String bugPage)1004     public void setBugPage(String bugPage) {
1005         syncWriteConfiguration(bugPage, Configuration::setBugPage);
1006     }
1007 
1008     /**
1009      * Returns the bug regex for the history listing.
1010      *
1011      * @return the regex that is looked for in history comments
1012      */
getBugPattern()1013     public String getBugPattern() {
1014         return syncReadConfiguration(Configuration::getBugPattern);
1015     }
1016 
1017     /**
1018      * Sets the bug regex for the history listing.
1019      *
1020      * @param bugPattern the regex to search history comments
1021      */
setBugPattern(String bugPattern)1022     public void setBugPattern(String bugPattern) {
1023         syncWriteConfiguration(bugPattern, Configuration::setBugPattern);
1024     }
1025 
1026     /**
1027      * Returns the review(ARC) page for the history listing.
1028      *
1029      * @return the URL string fragment preceeding the review page ID
1030      */
getReviewPage()1031     public String getReviewPage() {
1032         return syncReadConfiguration(Configuration::getReviewPage);
1033     }
1034 
1035     /**
1036      * Sets the review(ARC) page for the history listing.
1037      *
1038      * @param reviewPage the URL fragment preceeding the review page ID
1039      */
setReviewPage(String reviewPage)1040     public void setReviewPage(String reviewPage) {
1041         syncWriteConfiguration(reviewPage, Configuration::setReviewPage);
1042     }
1043 
1044     /**
1045      * Returns the review(ARC) regex for the history listing.
1046      *
1047      * @return the regex that is looked for in history comments
1048      */
getReviewPattern()1049     public String getReviewPattern() {
1050         return syncReadConfiguration(Configuration::getReviewPattern);
1051     }
1052 
1053     /**
1054      * Sets the review(ARC) regex for the history listing.
1055      *
1056      * @param reviewPattern the regex to search history comments
1057      */
setReviewPattern(String reviewPattern)1058     public void setReviewPattern(String reviewPattern) {
1059         syncWriteConfiguration(reviewPattern, Configuration::setReviewPattern);
1060     }
1061 
getWebappLAF()1062     public String getWebappLAF() {
1063         return syncReadConfiguration(Configuration::getWebappLAF);
1064     }
1065 
setWebappLAF(String webappLAF)1066     public void setWebappLAF(String webappLAF) {
1067         syncWriteConfiguration(webappLAF, Configuration::setWebappLAF);
1068     }
1069 
1070     /**
1071      * Gets a value indicating if the web app should run ctags as necessary.
1072      * @return the value of {@link Configuration#isWebappCtags()}
1073      */
isWebappCtags()1074     public boolean isWebappCtags() {
1075         return syncReadConfiguration(Configuration::isWebappCtags);
1076     }
1077 
getRemoteScmSupported()1078     public Configuration.RemoteSCM getRemoteScmSupported() {
1079         return syncReadConfiguration(Configuration::getRemoteScmSupported);
1080     }
1081 
setRemoteScmSupported(Configuration.RemoteSCM remoteScmSupported)1082     public void setRemoteScmSupported(Configuration.RemoteSCM remoteScmSupported) {
1083         syncWriteConfiguration(remoteScmSupported, Configuration::setRemoteScmSupported);
1084     }
1085 
isOptimizeDatabase()1086     public boolean isOptimizeDatabase() {
1087         return syncReadConfiguration(Configuration::isOptimizeDatabase);
1088     }
1089 
setOptimizeDatabase(boolean optimizeDatabase)1090     public void setOptimizeDatabase(boolean optimizeDatabase) {
1091         syncWriteConfiguration(optimizeDatabase, Configuration::setOptimizeDatabase);
1092     }
1093 
getLuceneLocking()1094     public LuceneLockName getLuceneLocking() {
1095         return syncReadConfiguration(Configuration::getLuceneLocking);
1096     }
1097 
isIndexVersionedFilesOnly()1098     public boolean isIndexVersionedFilesOnly() {
1099         return syncReadConfiguration(Configuration::isIndexVersionedFilesOnly);
1100     }
1101 
setIndexVersionedFilesOnly(boolean indexVersionedFilesOnly)1102     public void setIndexVersionedFilesOnly(boolean indexVersionedFilesOnly) {
1103         syncWriteConfiguration(indexVersionedFilesOnly, Configuration::setIndexVersionedFilesOnly);
1104     }
1105 
1106     /**
1107      * Gets the value of {@link Configuration#getIndexingParallelism()} -- or
1108      * if zero, then as a default gets the number of available processors.
1109      * @return a natural number &gt;= 1
1110      */
getIndexingParallelism()1111     public int getIndexingParallelism() {
1112         int parallelism = syncReadConfiguration(Configuration::getIndexingParallelism);
1113         return parallelism < 1 ? Runtime.getRuntime().availableProcessors() :
1114                 parallelism;
1115     }
1116 
1117     /**
1118      * Gets the value of {@link Configuration#getRepositoryInvalidationParallelism()} -- or
1119      * if zero, then as a default gets the number of available processors halved.
1120      * @return a natural number &gt;= 1
1121      */
getRepositoryInvalidationParallelism()1122     public int getRepositoryInvalidationParallelism() {
1123         int parallelism = syncReadConfiguration(Configuration::getRepositoryInvalidationParallelism);
1124         return parallelism < 1 ? (Runtime.getRuntime().availableProcessors() / 2) : parallelism;
1125     }
1126 
1127     /**
1128      * Gets the value of {@link Configuration#getHistoryParallelism()} -- or
1129      * if zero, then as a default gets the number of available processors.
1130      * @return a natural number &gt;= 1
1131      */
getHistoryParallelism()1132     public int getHistoryParallelism() {
1133         int parallelism = syncReadConfiguration(Configuration::getHistoryParallelism);
1134         return parallelism < 1 ? Runtime.getRuntime().availableProcessors() :
1135                 parallelism;
1136     }
1137 
1138     /**
1139      * Gets the value of {@link Configuration#getHistoryFileParallelism()} -- or
1140      * if zero, then as a default gets the number of available processors.
1141      * @return a natural number &gt;= 1
1142      */
getHistoryFileParallelism()1143     public int getHistoryFileParallelism() {
1144         int parallelism = syncReadConfiguration(Configuration::getHistoryFileParallelism);
1145         return parallelism < 1 ? Runtime.getRuntime().availableProcessors() :
1146                 parallelism;
1147     }
1148 
isTagsEnabled()1149     public boolean isTagsEnabled() {
1150         return syncReadConfiguration(Configuration::isTagsEnabled);
1151     }
1152 
setTagsEnabled(boolean tagsEnabled)1153     public void setTagsEnabled(boolean tagsEnabled) {
1154         syncWriteConfiguration(tagsEnabled, Configuration::setTagsEnabled);
1155     }
1156 
isScopesEnabled()1157     public boolean isScopesEnabled() {
1158         return syncReadConfiguration(Configuration::isScopesEnabled);
1159     }
1160 
setScopesEnabled(boolean scopesEnabled)1161     public void setScopesEnabled(boolean scopesEnabled) {
1162         syncWriteConfiguration(scopesEnabled, Configuration::setScopesEnabled);
1163     }
1164 
isProjectsEnabled()1165     public boolean isProjectsEnabled() {
1166         return syncReadConfiguration(Configuration::isProjectsEnabled);
1167     }
1168 
setProjectsEnabled(boolean projectsEnabled)1169     public void setProjectsEnabled(boolean projectsEnabled) {
1170         syncWriteConfiguration(projectsEnabled, Configuration::setProjectsEnabled);
1171     }
1172 
isFoldingEnabled()1173     public boolean isFoldingEnabled() {
1174         return syncReadConfiguration(Configuration::isFoldingEnabled);
1175     }
1176 
setFoldingEnabled(boolean foldingEnabled)1177     public void setFoldingEnabled(boolean foldingEnabled) {
1178         syncWriteConfiguration(foldingEnabled, Configuration::setFoldingEnabled);
1179     }
1180 
getDateForLastIndexRun()1181     public Date getDateForLastIndexRun() {
1182         return indexTime.getDateForLastIndexRun();
1183     }
1184 
getCTagsExtraOptionsFile()1185     public String getCTagsExtraOptionsFile() {
1186         return syncReadConfiguration(Configuration::getCTagsExtraOptionsFile);
1187     }
1188 
setCTagsExtraOptionsFile(String ctagsExtraOptionsFile)1189     public void setCTagsExtraOptionsFile(String ctagsExtraOptionsFile) {
1190         syncWriteConfiguration(ctagsExtraOptionsFile, Configuration::setCTagsExtraOptionsFile);
1191     }
1192 
getAllowedSymlinks()1193     public Set<String> getAllowedSymlinks() {
1194         return syncReadConfiguration(Configuration::getAllowedSymlinks);
1195     }
1196 
setAllowedSymlinks(Set<String> allowedSymlinks)1197     public void setAllowedSymlinks(Set<String> allowedSymlinks) {
1198         syncWriteConfiguration(allowedSymlinks, Configuration::setAllowedSymlinks);
1199     }
1200 
getCanonicalRoots()1201     public Set<String> getCanonicalRoots() {
1202         return syncReadConfiguration(Configuration::getCanonicalRoots);
1203     }
1204 
setCanonicalRoots(Set<String> canonicalRoots)1205     public void setCanonicalRoots(Set<String> canonicalRoots) {
1206         syncWriteConfiguration(canonicalRoots, Configuration::setCanonicalRoots);
1207     }
1208 
1209     /**
1210      * Return whether e-mail addresses should be obfuscated in the xref.
1211      * @return if we obfuscate emails
1212      */
isObfuscatingEMailAddresses()1213     public boolean isObfuscatingEMailAddresses() {
1214         return syncReadConfiguration(Configuration::isObfuscatingEMailAddresses);
1215     }
1216 
1217     /**
1218      * Set whether e-mail addresses should be obfuscated in the xref.
1219      * @param obfuscatingEMailAddresses should we obfuscate emails?
1220      */
setObfuscatingEMailAddresses(boolean obfuscatingEMailAddresses)1221     public void setObfuscatingEMailAddresses(boolean obfuscatingEMailAddresses) {
1222         syncWriteConfiguration(obfuscatingEMailAddresses,
1223                 Configuration::setObfuscatingEMailAddresses);
1224     }
1225 
1226     /**
1227      * Should status.jsp print internal settings, like paths and database URLs?
1228      *
1229      * @return {@code true} if status.jsp should show the configuration,
1230      * {@code false} otherwise
1231      */
isChattyStatusPage()1232     public boolean isChattyStatusPage() {
1233         return syncReadConfiguration(Configuration::isChattyStatusPage);
1234     }
1235 
1236     /**
1237      * Set whether status.jsp should print internal settings.
1238      *
1239      * @param chattyStatusPage {@code true} if internal settings should be printed,
1240      * {@code false} otherwise
1241      */
setChattyStatusPage(boolean chattyStatusPage)1242     public void setChattyStatusPage(boolean chattyStatusPage) {
1243         syncWriteConfiguration(chattyStatusPage, Configuration::setChattyStatusPage);
1244     }
1245 
setFetchHistoryWhenNotInCache(boolean fetchHistoryWhenNotInCache)1246     public void setFetchHistoryWhenNotInCache(boolean fetchHistoryWhenNotInCache) {
1247         syncWriteConfiguration(fetchHistoryWhenNotInCache,
1248                 Configuration::setFetchHistoryWhenNotInCache);
1249     }
1250 
isFetchHistoryWhenNotInCache()1251     public boolean isFetchHistoryWhenNotInCache() {
1252         return syncReadConfiguration(Configuration::isFetchHistoryWhenNotInCache);
1253     }
1254 
isHistoryCache()1255     public boolean isHistoryCache() {
1256         return syncReadConfiguration(Configuration::isHistoryCache);
1257     }
1258 
setHandleHistoryOfRenamedFiles(boolean handleHistoryOfRenamedFiles)1259     public void setHandleHistoryOfRenamedFiles(boolean handleHistoryOfRenamedFiles) {
1260         syncWriteConfiguration(handleHistoryOfRenamedFiles,
1261                 Configuration::setHandleHistoryOfRenamedFiles);
1262     }
1263 
isHandleHistoryOfRenamedFiles()1264     public boolean isHandleHistoryOfRenamedFiles() {
1265         return syncReadConfiguration(Configuration::isHandleHistoryOfRenamedFiles);
1266     }
1267 
setMergeCommitsEnabled(boolean flag)1268     public void setMergeCommitsEnabled(boolean flag) {
1269         syncWriteConfiguration(flag, Configuration::setMergeCommitsEnabled);
1270     }
1271 
isMergeCommitsEnabled()1272     public boolean isMergeCommitsEnabled() {
1273         return syncReadConfiguration(Configuration::isMergeCommitsEnabled);
1274     }
1275 
setNavigateWindowEnabled(boolean navigateWindowEnabled)1276     public void setNavigateWindowEnabled(boolean navigateWindowEnabled) {
1277         syncWriteConfiguration(navigateWindowEnabled, Configuration::setNavigateWindowEnabled);
1278     }
1279 
isNavigateWindowEnabled()1280     public boolean isNavigateWindowEnabled() {
1281         return syncReadConfiguration(Configuration::isNavigateWindowEnabled);
1282     }
1283 
setRevisionMessageCollapseThreshold(int revisionMessageCollapseThreshold)1284     public void setRevisionMessageCollapseThreshold(int revisionMessageCollapseThreshold) {
1285         syncWriteConfiguration(revisionMessageCollapseThreshold,
1286                 Configuration::setRevisionMessageCollapseThreshold);
1287     }
1288 
getRevisionMessageCollapseThreshold()1289     public int getRevisionMessageCollapseThreshold() {
1290         return syncReadConfiguration(Configuration::getRevisionMessageCollapseThreshold);
1291     }
1292 
setMaxSearchThreadCount(int maxSearchThreadCount)1293     public void setMaxSearchThreadCount(int maxSearchThreadCount) {
1294         syncWriteConfiguration(maxSearchThreadCount, Configuration::setMaxSearchThreadCount);
1295     }
1296 
getMaxSearchThreadCount()1297     public int getMaxSearchThreadCount() {
1298         return syncReadConfiguration(Configuration::getMaxSearchThreadCount);
1299     }
1300 
setMaxRevisionThreadCount(int maxRevisionThreadCount)1301     public void setMaxRevisionThreadCount(int maxRevisionThreadCount) {
1302         syncWriteConfiguration(maxRevisionThreadCount, Configuration::setMaxRevisionThreadCount);
1303     }
1304 
getMaxRevisionThreadCount()1305     public int getMaxRevisionThreadCount() {
1306         return syncReadConfiguration(Configuration::getMaxRevisionThreadCount);
1307     }
1308 
getCurrentIndexedCollapseThreshold()1309     public int getCurrentIndexedCollapseThreshold() {
1310         return syncReadConfiguration(Configuration::getCurrentIndexedCollapseThreshold);
1311     }
1312 
setCurrentIndexedCollapseThreshold(int currentIndexedCollapseThreshold)1313     public void setCurrentIndexedCollapseThreshold(int currentIndexedCollapseThreshold) {
1314         syncWriteConfiguration(currentIndexedCollapseThreshold,
1315                 Configuration::setCurrentIndexedCollapseThreshold);
1316     }
1317 
getGroupsCollapseThreshold()1318     public int getGroupsCollapseThreshold() {
1319         return syncReadConfiguration(Configuration::getGroupsCollapseThreshold);
1320     }
1321 
1322     // The URI is not necessary to be present in the configuration
1323     // (so that when -U option of the indexer is omitted, the config will not
1324     // be sent to the webapp) so store it only in the RuntimeEnvironment.
setConfigURI(String host)1325     public void setConfigURI(String host) {
1326         configURI = host;
1327     }
1328 
getConfigURI()1329     public String getConfigURI() {
1330         return configURI;
1331     }
1332 
isHistoryEnabled()1333     public boolean isHistoryEnabled() {
1334         return syncReadConfiguration(Configuration::isHistoryEnabled);
1335     }
1336 
setHistoryEnabled(boolean historyEnabled)1337     public void setHistoryEnabled(boolean historyEnabled) {
1338         syncWriteConfiguration(historyEnabled, Configuration::setHistoryEnabled);
1339     }
1340 
isDisplayRepositories()1341     public boolean isDisplayRepositories() {
1342         return syncReadConfiguration(Configuration::isDisplayRepositories);
1343     }
1344 
setDisplayRepositories(boolean displayRepositories)1345     public void setDisplayRepositories(boolean displayRepositories) {
1346         syncWriteConfiguration(displayRepositories, Configuration::setDisplayRepositories);
1347     }
1348 
getListDirsFirst()1349     public boolean getListDirsFirst() {
1350         return syncReadConfiguration(Configuration::getListDirsFirst);
1351     }
1352 
setListDirsFirst(boolean listDirsFirst)1353     public void setListDirsFirst(boolean listDirsFirst) {
1354         syncWriteConfiguration(listDirsFirst, Configuration::setListDirsFirst);
1355     }
1356 
setTabSize(int tabSize)1357     public void setTabSize(int tabSize) {
1358         syncWriteConfiguration(tabSize, Configuration::setTabSize);
1359     }
1360 
getTabSize()1361     public int getTabSize() {
1362         return syncReadConfiguration(Configuration::getTabSize);
1363     }
1364 
1365     /**
1366      * Gets the total number of context lines per file to show.
1367      * @return a value greater than zero
1368      */
getContextLimit()1369     public short getContextLimit() {
1370         return syncReadConfiguration(Configuration::getContextLimit);
1371     }
1372 
1373     /**
1374      * Gets the number of context lines to show before or after any match.
1375      * @return a value greater than or equal to zero
1376      */
getContextSurround()1377     public short getContextSurround() {
1378         return syncReadConfiguration(Configuration::getContextSurround);
1379     }
1380 
getHistoryChunkCount()1381     public int getHistoryChunkCount() {
1382         return syncReadConfiguration(Configuration::getHistoryChunkCount);
1383     }
1384 
setHistoryChunkCount(int chunkCount)1385     public void setHistoryChunkCount(int chunkCount) {
1386         syncWriteConfiguration(chunkCount, Configuration::setHistoryChunkCount);
1387     }
1388 
isHistoryCachePerPartesEnabled()1389     public boolean isHistoryCachePerPartesEnabled() {
1390         return syncReadConfiguration(Configuration::isHistoryCachePerPartesEnabled);
1391     }
1392 
setHistoryCachePerPartesEnabled(boolean enabled)1393     public void setHistoryCachePerPartesEnabled(boolean enabled) {
1394         syncWriteConfiguration(enabled, Configuration::setHistoryCachePerPartesEnabled);
1395     }
1396 
getDisabledRepositories()1397     public Set<String> getDisabledRepositories() {
1398         return syncReadConfiguration(Configuration::getDisabledRepositories);
1399     }
1400 
setDisabledRepositories(Set<String> disabledRepositories)1401     public void setDisabledRepositories(Set<String> disabledRepositories) {
1402         syncWriteConfiguration(disabledRepositories, Configuration::setDisabledRepositories);
1403     }
1404 
getServerName()1405     public String getServerName() {
1406         return syncReadConfiguration(Configuration::getServerName);
1407     }
1408 
setServerName(String serverName)1409     public void setServerName(String serverName) {
1410         syncWriteConfiguration(serverName, Configuration::setServerName);
1411     }
1412 
getApiTimeout()1413     public int getApiTimeout() {
1414         return syncReadConfiguration(Configuration::getApiTimeout);
1415     }
1416 
setApiTimeout(int apiTimeout)1417     public void setApiTimeout(int apiTimeout) {
1418         syncWriteConfiguration(apiTimeout, Configuration::setApiTimeout);
1419     }
1420 
getConnectTimeout()1421     public int getConnectTimeout() {
1422         return syncReadConfiguration(Configuration::getConnectTimeout);
1423     }
1424 
setConnectTimeout(int connectTimeout)1425     public void setConnectTimeout(int connectTimeout) {
1426         syncWriteConfiguration(connectTimeout, Configuration::setConnectTimeout);
1427     }
1428 
isHistoryBasedReindex()1429     public boolean isHistoryBasedReindex() {
1430         return syncReadConfiguration(Configuration::isHistoryBasedReindex);
1431     }
1432 
setHistoryBasedReindex(boolean flag)1433     public void setHistoryBasedReindex(boolean flag) {
1434         syncWriteConfiguration(flag, Configuration::setHistoryBasedReindex);
1435     }
1436 
getFileCollector(String name)1437     public FileCollector getFileCollector(String name) {
1438         return fileCollectorMap.get(name);
1439     }
1440 
setFileCollector(String name, FileCollector fileCollector)1441     public void setFileCollector(String name, FileCollector fileCollector) {
1442         fileCollectorMap.put(name, fileCollector);
1443     }
1444 
1445     @VisibleForTesting
clearFileCollector()1446     public void clearFileCollector() {
1447         fileCollectorMap.clear();
1448     }
1449 
1450     /**
1451      * Read an configuration file and set it as the current configuration.
1452      *
1453      * @param file the file to read
1454      * @throws IOException if an error occurs
1455      */
readConfiguration(File file)1456     public void readConfiguration(File file) throws IOException {
1457         // The following method handles the locking.
1458         setConfiguration(Configuration.read(file));
1459     }
1460 
1461     /**
1462      * Read configuration from a file and put it into effect.
1463      * @param file the file to read
1464      * @param cmdType command timeout type
1465      * @throws IOException I/O
1466      */
readConfiguration(File file, CommandTimeoutType cmdType)1467     public void readConfiguration(File file, CommandTimeoutType cmdType) throws IOException {
1468         // The following method handles the locking.
1469         setConfiguration(Configuration.read(file), null, cmdType);
1470     }
1471 
1472     /**
1473      * Write the current configuration to a file.
1474      *
1475      * @param file the file to write the configuration into
1476      * @throws IOException if an error occurs
1477      */
writeConfiguration(File file)1478     public void writeConfiguration(File file) throws IOException {
1479         try (ResourceLock resourceLock = configLock.readLockAsResource()) {
1480             //noinspection ConstantConditions to avoid warning of no reference to auto-closeable
1481             assert resourceLock != null;
1482             configuration.write(file);
1483         }
1484     }
1485 
getConfigurationXML()1486     public String getConfigurationXML() {
1487         return syncReadConfiguration(Configuration::getXMLRepresentationAsString);
1488     }
1489 
1490     /**
1491      * Write the current configuration to a socket and waits for the result.
1492      *
1493      * @param host the host address to receive the configuration
1494      * @throws IOException if an error occurs
1495      */
writeConfiguration(String host)1496     public void writeConfiguration(String host) throws IOException, InterruptedException, IllegalArgumentException {
1497         String configXML = syncReadConfiguration(Configuration::getXMLRepresentationAsString);
1498 
1499         Response response = ClientBuilder.newClient()
1500                 .target(host)
1501                 .path("api")
1502                 .path("v1")
1503                 .path("configuration")
1504                 .queryParam("reindex", true)
1505                 .request()
1506                 .headers(getWebAppHeaders())
1507                 .put(Entity.xml(configXML));
1508 
1509         if (response.getStatus() == Response.Status.ACCEPTED.getStatusCode()) {
1510             response = ApiUtils.waitForAsyncApi(response);
1511         }
1512 
1513         if (response.getStatusInfo().getFamily() != Response.Status.Family.SUCCESSFUL) {
1514             throw new IOException(response.toString());
1515         }
1516     }
1517 
1518     /**
1519      * Generate a TreeMap of projects with corresponding repository information.
1520      * <p>
1521      * Project with some repository information is considered as a repository
1522      * otherwise it is just a simple project.
1523      */
1524     @VisibleForTesting
generateProjectRepositoriesMap()1525     public void generateProjectRepositoriesMap() throws IOException {
1526         repository_map.clear();
1527         for (RepositoryInfo r : getRepositories()) {
1528             Project proj;
1529             String repoPath;
1530             try {
1531                 repoPath = getPathRelativeToSourceRoot(new File(r.getDirectoryName()));
1532             } catch (ForbiddenSymlinkException e) {
1533                 LOGGER.log(Level.FINER, e.getMessage());
1534                 continue;
1535             }
1536 
1537             if ((proj = Project.getProject(repoPath)) != null) {
1538                 List<RepositoryInfo> values = repository_map.computeIfAbsent(proj, k -> new ArrayList<>());
1539                 // the map is held under the lock because the next call to
1540                 // values.add(r) which should not be called from multiple threads at the same time
1541                 values.add(r);
1542             }
1543         }
1544     }
1545 
1546     /**
1547      * Classifies projects and puts them in their groups.
1548      * <p>
1549      * If any of the groups contain some projects or repositories already,
1550      * these get discarded.
1551      *
1552      * @param groups   set of groups to be filled with matching projects
1553      * @param projects projects to classify
1554      */
populateGroups(Set<Group> groups, Set<Project> projects)1555     public void populateGroups(Set<Group> groups, Set<Project> projects) {
1556         if (projects == null || groups == null) {
1557             return;
1558         }
1559 
1560         // clear the groups first if they had something in them
1561         for (Group group : groups) {
1562             group.getRepositories().clear();
1563             group.getProjects().clear();
1564         }
1565 
1566         // now fill the groups with appropriate projects
1567         for (Project project : projects) {
1568             // clear the project's groups
1569             project.getGroups().clear();
1570 
1571             // filter projects only to groups which match project's name
1572             Set<Group> copy = Group.matching(project, groups);
1573 
1574             // add project to the groups
1575             for (Group group : copy) {
1576                 if (repository_map.get(project) == null) {
1577                     group.addProject(project);
1578                 } else {
1579                     group.addRepository(project);
1580                 }
1581                 project.addGroup(group);
1582             }
1583         }
1584     }
1585 
1586     /**
1587      * Sets the configuration and performs necessary actions.
1588      *
1589      * Mainly it classifies the projects in their groups and generates project -
1590      * repositories map
1591      *
1592      * @param configuration what configuration to use
1593      */
setConfiguration(Configuration configuration)1594     public void setConfiguration(Configuration configuration) {
1595         setConfiguration(configuration, null, CommandTimeoutType.INDEXER);
1596     }
1597 
1598     /**
1599      * Sets the configuration and performs necessary actions.
1600      * @param configuration new configuration
1601      * @param cmdType command timeout type
1602      */
setConfiguration(Configuration configuration, CommandTimeoutType cmdType)1603     public void setConfiguration(Configuration configuration, CommandTimeoutType cmdType) {
1604         setConfiguration(configuration, null, cmdType);
1605     }
1606 
1607     /**
1608      * Sets the configuration and performs necessary actions.
1609      *
1610      * @param configuration new configuration
1611      * @param subFileList   collection of repositories
1612      * @param cmdType       command timeout type
1613      */
setConfiguration(Configuration configuration, Collection<String> subFileList, CommandTimeoutType cmdType)1614     public synchronized void setConfiguration(Configuration configuration, Collection<String> subFileList, CommandTimeoutType cmdType) {
1615         try (ResourceLock resourceLock = configLock.writeLockAsResource()) {
1616             //noinspection ConstantConditions to avoid warning of no reference to auto-closeable
1617             assert resourceLock != null;
1618             this.configuration = configuration;
1619         }
1620 
1621         // HistoryGuru constructor needs environment properties so no locking is done here.
1622         HistoryGuru histGuru = HistoryGuru.getInstance();
1623 
1624         // Set the working repositories in HistoryGuru.
1625         if (subFileList != null) {
1626             histGuru.invalidateRepositories(getRepositories(), subFileList, cmdType);
1627         } else {
1628             histGuru.invalidateRepositories(getRepositories(), cmdType);
1629         }
1630 
1631         // The invalidation of repositories above might have excluded some
1632         // repositories in HistoryGuru so the configuration needs to reflect that.
1633         setRepositories(new ArrayList<>(histGuru.getRepositories()));
1634 
1635         // generate repository map is dependent on getRepositories()
1636         try {
1637             generateProjectRepositoriesMap();
1638         } catch (IOException ex) {
1639             LOGGER.log(Level.SEVERE, "Cannot generate project - repository map", ex);
1640         }
1641 
1642         // populate groups is dependent on repositories map
1643         populateGroups(getGroups(), new TreeSet<>(getProjects().values()));
1644 
1645         includeFiles.reloadIncludeFiles();
1646     }
1647 
getIncludeFiles()1648     public IncludeFiles getIncludeFiles() {
1649         return includeFiles;
1650     }
1651 
1652     /**
1653      * Return the authorization framework used in this environment.
1654      *
1655      * @return the framework
1656      */
getAuthorizationFramework()1657     public AuthorizationFramework getAuthorizationFramework() {
1658         synchronized (authFrameworkLock) {
1659             if (authFramework == null) {
1660                 authFramework = new AuthorizationFramework(getPluginDirectory(), getPluginStack());
1661             }
1662             return authFramework;
1663         }
1664     }
1665 
1666     /**
1667      * Set the authorization framework for this environment. Unload all
1668      * previously load plugins.
1669      *
1670      * @param fw the new framework
1671      */
setAuthorizationFramework(AuthorizationFramework fw)1672     public void setAuthorizationFramework(AuthorizationFramework fw) {
1673         synchronized (authFrameworkLock) {
1674            if (this.authFramework != null) {
1675                 this.authFramework.removeAll();
1676             }
1677             this.authFramework = fw;
1678         }
1679     }
1680 
1681     /**
1682      * Re-apply the configuration.
1683      * @param reindex is the message result of reindex
1684      * @param cmdType command timeout type
1685      * @see #applyConfig(org.opengrok.indexer.configuration.Configuration,
1686      * boolean, CommandTimeoutType) applyConfig(config, reindex, cmdType)
1687      */
applyConfig(boolean reindex, CommandTimeoutType cmdType)1688     public void applyConfig(boolean reindex, CommandTimeoutType cmdType) {
1689         applyConfig(configuration, reindex, cmdType);
1690     }
1691 
1692     /**
1693      * Set configuration from a message. The message could have come from the
1694      * Indexer (in which case some extra work is needed) or is it just a request
1695      * to set new configuration in place.
1696      *
1697      * @param configuration XML configuration
1698      * @param reindex is the message result of reindex
1699      * @param cmdType command timeout type
1700      * @see #applyConfig(org.opengrok.indexer.configuration.Configuration,
1701      * boolean, CommandTimeoutType) applyConfig(config, reindex, cmdType)
1702      */
applyConfig(String configuration, boolean reindex, CommandTimeoutType cmdType)1703     public void applyConfig(String configuration, boolean reindex, CommandTimeoutType cmdType) {
1704         Configuration config;
1705         try {
1706             config = makeXMLStringAsConfiguration(configuration);
1707         } catch (IOException ex) {
1708             LOGGER.log(Level.WARNING, "Configuration decoding failed", ex);
1709             return;
1710         }
1711 
1712         applyConfig(config, reindex, cmdType);
1713     }
1714 
1715     /**
1716      * Set configuration from the incoming parameter. The configuration could
1717      * have come from the Indexer (in which case some extra work is needed) or
1718      * is it just a request to set new configuration in place.
1719      *
1720      * The classes that have registered their listener will be pinged here.
1721      * @see ConfigurationChangedListener
1722      *
1723      * @param config the incoming configuration
1724      * @param reindex is the message result of reindex
1725      * @param cmdType command timeout type
1726      */
applyConfig(Configuration config, boolean reindex, CommandTimeoutType cmdType)1727     public void applyConfig(Configuration config, boolean reindex, CommandTimeoutType cmdType) {
1728         setConfiguration(config, cmdType);
1729         LOGGER.log(Level.INFO, "Configuration updated");
1730 
1731         if (reindex) {
1732             // We are assuming that each update of configuration means reindex. If dedicated thread is introduced
1733             // in the future solely for the purpose of getting the event of reindex, the 2 calls below should
1734             // be moved there.
1735             refreshSearcherManagerMap();
1736             maybeRefreshIndexSearchers();
1737             // Force timestamp to update itself upon new config arrival.
1738             refreshDateForLastIndexRun();
1739         }
1740 
1741         // start/stop the watchdog if necessary
1742         if (isAuthorizationWatchdog() && getPluginDirectory() != null) {
1743             watchDog.start(new File(getPluginDirectory()));
1744         } else {
1745             watchDog.stop();
1746         }
1747 
1748         // set the new plugin directory and reload the authorization framework
1749         getAuthorizationFramework().setPluginDirectory(getPluginDirectory());
1750         getAuthorizationFramework().setStack(getPluginStack());
1751         getAuthorizationFramework().reload();
1752 
1753         messagesContainer.setMessageLimit(getMessageLimit());
1754 
1755         for (ConfigurationChangedListener l : listeners) {
1756             l.onConfigurationChanged();
1757         }
1758 
1759         LOGGER.log(Level.INFO, "Done applying configuration");
1760     }
1761 
setIndexTimestamp()1762     public void setIndexTimestamp() throws IOException {
1763         indexTime.stamp();
1764     }
1765 
refreshDateForLastIndexRun()1766     public void refreshDateForLastIndexRun() {
1767         indexTime.refreshDateForLastIndexRun();
1768     }
1769 
maybeRefreshSearcherManager(SearcherManager sm)1770     private void maybeRefreshSearcherManager(SearcherManager sm) {
1771         try {
1772             sm.maybeRefresh();
1773         }  catch (AlreadyClosedException ex) {
1774             // This is a case of removed project. See refreshSearcherManagerMap() for details.
1775         } catch (IOException ex) {
1776             LOGGER.log(Level.SEVERE, "maybeRefresh failed", ex);
1777         }
1778     }
1779 
maybeRefreshIndexSearchers(Iterable<String> projects)1780     public void maybeRefreshIndexSearchers(Iterable<String> projects) {
1781         for (String proj : projects) {
1782             if (searcherManagerMap.containsKey(proj)) {
1783                 maybeRefreshSearcherManager(searcherManagerMap.get(proj));
1784             }
1785         }
1786     }
1787 
maybeRefreshIndexSearchers()1788     public void maybeRefreshIndexSearchers() {
1789         LOGGER.log(Level.INFO, "refreshing searcher managers");
1790         Statistics stat = new Statistics();
1791         for (Map.Entry<String, SearcherManager> entry : searcherManagerMap.entrySet()) {
1792             maybeRefreshSearcherManager(entry.getValue());
1793         }
1794         stat.report(LOGGER, "Done refreshing searcher managers");
1795     }
1796 
1797     /**
1798      * Get IndexSearcher for given project.
1799      * Each IndexSearcher is born from a SearcherManager object. There is one SearcherManager for every project.
1800      * This schema makes it possible to reuse IndexSearcher/IndexReader objects so the heavy lifting
1801      * (esp. system calls) performed in FSDirectory and DirectoryReader happens only once for a project.
1802      * The caller has to make sure that the IndexSearcher is returned back
1803      * to the SearcherManager. This is done with returnIndexSearcher().
1804      * The return of the IndexSearcher should happen only after the search result data are read fully.
1805      *
1806      * @param projectName project
1807      * @return SearcherManager for given project
1808      * @throws IOException I/O exception
1809      */
1810     @SuppressWarnings("java:S2095")
getIndexSearcher(String projectName)1811     public SuperIndexSearcher getIndexSearcher(String projectName) throws IOException {
1812         SearcherManager mgr = searcherManagerMap.get(projectName);
1813         SuperIndexSearcher searcher;
1814 
1815         if (mgr == null) {
1816             File indexDir = new File(getDataRootPath(), IndexDatabase.INDEX_DIR);
1817             Directory dir = FSDirectory.open(new File(indexDir, projectName).toPath());
1818             mgr = new SearcherManager(dir, new ThreadpoolSearcherFactory());
1819             searcherManagerMap.put(projectName, mgr);
1820         }
1821         searcher = (SuperIndexSearcher) mgr.acquire();
1822         searcher.setSearcherManager(mgr);
1823 
1824         return searcher;
1825     }
1826 
1827     /**
1828      * After new configuration is put into place, the set of projects might
1829      * change so we go through the SearcherManager objects and close those where
1830      * the corresponding project is no longer present.
1831      */
refreshSearcherManagerMap()1832     public void refreshSearcherManagerMap() {
1833         ArrayList<String> toRemove = new ArrayList<>();
1834 
1835         for (Map.Entry<String, SearcherManager> entry : searcherManagerMap.entrySet()) {
1836             // If a project is gone, close the corresponding SearcherManager
1837             // so that it cannot produce new IndexSearcher objects.
1838             if (!getProjectNames().contains(entry.getKey())) {
1839                 try {
1840                     LOGGER.log(Level.FINE, "closing SearcherManager for project {0}", entry.getKey());
1841                     entry.getValue().close();
1842                 } catch (IOException ex) {
1843                     LOGGER.log(Level.SEVERE,
1844                         String.format("cannot close SearcherManager for project %s", entry.getKey()), ex);
1845                 }
1846                 toRemove.add(entry.getKey());
1847             }
1848         }
1849 
1850         for (String proj : toRemove) {
1851             searcherManagerMap.remove(proj);
1852         }
1853     }
1854 
1855     /**
1856      * Return collection of IndexReader objects as MultiReader object
1857      * for given list of projects.
1858      * The caller is responsible for releasing the IndexSearcher objects
1859      * so we add them to the map.
1860      *
1861      * @param projects list of projects
1862      * @param searcherList each SuperIndexSearcher produced will be put into this list
1863      * @return MultiReader for the projects
1864      */
getMultiReader(SortedSet<String> projects, ArrayList<SuperIndexSearcher> searcherList)1865     public MultiReader getMultiReader(SortedSet<String> projects,
1866         ArrayList<SuperIndexSearcher> searcherList) {
1867 
1868         IndexReader[] subreaders = new IndexReader[projects.size()];
1869         int ii = 0;
1870 
1871         // TODO might need to rewrite to Project instead of String, need changes in projects.jspf too.
1872         for (String proj : projects) {
1873             try {
1874                 SuperIndexSearcher searcher = getIndexSearcher(proj);
1875                 subreaders[ii++] = searcher.getIndexReader();
1876                 searcherList.add(searcher);
1877             } catch (IOException | NullPointerException ex) {
1878                 LOGGER.log(Level.SEVERE,
1879                     "cannot get IndexReader for project " + proj, ex);
1880                 return null;
1881             }
1882         }
1883         MultiReader multiReader = null;
1884         try {
1885             multiReader = new MultiReader(subreaders, true);
1886         } catch (IOException ex) {
1887             LOGGER.log(Level.SEVERE,
1888                 "cannot construct MultiReader for set of projects", ex);
1889         }
1890         return multiReader;
1891     }
1892 
startExpirationTimer()1893     public void startExpirationTimer() {
1894         messagesContainer.setMessageLimit(getMessageLimit());
1895         messagesContainer.startExpirationTimer();
1896     }
1897 
stopExpirationTimer()1898     public void stopExpirationTimer() {
1899         messagesContainer.stopExpirationTimer();
1900     }
1901 
1902     /**
1903      * Get the default set of messages for the main tag.
1904      *
1905      * @return set of messages
1906      */
getMessages()1907     public SortedSet<AcceptedMessage> getMessages() {
1908         return messagesContainer.getMessages();
1909     }
1910 
1911     /**
1912      * Get the set of messages for the arbitrary tag.
1913      *
1914      * @param tag the message tag
1915      * @return set of messages
1916      */
getMessages(final String tag)1917     public SortedSet<AcceptedMessage> getMessages(final String tag) {
1918         return messagesContainer.getMessages(tag);
1919     }
1920 
1921     /**
1922      * Add a message to the application.
1923      * Also schedules a expiration timer to remove this message after its expiration.
1924      *
1925      * @param message the message
1926      */
addMessage(final Message message)1927     public void addMessage(final Message message) {
1928         messagesContainer.addMessage(message);
1929     }
1930 
1931     /**
1932      * Remove all messages containing at least one of the tags.
1933      * @param tags set of tags
1934      * @param text message text (can be null, empty)
1935      */
removeAnyMessage(final Set<String> tags, final String text)1936     public void removeAnyMessage(final Set<String> tags, final String text) {
1937         messagesContainer.removeAnyMessage(tags, text);
1938     }
1939 
1940     /**
1941      * @return all messages regardless their tag
1942      */
getAllMessages()1943     public Set<AcceptedMessage> getAllMessages() {
1944         return messagesContainer.getAllMessages();
1945     }
1946 
getDtagsEftarPath()1947     public Path getDtagsEftarPath() {
1948         return syncReadConfiguration(Configuration::getDtagsEftarPath);
1949     }
1950 
1951     /**
1952      * Get the eftar file, which contains definition tags for path descriptions.
1953      *
1954      * @return {@code null} if there is no such file, the file otherwise.
1955      */
getDtagsEftar()1956     public File getDtagsEftar() {
1957         if (dtagsEftar == null) {
1958             File tmp = getDtagsEftarPath().toFile();
1959             if (tmp.canRead()) {
1960                 dtagsEftar = tmp;
1961             }
1962         }
1963         return dtagsEftar;
1964     }
1965 
getSuggesterConfig()1966     public SuggesterConfig getSuggesterConfig() {
1967         return syncReadConfiguration(Configuration::getSuggesterConfig);
1968     }
1969 
setSuggesterConfig(SuggesterConfig suggesterConfig)1970     public void setSuggesterConfig(SuggesterConfig suggesterConfig) {
1971         syncWriteConfiguration(suggesterConfig, Configuration::setSuggesterConfig);
1972     }
1973 
getStatsdConfig()1974     public StatsdConfig getStatsdConfig() {
1975         return syncReadConfiguration(Configuration::getStatsdConfig);
1976     }
1977 
setStatsdConfig(StatsdConfig statsdConfig)1978     public void setStatsdConfig(StatsdConfig statsdConfig) {
1979         syncWriteConfiguration(statsdConfig, Configuration::setStatsdConfig);
1980     }
1981 
1982     /**
1983      * Applies the specified function to the runtime configuration, after having
1984      * obtained the configuration read-lock (and releasing afterward).
1985      * @param function a defined function
1986      * @param <R> the type of the result of the function
1987      * @return the function result
1988      */
syncReadConfiguration(Function<Configuration, R> function)1989     public <R> R syncReadConfiguration(Function<Configuration, R> function) {
1990         try (ResourceLock resourceLock = configLock.readLockAsResource()) {
1991             //noinspection ConstantConditions to avoid warning of no reference to auto-closeable
1992             assert resourceLock != null;
1993             return function.apply(configuration);
1994         }
1995     }
1996 
1997     /**
1998      * Performs the specified operation which is provided the runtime
1999      * configuration and the specified argument, after first having obtained the
2000      * configuration write-lock (and releasing afterward).
2001      * @param <V> the type of the input to the operation
2002      * @param v the input argument
2003      * @param consumer a defined consumer
2004      */
syncWriteConfiguration(V v, ConfigurationValueConsumer<V> consumer)2005     public <V> void syncWriteConfiguration(V v, ConfigurationValueConsumer<V> consumer) {
2006         try (ResourceLock resourceLock = configLock.writeLockAsResource()) {
2007             //noinspection ConstantConditions to avoid warning of no reference to auto-closeable
2008             assert resourceLock != null;
2009             consumer.accept(configuration, v);
2010         }
2011     }
2012 
getMessageLimit()2013     private int getMessageLimit() {
2014         return syncReadConfiguration(Configuration::getMessageLimit);
2015     }
2016 
getIndexerAuthenticationToken()2017     public String getIndexerAuthenticationToken() {
2018         return syncReadConfiguration(Configuration::getIndexerAuthenticationToken);
2019     }
2020 
setIndexerAuthenticationToken(String token)2021     public void setIndexerAuthenticationToken(String token) {
2022         syncWriteConfiguration(token, Configuration::setIndexerAuthenticationToken);
2023     }
2024 
getAuthenticationTokens()2025     public Set<String> getAuthenticationTokens() {
2026         return Collections.unmodifiableSet(syncReadConfiguration(Configuration::getAuthenticationTokens));
2027     }
2028 
setAuthenticationTokens(Set<String> tokens)2029     public void setAuthenticationTokens(Set<String> tokens) {
2030         syncWriteConfiguration(tokens, Configuration::setAuthenticationTokens);
2031     }
2032 
isAllowInsecureTokens()2033     public boolean isAllowInsecureTokens() {
2034         return syncReadConfiguration(Configuration::isAllowInsecureTokens);
2035     }
2036 
setAllowInsecureTokens(boolean value)2037     public void setAllowInsecureTokens(boolean value) {
2038         syncWriteConfiguration(value, Configuration::setAllowInsecureTokens);
2039     }
2040 
registerListener(ConfigurationChangedListener listener)2041     public void registerListener(ConfigurationChangedListener listener) {
2042         listeners.add(listener);
2043     }
2044 }
2045