/* * 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) 2007, 2012, Oracle and/or its affiliates. All rights reserved. * Portions Copyright 2011, 2012 Jens Elkner. */ package org.opensolaris.opengrok.configuration; import java.beans.XMLDecoder; import java.beans.XMLEncoder; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import org.opensolaris.opengrok.history.RepositoryInfo; import org.opensolaris.opengrok.index.Filter; import org.opensolaris.opengrok.index.IgnoredNames; import org.opensolaris.opengrok.util.IOUtils; import org.opensolaris.opengrok.web.Prefix; /** * Placeholder class for all configuration variables. Due to the multithreaded * nature of the web application, each thread will use the same instance of the * configuration object for each page request. Class and methods should have * package scope, but that didn't work with the XMLDecoder/XMLEncoder. */ public final class Configuration { /** The common property key prefix used to access opengrok system properties */ public static final String PROPERTY_KEY_PREFIX = "org.opensolaris.opengrok."; /** The property name used to obtain the ctags command to use. */ public static final String CTAGS_CMD_PROPERTY_KEY = PROPERTY_KEY_PREFIX + "analysis.Ctags"; /** The command to use if no ctags command was given explicitly */ public static final String CTAGS_CMD_FALLBACK = "ctags"; /** Relative path wrt. servlet context to the default CSS to use */ public static final String DEFAULT_STYLE = "static/default"; /** The string property name used to get the date format pattern to use for * directory listings in the web app. It needs to be a pattern acceptable * by {@link java.text.SimpleDateFormat}. * @see #DIRLIST_DATE_DEFAULT */ public static final String DIRLIST_DATE_PROPERTY_KEY = PROPERTY_KEY_PREFIX + "web.dirlist.date"; /** the default/fallback value to use wrt. {@link #DIRLIST_DATE_PROPERTY_KEY} */ public static final String DIRLIST_DATE_DEFAULT = "dd-MMM-yyyy"; /** The boolean property name used to determine, whether the web app should * show {@code Today} instead of the last modified date, if the file has * been changed in the last 24 hours. */ public static final String DIRLIST_TODAY_PROPERTY_KEY = PROPERTY_KEY_PREFIX + "web.dirlist.today"; /** the default/fallback value to use wrt. {@link #DIRLIST_TODAY_PROPERTY_KEY} */ public static final boolean DIRLIST_TODAY_DEFAULT = false; private String ctags; /** * Should the history log be cached? */ private boolean historyCache; /** * The maximum time in milliseconds {@code HistoryCache.get()} can take * before its result is cached. */ private int historyCacheTime; /** * Should the history cache be stored in a database? */ private boolean historyCacheInDB; private List projects; private String sourceRoot; private String dataRoot; private List repositories; private String urlPrefix; private boolean generateHtml; /** * Default project will be used, when no project is selected and no project * is in cookie, so basically only the first time you open the first page, * or when you clear your web cookies */ private Project defaultProject; private int indexWordLimit; private boolean verbose; //if below is set, then we count how many files per project we need to process and print percentage of completion per project private boolean printProgress; private boolean allowLeadingWildcard; private IgnoredNames ignoredNames; private Filter includedNames; private String userPage; private String userPageSuffix; private String bugPage; private String bugPattern; private String reviewPage; private String reviewPattern; private String webappLAF; private boolean remoteScmSupported; private boolean optimizeDatabase; private boolean useLuceneLocking; private boolean compressXref; private boolean indexVersionedFilesOnly; private int hitsPerPage; private int cachePages; private String databaseDriver; private String databaseUrl; private String ctagsOptionsFile; private int scanningDepth; private Set allowedSymlinks; private boolean obfuscatingEMailAddresses; private boolean chattyStatusPage; private final Map cmds; private int tabSize; private boolean dirlistUseToday; private String dirlistDatePattern; private static final Logger logger = Logger.getLogger(Configuration.class.getName()); /** * Get the default tab size (number of space characters per tab character) * to use for each project. If {@code <= 0} tabs are read/write as is. * * @return current tab size set. * @see Project#getTabSize() * @see org.opensolaris.opengrok.analysis.ExpandTabsReader */ public int getTabSize() { return tabSize; } /** * Set the default tab size (number of space characters per tab character) * to use for each project. If {@code <= 0} tabs are read/write as is. * * @param tabSize tabsize to set. * @see Project#setTabSize(int) * @see org.opensolaris.opengrok.analysis.ExpandTabsReader */ public void setTabSize(int tabSize) { this.tabSize = tabSize; } /** * Get the depth of scanning for repositories in the directory tree relative * to source root. * @return the scan depth. */ public int getScanningDepth() { return scanningDepth; } /** * Set the depth of scanning for repositories in the directory tree relative * to source root. * @param scanningDepth the scan depth to set. */ public void setScanningDepth(int scanningDepth) { this.scanningDepth = scanningDepth; } /** * Creates a new instance of Configuration */ public Configuration() { //defaults for an opengrok instance configuration setHistoryCache(true); setHistoryCacheTime(30); setHistoryCacheInDB(false); setProjects(new ArrayList()); setRepositories(new ArrayList()); setUrlPrefix("/source" + Prefix.SEARCH_R + '?'); // TODO generate relative search paths, get rid of -w option to indexer ! // setUrlPrefix(".." + Prefix.SEARCH_R + '?'); setCtags(System.getProperty(CTAGS_CMD_PROPERTY_KEY, CTAGS_CMD_FALLBACK)); //below can cause an outofmemory error, since it is defaulting to NO LIMIT setIndexWordLimit(Integer.MAX_VALUE); setVerbose(false); setPrintProgress(false); setGenerateHtml(true); setQuickContextScan(true); setIgnoredNames(new IgnoredNames()); setIncludedNames(new Filter()); setUserPage("http://www.opensolaris.org/viewProfile.jspa?username="); setBugPage("http://bugs.opensolaris.org/bugdatabase/view_bug.do?bug_id="); setBugPattern("\\b([12456789][0-9]{6})\\b"); setReviewPage("http://arc.opensolaris.org/caselog/PSARC/"); setReviewPattern("\\b(\\d{4}/\\d{3})\\b"); // in form e.g. PSARC 2008/305 setWebappLAF(DEFAULT_STYLE); setRemoteScmSupported(false); setOptimizeDatabase(true); setUsingLuceneLocking(false); setCompressXref(true); setIndexVersionedFilesOnly(false); setHitsPerPage(25); setCachePages(5); setScanningDepth(3); // default depth of scanning for repositories setAllowedSymlinks(new HashSet()); //setTabSize(4); cmds = new HashMap(); setDirlistUseToday(DIRLIST_TODAY_DEFAULT); setDirlistDatePattern(DIRLIST_DATE_DEFAULT); } /** * Get the {@link SimpleDateFormat} pattern to use to show the last modified * time in web app directory listings. * @return an always usuable pattern for date formatting. */ public String getDirlistDatePattern() { return dirlistDatePattern; } /** * Set the {@link SimpleDateFormat} pattern to use to show the last modified * time in web app directory listings. * @param pattern the pattern to use. Ignored if {@code null} or invalid. */ public void setDirlistDatePattern(String pattern) { if (pattern == null) { return; } try { @SuppressWarnings("unused") SimpleDateFormat sdf = new SimpleDateFormat(pattern); dirlistDatePattern = pattern; } catch (Exception e) { logger.warning("Invalid web directory list date format pattern (" + pattern + ") ignored: " + e.getMessage()); } } /** * Check, whether the web app should show {@code Today} instead of the last * modified time if the target was changed within the last 24 hours. * @return {@code true} if {@code Today} should be shown. */ public boolean isDirlistUseToday() { return dirlistUseToday; } /** * Set, whether the web app should show {@code Today} instead of the last * modified time if the target was changed within the last 24 hours. * @param enable use {@code true} if {@code Today} should be shown. * @see #DIRLIST_TODAY_PROPERTY_KEY */ public void setDirlistUseToday(boolean enable) { this.dirlistUseToday = enable; } /** * Get the client command to use to access the repository for the given * fully quallified classname. * @param clazzName name of the targeting class * @return {@code null} if not yet set, the client command otherwise. */ public String getRepoCmd(String clazzName) { return cmds.get(clazzName); } /** * Set the client command to use to access the repository for the given * fully quallified 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) { if (clazzName == null) { return null; } if (cmd == null || cmd.length() == 0) { return cmds.remove(clazzName); } return cmds.put(clazzName, cmd); } /** * Get a map of repository class names with the corresponding CLI command to * access the repository it is able to handle. * Basically exists to satisfy bean/de|encoder stuff, only. * @return a unmodifyable map. */ public Map getCmds() { return Collections.unmodifiableMap(cmds); } /** * Set the map of repository class names with the corresponding CLI command * to access the repository it is able to handle. * Basically exists to satisfy bean/de|encoder stuff, only. * @param cmds the map to copy. */ public void setCmds(Map cmds) { this.cmds.clear(); this.cmds.putAll(cmds); } /** * Get the name of the ctags program in use * @return the name of the ctags program in use */ public String getCtags() { return ctags; } /** * Specify the CTags program to use * @param ctags the ctags program to use */ public void setCtags(String ctags) { this.ctags = ctags; } /** * Get the number of search result pages, which should be cached in * a search result. * @return the number of result pages to cache. * @see #getHitsPerPage() */ public int getCachePages() { return cachePages; } /** * Set the number of search result pages, which should be cached in * a search result. * @param cachePages the number of result pages to cache. * @see #getHitsPerPage() */ public void setCachePages(int cachePages) { this.cachePages = cachePages; } /** * Get the number of hits to display on one search result page. * @return number of hits per page. */ public int getHitsPerPage() { return hitsPerPage; } /** * Set the number of hits to display on one search result page. * @param hitsPerPage number of hits per page to set. */ public void setHitsPerPage(int hitsPerPage) { this.hitsPerPage = hitsPerPage; } /** * Should the history log be cached? * * @return {@code true} if a {@code HistoryCache} implementation should be * used, {@code false} otherwise */ public boolean isHistoryCache() { return historyCache; } /** * Set whether history should be cached. * * @param historyCache if {@code true} enable history cache */ public void setHistoryCache(boolean historyCache) { this.historyCache = historyCache; } /** * How long can a history request take before it's cached? If the time is * exceeded, the result is cached. This setting only affects * {@code FileHistoryCache}. * * @return the maximum time in milliseconds a history request can take * before it's cached */ public int getHistoryCacheTime() { return historyCacheTime; } /** * Set the maximum time a history request can take before it's cached. This * setting is only respected if {@code FileHistoryCache} is used. * * @param historyCacheTime maximum time in milliseconds */ public void setHistoryCacheTime(int historyCacheTime) { this.historyCacheTime = historyCacheTime; } /** * Should the history cache be stored in a database? If yes, * {@code JDBCHistoryCache} will be used to cache the history; otherwise, * {@code FileHistoryCache} is used. * * @return whether the history cache should be stored in a database */ public boolean isHistoryCacheInDB() { return historyCacheInDB; } /** * Set whether the history cache should be stored in a database, and * {@code JDBCHistoryCache} should be used instead of {@code * FileHistoryCache}. * * @param historyCacheInDB whether the history cached should be stored in a * database */ public void setHistoryCacheInDB(boolean historyCacheInDB) { this.historyCacheInDB = historyCacheInDB; } /** * Get all of the projects * @return a list containing all of the projects (may be null) */ public List getProjects() { return projects; } /** * Set the list of the projects * @param projects the list of projects to use */ public void setProjects(List projects) { this.projects = projects; } /** * Do we have projects? * @return true if we have projects */ public boolean hasProjects() { return projects != null && !projects.isEmpty(); } private static String getCanonicalPath(String s) { try { File file = new File(s); if (!file.exists()) { return s; } return file.getCanonicalPath(); } catch (IOException ex) { logger.warning("Failed to get canonical path for '" + s + "': " + ex.getMessage()); logger.log(Level.FINE, "getCanonicalPath", ex); return s; } } /** * Specify the source root * @param sourceRoot the location of the sources */ public void setSourceRoot(String sourceRoot) { String p = getCanonicalPath(sourceRoot); if (p != null) { this.sourceRoot = p; } } /** * Get the path to where the sources are located * @return path to where the sources are located */ public String getSourceRoot() { return 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() { return sourceRoot != null ? new File(sourceRoot) : null; } /** * 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 * @param stripCount Number of characters past source root to strip * @throws IOException If an IO error occurs * @throws FileNotFoundException If the file is not relative to source root * @return Path relative to source root */ public String getPathRelativeToSourceRoot(File file, int stripCount) throws IOException { String canonicalPath = file.getCanonicalPath(); if (canonicalPath.startsWith(sourceRoot)) { return canonicalPath.substring(sourceRoot.length() + stripCount); } for (String allowedSymlink : getAllowedSymlinks()) { String allowedTarget = new File(allowedSymlink).getCanonicalPath(); if (canonicalPath.startsWith(allowedTarget)) { return canonicalPath.substring(allowedTarget.length() + stripCount); } } throw new FileNotFoundException("Failed to resolve '" + canonicalPath + "' relative to source root '" + sourceRoot + "'"); } /** * Set the path to where the index database is stored * @param dataRoot the index database */ public void setDataRoot(String dataRoot) { String p = getCanonicalPath(dataRoot); if (p != null) { this.dataRoot = p; } } /** * Get the path to the where the index database is stored * @return the path to the index database */ public String getDataRoot() { return dataRoot; } /** * Get a file representing the index database * @return the index database */ public File getDataRootFile() { return dataRoot != null ? new File(dataRoot) : null; } /** * Get the map of known repositories. * @return a possible empty list including {@code null}. */ public List getRepositories() { return repositories; } /** * Set the map of known repositories. * @param repositories the repositories to set. */ public void setRepositories(List repositories) { this.repositories = repositories; } /** * Get the the URL prefix to be used by the {@link * org.opensolaris.opengrok.analysis.executables.JavaClassAnalyzer} as well * as lexers (see {@link org.opensolaris.opengrok.analysis.JFlexXref}) when * they create output with html links. * * @return the URI encoded prefix to use. */ public String getUrlPrefix() { return urlPrefix; } /** * Set the URL prefix to be used by the {@link * org.opensolaris.opengrok.analysis.executables.JavaClassAnalyzer} as well * as lexers (see {@link org.opensolaris.opengrok.analysis.JFlexXref}) when * they create output with html links. * * @param urlPrefix URI encoded prefix to set. */ public void setUrlPrefix(String urlPrefix) { this.urlPrefix = urlPrefix; } /** * Set whether to generate hyper text cross reference (xref) files * offline (i.e. when indexing). If set to {@code false}, xref files are * created on the fly on demand and gets disposed when served to the client * - so saves disk space, but could be sightly slow and more resource * demanding for the servlet container. * @param generateHtml {@code true} to generate and cache xref files immediately. */ public void setGenerateHtml(boolean generateHtml) { this.generateHtml = generateHtml; } /** * Check, whether to generate and cache xref files immediately. * @return {@code true} if immediate generation/caching is preferred. */ public boolean isGenerateHtml() { return generateHtml; } /** * Set the project that is specified to be the default project to use. The * default project is the project you will search (from the web application) * if the page request didn't contain the cookie. * @param defaultProject The default project to use */ public void setDefaultProject(Project defaultProject) { this.defaultProject = defaultProject; } /** * Get the project that is specified to be the default project to use. The * default project is the project you will search (from the web application) * if the page request didn't contain the cookie. * @return the default project (may be null if not specified) */ public Project getDefaultProject() { return defaultProject; } /** * Chandan wrote the following answer on the opengrok-discuss list: * "Traditionally search engines (specially spiders) think that large files * are junk. Large files tend to be multimedia files etc., which text * search spiders do not want to chew. So they ignore the contents of * the file after a cutoff length. Lucene does this by number of words, * which is by default is 10,000." * By default OpenGrok will increase this limit to 60000, but it may be * overridden in the configuration file * @return The maximum words to index */ public int getIndexWordLimit() { return indexWordLimit; } /** * Set the number of words in a file Lucene will index. * See getIndexWordLimit for a better description. * @param indexWordLimit the number of words to index in a single file */ public void setIndexWordLimit(int indexWordLimit) { this.indexWordLimit = indexWordLimit; } /** * Is the verbosity flag turned on? * @return true if we can print extra information */ public boolean isVerbose() { return verbose; } /** * Set the verbosity flag (to add extra debug information in output) * @param verbose new value */ public void setVerbose(boolean verbose) { this.verbose = verbose; } /** * Is the progress print flag turned on? * @return {@code true} if we can print per project progress % */ public boolean isPrintProgress() { return printProgress; } /** * Set the printing of progress % flag (user convenience). * @param enable {@code true} to enable progress printing. */ public void setPrintProgress(boolean enable) { this.printProgress = enable; } /** * 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 performace (disabled by default). * @param allowLeadingWildcard set to {@code true} to activate */ public void setAllowLeadingWildcard(boolean allowLeadingWildcard) { this.allowLeadingWildcard = allowLeadingWildcard; } /** * Is leading wildcards allowed? * @return {@code true} if a search may start with a wildcard */ public boolean isAllowLeadingWildcard() { return allowLeadingWildcard; } private boolean quickContextScan; /** * Check whether a quick context scan should be done. * @return {@code true} if quick scan should be made. * @see #setQuickContextScan(boolean) */ public boolean isQuickContextScan() { return quickContextScan; } /** * If set to {@code true}, at most 1 MiB of a file gets read into a buffer * at once and than tokenized. Otherwise the whole file gets processed as * usual, i.e. the file content gets read on demand when tokenizing and the * whole file gets processed (no matter of its size). * @param quickContextScan if {@code true} enable quick scanning */ public void setQuickContextScan(boolean quickContextScan) { this.quickContextScan = quickContextScan; } /** * Set the list of names/patterns to determine file, which should be * ignored/excluded from indexing. * Takes precedence over {@link #getIncludedNames()}. * @param ignoredNames stuff to ignore. If {@code null} defaults will be * used instead. */ public void setIgnoredNames(IgnoredNames ignoredNames) { this.ignoredNames = ignoredNames == null ? new IgnoredNames() : ignoredNames; } /** * Get the list of names/patterns to determine file, which should be * ignored/excluded from indexing. * Takes precedence over {@link #getIncludedNames()}. * @return stuff to ignore. */ public IgnoredNames getIgnoredNames() { return ignoredNames; } /** * Set the list of names/patterns to determine, which are allowed to * be indexed. * @param includedNames stuff to index. {@code null} means everything. * @see #setIgnoredNames(IgnoredNames) */ public void setIncludedNames(Filter includedNames) { this.includedNames = includedNames; } /** * Get the list of names/patterns to determine, which are allowed to * be indexed. * @return stuff to index. {@code null} means everything. * @see #getIgnoredNames() */ public Filter getIncludedNames() { return includedNames; } /** * Set the user page for the history listing * @param userPage the URI encoded URL fragment preceeding the username from * history */ public void setUserPage(String userPage) { this.userPage = userPage; } /** * Get the user page for the history listing * @return the URI encoded URL string fragment preceeding the username */ public String getUserPage() { return userPage; } /** * Set the user page suffix for the history listing * @param userPageSuffix the URI encoded URL fragment following the username * from history */ public void setUserPageSuffix(String userPageSuffix) { this.userPageSuffix = userPageSuffix; } /** * Get the user page suffix for the history listing * @return the URI encoded URL string fragment following the username */ public String getUserPageSuffix() { return userPageSuffix; } /** * Set the bug page for the history listing * @param bugPage the URI encoded URL fragment preceeding the bug ID */ public void setBugPage(String bugPage) { this.bugPage = bugPage; } /** * Get the bug page for the history listing * @return the URI encoded URL string fragment preceeding the bug ID */ public String getBugPage() { return bugPage; } /** * Set the bug regex for the history listing * @param bugPattern the regex to search history comments */ public void setBugPattern(String bugPattern) { this.bugPattern = bugPattern; } /** * Get the bug regex for the history listing * @return the regex that is looked for in history comments */ public String getBugPattern() { return bugPattern; } /** * Get the review (ARC) page for the history listing * @return the URI encoded URL string fragment preceeding the review page ID */ public String getReviewPage() { return reviewPage; } /** * Set the review (ARC) page for the history listing * @param reviewPage the URI encoded URL fragment preceeding the review page ID */ public void setReviewPage(String reviewPage) { this.reviewPage = reviewPage; } /** * Get the review (ARC) regex for the history listing * @return the regex that is looked for in history comments */ public String getReviewPattern() { return reviewPattern; } /** * Set the review (ARC) regex for the history listing * @param reviewPattern the regex to search history comments */ public void setReviewPattern(String reviewPattern) { this.reviewPattern = reviewPattern; } /** * Get the name of the web app Look And Feel (LAF) theme to use. It gets * used to construct the stylesheet and image include links in web pages * and names a directory, which contains all this stuff. * @return the name of the web app LAF * @see #DEFAULT_STYLE */ public String getWebappLAF() { return webappLAF; } /** * Set the name of the web app Look And Feel (LAF) theme to use. It gets * used to construct the stylesheet and image include links in web pages * and names a directory, which contains all this stuff. * @param webappLAF the name of the web app LAF. If {@code null} the default * will be used. * @see #DEFAULT_STYLE */ public void setWebappLAF(String webappLAF) { this.webappLAF = webappLAF == null ? DEFAULT_STYLE : webappLAF; } /** * Check whether file histories should be retrieved from the related remote * repositories when needed. * @return {@code true} if fetch on demand is enabled. */ public boolean isRemoteScmSupported() { return remoteScmSupported; } /** * Set whether file histories should be retrieved from the related remote * repositories when needed. * @param remoteScmSupported {@code true} if fetch on demand is enabled. */ public void setRemoteScmSupported(boolean remoteScmSupported) { this.remoteScmSupported = remoteScmSupported; } /** * Check, whether the lucene index database should be optimized (compact * segments etc.) after indexing has been run. * @return {@code true} if optimization is enabled. */ public boolean isOptimizeDatabase() { return optimizeDatabase; } /** * Set, whether the lucene index database should be optimized (compact * segments etc.) after indexing has been run. * @param optimizeDatabase {@code true} if optimization should be done. */ public void setOptimizeDatabase(boolean optimizeDatabase) { this.optimizeDatabase = optimizeDatabase; } /** * Check, wheter lucene should lock index files when indexing to avoid * concurrent access, reading invalid data. * @return {@code true} if locking should be used. */ public boolean isUsingLuceneLocking() { return useLuceneLocking; } /** * Set wheter lucene should lock index files when indexing to avoid * concurrent access, reading invalid data. * @param useLuceneLocking {@code true} if locking should be used. */ public void setUsingLuceneLocking(boolean useLuceneLocking) { this.useLuceneLocking = useLuceneLocking; } /** * Set if we should compress the xref files or not. Applies to newly * generated/updated files, only. Re-indexing is not needed, since the * application automatically detects, whether an xref file is compressed * and processes it properly. * @param compressXref set to {@code true} if generated xref html files * should be compressed */ public void setCompressXref(boolean compressXref) { this.compressXref = compressXref; } /** * Set if we should compress generated xref html files? * @return {@code true} if the xref html files should be compressed. */ public boolean isCompressXref() { return compressXref; } /** * Check, whether unversioned files should not be indexed. * If {@code true}, it takes precedence over {@link #getIncludedNames()}, * i.e. files are not indexed when unversioned, even if they match an * include name. * @return {@code true} if indexing of unversioned files is disabled. */ public boolean isIndexVersionedFilesOnly() { return indexVersionedFilesOnly; } /** * Set, whether unversioned files should not be indexed. * If set to {@code true}, it takes precedence over {@link #getIncludedNames()}, * i.e. files are not indexed when unversioned, even if they match an * include name. * @param indexVersionedFilesOnly {@code true} to disable indexing of * unversioned files */ public void setIndexVersionedFilesOnly(boolean indexVersionedFilesOnly) { this.indexVersionedFilesOnly = indexVersionedFilesOnly; } private transient long lastModified; /** * Get the time, when this instance has been modified. If not explicitly set * or set to {@code 0}, it gets initialized with the time of the first call * to this method after instantiation/de-serialization. * * @return time in milliseconds since 1 Jan 1970 00:00:00 GMT */ public long getLastModified() { if (lastModified == 0) { // since we use it for If-Last-Modified-Since evals, which has no // millis, we need to clear the milliseconds part lastModified = System.currentTimeMillis()/1000 * 1000; } return lastModified; } /** * Set the time, when this configuration has been modified for the last time. * @param lastModified the time in milliseconds since 1 Jan 1970 00:00:00 GMT */ public void setLastModified(long lastModified) { this.lastModified = lastModified/1000 * 1000; } private transient Date indexLastModified; /** * Get the date of the last index update. * * @return the time of the last index update. */ public Date getDateForLastIndexRun() { if (indexLastModified == null) { File timestamp = new File(getDataRoot(), "timestamp"); indexLastModified = new Date(timestamp.lastModified()); } return indexLastModified; } /** * Get the contents of a file or empty string if the file cannot be read. */ @SuppressWarnings("resource") private static String getFileContent(File file) { if (file == null || ! file.canRead()) { return ""; } FileReader fin = null; BufferedReader input = null; try { fin = new FileReader(file); input = new BufferedReader(fin); String line = null; StringBuilder contents = new StringBuilder(); String EOL = System.getProperty("line.separator"); while (( line = input.readLine()) != null) { contents.append(line).append(EOL); } return contents.toString(); } catch (java.io.FileNotFoundException e) { /* * should usually not happen */ } catch (java.io.IOException e) { logger.warning("failed to read include file '" + file.getName() + "': " + e.getMessage()); } finally { if (input != null) { IOUtils.close(input); } else if (fin != null) { IOUtils.close(fin); } } return ""; } /** * The name of the file relative to the DATA_ROOT, which should * be included into the footer of generated web pages. */ public static final String FOOTER_INCLUDE_FILE = "footer_include"; private transient String footer = null; /** * Get the contents of the footer include file. * * @return an empty string if it could not be read successfully, the * contents of the file otherwise. * @see #FOOTER_INCLUDE_FILE */ public String getFooterIncludeFileContent() { if (footer == null) { footer = getFileContent(new File(getDataRoot(), FOOTER_INCLUDE_FILE)); } return footer; } /** * The name of the file relative to the DATA_ROOT, which should * be included into the footer of generated web pages. */ public static final String HEADER_INCLUDE_FILE = "header_include"; private transient String header = null; /** * Get the contents of the footer include file. * * @return an empty string if it could not be read successfully, the * contents of the file otherwise. * @see #HEADER_INCLUDE_FILE */ public String getHeaderIncludeFileContent() { if (header == null) { header = getFileContent(new File(getDataRoot(), HEADER_INCLUDE_FILE)); } return header; } /** * The name of the file relative to the DATA_ROOT, which should * be included into the body of web app's "Home" page. */ public static final String BODY_INCLUDE_FILE = "body_include"; private transient String body = null; /** * Get the contents of the body include file. * @return an empty string if it could not be read successfully, the * contents of the file otherwise. * @see Configuration#BODY_INCLUDE_FILE */ public String getBodyIncludeFileContent() { if (body == null) { body = getFileContent(new File(getDataRoot(), BODY_INCLUDE_FILE)); } return body; } /** * The name of the eftar file relative to the DATA_ROOT, which * contains definition tags. */ public static final String EFTAR_DTAGS_FILE = "index/dtags.eftar"; private transient String dtagsEftar = null; /** * Get the eftar file, which contains definition tags. * * @return {@code null} if there is no such file, the file otherwise. */ public File getDtagsEftar() { if (dtagsEftar == null) { File tmp = new File(getDataRoot() + "/" + EFTAR_DTAGS_FILE); if (tmp.canRead()) { try { dtagsEftar = tmp.getAbsolutePath(); } catch (SecurityException e) { logger.warning(e.getLocalizedMessage()); dtagsEftar = ""; } } else { dtagsEftar = ""; } } return dtagsEftar.isEmpty() ? null : new File(dtagsEftar); } /** * Get the fully quallified class name of the history database driver to use. * @return the database driver class name. */ public String getDatabaseDriver() { return databaseDriver; } /** * Set the fully quallified class name of the history database driver to use. * @param databaseDriver the database driver class name to set. */ public void setDatabaseDriver(String databaseDriver) { this.databaseDriver = databaseDriver; } /** * Get the JDBC connection URL to use to connect to the history database. * @return the JDBC connection URL to use. */ public String getDatabaseUrl() { return databaseUrl; } /** * Set the JDBC connection URL to use to connect to the history database. * @param databaseUrl the JDBC connection URL to set. */ public void setDatabaseUrl(String databaseUrl) { this.databaseUrl = databaseUrl; } /** * Get the optional file to be used to handover additional options to the * ctags command. * @return {@code null} if not set, the options filename otherwise. */ public String getCtagsOptionsFile() { return ctagsOptionsFile; } /** * Set the file to be used to handover additional options to the ctags * command. * @param filename the options filename to use. Might be {@code null}. */ public void setCtagsOptionsFile(String filename) { this.ctagsOptionsFile = filename; } /** * Get the pathnames of symlinks, which are allowed to be processed (e.g. * indexed, tokenized, etc.). * @return a possible empty set. */ public Set getAllowedSymlinks() { return allowedSymlinks; } /** * Set the pathnames of symlinks, which are allowed to be processed (e.g. * indexed, tokenized, etc.). * @param allowedSymlinks symlinks to allow. {@code null} means none. */ public void setAllowedSymlinks(Set allowedSymlinks) { if (allowedSymlinks == null) { this.allowedSymlinks.clear(); } else { this.allowedSymlinks = new HashSet(allowedSymlinks); } } /** * Check whether e-mail addresses should be obfuscated in the xref. * @return {@code true} if obfuscation is needed. */ public boolean isObfuscatingEMailAddresses() { return obfuscatingEMailAddresses; } /** * Set whether e-mail addresses should be obfuscated in the xref. * @param obfuscate {@code true} if obfuscation is needed. */ public void setObfuscatingEMailAddresses(boolean obfuscate) { this.obfuscatingEMailAddresses = obfuscate; } /** * Should status.jsp print internal settings, like paths and database * URLs? * * @return {@code true} if status.jsp should show the configuration. */ public boolean isChattyStatusPage() { return chattyStatusPage; } /** * Set whether status.jsp should print internal settings. * * @param chattyStatusPage {@code true} if internal settings should be printed. */ public void setChattyStatusPage(boolean chattyStatusPage) { this.chattyStatusPage = chattyStatusPage; } /** * Write the current configuration to a file * * @param file the file to write the configuration into * @throws IOException if an error occurs */ public void write(File file) throws IOException { @SuppressWarnings("resource") final FileOutputStream out = new FileOutputStream(file); try { this.encodeObject(out); } finally { IOUtils.close(out); } } /** * Serialize this instance into an XML formatted string. * @return this instance as xml string */ public String getXMLRepresentationAsString() { ByteArrayOutputStream bos = new ByteArrayOutputStream(); this.encodeObject(bos); return bos.toString(); } private void encodeObject(OutputStream out) { XMLEncoder e = new XMLEncoder(new BufferedOutputStream(out)); e.writeObject(this); e.close(); } /** * Read the file containing a serialized {@link Configuration} instance and * return the marshalled instance. * @param file the file to read * @return the un-serialized Configuration instance represented by the file. * @throws IOException if the file cannot be read/decoded. */ @SuppressWarnings("resource") public static Configuration read(File file) throws IOException { final FileInputStream in = new FileInputStream(file); try { return decodeObject(in); } finally { IOUtils.close(in); } } /** * Read the given string representing a in XML serialized {@link Configuration} * and return the marshalled instance. * @param xmlconfig serialized config to read. * @return the un-serialized Configuration instance represented by the string. * @throws IOException if the string cannot be decoded. */ public static Configuration makeXMLStringAsConfiguration(String xmlconfig) throws IOException { final Configuration ret; final ByteArrayInputStream in = new ByteArrayInputStream(xmlconfig.getBytes()); ret = decodeObject(in); return ret; } private static Configuration decodeObject(InputStream in) throws IOException { XMLDecoder d = new XMLDecoder(new BufferedInputStream(in)); final Object ret = d.readObject(); d.close(); if (!(ret instanceof Configuration)) { throw new IOException("Not a valid config file"); } return (Configuration)ret; } }