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