/*
* Copyright 2013 Atteo.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.atteo.moonshine;
import java.io.IOException;
import javax.annotation.Nullable;
import org.atteo.config.Configuration;
import org.atteo.filtering.EnvironmentPropertyResolver;
import org.atteo.filtering.PropertyResolver;
import org.atteo.filtering.SystemPropertyResolver;
import org.atteo.filtering.XmlPropertyResolver;
import org.atteo.moonshine.directories.FileAccessor;
import org.atteo.moonshine.logging.Logback;
import org.atteo.moonshine.logging.Logging;
import org.atteo.moonshine.services.LifeCycleListener;
import org.atteo.moonshine.services.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.beust.jcommander.JCommander;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;
import ch.qos.logback.classic.jul.LevelChangePropagator;
/**
* Moonshine container starting class.
* <h3>The following operations are performed:</h3>
* <p>
* <ul>
* <li>first, 'Bootstrapping Moonshine' message is logged through SLF4J, this should trigger
* logging framework initialization, by default Moonshine is configured to use {@link Logback}
* and also provides the default logback.xml file which logs WARN and ERROR messages to the console</li>
* <li>{@link Runtime#addShutdownHook(Thread) shutdown hook} is registered, so Moonshine can shutdown
* cleanly before virtual machine stops</li>
* <li>{@link Logging#earlyBootstrap()} is called, the default implementation for Logback redirects
* JUL logs through SLF4J and also initializes {@link LevelChangePropagator}</li>
* <li>command line parameters are parsed</li>
* <li>{@link FileAccessor file accessor} is initialized</li>
* <li>{@link Logging#initialize(FileAccessor, Properties)} is called, the default implementation
* for Logback loads logback-moonshine.xml which should load final logging configuration, by default Moonshine
* contains logback-moonshine.xml file which logs INFO messages to the file in ${logHome}
* directory</li>
* <li>configuration is loaded from a number of configuration files, by default '/default-config.xml'
* classpath resource and ${configHome}/config.xml file, the result is {@link Config} object,</li>
* <li>list of configured services is obtained by executing {@link Config#getSubServices()},</li>
* <li> {@link Guice} {@link Injector injector} is created based on the modules returned from each Service's
* {@link Service#configure()} method,</li>
* <li>{@link Injector#injectMembers members injection} is performed on each service,</li>
* <li>{@link Service#start() start} is executed on each service.</li>
* </ul>
* </p>
*
* <h3>Configuration files</h3>
* <p>
* Two configuration files are searched for. First the classpath is searched for '/default-config.xml'.
* The idea is that this file contains default configuration prepared by the application programmer.
* Next application configuration directory '${configHome} is searched for config.xml file which should
* contain the configuration prepared by the application administrator. If config.xml file does not exist
* it is created with the reference to the XSD schema to which the file should conform.
* </p>
*
* <h3>Property resolvers</h3>
* <p>
* Configuration files are merged and then filtered with a number of predefined {@link PropertyResolver}s.
* In the files @code ${name}} placeholder will be replaced in the following ways:
* <ul>
* <li>all Java system properties, see {@link SystemPropertyResolver},</li>
* <li>environment variables can be referenced using env prefix, ex ${env.PATH},
* see {@link EnvironmentPropertyResolver},</li>
* <li>all elements in the XML configuration file can be referenced using dot to separate tag names,
* see {@link XmlPropertyResolver},</li>
* <li>custom properties under {@code <properties>} section in the configuration file,</li>
* <li>properties are resolved recursively, for instance: ${env.${VARNAME}}</li>
* <li>you can add your own custom {@link PropertyResolver}s using
* {@link Moonshine.Builder#addPropertyResolver(PropertyResolver)}.</li>
* </ul>
* Read the description of the {@link Configuration} engine to learn more about merging, filtering
* and validating the configuration file.
* </p>
*
* </li>
* </ul>
* </p>
*/
public interface Moonshine extends AutoCloseable {
public interface RestrictedBuilder {
/**
* Enables or disables registering shutdown hook with {@link Runtime#addShutdownHook(Thread)}.
*/
RestrictedBuilder shutdownHook(boolean shutdownHook);
/**
* Sets home directory for default file accessor.
*/
RestrictedBuilder homeDirectory(String homeDirectory);
/**
* Enables auto configuration.
* <p>
* Automatic configuration automatically adds one instance of every top-level service found on classpath
* which does not require any configuration.
* </p>
*/
Builder autoConfiguration();
/**
* Skips default configuration files.
* <p>
* By default '/default-config.xml' classpath resource and ${configHome}/config.xml configuration
* files are read. This option ignores them.
* </p>
*/
RestrictedBuilder skipDefaultConfigurationFiles();
/**
* Adds configuration from given resource.
*/
RestrictedBuilder addConfigurationFromResource(String resource);
/**
* Adds configuration from given resource, if it exists.
*
* <p>
* Ignores the resource, if it does not exist.
* </p>
*/
RestrictedBuilder addOptionalConfigurationFromResource(String config);
/**
* Adds custom Guice module.
*/
RestrictedBuilder addModule(Module module);
/**
* Adds life cycle listener.
*/
RestrictedBuilder registerListener(LifeCycleListener listener);
/**
* Adds configuration from given string.
*/
RestrictedBuilder addConfigurationFromString(String string);
/**
* Add property resolver.
*/
RestrictedBuilder addPropertyResolver(PropertyResolver propertyResolver);
/**
* Add read-only config directory.
*/
RestrictedBuilder addConfigDir(String path);
/**
* Add read-only data directory.
*/
RestrictedBuilder addDataDir(String path);
}
public interface Builder extends RestrictedBuilder {
/**
* Sets application name.
*/
Builder applicationName(String applicationName);
/**
* Sets program arguments.
*
* <p>
* Usually this will be the value of argv parameter from main(String[]) method.
* </p>
*/
Builder arguments(String[] arguments);
/**
* Do not register uncaught exception handler.
*/
Builder skipUncaughtExceptionHandler();
/**
* Adds parameter processor.
*
* <p>
* Moonshine parses the parameters using {@link JCommander}. The provided object
* will be added to the list of parameter processors with {@link JCommander#addObject(Object)}.
* </p>
*/
Builder addParameterProcessor(ParameterProcessor parameterProcessor);
@Override
Builder shutdownHook(boolean shutdownHook);
/**
* Sets logging framework implementation.
*/
Builder loggingFramework(Logging logging);
@Override
Builder homeDirectory(String homeDirectory);
@Override
Builder autoConfiguration();
@Override
Builder skipDefaultConfigurationFiles();
@Override
Builder addConfigurationFromResource(String resource);
@Override
Builder addOptionalConfigurationFromResource(String config);
@Override
Builder addModule(Module module);
@Override
Builder registerListener(LifeCycleListener listener);
@Override
Builder addConfigurationFromString(String string);
@Override
Builder addPropertyResolver(PropertyResolver propertyResolver);
@Override
Builder addConfigDir(String path);
@Override
Builder addDataDir(String path);
/**
* Builds Moonshine based on this builder parameters.
* <p>
* Can return null, if based on provided configuration Moonshine container
* is not supposed to be started. For instance, when '--help' command line parameter
* was specified help message should be logged and program should exit immediately.
* </p>
* @return created Moonshine container, or null if intended behavior is to skip container creation
* @throws IOException when configuration could not be accessed
* @throws MoonshineException when configuration is incorrect
*/
@Nullable
Moonshine build() throws MoonshineException, IOException;
}
public static class Factory {
public static Builder builder() {
return new MoonshineImplementation();
}
public static void logException(MoonshineException e) {
Logger logger = LoggerFactory.getLogger("Moonshine");
if (e instanceof ConfigurationException) {
logger.error("Incorrect configuration file: " + e.getMessage());
logger.debug("Incorrect configuration file", e);
} else if (e instanceof CommandLineParameterException) {
logger.error(e.getMessage());
logger.debug(e.getMessage(), e);
} else {
logger.error("Fatal error: " + e.getMessage());
logger.debug("Fatal error", e);
}
}
}
/**
* Starts all configured services.
*/
void start();
/**
* Stops all configured services.
*/
void stop();
/**
* Returns the global injector of the Moonshine.
* @return Moonshine global injector
*/
Injector getGlobalInjector();
/**
* Stops Moonshine framework.
*/
@Override
void close();
}