/* * 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.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.Properties; import org.atteo.config.IncorrectConfigurationException; import org.atteo.filtering.Filtering; import org.atteo.filtering.PropertiesPropertyResolver; import org.atteo.filtering.PropertyFilter; import org.atteo.filtering.PropertyResolver; import org.atteo.moonshine.directories.DefaultFileAccessor; import org.atteo.moonshine.directories.FileAccessor; import org.atteo.moonshine.directories.FileAccessorFactory; import org.atteo.moonshine.directories.SubdirectoryLayout; import org.atteo.moonshine.logging.Logback; import org.atteo.moonshine.logging.Logging; import org.atteo.moonshine.services.LifeCycleListener; import org.atteo.moonshine.services.Services; import org.atteo.moonshine.services.internal.GuiceBindingsHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.beust.jcommander.JCommander; import com.beust.jcommander.ParameterException; import com.google.inject.AbstractModule; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.Module; class MoonshineImplementation implements Moonshine.Builder, Moonshine { private final Thread shutdownThread = new Thread() { @Override public void run() { close(); } }; private static class MoonshineUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler { @Override public void uncaughtException(Thread t, Throwable e) { if (e instanceof MoonshineException) { Moonshine.Factory.logException((MoonshineException) e); } else { Logger logger = LoggerFactory.getLogger("Moonshine"); logger.error("Fatal error: " + e.getMessage(), e); } } } private Logger logger; private Services services; private Logging logging; private FileAccessorFactory fileAccessorFactory; private String applicationName; private String[] arguments; private String homeDirectory; private final List<ParameterProcessor> parameterProcessors = new ArrayList<>(); private final List<String> configurationResources = new ArrayList<>(); private final List<String> optionalConfigurationResources = new ArrayList<>(); private final List<String> configurationStrings = new ArrayList<>(); private final List<Module> modules = new ArrayList<>(); private final List<LifeCycleListener> listeners = new ArrayList<>(); private final List<PropertyResolver> propertyResolvers = new ArrayList<>(); private final List<String> configDirs = new ArrayList<>(); private final List<String> dataDirs = new ArrayList<>(); private boolean shutdownHook = true; private boolean autoConfiguration = false; private boolean skipDefaultConfigurationFile = false; private boolean skipExceptionHandler = false; @Override public Builder applicationName(String applicationName) { this.applicationName = applicationName; return this; } @Override public Builder skipUncaughtExceptionHandler() { skipExceptionHandler = true; return this; } @Override public Builder arguments(String[] arguments) { this.arguments = arguments; return this; } @Override public Builder shutdownHook(boolean shutdownHook) { this.shutdownHook = shutdownHook; return this; } @Override public Builder loggingFramework(Logging logging) { this.logging = logging; return this; } @Override public Builder addParameterProcessor(ParameterProcessor processor) { this.parameterProcessors.add(processor); return this; } @Override public Moonshine build() throws IOException, CommandLineParameterException, ConfigurationException { if (!skipExceptionHandler) { Thread.currentThread().setUncaughtExceptionHandler(new MoonshineUncaughtExceptionHandler()); } if (logging == null) { logging = new Logback(); } // don't access logger before this method call // or set property 'logback.ContextSelector' to 'org.atteo.moonshine.logging.Logback$MoonshineContextSelector' logging.earlyBootstrap(); logger = LoggerFactory.getLogger("Moonshine"); logger.info("Bootstrapping {}", applicationName != null ? applicationName : "Moonshine"); fileAccessorFactory = new DefaultFileAccessor(); JCommander commander = new JCommander(); MoonshineCommandLineParameters moonshineParameters = new MoonshineCommandLineParameters(); commander.setProgramName(applicationName); commander.addObject(moonshineParameters); commander.addObject(logging.getParameters()); commander.addObject(fileAccessorFactory.getParameters()); for (Object object : parameterProcessors) { commander.addObject(object); } if (arguments == null) { arguments = new String[]{}; } try { commander.parse(arguments); } catch (ParameterException e) { throw new CommandLineParameterException("Cannot parse command line parameters: " + e.getMessage(), e); } for (ParameterProcessor parameterProcessor : parameterProcessors) { parameterProcessor.configure(this); } Path homePath; if (homeDirectory == null) { homePath = Paths.get(""); } else { homePath = Paths.get(homeDirectory); } fileAccessorFactory.setWriteableLayout(new SubdirectoryLayout(homePath)); for (String configDir : configDirs) { fileAccessorFactory.addConfigDir(configDir); } for (String dataDir : dataDirs) { fileAccessorFactory.addDataDir(dataDir); } if (applicationName == null) { applicationName = "moonshine"; } if (moonshineParameters.isHelp()) { StringBuilder builder = new StringBuilder(); commander.usage(builder); logger.info(builder.toString()); return null; } FileAccessor fileAccessor = fileAccessorFactory.getFileAccessor(); Properties fileAccessorProperties = fileAccessor.getProperties(); fileAccessorProperties.setProperty("applicationName", applicationName); logging.initialize(fileAccessor, fileAccessorProperties); final ConfigurationReader configuration = new ConfigurationReader(fileAccessor); Config config; try { setupConfiguration(moonshineParameters, configuration); configuration.addCustomPropertyResolver(new PropertiesPropertyResolver(fileAccessorProperties)); Path configPropertiesFile = fileAccessor.getConfigFile("config.properties"); if (configPropertiesFile != null) { Properties configProperties = new Properties(); configProperties.load(Files.newBufferedReader(configPropertiesFile, StandardCharsets.UTF_8)); configuration.addCustomPropertyResolver(new PropertiesPropertyResolver(configProperties)); } if (moonshineParameters.isPrintConfig()) { logger.info("Configuration is:\n" + configuration.printCombinedXml()); return null; } configuration.filter(); if (moonshineParameters.isPrintFilteredConfig()) { logger.info("Filtered configuration is:\n" + configuration.printCombinedXml()); return null; } config = configuration.read(); } catch (IncorrectConfigurationException e) { throw new ConfigurationException(e.getMessage(), e); } final PropertyResolver propertyResolver = configuration.getPropertyResolver(); modules.add(new AbstractModule() { @Override protected void configure() { bind(Key.get(PropertyResolver.class, ApplicationProperties.class)).toInstance(propertyResolver); bind(Key.get(PropertyFilter.class, ApplicationProperties.class)).toInstance( Filtering.getFilter(propertyResolver)); } }); Services.Builder builder = createServicesBuilder(); for (Module module : modules) { builder.addModule(module); } for (LifeCycleListener listener : listeners) { builder.registerListener(listener); } builder.applicationName(applicationName); builder.configuration(config); services = builder.build(); if (moonshineParameters.isPrintGuiceBindings()) { GuiceBindingsHelper.printServiceElements(services.getServiceElements()); } if (shutdownHook) { Runtime.getRuntime().addShutdownHook(shutdownThread); } return this; } @Override public Builder homeDirectory(String homeDirectory) { this.homeDirectory = homeDirectory; return this; } @Override public Builder addConfigDir(String path) { dataDirs.add(path); return this; } @Override public Builder addDataDir(String path) { configDirs.add(path); return this; } @Override public Builder autoConfiguration() { autoConfiguration = true; return this; } @Override public Builder skipDefaultConfigurationFiles() { skipDefaultConfigurationFile = true; return this; } @Override public Builder addConfigurationFromResource(String resource) { configurationResources.add(resource); return this; } @Override public Builder addOptionalConfigurationFromResource(String resource) { optionalConfigurationResources.add(resource); return this; } @Override public Builder addConfigurationFromString(String string) { configurationStrings.add(string); return this; } @Override public Builder addModule(Module module) { modules.add(module); return this; } @Override public Builder registerListener(LifeCycleListener listener) { listeners.add(listener); return this; } @Override public Builder addPropertyResolver(PropertyResolver propertyResolver) { propertyResolvers.add(propertyResolver); return this; } protected Services.Builder createServicesBuilder() { return Services.Factory.builder(); } @Override public void start() { services.start(); } @Override public void stop() { services.stop(); } @Override public void close() { if (logger == null) { logger = LoggerFactory.getLogger("Moonshine"); } logger.info("Shutting down {}", applicationName != null ? applicationName : "Moonshine"); try { Runtime.getRuntime().removeShutdownHook(shutdownThread); } catch (IllegalStateException e) { // ok, will be thrown if we are already in the process of shutting down JVM } if (services != null) { services.close(); services = null; } } @Override public Injector getGlobalInjector() { return services.getGlobalInjector(); } protected void setupConfiguration(MoonshineCommandLineParameters moonshineParameters, final ConfigurationReader configuration) throws IOException, IncorrectConfigurationException { if (autoConfiguration || moonshineParameters.isAutoConfiguration()) { configuration.generateAutoConfiguration(); configuration.combineAutoConfiguration(); } else { configuration.removeAutoConfiguration(); } if (!skipDefaultConfigurationFile && !moonshineParameters.isNoDefaults()) { configuration.combineDefaultConfiguration(); } for (String resource : configurationResources) { configuration.combineConfigurationFromResource(resource, true); } for (String resource : optionalConfigurationResources) { configuration.combineConfigurationFromResource(resource, false); } for (String string : configurationStrings) { configuration.combineConfigurationFromString(string); } if (moonshineParameters.getConfigurationFiles().isEmpty()) { configuration.combineConfigDirConfiguration(); } else { for (String fileName : moonshineParameters.getConfigurationFiles()) { configuration.combineConfigurationFromFile(new File(fileName), true); } } configuration.generateTemplateConfigurationFile(); for (PropertyResolver resolver : propertyResolvers) { configuration.addCustomPropertyResolver(resolver); } } }