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

sirius.kernel.Sirius Maven / Gradle / Ivy

Go to download

Provides common core classes and the microkernel powering all Sirius applications

There is a newer version: 12.9.1
Show newest version
/*
 * Made with all the love in the world
 * by scireum in Remshalden, Germany
 *
 * Copyright by scireum GmbH
 * http://www.scireum.de - [email protected]
 */

package sirius.kernel;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import org.apache.log4j.Level;
import sirius.kernel.async.Future;
import sirius.kernel.async.Operation;
import sirius.kernel.async.Tasks;
import sirius.kernel.commons.Strings;
import sirius.kernel.commons.Value;
import sirius.kernel.commons.Watch;
import sirius.kernel.di.Injector;
import sirius.kernel.di.std.Part;
import sirius.kernel.di.std.PriorityParts;
import sirius.kernel.health.Exceptions;
import sirius.kernel.health.Log;
import sirius.kernel.nls.NLS;
import sirius.kernel.settings.ExtendedSettings;

import javax.annotation.Nullable;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.time.Duration;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * Loads and initializes the framework.
 * 

* This can be considered the stage2 when booting the framework, as it is responsible to discover and * initialize all components. Call {@link #start(Setup)} to initialize the framework. *

* To make a jar or other classpath-root visible to SIRIUS an empty file called "component.marker" must be placed in * its root directory. */ public class Sirius { /** * Contains the name of the system property which is used to select which scenario to execute. */ public static final String SIRIUS_TEST_SCENARIO_PROPERTY = "SIRIUS_TEST_SCENARIO"; private static final String CONFIG_KEY_CUSTOMIZATIONS = "sirius.customizations"; private static final String SEPARATOR_LINE = "---------------------------------------------------------"; private static Setup setup; private static Config config; private static ExtendedSettings settings; private static Map frameworks = Maps.newHashMap(); private static List customizations = Lists.newArrayList(); private static Classpath classpath; private static volatile boolean started = false; private static volatile boolean initialized = false; private static final long START_TIMESTAMP = System.currentTimeMillis(); protected static final Log LOG = Log.get("sirius"); private static final String DEBUG_LOGGER_NAME = "debug"; /** * This debug logger will be logging all messages when {@link sirius.kernel.Sirius#isDev()} is true. Otherwise, * this logger is set to "OFF". */ public static final Log DEBUG = Log.get(DEBUG_LOGGER_NAME); @PriorityParts(Startable.class) private static List lifecycleStartParticipants; @PriorityParts(Stoppable.class) private static List lifecycleStopParticipants; @PriorityParts(Killable.class) private static List lifecycleKillParticipants; @Part private static Tasks tasks; private Sirius() { } /** * Determines if the framework is running in development or in production mode. * * @return {@code true} is the framework runs in development mode, false otherwise. */ public static boolean isDev() { return setup.getMode() == Setup.Mode.DEV; } /** * Determines if the framework was started as test run (JUNIT or the like). * * @return true if the framework was started as test, false otherwise */ public static boolean isStartedAsTest() { return setup != null && setup.getMode() == Setup.Mode.TEST; } /** * Determines if the framework is running in development or in production mode. * * @return {@code true} is the framework runs in production mode, false otherwise. */ public static boolean isProd() { return !isDev(); } /** * Determines if the framework is up and running. *

* This flag will be set to true once the framework is being setup and will be immediatelly * set to false one the framework starts to shut down. * * @return true once the framework is setup and running and not shutting down yet. * @see Tasks#isRunning() Provides a similar flag with slightly different semantics */ public static boolean isRunning() { return started; } /* * Once the configuration is loaded, this method applies the log level to all log4j and java.util.logging * loggers */ private static void setupLogLevels() { if (Sirius.isDev()) { Log.setLevel(DEBUG_LOGGER_NAME, Level.ALL); } else { Log.setLevel(DEBUG_LOGGER_NAME, Level.OFF); } if (config.hasPath("log")) { LOG.WARN("Found 'log' in the system configuration - use 'logging' to configure loggers!"); } if (config.hasPath("logs")) { LOG.WARN("Found 'logs' in the system configuration - use 'logging' to configure loggers!"); } if (!config.hasPath("logging")) { LOG.INFO("No 'logger' section in the system config - using defaults..."); return; } LOG.INFO("Initializing the log system:"); Config logging = config.getConfig("logging"); for (Map.Entry entry : logging.entrySet()) { LOG.INFO("* Setting %s to: %s", entry.getKey(), logging.getString(entry.getKey())); Log.setLevel(entry.getKey(), Level.toLevel(logging.getString(entry.getKey()))); } } /* * Scans the system config (sirius.frameworks) and determines which frameworks are enabled. This will affect * which classes are loaded into the component model. */ private static void setupFrameworks() { Config frameworkConfig = config.getConfig("sirius.frameworks"); Map frameworkStatus = Maps.newHashMap(); int total = 0; int numEnabled = 0; LOG.DEBUG_INFO("Scanning framework status (sirius.frameworks):"); for (Map.Entry entry : frameworkConfig.entrySet()) { String framework = entry.getKey(); try { boolean enabled = Value.of(entry.getValue().unwrapped()).asBoolean(false); frameworkStatus.put(framework, enabled); total++; numEnabled += enabled ? 1 : 0; LOG.DEBUG_INFO(Strings.apply(" * %s: %b", framework, enabled)); } catch (Exception e) { Exceptions.ignore(e); LOG.WARN("Cannot convert status '%s' of framework '%s' to a boolean! Framework will be disabled.", entry.getValue().render(), framework); frameworkStatus.put(framework, false); } } LOG.INFO("Enabled %d of %d frameworks...", numEnabled, total); // Although customizations are loaded in setupConfiguration, we output the status here, // as this seems more intiutive for the customer (the poor guy reading the logs...) LOG.INFO("Active Customizations: %s", customizations); frameworks = frameworkStatus; } /* * Starts all framework components */ private static void startComponents() { if (started) { stop(); } boolean startFailed = false; for (final Startable lifecycle : lifecycleStartParticipants) { Future future = tasks.defaultExecutor().fork(() -> startLifecycle(lifecycle)); if (!future.await(Duration.ofMinutes(1))) { LOG.WARN("Lifecycle '%s' did not start within one minute....", lifecycle.getClass().getName()); startFailed = true; } } if (startFailed) { outputActiveOperations(); } started = true; } private static void startLifecycle(Startable lifecycle) { LOG.INFO("Starting: %s", lifecycle.getClass().getName()); try { lifecycle.started(); } catch (Exception e) { Exceptions.handle() .error(e) .to(LOG) .withSystemErrorMessage("Startup of: %s failed!", lifecycle.getClass().getName()) .handle(); } } /** * Discovers all components in the class path and initializes the {@link Injector} */ private static void init() { if (initialized) { return; } initialized = true; setupLocalConfig(); setupClasspath(); setupApplicationAndSystemConfig(); LOG.INFO(SEPARATOR_LINE); // Setup log-system based on configuration setupLogLevels(); // Output enabled frameworks... setupFrameworks(); // Setup native language support NLS.init(classpath); // Initialize dependency injection... Injector.init(classpath); startComponents(); // Start resource monitoring... NLS.startMonitoring(classpath); } /** * Loads the local configuration. *

* These are settings.conf, develop.conf, test.conf * and finally, instance.conf. */ private static void setupLocalConfig() { LOG.INFO("Loading local config..."); LOG.INFO(SEPARATOR_LINE); config = ConfigFactory.empty(); Config instanceConfig = null; if (isStartedAsTest()) { config = setup.applyTestConfig(config); config = setup.applyTestScenarioConfig(System.getProperty(SIRIUS_TEST_SCENARIO_PROPERTY), config); } else { // instance.conf and develop.conf are not used to tests to permit uniform behaviour on local // machines and build servers... if (Sirius.isDev()) { config = setup.applyDeveloperConfig(config); } instanceConfig = setup.loadInstanceConfig(); } // Setup customer customizations... if (instanceConfig != null && instanceConfig.hasPath(CONFIG_KEY_CUSTOMIZATIONS)) { customizations = instanceConfig.getStringList(CONFIG_KEY_CUSTOMIZATIONS); } else if (config.hasPath(CONFIG_KEY_CUSTOMIZATIONS)) { customizations = config.getStringList(CONFIG_KEY_CUSTOMIZATIONS); } // Load settings.conf for customizations... for (String conf : customizations) { if (Sirius.class.getResource("/customizations/" + conf + "/settings.conf") != null) { LOG.INFO("loading settings.conf for customization '" + conf + "'"); String configName = "customizations/" + conf + "/settings.conf"; try { config = ConfigFactory.load(setup.getLoader(), configName).withFallback(config); } catch (Exception e) { handleConfigError(configName, e); } } else { LOG.INFO("customization '" + conf + "' has no settings.conf..."); } } // Apply instance config at last for override all other configs... if (instanceConfig != null) { config = instanceConfig.withFallback(config); } LOG.INFO(SEPARATOR_LINE); } /** * Loads the application config files. *

* These are component-XXX.conf, component-test-XXX.conf, application-XXX.conf * and finally, application.conf. */ private static void setupApplicationAndSystemConfig() { LOG.INFO("Loading system config..."); LOG.INFO(SEPARATOR_LINE); config = config.withFallback(setup.loadApplicationConfig()); if (isStartedAsTest()) { // Load test configurations (will override component configs) classpath.find(Pattern.compile("component-test-([^.]*?)\\.conf")).forEach(value -> { try { LOG.INFO("Loading test config: %s", value.group()); config = config.withFallback(ConfigFactory.load(setup.getLoader(), value.group())); } catch (Exception e) { handleConfigError(value.group(), e); } }); } // Load component configurations classpath.find(Pattern.compile("component-([^\\-]*?)([^.]*?)\\.conf")).forEach(value -> { if (!"test".equals(value.group(1))) { try { LOG.INFO("Loading config: %s", value.group()); config = config.withFallback(ConfigFactory.load(setup.getLoader(), value.group())); } catch (Exception e) { handleConfigError(value.group(), e); } } }); LOG.INFO(SEPARATOR_LINE); } private static void setupClasspath() { classpath = new Classpath(setup.getLoader(), "component.marker", customizations); classpath.getComponentRoots().forEach(url -> { LOG.INFO("Classpath: %s", url); }); } private static void handleConfigError(String file, Exception e) { Exceptions.ignore(e); Sirius.LOG.WARN("Cannot load %s: %s", file, e.getMessage()); } /** * Provides access to the classpath used to load the framework. * * @return the classpath used to load the framework */ public static Classpath getClasspath() { return classpath; } /** * Stops the framework. */ public static void stop() { if (!started) { return; } started = false; LOG.INFO("Stopping Sirius"); LOG.INFO(SEPARATOR_LINE); outputActiveOperations(); stopLifecycleParticipants(); outputActiveOperations(); waitForLifecyclePaticipants(); outputThreadState(); initialized = false; settings = null; } private static void outputThreadState() { LOG.INFO("System halted! - Thread State"); LOG.INFO(SEPARATOR_LINE); LOG.INFO("%-15s %10s %53s", "STATE", "ID", "NAME"); for (ThreadInfo info : ManagementFactory.getThreadMXBean().dumpAllThreads(false, false)) { LOG.INFO("%-15s %10s %53s", info.getThreadState().name(), info.getThreadId(), info.getThreadName()); } LOG.INFO(SEPARATOR_LINE); } private static void waitForLifecyclePaticipants() { LOG.INFO("Awaiting system halt..."); LOG.INFO(SEPARATOR_LINE); for (int i = lifecycleKillParticipants.size() - 1; i >= 0; i--) { Killable killable = lifecycleKillParticipants.get(i); try { Watch w = Watch.start(); killable.awaitTermination(); LOG.INFO("Terminated: %s (Took: %s)", killable.getClass().getName(), w.duration()); } catch (Exception e) { Exceptions.handle() .error(e) .to(LOG) .withSystemErrorMessage("Termination of: %s failed!", killable.getClass().getName()) .handle(); } } } private static void stopLifecycleParticipants() { LOG.INFO("Stopping lifecycles..."); LOG.INFO(SEPARATOR_LINE); for (int i = lifecycleStopParticipants.size() - 1; i >= 0; i--) { Stoppable stoppable = lifecycleStopParticipants.get(i); Future future = tasks.defaultExecutor().fork(() -> stopLifecycle(stoppable)); if (!future.await(Duration.ofSeconds(10))) { LOG.WARN("Lifecycle '%s' did not stop within 10 seconds....", stoppable.getClass().getName()); } } LOG.INFO(SEPARATOR_LINE); } private static void stopLifecycle(Stoppable lifecycle) { LOG.INFO("Stopping: %s", lifecycle.getClass().getName()); try { lifecycle.stopped(); } catch (Exception e) { Exceptions.handle() .error(e) .to(LOG) .withSystemErrorMessage("Stop of: %s failed!", lifecycle.getClass().getName()) .handle(); } } private static void outputActiveOperations() { if (!Operation.getActiveOperations().isEmpty()) { LOG.INFO("Active Operations"); LOG.INFO(SEPARATOR_LINE); for (Operation op : Operation.getActiveOperations()) { LOG.INFO(op.toString()); } LOG.INFO(SEPARATOR_LINE); } } /** * Initializes the framework. *

* This is called by IPL.main once the class loader is fully populated. * * @param setup the setup class used to configure the framework */ public static void start(Setup setup) { Watch w = Watch.start(); Sirius.setup = setup; setup.init(); LOG.INFO(SEPARATOR_LINE); LOG.INFO("System is STARTING..."); LOG.INFO(SEPARATOR_LINE); init(); LOG.INFO(SEPARATOR_LINE); LOG.INFO("System is UP and RUNNING - %s", w.duration()); LOG.INFO(SEPARATOR_LINE); Runtime.getRuntime().addShutdownHook(new Thread(Sirius::stop)); } /** * Determines if the framework with the given name is enabled. *

* Frameworks can be enabled or disabled using the config path sirius.framework.[name]. This is * intensively used by the app part, as it provides a lot of basic frameworks which can be turned off or * on as required. * * @param framework the framework to check * @return true if the framework is enabled, false otherwise */ public static boolean isFrameworkEnabled(String framework) { if (Strings.isEmpty(framework)) { return true; } if (Sirius.isDev() && !frameworks.containsKey(framework)) { LOG.WARN("Status of unknown framework '%s' requested. Will report as disabled framework.", framework); } return Boolean.TRUE.equals(frameworks.get(framework)); } /** * Returns a list of all active customer configurations. *

* A customer configuration can be used to override basic functionality with more specialized classes or resources * which were adapted based on customer needs. *

* As often groups of customers share the same requirements, not only a single configuration can be activated * but a list. Within this list each configuration may override classes and resources of all former * configurations. Therefore the last configuration will always "win". *

* Note that classes must be placed in the package: configuration.[name] (with arbitrary sub packages). * Also resources must be placed in: configuration/[name]/resource-path. * * @return a list of all active configurations */ public static List getActiveConfigurations() { return Collections.unmodifiableList(customizations); } /** * Determines if the given config is active (or null which is considered active) * * @param configName the name of the config to check. null will be considered as active * @return true if the named customization is active, false otherwise */ public static boolean isActiveCustomization(@Nullable String configName) { return configName == null || Sirius.getActiveConfigurations().contains(configName); } /** * Determines if the given resource is part of a customization. * * @param resource the resource path to check * @return true if the given resource is part of a customization, false otherwise */ public static boolean isCustomizationResource(@Nullable String resource) { return resource != null && resource.startsWith("customizations"); } /** * Extracts the customization name from a resource. *

* Valid names are paths like "customizations/[name]/..." or classes like "customizations.[name]...". * * @param resource the name of the resource * @return the name of the customizations or null if no config name is contained */ @Nullable public static String getCustomizationName(@Nullable String resource) { if (resource == null) { return null; } if (resource.startsWith("customizations/")) { return Strings.split(resource.substring(15), "/").getFirst(); } else if (resource.startsWith("customizations.")) { return Strings.split(resource.substring(15), ".").getFirst(); } else { return null; } } /** * Compares the two given customizations according to the order given in the system config. * * @param configA the name of the first customization * @param configB the name of the second customization * @return an int value which can be used to compare the order of the two given configs. */ public static int compareCustomizations(@Nullable String configA, @Nullable String configB) { if (configA == null) { if (configB == null) { return 0; } return 1; } if (configB == null) { return -1; } return customizations.indexOf(configA) - customizations.indexOf(configB); } /** * Returns the system config based on the current instance.conf (file system), application.conf (classpath) and * all component-XXX.conf wrapped as Settings * * @return the initialized settings object or null if the framework is not setup yet. */ public static ExtendedSettings getSettings() { if (settings == null) { if (config != null) { settings = new ExtendedSettings(config); } } return settings; } /** * Provides access to the setup instance which was used to configure Sirius. * * @return the setup instance used to configure Sirius.+ */ public static Setup getSetup() { return setup; } /** * Returns the up time of the system in milliseconds. * * @return the number of milliseconds the system is running */ public static long getUptimeInMilliseconds() { return System.currentTimeMillis() - START_TIMESTAMP; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy