/* * Copyright 2010 Proofpoint, Inc. * * 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 io.airlift.bootstrap; import com.google.common.annotations.Beta; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList.Builder; import com.google.common.collect.ImmutableSortedMap; import com.google.inject.Binder; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Module; import com.google.inject.Stage; import com.google.inject.spi.Message; import io.airlift.bootstrap.LoggingWriter.Type; import io.airlift.configuration.ConfigurationFactory; import io.airlift.configuration.ConfigurationInspector; import io.airlift.configuration.ConfigurationInspector.ConfigAttribute; import io.airlift.configuration.ConfigurationInspector.ConfigRecord; import io.airlift.configuration.ConfigurationLoader; import io.airlift.configuration.ConfigurationModule; import io.airlift.configuration.ValidationErrorModule; import io.airlift.configuration.WarningsMonitor; import io.airlift.log.Logger; import io.airlift.log.Logging; import io.airlift.log.LoggingConfiguration; import java.io.PrintWriter; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.SortedMap; import java.util.TreeMap; import static com.google.common.collect.Maps.fromProperties; /** * Entry point for an application built using the platform codebase. * <p> * This class will: * <ul> * <li>load, validate and bind configurations</li> * <li>initialize logging</li> * <li>set up bootstrap management</li> * <li>create an Guice injector</li> * </ul> */ public class Bootstrap { private final Logger log = Logger.get("Bootstrap"); private final List<Module> modules; private Map<String, String> requiredConfigurationProperties; private Map<String, String> optionalConfigurationProperties; private boolean initializeLogging = true; private boolean quiet; private boolean strictConfig; private boolean requireExplicitBindings = true; private boolean initialized; public Bootstrap(Module... modules) { this(ImmutableList.copyOf(modules)); } public Bootstrap(Iterable<? extends Module> modules) { this.modules = ImmutableList.copyOf(modules); } @Beta public Bootstrap setRequiredConfigurationProperty(String key, String value) { if (this.requiredConfigurationProperties == null) { this.requiredConfigurationProperties = new TreeMap<>(); } this.requiredConfigurationProperties.put(key, value); return this; } @Beta public Bootstrap setRequiredConfigurationProperties(Map<String, String> requiredConfigurationProperties) { if (this.requiredConfigurationProperties == null) { this.requiredConfigurationProperties = new TreeMap<>(); } this.requiredConfigurationProperties.putAll(requiredConfigurationProperties); return this; } @Beta public Bootstrap setOptionalConfigurationProperty(String key, String value) { if (this.optionalConfigurationProperties == null) { this.optionalConfigurationProperties = new TreeMap<>(); } this.optionalConfigurationProperties.put(key, value); return this; } @Beta public Bootstrap setOptionalConfigurationProperties(Map<String, String> optionalConfigurationProperties) { if (this.optionalConfigurationProperties == null) { this.optionalConfigurationProperties = new TreeMap<>(); } this.optionalConfigurationProperties.putAll(optionalConfigurationProperties); return this; } @Beta public Bootstrap doNotInitializeLogging() { this.initializeLogging = false; return this; } public Bootstrap quiet() { this.quiet = true; return this; } public Bootstrap strictConfig() { this.strictConfig = true; return this; } @SuppressWarnings("unused") public Bootstrap requireExplicitBindings(boolean requireExplicitBindings) { this.requireExplicitBindings = requireExplicitBindings; return this; } public Injector initialize() throws Exception { Preconditions.checkState(!initialized, "Already initialized"); initialized = true; Logging logging = null; if (initializeLogging) { logging = Logging.initialize(); } Thread.currentThread().setUncaughtExceptionHandler((thread, throwable) -> log.error(throwable, "Uncaught exception in thread %s", thread.getName())); Map<String, String> requiredProperties; ConfigurationFactory configurationFactory; if (requiredConfigurationProperties == null) { // initialize configuration log.info("Loading configuration"); ConfigurationLoader loader = new ConfigurationLoader(); requiredProperties = Collections.emptyMap(); String configFile = System.getProperty("config"); if (configFile != null) { requiredProperties = loader.loadPropertiesFrom(configFile); } } else { requiredProperties = requiredConfigurationProperties; } SortedMap<String, String> properties = new TreeMap<>(); if (optionalConfigurationProperties != null) { properties.putAll(optionalConfigurationProperties); } properties.putAll(requiredProperties); properties.putAll(fromProperties(System.getProperties())); properties = ImmutableSortedMap.copyOf(properties); configurationFactory = new ConfigurationFactory(properties, log::warn); if (logging != null) { // initialize logging log.info("Initializing logging"); LoggingConfiguration configuration = configurationFactory.build(LoggingConfiguration.class); logging.configure(configuration); } // Register configuration classes defined in the modules configurationFactory.registerConfigurationClasses(modules); // Validate configuration classes List<Message> messages = configurationFactory.validateRegisteredConfigurationProvider(); // at this point all config file properties should be used // so we can calculate the unused properties TreeMap<String, String> unusedProperties = new TreeMap<>(); unusedProperties.putAll(requiredProperties); unusedProperties.keySet().removeAll(configurationFactory.getUsedProperties()); // Log effective configuration if (!quiet) { logConfiguration(configurationFactory, unusedProperties); } // system modules Builder<Module> moduleList = ImmutableList.builder(); moduleList.add(new LifeCycleModule()); moduleList.add(new ConfigurationModule(configurationFactory)); if (!messages.isEmpty()) { moduleList.add(new ValidationErrorModule(messages)); } moduleList.add(binder -> binder.bind(WarningsMonitor.class).toInstance(log::warn)); // disable broken Guice "features" moduleList.add(Binder::disableCircularProxies); if (requireExplicitBindings) { moduleList.add(Binder::requireExplicitBindings); } // todo this should be part of the ValidationErrorModule if (strictConfig) { moduleList.add(binder -> { for (Entry<String, String> unusedProperty : unusedProperties.entrySet()) { binder.addError("Configuration property '%s=%s' was not used", unusedProperty.getKey(), unusedProperty.getValue()); } }); } moduleList.addAll(modules); // create the injector Injector injector = Guice.createInjector(Stage.PRODUCTION, moduleList.build()); // Create the life-cycle manager LifeCycleManager lifeCycleManager = injector.getInstance(LifeCycleManager.class); // Start services if (lifeCycleManager.size() > 0) { lifeCycleManager.start(); } return injector; } private static final String PROPERTY_NAME_COLUMN = "PROPERTY"; private static final String DEFAULT_VALUE_COLUMN = "DEFAULT"; private static final String CURRENT_VALUE_COLUMN = "RUNTIME"; private static final String DESCRIPTION_COLUMN = "DESCRIPTION"; private void logConfiguration(ConfigurationFactory configurationFactory, Map<String, String> unusedProperties) { ColumnPrinter columnPrinter = makePrinterForConfiguration(configurationFactory); try (PrintWriter out = new PrintWriter(new LoggingWriter(log, Type.INFO))) { columnPrinter.print(out); } // Warn about unused properties if (!unusedProperties.isEmpty()) { log.warn("UNUSED PROPERTIES"); for (Entry<String, String> unusedProperty : unusedProperties.entrySet()) { log.warn("%s=%s", unusedProperty.getKey(), unusedProperty.getValue()); } log.warn(""); } } private static ColumnPrinter makePrinterForConfiguration(ConfigurationFactory configurationFactory) { ConfigurationInspector configurationInspector = new ConfigurationInspector(); ColumnPrinter columnPrinter = new ColumnPrinter(); columnPrinter.addColumn(PROPERTY_NAME_COLUMN); columnPrinter.addColumn(DEFAULT_VALUE_COLUMN); columnPrinter.addColumn(CURRENT_VALUE_COLUMN); columnPrinter.addColumn(DESCRIPTION_COLUMN); for (ConfigRecord<?> record : configurationInspector.inspect(configurationFactory)) { for (ConfigAttribute attribute : record.getAttributes()) { columnPrinter.addValue(PROPERTY_NAME_COLUMN, attribute.getPropertyName()); columnPrinter.addValue(DEFAULT_VALUE_COLUMN, attribute.getDefaultValue()); columnPrinter.addValue(CURRENT_VALUE_COLUMN, attribute.getCurrentValue()); columnPrinter.addValue(DESCRIPTION_COLUMN, attribute.getDescription()); } } return columnPrinter; } }