All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.teamscale.commons.TeamscaleInstallationUtils Maven / Gradle / Ivy

package com.teamscale.commons;

import java.io.File;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.conqat.engine.core.configuration.EFeatureToggle;
import org.conqat.lib.commons.assertion.CCSMAssert;
import org.conqat.lib.commons.collections.CollectionUtils;
import org.conqat.lib.commons.collections.UnmodifiableSet;
import org.conqat.lib.commons.string.StringUtils;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Streams;

/**
 * Utilities for retrieving and resolving file paths of a Teamscale installation, e.g. for
 * configuration files. The main aim of this class is to ensure file-locating precedence. So
 * anything that is user-configurable should be checked first, then the working directory and as
 * last resort the installation directory.
 * 

* Terms in this class are defined as follows: *

  • Working directory: The working directory of the Java process. Usually this is the directory * where Teamscale is started from. It is expected that this directory is writable. *
  • Installation directory: The directory Teamscale is installed in. This is only available for * production distributions and not available during development. *
  • Root directories: Directories that contain Teamscale relevant data. These are the working * directory and, if set, the installation directory. *
  • Config directories: Directories that may contain Teamscale relevant configuration. These are * a user configurable directory via {@value #TEAMSCALE_CONFIG_ENV_VAR} environment variable, and a * config subdirectory within any of the root directories. *

    * All methods in this class that aim to locate a file or path check for its existence and return an * empty {@link Optional} if the file/path could not be located. */ public final class TeamscaleInstallationUtils { /** @see #getInstallationDirectory() */ private static final String TEAMSCALE_HOME_ENV_VAR = "TEAMSCALE_HOME"; /** * Environment variable name that indicates the Teamscale configuration directory. Setting this * environment variable is optional. */ private static final String TEAMSCALE_CONFIG_ENV_VAR = "TEAMSCALE_CONFIG"; /** * The name of the config dir that is resolved relative to the process working directory or * Teamscale installation directory. */ private static final String CONFIG_DIR_NAME = "config"; private static final Path WORKING_DIRECTORY = detectWorkingDirectory(); private static final @Nullable Path INSTALLATION_DIRECTORY = detectInstallationDirectory(); private static final ImmutableList ROOT_DIRECTORIES = detectRootDirectories(WORKING_DIRECTORY, INSTALLATION_DIRECTORY); private static final ImmutableList CONFIG_DIRECTORIES = detectConfigDirectories(ROOT_DIRECTORIES); // LOGGER must be initialized last, as the LogManager initialization requires // the loading of config files, which can only be loaded if the statements above // were executed. // Only happens when this class is the first one, which uses LogManager. // Unlikely, but not impossible (e.g., during single Unit test execution). private static final Logger LOGGER = LogManager.getLogger(); private TeamscaleInstallationUtils() { // make class non-instantiable } private static Path detectWorkingDirectory() { return Paths.get(System.getProperty("user.dir")); } private static Path detectInstallationDirectory() { String teamscaleHome = System.getenv(TEAMSCALE_HOME_ENV_VAR); if (!StringUtils.isEmpty(teamscaleHome)) { return Paths.get(teamscaleHome); } return null; } /** * Detects the Teamscale root directories. * * @see TeamscaleInstallationUtils */ @SuppressWarnings("SameParameterValue" /* We pass variables to ensure initialization order. */) private static ImmutableList detectRootDirectories(Path workingDirectory, Path installationDirectory) { List rootDirs = new ArrayList<>(); rootDirs.add(workingDirectory); if (installationDirectory != null) { rootDirs.add(installationDirectory); } return ImmutableList.copyOf(rootDirs); } /** * Detects the Teamscale configuration directories. * * @see TeamscaleInstallationUtils */ @SuppressWarnings("SameParameterValue" /* We pass variables to ensure initialization order. */) private static ImmutableList detectConfigDirectories(ImmutableList rootDirectories) { List configDirs = new ArrayList<>(); String customConfigDir = System.getenv(TEAMSCALE_CONFIG_ENV_VAR); if (!StringUtils.isEmpty(customConfigDir)) { configDirs.add(Paths.get(customConfigDir)); } rootDirectories.forEach(dir -> configDirs.add(dir.resolve(CONFIG_DIR_NAME))); return configDirs.stream().filter(Files::isDirectory).map(Path::normalize).distinct() .collect(ImmutableList.toImmutableList()); } /** * Returns the path to the directory of the Teamscale installation. An empty optional is returned if * the environment variable {@value #TEAMSCALE_HOME_ENV_VAR} is not set. This variable is usually * set by Teamscale startup scripts, but unset in development mode. It is not expected that the user * sets this variable manually. */ public static Optional getInstallationDirectory() { return Optional.ofNullable(INSTALLATION_DIRECTORY); } /** * Attempts to locate a file as absolute path, relative to the working directory or relative to the * Teamscale installation directory. Returns an empty optional if not existing or not readable. * * @see TeamscaleInstallationUtils */ public static Optional locateRootPath(String name) { return Streams.concat(Stream.of(Paths.get(name)), ROOT_DIRECTORIES.stream().map(dir -> dir.resolve(name))) .filter(Files::isReadable).findFirst(); } /** * Similar to {@link #locateRootPath(String)}, but returns the absolute path (= provided name) if * the path cannot be resolved . * * @see TeamscaleInstallationUtils */ public static Path getRootPath(String name) { return locateRootPath(name).orElse(Paths.get(name)); } /** * Returns all existing directories with the given {@code name} in the current working directory, * and within all directories within the "server" folder of Teamscale's own git repository. *

    * Make sure this method is only called, iff {@link EFeatureToggle#ENABLE_DEV_MODE} is enabled, and * the working directory is within the teamscale git repository, i.e., only during actual Teamscale * development. */ public static UnmodifiableSet getRepositoryDirectories(String name) { Set result = new HashSet<>(); Path rootPath = Paths.get(name).toAbsolutePath(); if (Files.isDirectory(rootPath)) { // Always support the configured directory if it exists result.add(rootPath.toAbsolutePath()); } Path serverPath = getTeamscaleRepositoryRootPath(rootPath).resolve("server"); CCSMAssert.isTrue(Files.isDirectory(serverPath), () -> String.format("Expected to find a \"server\" directory within the teamscale repository: %s", serverPath.getParent().toAbsolutePath())); try { Files.walkFileTree(serverPath, new SimpleFileVisitor() { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { if (dir.equals(serverPath) || dir.getParent().equals(serverPath)) { return FileVisitResult.CONTINUE; } if (dir.endsWith(name)) { result.add(dir.toAbsolutePath()); // Already found the directory, no need to look into sibling folders return FileVisitResult.SKIP_SIBLINGS; } // dir is a directory within a module in the "server" folder e.g. // engine/com.teamscale.service/src // No need to further look into children as we are only interested in folders // directly within the module return FileVisitResult.SKIP_SUBTREE; } }); } catch (IOException e) { LOGGER.error("Unable to detect all check-descriptions paths", e); } return CollectionUtils.asUnmodifiable(result); } private static Path getTeamscaleRepositoryRootPath(Path rootPath) { CCSMAssert.isTrue(EFeatureToggle.ENABLE_DEV_MODE.isEnabled(), () -> String.format("Expected feature toggle \"%s\" to be enabled", EFeatureToggle.ENABLE_DEV_MODE)); Path currentPath = rootPath; // Detect the teamscale root directory by looking for the "gradlew.bat" file, // which is only present at the repository root while (currentPath != null && !Files.exists(currentPath.resolve("gradlew.bat"))) { currentPath = currentPath.getParent(); } return Objects.requireNonNull(currentPath, () -> String.format("Could not find \"teamscale\" repository root directory within: %s", rootPath)); } /** * Attempts to locate a file relative to the available configuration directories. Returns the first * existing and readable configuration file, or an empty optional of none is found. * * @see TeamscaleInstallationUtils */ public static Optional locateConfigFile(String filename) { return CONFIG_DIRECTORIES.stream().map(configDir -> configDir.resolve(filename).toFile()).filter(File::canRead) .findFirst(); } /** * Returns the config directories used by Teamscale. * * @see #detectConfigDirectories(ImmutableList) */ public static List getConfigDirectories() { return CONFIG_DIRECTORIES; } /** * Lists all readable config files from the config directories. The returned stream starts with * listing files from the most relevant configuration directory and ends with the least relevant * one. * * @see TeamscaleInstallationUtils */ public static Stream listConfigFiles() { return CONFIG_DIRECTORIES.stream().flatMap(TeamscaleInstallationUtils::listReadableFiles).map(Path::toFile); } private static Stream listReadableFiles(Path directory) { try { return Files.list(directory).filter(Files::isReadable).filter(Files::isRegularFile); } catch (IOException e) { // Swallow as we're just interested in readable files. return Stream.empty(); } } }





  • © 2015 - 2025 Weber Informatics LLC | Privacy Policy