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

org.sakaiproject.modi.Environment Maven / Gradle / Ivy

The newest version!
package org.sakaiproject.modi;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.helpers.MessageFormatter;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Optional;

import static org.sakaiproject.modi.SysProp.*;

/**
 * Handles the essential filename and directory conventions needed for starting up Sakai. This includes initial
 * resolution of the Sakai home directory ("sakai.home"). The rest of the runtime configuration is handled elsewhere
 * (e.g., {@link org.sakaiproject.util.SakaiProperties} and
 * {@link org.sakaiproject.component.api.ServerConfigurationService}).
 * 

* The Environment makes these fundamental values available for read, rather than requiring other classes to use the * string keys and system properties while running. That is fine for an extensible configuration system, but problematic * for what should be fundamental constants for a given launch. Paths to files and directories are exposed as * {@link Path} objects, rather than bare Strings, giving a better API with less tedious string manipulation. *

* When calling {@link Environment#initialize()}, the system properties are read, and resolved with the conventional * defaults/fallback: *

    *
  1. If sakai.home is set and writable, it is used
  2. *
  3. If sakai.home is set, but does not exist, it is created and used
  4. *
  5. If sakai.home is set, and is not a directory, or is not readable and writable, an exception is thrown
  6. *
  7. If sakai.home is not set, the above three rules are applied for catalina.base
  8. *
  9. If catalina.base is not set, the first three rules are applied for /tmp/sakai
  10. *
* If a suitable directory is found, it is written back to sakai.home, with a trailing separator (slash). *

* Note that ever attempting to run in /tmp/sakai is dubious. It is worth revisiting whether we should fail before * trying it. */ @Slf4j public final class Environment { /** Public construction is prohibited. We only allow entry through the {@link #initialize()} method. */ private Environment() { ensureRequirements(); this.catalinaBase = catalina_base.getRawPath(); this.sakaiHome = sakai_home.getRawPath(); this.componentsRoot = sakai_components_root.getRawPath(); this.sakaiSecurity = sakai_security.getRawPath(); this.configurationFile = sakai_home.getRawPathPlus("sakai-configuration.xml"); this.overridesFolder = sakai_home.getRawPathPlus("override"); } /** The Tomcat/Catalina base directory, catalina.base */ @Getter private final Path catalinaBase; /** The main Sakai Home directory, sakai.home -- usually ${catalina.base}/sakai */ @Getter private final Path sakaiHome; /** The directory for traditional on-disk components, sakai.components.root -- usually ${catalina.base}/components */ @Getter private final Path componentsRoot; /** The optional directory for properties files sensitive data, sakai.security -- usually null/empty */ @Getter private final Path sakaiSecurity; /** The main, optional, user-supplied Spring bean file for startup, ${sakai.home}/sakai-configuration.xml */ @Getter private final Path configurationFile; /** The directory where component-specific Spring overrides can be, ${sakai.home}/override */ @Getter private final Path overridesFolder; /** * Set up the required base operating environment for starting Sakai. *

* Uses or infers the sakai.home value, and then checks/creates the directory. *

* When done, we set the System property again to ensure that it has a trailing slash because many places use it for * bare concatenation. * * @return an initialized environment; see the getters for what is considered core for startup * @throws InitializationException if the directory cannot be created/read/written */ public static Environment initialize() throws InitializationException { return new Environment(); } /** The main workflow method for setup. */ private void ensureRequirements() { ensureCatalinaBase(); ensureSakaiHome(); ensureComponents(); ensureSecurity(); } private void ensureCatalinaBase() { catalina_base.getPath() .filter(Files::isDirectory) .filter(Files::isReadable) .orElseThrow(this::catalinaBaseMissing); } private void ensureSakaiHome() { Path home = computedHomePath(); createHomeIfNeeded(home); checkHomeReadWrite(home); // Other code that does not use the environment expects a trailing slash sakai_home.set(home + "/"); } private void ensureComponents() { computedComponentsRoot() .filter(Files::isDirectory) .filter(Files::isReadable) .ifPresentOrElse(sakai_components_root::set, () -> { throw componentsUnreadable(); }); } private void ensureSecurity() { Path path = sakai_security.getRawPath(); if (path == null) return; if (!(Files.isDirectory(path) && Files.isReadable(path))) throw securityUnreable(); sakai_security.set(path); } /** * The path to use for the home directory, as computed by property fallthrough. */ private Path computedHomePath() { return sakai_home.getPath() .or(this::defaultHomePath) .orElse(Path.of("/tmp/sakai")); } /** The default path if no value is set; the "sakai/" directory within Tomcat's base directory. */ private Optional defaultHomePath() { return catalina_base.getPathPlus("sakai"); } /** The path to use as the component directory; sakai.components.root or the default */ private Optional computedComponentsRoot() { return sakai_components_root.getPath().or(this::defaultComponentsPath); } /** The default components directory, ${catalina.base}/components */ private Optional defaultComponentsPath() { return catalina_base.getPathPlus("components"); } /** Create the computed directory, if needed */ private void createHomeIfNeeded(Path path) throws InitializationException { try { if (!Files.isDirectory(path)) Files.createDirectory(path); log.info("Created Sakai home directory (sakai.home) at: {}", path); } catch (IOException e) { throw couldNotCreateSakaiHome(); } } /** Ensure a readable/writable home directory. */ private void checkHomeReadWrite(Path path) throws InitializationException { if (!(Files.isDirectory(path) && Files.isReadable(path) && Files.isWritable(path))) throw couldNotReadWriteSakaiHome(); } private static final String COULD_NOT_CREATE = "Cannot finish initialization; could not create {}.\n" + " It is set to: '{}'\n" + " Check your Tomcat configuration and environment variables."; private static final String COULD_NOT_FIND = "Cannot finish initialization; {} is missing or unreadable.\n" + " It is set to: '{}'\n" + " Check your Tomcat configuration and environment variables."; private String format(String msg, Object... args) { return MessageFormatter.arrayFormat(msg, Arrays.stream(args).toArray()).getMessage(); } private InitializationException fatalError(String msg, Object... args) { return new InitializationException(format(msg, args)); } private InitializationException couldNotCreateSakaiHome() { return fatalError(COULD_NOT_CREATE, "sakai.home", sakai_home.getRaw()); } private InitializationException couldNotReadWriteSakaiHome() { return fatalError(COULD_NOT_FIND, "sakai.home", sakai_home.getRaw()); } private InitializationException catalinaBaseMissing() { return fatalError(COULD_NOT_FIND, "catalina.base", catalina_base.getRaw()); } private InitializationException componentsUnreadable() { return fatalError(COULD_NOT_FIND, "sakai.components.root", catalina_base.getRaw()); } private InitializationException securityUnreable() { return fatalError(COULD_NOT_FIND, "sakai.security", sakai_security.getRaw()); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy