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