Java tutorial
/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * See LICENSE.txt included in this distribution for the specific * language governing permissions and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at LICENSE.txt. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2006, 2019, Oracle and/or its affiliates. All rights reserved. * Portions Copyright (c) 2017-2018, Chris Fraire <cfraire@me.com>. */ package org.opengrok.indexer.configuration; import static org.opengrok.indexer.configuration.Configuration.makeXMLStringAsConfiguration; import static org.opengrok.indexer.util.ClassUtil.getFieldValue; import static org.opengrok.indexer.util.ClassUtil.setFieldValue; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.Entity; import javax.ws.rs.core.Response; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.MultiReader; import org.apache.lucene.search.SearcherManager; import org.apache.lucene.store.AlreadyClosedException; import org.apache.lucene.store.Directory; import org.apache.lucene.store.FSDirectory; import org.opengrok.indexer.authorization.AuthorizationFramework; import org.opengrok.indexer.authorization.AuthorizationStack; import org.opengrok.indexer.history.HistoryGuru; import org.opengrok.indexer.history.RepositoryInfo; import org.opengrok.indexer.index.Filter; import org.opengrok.indexer.index.IgnoredNames; import org.opengrok.indexer.index.IndexDatabase; import org.opengrok.indexer.index.IndexerParallelizer; import org.opengrok.indexer.logger.LoggerFactory; import org.opengrok.indexer.util.CtagsUtil; import org.opengrok.indexer.util.ForbiddenSymlinkException; import org.opengrok.indexer.util.LazilyInstantiate; import org.opengrok.indexer.util.PathUtils; import org.opengrok.indexer.web.Prefix; import org.opengrok.indexer.web.Statistics; import org.opengrok.indexer.web.Util; import org.opengrok.indexer.web.messages.Message; import org.opengrok.indexer.web.messages.MessagesContainer; import org.opengrok.indexer.web.messages.MessagesContainer.AcceptedMessage; /** * The RuntimeEnvironment class is used as a placeholder for the current * configuration this execution context (classloader) is using. */ public final class RuntimeEnvironment { private static final Logger LOGGER = LoggerFactory.getLogger(RuntimeEnvironment.class); /** {@code "/source"} + {@link Prefix#SEARCH_R} + {@code "?"} */ private static final String URL_PREFIX = "/source" + Prefix.SEARCH_R + "?"; private Configuration configuration; private final ReentrantReadWriteLock configLock; private final LazilyInstantiate<IndexerParallelizer> lzIndexerParallelizer; private final LazilyInstantiate<ExecutorService> lzSearchExecutor; private static final RuntimeEnvironment instance = new RuntimeEnvironment(); private final Map<Project, List<RepositoryInfo>> repository_map = new ConcurrentHashMap<>(); private final Map<String, SearcherManager> searcherManagerMap = new ConcurrentHashMap<>(); private String configURI; private Statistics statistics = new Statistics(); public IncludeFiles includeFiles = new IncludeFiles(); private final MessagesContainer messagesContainer = new MessagesContainer(); private static final IndexTimestamp indexTime = new IndexTimestamp(); /** * Stores a transient value when * {@link #setCtags(java.lang.String)} is called -- i.e. the * value is not mediated to {@link Configuration}. */ private String ctags; /** * Stores a transient value when * {@link #setMandoc(java.lang.String)} is called -- i.e. the * value is not mediated to {@link Configuration}. */ private String mandoc; private transient File dtagsEftar = null; /** * Creates a new instance of RuntimeEnvironment. Private to ensure a * singleton anti-pattern. */ private RuntimeEnvironment() { configuration = new Configuration(); configLock = new ReentrantReadWriteLock(); watchDog = new WatchDogService(); lzIndexerParallelizer = LazilyInstantiate.using(() -> new IndexerParallelizer(this)); lzSearchExecutor = LazilyInstantiate.using(() -> newSearchExecutor()); } /** Instance of authorization framework.*/ private AuthorizationFramework authFramework; /** Gets the thread pool used for multi-project searches. */ public ExecutorService getSearchExecutor() { return lzSearchExecutor.get(); } private ExecutorService newSearchExecutor() { return Executors.newFixedThreadPool(this.getMaxSearchThreadCount(), new ThreadFactory() { @Override public Thread newThread(Runnable runnable) { Thread thread = Executors.defaultThreadFactory().newThread(runnable); thread.setName("search-" + thread.getId()); return thread; } }); } /** * Get the one and only instance of the RuntimeEnvironment * * @return the one and only instance of the RuntimeEnvironment */ public static RuntimeEnvironment getInstance() { return instance; } public WatchDogService watchDog; public IndexerParallelizer getIndexerParallelizer() { return lzIndexerParallelizer.get(); } private String getCanonicalPath(String s) { if (s == null) { return null; } try { File file = new File(s); if (!file.exists()) { return s; } return file.getCanonicalPath(); } catch (IOException ex) { LOGGER.log(Level.SEVERE, "Failed to get canonical path", ex); return s; } } /** * Get value of configuration field * @param fieldName name of the field * @return object value */ public Object getConfigurationValue(String fieldName) { try { configLock.readLock().lock(); return getFieldValue(configuration, fieldName); } catch (IOException e) { return null; } finally { configLock.readLock().unlock(); } } /** * Get value of configuration field * @param fieldName name of the field * @return object value * @throws IOException I/O */ public Object getConfigurationValueException(String fieldName) throws IOException { try { configLock.readLock().lock(); return getFieldValue(configuration, fieldName); } catch (IOException e) { throw new IOException("getter", e); } finally { configLock.readLock().unlock(); } } /** * Set configuration value * @param fieldName name of the field * @param value string value */ public void setConfigurationValue(String fieldName, String value) { try { configLock.writeLock().lock(); setFieldValue(configuration, fieldName, value); } catch (IOException e) { LOGGER.log(Level.WARNING, "failed to set value of field {}: {}", new Object[] { fieldName, e }); } finally { configLock.writeLock().unlock(); } } /** * Set configuration value * @param fieldName name of the field * @param value value */ public void setConfigurationValue(String fieldName, Object value) { try { configLock.writeLock().lock(); setFieldValue(configuration, fieldName, value); } catch (IOException e) { LOGGER.log(Level.WARNING, "failed to set value of field {}: {}", new Object[] { fieldName, e }); } finally { configLock.writeLock().unlock(); } } /** * Set configuration value * @param fieldName name of the field * @param value value * @throws IOException I/O exception */ public void setConfigurationValueException(String fieldName, Object value) throws IOException { try { configLock.writeLock().lock(); setFieldValue(configuration, fieldName, value); } finally { configLock.writeLock().unlock(); } } /** * Set configuration value * @param fieldName name of the field * @param value string value * @throws IOException I/O exception */ public void setConfigurationValueException(String fieldName, String value) throws IOException { try { configLock.writeLock().lock(); setFieldValue(configuration, fieldName, value); } finally { configLock.writeLock().unlock(); } } public int getScanningDepth() { return (int) getConfigurationValue("scanningDepth"); } public void setScanningDepth(int scanningDepth) { setConfigurationValue("scanningDepth", scanningDepth); } public int getCommandTimeout() { return (int) getConfigurationValue("commandTimeout"); } public void setCommandTimeout(int timeout) { setConfigurationValue("commandTimeout", timeout); } public int getInteractiveCommandTimeout() { return (int) getConfigurationValue("interactiveCommandTimeout"); } public void setInteractiveCommandTimeout(int timeout) { setConfigurationValue("interactiveCommandTimeout", timeout); } public Statistics getStatistics() { return statistics; } public void setStatistics(Statistics statistics) { this.statistics = statistics; } public void setLastEditedDisplayMode(boolean lastEditedDisplayMode) { setConfigurationValue("lastEditedDisplayMode", lastEditedDisplayMode); } public boolean isLastEditedDisplayMode() { return (boolean) getConfigurationValue("lastEditedDisplayMode"); } /** * Get the path to the where the web application includes are stored * * @return the path to the web application include files */ public String getIncludeRootPath() { return (String) getConfigurationValue("includeRoot"); } /** * Set include root path * @param includeRoot path */ public void setIncludeRoot(String includeRoot) { setConfigurationValue("includeRoot", getCanonicalPath(includeRoot)); } /** * Get the path to the where the index database is stored * * @return the path to the index database */ public String getDataRootPath() { return (String) getConfigurationValue("dataRoot"); } /** * Get a file representing the index database * * @return the index database */ public File getDataRootFile() { File ret = null; String file = getDataRootPath(); if (file != null) { ret = new File(file); } return ret; } /** * Set the path to where the index database is stored * * @param dataRoot the index database */ public void setDataRoot(String dataRoot) { setConfigurationValue("dataRoot", getCanonicalPath(dataRoot)); } /** * Get the path to where the sources are located * * @return path to where the sources are located */ public String getSourceRootPath() { return (String) getConfigurationValue("sourceRoot"); } /** * Get a file representing the directory where the sources are located * * @return A file representing the directory where the sources are located */ public File getSourceRootFile() { File ret = null; String file = getSourceRootPath(); if (file != null) { ret = new File(file); } return ret; } /** * Specify the source root * * @param sourceRoot the location of the sources */ public void setSourceRoot(String sourceRoot) { setConfigurationValue("sourceRoot", getCanonicalPath(sourceRoot)); } /** * Returns a path relative to source root. This would just be a simple * substring operation, except we need to support symlinks outside the * source root. * * @param file A file to resolve * @return Path relative to source root * @throws IOException If an IO error occurs * @throws FileNotFoundException if the file is not relative to source root * @throws ForbiddenSymlinkException if symbolic-link checking encounters * an ineligible link */ public String getPathRelativeToSourceRoot(File file) throws IOException, ForbiddenSymlinkException { return PathUtils.getPathRelativeToSourceRoot(file, 0); } /** * Do we have any projects ? * * @return true if we have projects */ public boolean hasProjects() { return (this.isProjectsEnabled() && getProjects().size() > 0); } /** * Get list of projects. * * @return a list containing all of the projects */ public List<Project> getProjectList() { return new ArrayList<>(getProjects().values()); } /** * Get project map. * * @return a Map with all of the projects */ @SuppressWarnings("unchecked") public Map<String, Project> getProjects() { return (Map<String, Project>) getConfigurationValue("projects"); } /** * Get names of all projects. * * @return a list containing names of all projects. */ public List<String> getProjectNames() { return getProjectList().stream().map(Project::getName).collect(Collectors.toList()); } /** * Set the list of the projects * * @param projects the map of projects to use */ public void setProjects(Map<String, Project> projects) { try { configLock.writeLock().lock(); if (projects != null) { populateGroups(getGroups(), new TreeSet<>(projects.values())); } setConfigurationValue("projects", projects); } finally { configLock.writeLock().unlock(); } } /** * Do we have groups? * * @return true if we have groups */ public boolean hasGroups() { return (getGroups() != null && !getGroups().isEmpty()); } /** * Get all of the groups * * @return a set containing all of the groups (may be null) */ @SuppressWarnings("unchecked") public Set<Group> getGroups() { return (Set<Group>) getConfigurationValue("groups"); } /** * Set the list of the groups * * @param groups the set of groups to use */ public void setGroups(Set<Group> groups) { populateGroups(groups, new TreeSet<>(getProjects().values())); setConfigurationValue("groups", groups); } /** * Returns constructed project - repositories map. * * @return the map * @see #generateProjectRepositoriesMap */ public Map<Project, List<RepositoryInfo>> getProjectRepositoriesMap() { return repository_map; } /** * Gets a static placeholder for the web application context name that is * translated to the true servlet {@code contextPath} on demand. * @return {@code "/source"} + {@link Prefix#SEARCH_R} + {@code "?"} */ public String getUrlPrefix() { return URL_PREFIX; } /** * Gets the name of the ctags program to use: either the last value passed * successfully to {@link #setCtags(java.lang.String)}, or * {@link Configuration#getCtags()}, or the system property for * {@code "org.opengrok.indexer.analysis.Ctags"}, or "ctags" as a * default. * @return a defined value */ public String getCtags() { String value; return ctags != null ? ctags : (value = (String) getConfigurationValue("ctags")) != null ? value : System.getProperty(CtagsUtil.SYSTEM_CTAGS_PROPERTY, "ctags"); } /** * Sets the name of the ctags program to use, or resets to use the fallbacks * documented for {@link #getCtags()}. * <p> * N.b. the value is not mediated to {@link Configuration}. * * @param ctags a defined value or {@code null} to reset to use the * {@link Configuration#getCtags()} fallbacks * @see #getCtags() */ public void setCtags(String ctags) { this.ctags = ctags; } /** * Gets the name of the mandoc program to use: either the last value passed * successfully to {@link #setMandoc(java.lang.String)}, or * {@link Configuration#getMandoc()}, or the system property for * {@code "org.opengrok.indexer.analysis.Mandoc"}, or {@code null} as a * default. * @return a defined instance or {@code null} */ public String getMandoc() { String value; return mandoc != null ? mandoc : (value = (String) getConfigurationValue("mandoc")) != null ? value : System.getProperty("org.opengrok.indexer.analysis.Mandoc"); } /** * Sets the name of the mandoc program to use, or resets to use the * fallbacks documented for {@link #getMandoc()}. * <p> * N.b. the value is not mediated to {@link Configuration}. * * @param value a defined value or {@code null} to reset to use the * {@link Configuration#getMandoc()} fallbacks * @see #getMandoc() */ public void setMandoc(String value) { this.mandoc = value; } public int getCachePages() { return (int) getConfigurationValue("cachePages"); } public void setCachePages(int cachePages) { setConfigurationValue("cachePages", cachePages); } public int getHitsPerPage() { return (int) getConfigurationValue("hitsPerPage"); } public void setHitsPerPage(int hitsPerPage) { setConfigurationValue("hitsPerPage", hitsPerPage); } private transient Boolean ctagsFound; /** * Validate that there is a Universal ctags program. * * @return true if success, false otherwise */ public boolean validateUniversalCtags() { if (ctagsFound == null) { if (!CtagsUtil.validate(getCtags())) { ctagsFound = false; } else { ctagsFound = true; } } return ctagsFound; } /** * Get the max time a SCM operation may use to avoid being cached * * @return the max time */ public int getHistoryReaderTimeLimit() { return (int) getConfigurationValue("historyCacheTime"); } /** * Specify the maximum time a SCM operation should take before it will be * cached (in ms) * * @param historyReaderTimeLimit the max time in ms before it is cached */ public void setHistoryReaderTimeLimit(int historyReaderTimeLimit) { setConfigurationValue("historyCacheTime", historyReaderTimeLimit); } /** * Is history cache currently enabled? * * @return true if history cache is enabled */ public boolean useHistoryCache() { return (boolean) getConfigurationValue("historyCache"); } /** * Specify if we should use history cache or not * * @param useHistoryCache set false if you do not want to use history cache */ public void setUseHistoryCache(boolean useHistoryCache) { setConfigurationValue("historyCache", useHistoryCache); } /** * Should we generate HTML or not during the indexing phase * * @return true if HTML should be generated during the indexing phase */ public boolean isGenerateHtml() { return (boolean) getConfigurationValue("generateHtml"); } /** * Specify if we should generate HTML or not during the indexing phase * * @param generateHtml set this to true to pregenerate HTML */ public void setGenerateHtml(boolean generateHtml) { setConfigurationValue("generateHtml", generateHtml); } /** * Set if we should compress the xref files or not * * @param compressXref set to true if the generated html files should be * compressed */ public void setCompressXref(boolean compressXref) { setConfigurationValue("compressXref", compressXref); } /** * Are we using compressed HTML files? * * @return {@code true} if the html-files should be compressed. */ public boolean isCompressXref() { return (boolean) getConfigurationValue("compressXref"); } public boolean isQuickContextScan() { return (boolean) getConfigurationValue("quickContextScan"); } public void setQuickContextScan(boolean quickContextScan) { setConfigurationValue("quickContextScan", quickContextScan); } @SuppressWarnings("unchecked") public List<RepositoryInfo> getRepositories() { return (List<RepositoryInfo>) getConfigurationValue("repositories"); } /** * Set the list of repositories. * * @param repositories the repositories to use */ public void setRepositories(List<RepositoryInfo> repositories) { setConfigurationValue("repositories", repositories); } public void removeRepositories() { try { configLock.writeLock().lock(); configuration.setRepositories(null); } finally { configLock.writeLock().unlock(); } } /** * Search through the directory for repositories and use the result to replace * the lists of repositories in both RuntimeEnvironment/Configuration and HistoryGuru. * * @param dir the root directory to start the search in */ public void setRepositories(String dir) { List<RepositoryInfo> repos = new ArrayList<>(HistoryGuru.getInstance() .addRepositories(new File[] { new File(dir) }, RuntimeEnvironment.getInstance().getIgnoredNames())); RuntimeEnvironment.getInstance().setRepositories(repos); } /** * Add repositories to the list. * @param repositories list of repositories */ public void addRepositories(List<RepositoryInfo> repositories) { Lock writeLock = configLock.writeLock(); try { writeLock.lock(); configuration.addRepositories(repositories); } finally { writeLock.unlock(); } } /** * Set the specified projects as default in the configuration. * This method should be called only after projects were discovered and became part of the configuration, * i.e. after {@link org.opengrok.indexer.index.Indexer#prepareIndexer} was called. * * @param defaultProjects The default project to use * @see #setDefaultProjects */ public void setDefaultProjectsFromNames(Set<String> defaultProjects) { if (defaultProjects != null && !defaultProjects.isEmpty()) { Set<Project> projects = new TreeSet<>(); for (String projectPath : defaultProjects) { if (projectPath.equals("__all__")) { projects.addAll(getProjects().values()); break; } for (Project p : getProjectList()) { if (p.getPath().equals(Util.fixPathIfWindows(projectPath))) { projects.add(p); break; } } } if (!projects.isEmpty()) { setDefaultProjects(projects); } } } /** * Set the projects that are specified to be the default projects to use. * The default projects are the projects you will search (from the web * application) if the page request didn't contain the cookie.. * * @param defaultProjects The default project to use */ public void setDefaultProjects(Set<Project> defaultProjects) { setConfigurationValue("defaultProjects", defaultProjects); } /** * Get the projects that are specified to be the default projects to use. * The default projects are the projects you will search (from the web * application) if the page request didn't contain the cookie.. * * @return the default projects (may be null if not specified) */ @SuppressWarnings("unchecked") public Set<Project> getDefaultProjects() { Set<Project> projects = (Set<Project>) getConfigurationValue("defaultProjects"); if (projects == null) { return null; } return Collections.unmodifiableSet(projects); } /** * * @return at what size (in MB) we should flush the buffer */ public double getRamBufferSize() { return (double) getConfigurationValue("ramBufferSize"); } /** * Set the size of buffer which will determine when the docs are flushed to * disk. Specify size in MB please. 16MB is default note that this is per * thread (lucene uses 8 threads by default in 4.x) * * @param ramBufferSize the size(in MB) when we should flush the docs */ public void setRamBufferSize(double ramBufferSize) { setConfigurationValue("ramBufferSize", ramBufferSize); } public void setPluginDirectory(String pluginDirectory) { setConfigurationValue("pluginDirectory", pluginDirectory); } public String getPluginDirectory() { return (String) getConfigurationValue("pluginDirectory"); } public boolean isAuthorizationWatchdog() { return (boolean) getConfigurationValue("authorizationWatchdogEnabled"); } public void setAuthorizationWatchdog(boolean authorizationWatchdogEnabled) { setConfigurationValue("authorizationWatchdogEnabled", authorizationWatchdogEnabled); } public AuthorizationStack getPluginStack() { return (AuthorizationStack) getConfigurationValue("pluginStack"); } public void setPluginStack(AuthorizationStack pluginStack) { setConfigurationValue("pluginStack", pluginStack); } /** * Is the progress print flag turned on? * * @return true if we can print per project progress % */ public boolean isPrintProgress() { return (boolean) getConfigurationValue("printProgress"); } /** * Set the printing of progress % flag (user convenience) * * @param printProgress new value */ public void setPrintProgress(boolean printProgress) { setConfigurationValue("printProgress", printProgress); } /** * Specify if a search may start with a wildcard. Note that queries that * start with a wildcard will give a significant impact on the search * performance. * * @param allowLeadingWildcard set to true to activate (disabled by default) */ public void setAllowLeadingWildcard(boolean allowLeadingWildcard) { setConfigurationValue("allowLeadingWildcard", allowLeadingWildcard); } /** * Is leading wildcards allowed? * * @return true if a search may start with a wildcard */ public boolean isAllowLeadingWildcard() { return (boolean) getConfigurationValue("allowLeadingWildcard"); } public IgnoredNames getIgnoredNames() { return (IgnoredNames) getConfigurationValue("ignoredNames"); } public void setIgnoredNames(IgnoredNames ignoredNames) { setConfigurationValue("ignoredNames", ignoredNames); } public Filter getIncludedNames() { return (Filter) getConfigurationValue("includedNames"); } public void setIncludedNames(Filter includedNames) { setConfigurationValue("includedNames", includedNames); } /** * Returns the user page for the history listing * * @return the URL string fragment preceeding the username */ public String getUserPage() { return (String) getConfigurationValue("userPage"); } /** * Get the client command to use to access the repository for the given * fully qualified classname. * * @param clazzName name of the targeting class * @return {@code null} if not yet set, the client command otherwise. */ public String getRepoCmd(String clazzName) { Lock readLock = configLock.readLock(); String cmd = null; try { readLock.lock(); cmd = configuration.getRepoCmd(clazzName); } finally { readLock.unlock(); } return cmd; } /** * Set the client command to use to access the repository for the given * fully qualified classname. * * @param clazzName name of the targeting class. If {@code null} this method * does nothing. * @param cmd the client command to use. If {@code null} the corresponding * entry for the given clazzName get removed. * @return the client command previously set, which might be {@code null}. */ public String setRepoCmd(String clazzName, String cmd) { Lock writeLock = configLock.writeLock(); try { writeLock.lock(); configuration.setRepoCmd(clazzName, cmd); } finally { writeLock.unlock(); } return cmd; } /** * Sets the user page for the history listing * * @param userPage the URL fragment preceeding the username from history */ public void setUserPage(String userPage) { setConfigurationValue("userPage", userPage); } /** * Returns the user page suffix for the history listing * * @return the URL string fragment following the username */ public String getUserPageSuffix() { return (String) getConfigurationValue("userPageSuffix"); } /** * Sets the user page suffix for the history listing * * @param userPageSuffix the URL fragment following the username from * history */ public void setUserPageSuffix(String userPageSuffix) { setConfigurationValue("userPageSuffix", userPageSuffix); } /** * Returns the bug page for the history listing * * @return the URL string fragment preceeding the bug ID */ public String getBugPage() { return (String) getConfigurationValue("bugPage"); } /** * Sets the bug page for the history listing * * @param bugPage the URL fragment preceeding the bug ID */ public void setBugPage(String bugPage) { setConfigurationValue("bugPage", bugPage); } /** * Returns the bug regex for the history listing * * @return the regex that is looked for in history comments */ public String getBugPattern() { return (String) getConfigurationValue("bugPattern"); } /** * Sets the bug regex for the history listing * * @param bugPattern the regex to search history comments * @throws IOException I/O */ public void setBugPattern(String bugPattern) throws IOException { setConfigurationValueException("bugPattern", bugPattern); } /** * Returns the review(ARC) page for the history listing * * @return the URL string fragment preceeding the review page ID */ public String getReviewPage() { return (String) getConfigurationValue("reviewPage"); } /** * Sets the review(ARC) page for the history listing * * @param reviewPage the URL fragment preceeding the review page ID */ public void setReviewPage(String reviewPage) { setConfigurationValue("reviewPage", reviewPage); } /** * Returns the review(ARC) regex for the history listing * * @return the regex that is looked for in history comments */ public String getReviewPattern() { return (String) getConfigurationValue("reviewPattern"); } /** * Sets the review(ARC) regex for the history listing * * @param reviewPattern the regex to search history comments * @throws IOException I/O */ public void setReviewPattern(String reviewPattern) throws IOException { setConfigurationValueException("reviewPattern", reviewPattern); } public String getWebappLAF() { return (String) getConfigurationValue("webappLAF"); } public void setWebappLAF(String laf) { setConfigurationValue("webappLAF", laf); } /** * Gets a value indicating if the web app should run ctags as necessary. * @return the value of {@link Configuration#isWebappCtags()} */ public boolean isWebappCtags() { return (boolean) getConfigurationValue("webappCtags"); } public Configuration.RemoteSCM getRemoteScmSupported() { return (Configuration.RemoteSCM) getConfigurationValue("remoteScmSupported"); } public void setRemoteScmSupported(Configuration.RemoteSCM supported) { setConfigurationValue("remoteScmSupported", supported); } public boolean isOptimizeDatabase() { return (boolean) getConfigurationValue("optimizeDatabase"); } public void setOptimizeDatabase(boolean optimizeDatabase) { setConfigurationValue("optimizeDatabase", optimizeDatabase); } public LuceneLockName getLuceneLocking() { return (LuceneLockName) getConfigurationValue("luceneLocking"); } public boolean isIndexVersionedFilesOnly() { return (boolean) getConfigurationValue("indexVersionedFilesOnly"); } public void setIndexVersionedFilesOnly(boolean indexVersionedFilesOnly) { setConfigurationValue("indexVersionedFilesOnly", indexVersionedFilesOnly); } /** * Gets the value of {@link Configuration#getIndexingParallelism()} -- or * if zero, then as a default gets the number of available processors. * @return a natural number >= 1 */ public int getIndexingParallelism() { int parallelism = (int) getConfigurationValue("indexingParallelism"); return parallelism < 1 ? Runtime.getRuntime().availableProcessors() : parallelism; } /** * Gets the value of {@link Configuration#getHistoryParallelism()} -- or * if zero, then as a default gets the number of available processors. * @return a natural number >= 1 */ public int getHistoryParallelism() { int parallelism = (int) getConfigurationValue("historyParallelism"); return parallelism < 1 ? Runtime.getRuntime().availableProcessors() : parallelism; } /** * Gets the value of {@link Configuration#getHistoryRenamedParallelism()} -- or * if zero, then as a default gets the number of available processors. * @return a natural number >= 1 */ public int getHistoryRenamedParallelism() { int parallelism = (int) getConfigurationValue("historyRenamedParallelism"); return parallelism < 1 ? Runtime.getRuntime().availableProcessors() : parallelism; } public boolean isTagsEnabled() { return (boolean) getConfigurationValue("tagsEnabled"); } public void setTagsEnabled(boolean tagsEnabled) { setConfigurationValue("tagsEnabled", tagsEnabled); } public boolean isScopesEnabled() { return (boolean) getConfigurationValue("scopesEnabled"); } public void setScopesEnabled(boolean scopesEnabled) { setConfigurationValue("scopesEnabled", scopesEnabled); } public boolean isProjectsEnabled() { return (boolean) getConfigurationValue("projectsEnabled"); } public void setProjectsEnabled(boolean projectsEnabled) { setConfigurationValue("projectsEnabled", projectsEnabled); } public boolean isFoldingEnabled() { return (boolean) getConfigurationValue("foldingEnabled"); } public void setFoldingEnabled(boolean foldingEnabled) { setConfigurationValue("foldingEnabled", foldingEnabled); } public Date getDateForLastIndexRun() { return indexTime.getDateForLastIndexRun(); } public String getCTagsExtraOptionsFile() { return (String) getConfigurationValue("CTagsExtraOptionsFile"); } public void setCTagsExtraOptionsFile(String filename) { setConfigurationValue("CTagsExtraOptionsFile", filename); } @SuppressWarnings("unchecked") public Set<String> getAllowedSymlinks() { return (Set<String>) getConfigurationValue("allowedSymlinks"); } public void setAllowedSymlinks(Set<String> allowedSymlinks) { setConfigurationValue("allowedSymlinks", allowedSymlinks); } /** * Return whether e-mail addresses should be obfuscated in the xref. * @return if we obfuscate emails */ public boolean isObfuscatingEMailAddresses() { return (boolean) getConfigurationValue("obfuscatingEMailAddresses"); } /** * Set whether e-mail addresses should be obfuscated in the xref. * @param obfuscate should we obfuscate emails? */ public void setObfuscatingEMailAddresses(boolean obfuscate) { setConfigurationValue("obfuscatingEMailAddresses", obfuscate); } /** * Should status.jsp print internal settings, like paths and database URLs? * * @return {@code true} if status.jsp should show the configuration, * {@code false} otherwise */ public boolean isChattyStatusPage() { return (boolean) getConfigurationValue("chattyStatusPage"); } /** * Set whether status.jsp should print internal settings. * * @param chatty {@code true} if internal settings should be printed, * {@code false} otherwise */ public void setChattyStatusPage(boolean chatty) { setConfigurationValue("chattyStatusPage", chatty); } public void setFetchHistoryWhenNotInCache(boolean nofetch) { setConfigurationValue("fetchHistoryWhenNotInCache", nofetch); } public boolean isFetchHistoryWhenNotInCache() { return (boolean) getConfigurationValue("fetchHistoryWhenNotInCache"); } public boolean isHistoryCache() { return (boolean) getConfigurationValue("historyCache"); } public void setHandleHistoryOfRenamedFiles(boolean enable) { setConfigurationValue("handleHistoryOfRenamedFiles", enable); } public boolean isHandleHistoryOfRenamedFiles() { return (boolean) getConfigurationValue("handleHistoryOfRenamedFiles"); } public void setNavigateWindowEnabled(boolean enable) { setConfigurationValue("navigateWindowEnabled", enable); } public boolean isNavigateWindowEnabled() { return (boolean) getConfigurationValue("navigateWindowEnabled"); } public void setRevisionMessageCollapseThreshold(int threshold) { setConfigurationValue("revisionMessageCollapseThreshold", threshold); } public int getRevisionMessageCollapseThreshold() { return (int) getConfigurationValue("revisionMessageCollapseThreshold"); } public void setMaxSearchThreadCount(int count) { setConfigurationValue("maxSearchThreadCount", count); } public int getMaxSearchThreadCount() { return (int) getConfigurationValue("maxSearchThreadCount"); } public int getCurrentIndexedCollapseThreshold() { return (int) getConfigurationValue("currentIndexedCollapseThreshold"); } public void setCurrentIndexedCollapseThreshold(int currentIndexedCollapseThreshold) { setConfigurationValue("currentIndexedCollapseThreshold", currentIndexedCollapseThreshold); } public int getGroupsCollapseThreshold() { return (int) getConfigurationValue("groupsCollapseThreshold"); } // The URI is not necessary to be present in the configuration // (so that when -U option of the indexer is omitted, the config will not // be sent to the webapp) so store it only in the RuntimeEnvironment. public void setConfigURI(String host) { configURI = host; } public String getConfigURI() { return configURI; } public boolean isHistoryEnabled() { return (boolean) getConfigurationValue("historyEnabled"); } public void setHistoryEnabled(boolean flag) { setConfigurationValue("historyEnabled", flag); } public boolean getDisplayRepositories() { return (boolean) getConfigurationValue("displayRepositories"); } public void setDisplayRepositories(boolean flag) { setConfigurationValue("displayRepositories", flag); } public boolean getListDirsFirst() { return (boolean) getConfigurationValue("listDirsFirst"); } public void setListDirsFirst(boolean flag) { setConfigurationValue("listDirsFirst", flag); } public void setTabSize(int size) { setConfigurationValue("tabSize", size); } public int getTabSize() { return (int) getConfigurationValue("tabSize"); } /** * Gets the total number of context lines per file to show * @return a value greater than zero */ public short getContextLimit() { return (short) getConfigurationValue("contextLimit"); } /** * Gets the number of context lines to show before or after any match: * @return a value greater than or equal to zero */ public short getContextSurround() { return (short) getConfigurationValue("contextSurround"); } /** * Read an configuration file and set it as the current configuration. * * @param file the file to read * @throws IOException if an error occurs */ public void readConfiguration(File file) throws IOException { setConfiguration(Configuration.read(file)); } /** * Read configuration from a file and put it into effect. * @param file the file to read * @param interactive true if run in interactive mode * @throws IOException I/O */ public void readConfiguration(File file, boolean interactive) throws IOException { setConfiguration(Configuration.read(file), null, interactive); } /** * Write the current configuration to a file * * @param file the file to write the configuration into * @throws IOException if an error occurs */ public void writeConfiguration(File file) throws IOException { Lock readLock = configLock.readLock(); try { readLock.lock(); configuration.write(file); } finally { readLock.unlock(); } } public String getConfigurationXML() { String configXML; try { configLock.readLock().lock(); configXML = configuration.getXMLRepresentationAsString(); } finally { configLock.readLock().unlock(); } return configXML; } /** * Write the current configuration to a socket * * @param host the host address to receive the configuration * @throws IOException if an error occurs */ public void writeConfiguration(String host) throws IOException { String configXML; try { configLock.readLock().lock(); configXML = configuration.getXMLRepresentationAsString(); } finally { configLock.readLock().unlock(); } Response r = ClientBuilder.newClient().target(host).path("api").path("v1").path("configuration") .queryParam("reindex", true).request().put(Entity.xml(configXML)); if (r.getStatusInfo().getFamily() != Response.Status.Family.SUCCESSFUL) { throw new IOException(r.toString()); } } /** * Send message to webapp to refresh SearcherManagers for given projects. * This is used for partial reindex. * * @param subFiles list of directories to refresh corresponding SearcherManagers * @param host the host address to receive the configuration */ public void signalTorefreshSearcherManagers(List<String> subFiles, String host) { // subFile entries start with path separator so get basename // to convert them to project names. subFiles.stream().map(proj -> new File(proj).getName()).forEach(project -> { Response r = ClientBuilder.newClient().target(host).path("api").path("v1").path("system") .path("refresh").request().put(Entity.text(project)); if (r.getStatusInfo().getFamily() != Response.Status.Family.SUCCESSFUL) { LOGGER.log(Level.WARNING, "Could not refresh search manager for {0}", project); } }); } /** * Generate a TreeMap of projects with corresponding repository information. * <p> * Project with some repository information is considered as a repository * otherwise it is just a simple project. */ private void generateProjectRepositoriesMap() throws IOException { repository_map.clear(); for (RepositoryInfo r : getRepositories()) { Project proj; String repoPath; try { repoPath = PathUtils.getPathRelativeToSourceRoot(new File(r.getDirectoryName()), 0); } catch (ForbiddenSymlinkException e) { LOGGER.log(Level.FINER, e.getMessage()); continue; } if ((proj = Project.getProject(repoPath)) != null) { List<RepositoryInfo> values = repository_map.computeIfAbsent(proj, k -> new ArrayList<>()); // the map is held under the lock because the next call to // values.add(r) which should not be called from multiple threads at the same time values.add(r); } } } /** * Classifies projects and puts them in their groups. * <p> * If any of the groups contain some projects or repositories already, * these get discarded. * * @param groups set of groups to be filled with matching projects * @param projects projects to classify */ public void populateGroups(Set<Group> groups, Set<Project> projects) { if (projects == null || groups == null) { return; } // clear the groups first if they had something in them for (Group group : groups) { group.getRepositories().clear(); group.getProjects().clear(); } // now fill the groups with appropriate projects for (Project project : projects) { // clear the project's groups project.getGroups().clear(); // filter projects only to groups which match project's name Set<Group> copy = Group.matching(project, groups); // add project to the groups for (Group group : copy) { if (repository_map.get(project) == null) { group.addProject(project); } else { group.addRepository(project); } project.addGroup(group); } } } /** * Sets the configuration and performs necessary actions. * * Mainly it classifies the projects in their groups and generates project - * repositories map * * @param configuration what configuration to use */ public void setConfiguration(Configuration configuration) { setConfiguration(configuration, null, false); } /** * Sets the configuration and performs necessary actions. * @param configuration new configuration * @param interactive true if in interactive mode */ public void setConfiguration(Configuration configuration, boolean interactive) { setConfiguration(configuration, null, interactive); } /** * Sets the configuration and performs necessary actions. * * @param configuration new configuration * @param subFileList list of repositories * @param interactive true if in interactive mode */ public synchronized void setConfiguration(Configuration configuration, List<String> subFileList, boolean interactive) { try { configLock.writeLock().lock(); this.configuration = configuration; } finally { configLock.writeLock().unlock(); } // HistoryGuru constructor needs environment properties so no locking is done here. HistoryGuru histGuru = HistoryGuru.getInstance(); // Set the working repositories in HistoryGuru. if (subFileList != null) { histGuru.invalidateRepositories(getRepositories(), subFileList, interactive); } else { histGuru.invalidateRepositories(getRepositories(), interactive); } // The invalidation of repositories above might have excluded some // repositories in HistoryGuru so the configuration needs to reflect that. setRepositories(new ArrayList<>(histGuru.getRepositories())); // generate repository map is dependent on getRepositories() try { generateProjectRepositoriesMap(); } catch (IOException ex) { LOGGER.log(Level.SEVERE, "Cannot generate project - repository map", ex); } // populate groups is dependent on repositories map populateGroups(getGroups(), new TreeSet<>(getProjects().values())); includeFiles.reloadIncludeFiles(); } public IncludeFiles getIncludeFiles() { return includeFiles; } public String getStatisticsFilePath() { return (String) getConfigurationValue("statisticsFilePath"); } public void setStatisticsFilePath(String path) { setConfigurationValue("statisticsFilePath", path); } /** * Return the authorization framework used in this environment. * * @return the framework */ public synchronized AuthorizationFramework getAuthorizationFramework() { if (authFramework == null) { authFramework = new AuthorizationFramework(getPluginDirectory(), getPluginStack()); } return authFramework; } /** * Set the authorization framework for this environment. Unload all * previously load plugins. * * @param fw the new framework */ public synchronized void setAuthorizationFramework(AuthorizationFramework fw) { if (this.authFramework != null) { this.authFramework.removeAll(); } this.authFramework = fw; } /** * Re-apply the configuration. * @param reindex is the message result of reindex * @param interactive true if in interactive mode */ public void applyConfig(boolean reindex, boolean interactive) { applyConfig(configuration, reindex, interactive); } /** * Set configuration from a message. The message could have come from the * Indexer (in which case some extra work is needed) or is it just a request * to set new configuration in place. * * @param configuration XML configuration * @param reindex is the message result of reindex * @param interactive true if in interactive mode * @see #applyConfig(org.opengrok.indexer.configuration.Configuration, * boolean, boolean) applyConfig(config, reindex, interactive) */ public void applyConfig(String configuration, boolean reindex, boolean interactive) { Configuration config; try { config = makeXMLStringAsConfiguration(configuration); } catch (IOException ex) { LOGGER.log(Level.WARNING, "Configuration decoding failed", ex); return; } applyConfig(config, reindex, interactive); } /** * Set configuration from the incoming parameter. The configuration could * have come from the Indexer (in which case some extra work is needed) or * is it just a request to set new configuration in place. * * @param config the incoming configuration * @param reindex is the message result of reindex * @param interactive true if in interactive mode */ public void applyConfig(Configuration config, boolean reindex, boolean interactive) { setConfiguration(config, interactive); LOGGER.log(Level.INFO, "Configuration updated"); if (reindex) { // We are assuming that each update of configuration means reindex. If dedicated thread is introduced // in the future solely for the purpose of getting the event of reindex, the 2 calls below should // be moved there. refreshSearcherManagerMap(); maybeRefreshIndexSearchers(); // Force timestamp to update itself upon new config arrival. refreshDateForLastIndexRun(); } // start/stop the watchdog if necessary if (isAuthorizationWatchdog() && getPluginDirectory() != null) { watchDog.start(new File(getPluginDirectory())); } else { watchDog.stop(); } // set the new plugin directory and reload the authorization framework getAuthorizationFramework().setPluginDirectory(getPluginDirectory()); getAuthorizationFramework().setStack(getPluginStack()); getAuthorizationFramework().reload(); messagesContainer.setMessageLimit(getMessageLimit()); } public void setIndexTimestamp() throws IOException { indexTime.stamp(); } public void refreshDateForLastIndexRun() { indexTime.refreshDateForLastIndexRun(); } private void maybeRefreshSearcherManager(SearcherManager sm) { try { sm.maybeRefresh(); } catch (AlreadyClosedException ex) { // This is a case of removed project. See refreshSearcherManagerMap() for details. } catch (IOException ex) { LOGGER.log(Level.SEVERE, "maybeRefresh failed", ex); } } public void maybeRefreshIndexSearchers(Iterable<String> projects) { for (String proj : projects) { if (searcherManagerMap.containsKey(proj)) { maybeRefreshSearcherManager(searcherManagerMap.get(proj)); } } } public void maybeRefreshIndexSearchers() { for (Map.Entry<String, SearcherManager> entry : searcherManagerMap.entrySet()) { maybeRefreshSearcherManager(entry.getValue()); } } /** * Get IndexSearcher for given project. * Each IndexSearcher is born from a SearcherManager object. There is one SearcherManager for every project. * This schema makes it possible to reuse IndexSearcher/IndexReader objects so the heavy lifting * (esp. system calls) performed in FSDirectory and DirectoryReader happens only once for a project. * The caller has to make sure that the IndexSearcher is returned back * to the SearcherManager. This is done with returnIndexSearcher(). * The return of the IndexSearcher should happen only after the search result data are read fully. * * @param projectName project * @return SearcherManager for given project * @throws IOException I/O exception */ public SuperIndexSearcher getIndexSearcher(String projectName) throws IOException { SearcherManager mgr = searcherManagerMap.get(projectName); SuperIndexSearcher searcher; if (mgr == null) { File indexDir = new File(getDataRootPath(), IndexDatabase.INDEX_DIR); Directory dir = FSDirectory.open(new File(indexDir, projectName).toPath()); mgr = new SearcherManager(dir, new ThreadpoolSearcherFactory()); searcherManagerMap.put(projectName, mgr); searcher = (SuperIndexSearcher) mgr.acquire(); searcher.setSearcherManager(mgr); } else { searcher = (SuperIndexSearcher) mgr.acquire(); searcher.setSearcherManager(mgr); } return searcher; } /** * After new configuration is put into place, the set of projects might * change so we go through the SearcherManager objects and close those where * the corresponding project is no longer present. */ public void refreshSearcherManagerMap() { ArrayList<String> toRemove = new ArrayList<>(); for (Map.Entry<String, SearcherManager> entry : searcherManagerMap.entrySet()) { // If a project is gone, close the corresponding SearcherManager // so that it cannot produce new IndexSearcher objects. if (!getProjectNames().contains(entry.getKey())) { try { LOGGER.log(Level.FINE, "closing SearcherManager for project" + entry.getKey()); entry.getValue().close(); } catch (IOException ex) { LOGGER.log(Level.SEVERE, "cannot close SearcherManager for project" + entry.getKey(), ex); } toRemove.add(entry.getKey()); } } for (String proj : toRemove) { searcherManagerMap.remove(proj); } } /** * Return collection of IndexReader objects as MultiReader object * for given list of projects. * The caller is responsible for releasing the IndexSearcher objects * so we add them to the map. * * @param projects list of projects * @param searcherList each SuperIndexSearcher produced will be put into this list * @return MultiReader for the projects */ public MultiReader getMultiReader(SortedSet<String> projects, ArrayList<SuperIndexSearcher> searcherList) { IndexReader[] subreaders = new IndexReader[projects.size()]; int ii = 0; // TODO might need to rewrite to Project instead of String, need changes in projects.jspf too. for (String proj : projects) { try { SuperIndexSearcher searcher = RuntimeEnvironment.getInstance().getIndexSearcher(proj); subreaders[ii++] = searcher.getIndexReader(); searcherList.add(searcher); } catch (IOException | NullPointerException ex) { LOGGER.log(Level.SEVERE, "cannot get IndexReader for project " + proj, ex); return null; } } MultiReader multiReader = null; try { multiReader = new MultiReader(subreaders, true); } catch (IOException ex) { LOGGER.log(Level.SEVERE, "cannot construct MultiReader for set of projects", ex); } return multiReader; } public void startExpirationTimer() { messagesContainer.setMessageLimit((int) getConfigurationValue("messageLimit")); messagesContainer.startExpirationTimer(); } public void stopExpirationTimer() { messagesContainer.stopExpirationTimer(); } /** * Get the default set of messages for the main tag. * * @return set of messages */ public SortedSet<AcceptedMessage> getMessages() { return messagesContainer.getMessages(); } /** * Get the set of messages for the arbitrary tag * * @param tag the message tag * @return set of messages */ public SortedSet<AcceptedMessage> getMessages(final String tag) { return messagesContainer.getMessages(tag); } /** * Add a message to the application. * Also schedules a expiration timer to remove this message after its expiration. * * @param message the message */ public void addMessage(final Message message) { messagesContainer.addMessage(message); } /** * Remove all messages containing at least one of the tags. * * @param tags set of tags */ public void removeAnyMessage(final Set<String> tags) { messagesContainer.removeAnyMessage(tags); } /** * @return all messages regardless their tag */ public Set<AcceptedMessage> getAllMessages() { return messagesContainer.getAllMessages(); } public Path getDtagsEftarPath() { Path path; try { configLock.readLock().lock(); path = configuration.getDtagsEftarPath(); } finally { configLock.readLock().unlock(); } return path; } /** * Get the eftar file, which contains definition tags for path descriptions. * * @return {@code null} if there is no such file, the file otherwise. */ public File getDtagsEftar() { if (dtagsEftar == null) { File tmp = getDtagsEftarPath().toFile(); if (tmp.canRead()) { dtagsEftar = tmp; } } return dtagsEftar; } public SuggesterConfig getSuggesterConfig() { return (SuggesterConfig) getConfigurationValue("suggesterConfig"); } public void setSuggesterConfig(SuggesterConfig config) { setConfigurationValue("suggesterConfig", config); } public int getMessageLimit() { return (int) getConfigurationValue("messageLimit"); } }