com.techempower.gemini.Configurator Maven / Gradle / Ivy
Show all versions of gemini Show documentation
/*******************************************************************************
* Copyright (c) 2018, TechEmpower, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name TechEmpower, Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL TECHEMPOWER, INC. BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************/
package com.techempower.gemini;
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.concurrent.*;
import com.techempower.*;
import com.techempower.gemini.configuration.*;
import com.techempower.gemini.lifecycle.*;
import com.techempower.helper.*;
import com.techempower.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Provides an application-wide configuration facility. Application components
* implement the Configurable interface and then add themselves to the
* Configurator via the addConfigurable method. The application is configured
* at application start time and may be re-configured on-the-fly by a system
* administrator.
*
* Application components implementing the Configurable interface will receive
* a call to their configure(EnhancedProperties) method, allowing them to read
* any configuration properties. Components should support run-time
* re-configuration gracefully. In many cases, that means immediately changing
* their behavior based on the new configuration; in some cases that may not
* be possible and the most graceful behavior is to simply keep the component
* running in the same manner as application start-time.
*
* Configurator is an application InitializationTask, meaning it is given the
* opportunity to load configuration and then remediate if the load fails.
*/
public class Configurator
implements InitializationTask
{
//
// Constants.
//
public static final String CONFIGURATION_FILENAME_EXT = ".conf";
// Special property names.
public static final String PROP_WEBINF = "Servlet.WebInf";
public static final String PROP_CONTEXTNAME = "Servlet.ContextName";
public static final String PROP_DEPLOYMENTROOT = "Servlet.DeploymentRoot";
public static final String PROP_DOCROOT = "Servlet.Docroot"; // alias for DeploymentRoot.
public static final String PROP_MACHINENAME = "Servlet.MachineName";
public static final String PROP_APPROOT = "Servlet.ApplicationRoot";
public static final String PROP_APPROOT_LASTDIR = "Servlet.ApplicationRoot.LastDir";
public static final String PROP_SERVLETPARAM_PREFIX = "Servlet.Parameter.";
public static final String PROP_SERVLETATTRIB_PREFIX = "Servlet.Attribute.";
public static final String PROP_ENVIRONMENT_PREFIX = "Environment.";
//
// Member variables.
//
private final List configurableComponents = new CopyOnWriteArrayList<>();
private final GeminiApplication application;
private final Logger log = LoggerFactory.getLogger(getClass());
private final List providers = new ArrayList<>(1);
private boolean configured = false;
private EnhancedProperties lastProps = null;
//
// Protected methods.
//
/**
* Constructor.
*/
protected Configurator(GeminiApplication application)
{
this.application = application;
addStandardProviders();
// Add ourselves as an application initialization task.
application.getLifecycle().addInitializationTask(this);
}
/**
* Gets the Application reference.
*/
protected GeminiApplication getApplication()
{
return application;
}
/**
* Called by the constructor to add standard ConfigurationProviders. By
* default, the Configurator will add the ClusterConfigurationProvider
* and FileConfigurationProvider. An application-specific Configurator
* may overload this method to use different providers.
*/
protected void addStandardProviders()
{
addProvider(new FileConfigurationProvider());
}
/**
* Add a ConfigurationProvider. Order is important, as providers added
* earlier will be given an earlier opportunity to provide a suitable
* configuration for the application. Once a suitable configuration is
* provided, the remaining Providers are not queried.
*/
protected void addProvider(ConfigurationProvider provider)
{
providers.add(provider);
}
//
// Member methods.
//
@Override
public void taskInitialize(GeminiApplication app)
{
try
{
configureIfNecessary();
if (!configured)
{
throw new GeminiInitializationError("Unable to read configuration.");
}
}
catch (Throwable t)
{
throw new GeminiInitializationError("Configuration failed.", t);
}
}
/**
* Returns the name of the machine running this application, in uppercase.
*
* The machine's name is read from the COMPUTERNAME environment variable,
* which is a default variable on Windows and presumably can be easily set
* on Linux.
*
* If the COMPUTERNAME environment variable was not present, it will
* attempt to find the name of the local host. This should work by default
* on both Windows and Linux.
*
* This method is exposed as a public method so that it can be used by
* implementations of ConfigurationProvider.
*/
public static String getMachineName()
{
// This works on Windows.
String machineName = System.getenv("COMPUTERNAME");
if (machineName == null)
{
try
{
// This works on Windows and Linux.
machineName = java.net.InetAddress.getLocalHost().getHostName();
}
catch (UnknownHostException e)
{
e.printStackTrace();
}
}
if (machineName != null)
{
// For consistency between Windows and Linux.
machineName = machineName.toUpperCase();
}
return machineName;
}
/**
* Configure the application by loading configuration from the Providers.
* If none of the ConfigurationProviders can provide a suitable
* configuration, this method will return false. Otherwise, it will
* return true.
*
* ConfigurationError may be thrown by any configurable components but the
* Configurator does not trap those directly.
*/
public boolean configure()
{
// Get a reference to the version.
final Version version = application.getVersion();
log.info("Configuring {}.", version.getAbbreviatedProductName());
// Get configuration properties from a provider.
final EnhancedProperties props = getConfigurationFromProviders();
// Configure if we have received properties.
if ( (props != null)
&& (props.size() > 0)
)
{
// Add special-purpose configuration properties that are specific
// to the application's deployment and can be discovered
// automatically, such as the location of the WEB-INF directory.
addStandardProperties(props, application);
// Do the configuration.
configureWithProps(props, version);
// Set the configured flag.
configured = true;
// Copy reference to last known Props.
lastProps = props;
// Log the completion.
log.info("Configuration complete.");
// Configuration successful.
return true;
}
else
{
log.error("Configuration failed; unable to read configuration file(s).");
}
// Not able to configure.
return false;
}
/**
* Configure the provided Configurable on demand with the last-read
* configuration. This is functionally identical to calling:
*
* configure(application.getConfigurator().getLastProperties());
*
* from within your Configurable's code. So instead you can call:
*
* application.getConfigurator().configure(this);
*/
public void configure(Configurable configurable)
{
configurable.configure(getLastProperties());
}
/**
* Creates an EnhancedProperties object pre-populated with some environment
* variables suitable for providing to the ConfigurationProviders.
*/
protected EnhancedProperties constructProperties()
{
// Create a new properties object for each provider to use.
final EnhancedProperties props = new EnhancedProperties(application);
// Add system properties passed
for (Map.Entry