Java tutorial
/** * Copyright 2013 Christian Hilmersson * * This file is part of config-bootstrapper * http://www.github.com/chilmers/config-bootstrapper * * config-bootstrapper is free software; you can redistribute it and/or modify * it under the terms of version 2.1 of the GNU Lesser General Public * License as published by the Free Software Foundation. * * config-bootstrapper is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with config-bootstrapper; if not, write to the * Free Software Foundation, Inc., 59 Temple Place, Suite 330, * Boston, MA 02111-1307 USA */ package com.chilmers.configbootstrapper; import java.io.InputStream; import java.util.Enumeration; import java.util.MissingResourceException; import java.util.PropertyResourceBundle; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import org.apache.commons.lang.StringUtils; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import org.apache.log4j.PropertyConfigurator; import org.apache.log4j.xml.DOMConfigurator; /** * Config Bootstrapper - https://github.com/chilmers/config-bootstrapper/<br/> * Helps initializing logging and application configuration for a system.<br/> * This class implements a ServletContextListener and initializes * config-bootstrapper upon servlet context initialization.<br/> * <br/> * * <h2>Quick start</h2> * A more comprehensive usage guide is found further down.<br/> * <br/> * Add this to your web.xml before any other listeners that need to use the configuration or that needs to log.<br/> *<pre> *<listener> * <listener-class>com.chilmers.configbootstrapper.ConfigServletContextListener</listener-class> *</listener> *</pre> * You can now use the system property "application.config.location" to read your config location, for example to inject your config in Spring. *<pre><context:property-placeholder location="${application.config.location}"/></pre> * Or use readApplicationConfiguration in ConfigHelper:<br/> *<pre>See {@link com.chilmers.configbootstrapper.ConfigHelper#readApplicationConfiguration()}</pre> * * The application will now read <i>application.properties from the classpath</i>, if you need to read from another location * you might specify this with a system property (or environment variable or servlet context parameter) at startup.<br/> * <br/>For example: *<pre> *mvn jetty:run -Dapplication.config.location=file:/Users/chilmers/myApp/app-config.properties *</pre> * or if you want to read config from classpath (other than application.properties) *<pre> *mvn jetty:run -Dapplication.config.location=classpath:app-config.properties *</pre> * * At this stage the application will try to find a default log4j configuration file i.e. <i>log4j.xml or log4j.properties on the classpath</i> * If you need to change this, add an entry like this to your application configuration file: *<pre>application.log4j.config.location=file:/Users/chilmers/myApp/app-log4j.xml</pre> * or if you want to read logging config from the classpath (other than log4j.xml/log4j.properties) *<pre>application.log4j.config.location=classpath:app-log4j.xml</pre> * <h2>Main functionalities:</h2> * <ul> * <li><strong>Determines which configuration file to use</strong><br/> * Looks in the given order in system properties, environment variables, and servlet context parameters for the location of a * properties file to use for configuration.<br/> * By default it looks for an entry named <strong>"application.config.location"</strong>. <br/> * If no such entry was found the location <strong>defaults to classpath:application.properties</strong><br/> * The location that was determined will be written to the system property (by default "application.config.location").<br/> * The obvious benefit of doing this is in when no location has been specified, you will still be able to read the configuration * location from this system property. In other environments where you want to add a configuration on the file system, you can do * this and add the location as a system property, environment variable or servlet context parameter and still read the location * from the system property.<br/> * This makes it easy to use separate configurations for separate environments. * Use classpath: for files on the classpath and file: to read from an external location. * </li> * <li><strong>Loads logging (log4J) configuration</strong><br/> * Uses the given application configuration to specify a location of a log4j configuration file. * By default it looks for an entry named <strong>"application.log4j.config.location"</strong> in the application configuration.<br/> * In this way it is easy to provide different logging configurations for different environments.<br/> * If no specific log4j-configuration is configured, it falls back to log4j's default configuration handling (e.g. log4j.xml or log4j.properties on the classpath)<br/> * </li> * <li><strong>Possibility to set system properties from application configuration</strong><br/> * Entries in the configuration file starting with "system.property." will automatically be written to * the system properties.<br/> * Example: * <br/> * <pre>system.property.foo=bar</pre> * Will write <tt>foo=bar</tt> as a system property, which is handy in some circumstances.<br/> * Don't use this feature if you don't understand what it is, since it might clutter your system properties.<br/> * </li> * </ul> * <br/> * * <h2>Finding correct application configuration</h2> * This context listener will in the given order look in the system properties, environment variables<br/> * or the servlet context parameters for the location of a configuration file to use in the application.<br/> * By default it will look for an entry with the key "application.config.location". (This key name can be overridden, see Overriding defaults below)<br/> * If no such entry is found it will by default fall back to using "classpath:application.properties" as configuration location. (The fallback location can also be overridden if necessary, see below)<br/> * <br/> * Whichever configuration location string is decided, will be set in the system properties using the same key as above, * i.e by default it will be set in system property "application.config.location"<br/> * This makes it possible to locate the configuration from within the application, for example by reading it into a * PropertyResourceBundle or by using Spring's PropertyPlaceholderConfigurer like this: * <br/> *<pre> *<bean id="placeholderConfig" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> * <property name="location" value="${application.config.location}" /> *</bean> *</pre> * Or, if you have the context namespace defined, simply:<br/> *<pre><context:property-placeholder location="${application.config.location}"/></pre> * Or use readApplicationConfiguration in ConfigHelper for non-Spring applications:<br/> * See {@link com.chilmers.configbootstrapper.ConfigHelper#readApplicationConfiguration()} * <br/> * <br/> * <h2>Logging configuration (i.e. Log4j)</h2> * This mechanism configures Log4j using a given file whose location is stated in the application configuration, * or if no such file is available falls back to Log4j's default configuration behavior, <br/> * i.e. looks for log4j.xml or log4j.properties on the classpath.<br/> * Use this format classpath:my-log4j-config.xml to point out a file on the classpath.<br/> * If you want to point out a file on the file system you can either prefix with file: or just write the location as it is. * <br/> * To use an external log4j file you will have to state the location of the file in the application configuration as * a property with the key given by "application.log4j.config.location". (This key name can be overriden, see Overriding defaults below)<br/> * <br/> * <br/> * <h2>Usage</h2> * Add this to your web.xml and make sure it is located before any application specific listeners that need * to use the configuration location property or that needs to log. E.g. before Spring's ContextLoaderListener<br/> *<pre> *<listener> * <listener-class>com.chilmers.configbootstrapper.ConfigServletContextListener</listener-class> *</listener> *</pre> * * If you want to specify an external configuration file (instead of the default "classpath:application.properties"), <br/> * add a context-param or more likely a system property or environment variable stating the location of your application configuration. <br/> * For example:<br/> * <br/> * As context param:<br/> *<pre> *<context-param> * <description>The location of the application configuration. * If not set it defaults to classpath:application.properties</description> * <param-name>application.config.location</param-name> * <param-value>file:/Users/myusername/my-app-config/app.properties</param-value> *</context-param> *</pre> * <br/> * As environment variable in a bash shell:<br/> *<pre>export application.config.location=file:/Users/myusername/my-app-config/app.properties</pre> * <br/> * As a system property upon starting your container:<br/> *<pre>java [your application] -Dapplication.config.location=file:/Users/myusername/my-app-config/app.properties</pre> * <h2>Overriding defaults</h2> * The following context-parameters can be set to configure the listener.<br/> * All of them have default values so they don't have to be set if not needed<br/> *<pre> *<context-param> * <description>Sets the key for the entry that holds the application configuration location. * If not set it defaults to application.config.location</description> * <param-name>configServletContextListener.configLocationPropertyKey</param-name> * <param-value>myown.config.location</param-value> *</context-param> *</pre> *<pre> *<context-param> * <description>Sets the key for where in the application configuration file to look for a log4j * configuration file location. * If not set it defaults to application.log4j.config.location</description> * <param-name>configServletContextListener.log4jConfigLocationPropertyKey</param-name> * <param-value>myown.log4j.config.location</param-value> *</context-param> *</pre> *<pre> *<context-param> * <description>Sets the location of the application configuration to fall back to if no other configuration * file location was set. E.g. a bundled configuration on the classpath. * If not set it defaults to classpath:application.properties</description> * <param-name>configServletContextListener.fallbackConfigLocation</param-name> * <param-value>classpath:myown.properties</param-value> *</context-param> *</pre> *<pre> *<context-param> * <description> * Application name that is printed when using System.out logging when no logging manager is available. * Defaults to the display-name of the web.xml or if no display-name exists it will be ConfigServletContextListener * </description> * <param-name>configServletContextListener.applicationName</param-name> * <param-value>My Application</param-value> *</context-param> *</pre> * * @author Christian Hilmersson (https://github.com/chilmers/) */ public class ConfigServletContextListener implements ServletContextListener { private static final Logger log = Logger.getLogger(ConfigServletContextListener.class); /** * Default value for {@link ConfigServletContextListener#applicationName} */ private static final String DEFAULT_APPLICATION_NAME = "ConfigServletContextListener"; /** * The name of the context param to use for overriding {@link ConfigServletContextListener#applicationName} */ private static final String OVERRIDE_DEFAULT_APPLICATION_NAME_PARAM = "configServletContextListener.applicationName"; /** * Default value for {@link ConfigServletContextListener#configLocationPropertyKey} */ protected static final String DEFAULT_CONFIG_LOCATION_PROPERTY_KEY = "application.config.location"; /** * The name of the context param to use for overriding {@link ConfigServletContextListener#configLocationPropertyKey} */ private static final String OVERRIDE_DEFAULT_CONFIG_LOCATION_PROPERTY_KEY_PARAM = "configServletContextListener.configLocationPropertyKey"; /** * Default value for {@link ConfigServletContextListener#fallbackConfigLocation} */ private static final String DEFAULT_FALLBACK_CONFIG_LOCATION = "classpath:application.properties"; /** * The name of the context param to use for overriding {@link ConfigServletContextListener#fallbackConfigLocation} */ private static final String OVERRIDE_DEFAULT_FALLBACK_CONFIG_LOCATION_PARAM = "configServletContextListener.fallbackConfigLocation"; /** * Default value for {@link ConfigServletContextListener#log4jConfigLocationPropertyKey} */ private static final String DEFAULT_LOG4J_CONFIG_LOCATION_PROPERTY_KEY = "application.log4j.config.location"; /** * The name of the context param to use for overriding {@link ConfigServletContextListener#log4jConfigLocationPropertyKey} */ private static final String OVERRIDE_DEFAULT_LOG4J_CONFIG_LOCATION_PROPERTY_KEY_PARAM = "configServletContextListener.log4jConfigLocationPropertyKey"; /** * Prefix for system properties found in the application configuration file */ private static final String CONFIG_SYSTEM_PROPERTY_PREFIX = "system.property."; /** * The location of the application configuration, to fall back on if the application configuration location * was not set by other means. * * Defaults to the value of {@link ConfigServletContextListener#DEFAULT_FALLBACK_CONFIG_LOCATION} * Can be overriden in web.xml by stating the following context-param *<pre> *<context-param> * <description>Sets the location of the application configuration to fall back to if no other configuration * file location was set. E.g. a bundled configuration on the classpath. * If not set it defaults to classpath:application.properties</description> * <param-name>configServletContextListener.fallbackConfigLocation</param-name> * <param-value>classpath:myown.properties</param-value> *</context-param> *</pre> */ private String fallbackConfigLocation; /** * The key used to find the application configuration location. * The system will in order use this key to look up the config location value in the system properties, * the environment variables and in the servlet context params. * It will use the first one that is defined, or default to "application.config.location" * will be used to locate the config location. */ private String configLocationPropertyKey; /** * This value is the key of a property in the application configuration which holds the log4j configuration file location. * It defaults to "application.log4j.config.location" and can be overridden in by stating a * context-param in web.xml like this: * *<pre> *<context-param> * <description>Sets the key for where in the configuration file to look for a log4j * configuration file location. * If not set it defaults to application.log4j.config.location</description> * <param-name>configServletContextListener.log4jConfigLocationPropertyKey</param-name> * <param-value>myown.log4j.config.location</param-value> *</context-param> *</pre> * * The entry that this key points to shall hold a value that ends with .xml or .properties and the * which is the location of the log4j configuration. * If no configuration entry with this key exists or if it is empty then the default Log4j configuration * mechanism will be utilized, e.g. Log4j will look for log4j.xml or log4j.properties on the classpath. */ private String log4jConfigLocationPropertyKey; /** * Application name that is printed when using System.out logging when no * logging manager is available. * Defaults to the display-name of the web.xml or if no display-name exists, the value of * {@link ConfigServletContextListener#DEFAULT_APPLICATION_NAME} */ private String applicationName; private ConfigHelper configHelper; /** * Configures the when the servlet context is initialized. * {@inheritDoc} */ public void contextInitialized(ServletContextEvent sce) { overrideDefaults(sce.getServletContext()); String configLocation = getApplicationConfigurationLocation(sce.getServletContext()); if (!configLocation.startsWith("classpath:") && !configLocation.startsWith("file:")) { configLocation = "file:" + configLocation; logToSystemOut("The application config location neither starts with classpath: nor file:, " + "assuming " + configLocation); } setSystemProperty(this.configLocationPropertyKey, configLocation); PropertyResourceBundle config = configHelper.getApplicationConfiguration(configLocation); if (config != null) { loadApplicationConfigurationSystemProperties(config); loadLoggingConfiguration(config); } } private void overrideDefaults(ServletContext ctx) { this.configLocationPropertyKey = ctx.getInitParameter(OVERRIDE_DEFAULT_CONFIG_LOCATION_PROPERTY_KEY_PARAM); if (StringUtils.isBlank(this.configLocationPropertyKey)) { this.configLocationPropertyKey = DEFAULT_CONFIG_LOCATION_PROPERTY_KEY; } this.log4jConfigLocationPropertyKey = ctx .getInitParameter(OVERRIDE_DEFAULT_LOG4J_CONFIG_LOCATION_PROPERTY_KEY_PARAM); if (StringUtils.isBlank(this.log4jConfigLocationPropertyKey)) { this.log4jConfigLocationPropertyKey = DEFAULT_LOG4J_CONFIG_LOCATION_PROPERTY_KEY; } this.fallbackConfigLocation = ctx.getInitParameter(OVERRIDE_DEFAULT_FALLBACK_CONFIG_LOCATION_PARAM); if (StringUtils.isBlank(this.fallbackConfigLocation)) { this.fallbackConfigLocation = DEFAULT_FALLBACK_CONFIG_LOCATION; } this.applicationName = ctx.getInitParameter(OVERRIDE_DEFAULT_APPLICATION_NAME_PARAM); if (StringUtils.isBlank(this.applicationName)) { this.applicationName = ctx.getServletContextName(); } if (StringUtils.isBlank(this.applicationName)) { this.applicationName = DEFAULT_APPLICATION_NAME; } configHelper = new ConfigHelper(applicationName); } /** * Decides which application configuration file to use and sets the location in the system properties. * By default the location will be set in the property with key "application.config.location" but this * can be overridden by configuration */ private String getApplicationConfigurationLocation(ServletContext ctx) { logToSystemOut("Servlet context initialized, checking for configuration location parameters..."); logToSystemOut("Checking for system property " + this.configLocationPropertyKey); String configLocation = System.getProperty(this.configLocationPropertyKey); if (configLocation == null) { logToSystemOut("Didn't find system property " + this.configLocationPropertyKey + " holding application configuration location, checking environment variable " + this.configLocationPropertyKey); configLocation = System.getenv(this.configLocationPropertyKey); } if (configLocation == null) { logToSystemOut("Didn't find environment variable " + this.configLocationPropertyKey + " holding application configuration location, checking servlet context-param " + this.configLocationPropertyKey); configLocation = ctx.getInitParameter(this.configLocationPropertyKey); } if (configLocation == null) { logToSystemOut("Didn't find servlet-context variable " + this.configLocationPropertyKey + " holding application configuration location, " + "using fallback configuration location: " + this.fallbackConfigLocation); configLocation = this.fallbackConfigLocation; } return configLocation; } /** * Short hand for ConfigHelper#logToSystemOut * @see ConfigHelper#logToSystemOut(String) * @param text the text to log to System.out */ private void logToSystemOut(String text) { configHelper.logToSystemOut(text); } protected void setSystemProperty(String key, String value) { logToSystemOut("Setting system property " + key); System.setProperty(key, value); } /** * Loads system properties from the application configuration * * @param configBundle The application configuration */ protected void loadApplicationConfigurationSystemProperties(PropertyResourceBundle configBundle) { logToSystemOut("Checking for system properties in application configuration"); Enumeration<String> keys = configBundle.getKeys(); while (keys.hasMoreElements()) { String key = keys.nextElement(); if (key.startsWith(CONFIG_SYSTEM_PROPERTY_PREFIX)) { String systemPropertyKey = key.substring(CONFIG_SYSTEM_PROPERTY_PREFIX.length()); if (systemPropertyKey.length() > 0) { setSystemProperty(systemPropertyKey, configBundle.getString(key)); } } } } /** * Loads the log4j configuration from a file whose location is given in the application configuration. * * The location of the log4j configuration file shall by default be specified in * an entry in the application configuration file with key "application.log4j.config.location" * This key can be overridden by configuration if you need to. * * If no such property is found in the application configuration, log4j's default * configuration mechanism will be used, e.g. it will look for log4j.xml * or log4j.properties on the classpath. */ private void loadLoggingConfiguration(PropertyResourceBundle configBundle) { logToSystemOut("Finding log4j configuration location in application configuration..."); String log4jConfigLocation = null; try { log4jConfigLocation = configBundle.getString(this.log4jConfigLocationPropertyKey); } catch (MissingResourceException e) { logToSystemOut("No log4j configuration location was found for property " + this.log4jConfigLocationPropertyKey + " in the application configuration. "); } if (StringUtils.isNotBlank(log4jConfigLocation)) { LogManager.resetConfiguration(); logToSystemOut("Found log4j configuration location in the application configuration. " + "Configuring logger using file: " + log4jConfigLocation); if (log4jConfigLocation.startsWith("file:")) { log4jConfigLocation = log4jConfigLocation.replaceFirst("file:", ""); } if (log4jConfigLocation.endsWith(".xml")) { if (log4jConfigLocation.startsWith("classpath:")) { log4jConfigLocation = log4jConfigLocation.replaceFirst("classpath:", ""); InputStream is = Thread.currentThread().getContextClassLoader() .getResourceAsStream(log4jConfigLocation); new DOMConfigurator().doConfigure(is, LogManager.getLoggerRepository()); } else { DOMConfigurator.configureAndWatch(log4jConfigLocation); } } else if (log4jConfigLocation.endsWith(".properties")) { if (log4jConfigLocation.startsWith("classpath:")) { log4jConfigLocation = log4jConfigLocation.replaceFirst("classpath:", ""); InputStream is = Thread.currentThread().getContextClassLoader() .getResourceAsStream(log4jConfigLocation); PropertyConfigurator.configure(is); } else { PropertyConfigurator.configureAndWatch(log4jConfigLocation); } } else { logToSystemOut("The log4j configuration file location must end with .xml or .properties. " + "\nFalling back to the default log4j configuration mechanism."); } } else { logToSystemOut("Didn't find log4j configuration location in application configuration. " + "Falling back to the default log4j configuration mechanism."); } log.info("Log4j was configured, see System.out log for initialization information."); } /** * Destroys the servlet context and shutting down the Log Manager * which in turn is stopping Log4j's watch dog thread. * {@inheritDoc} */ public void contextDestroyed(ServletContextEvent sce) { log.debug("Servlet context destroyed"); log.debug("Shutting down log manager..."); logToSystemOut("Destroying servlet context..."); logToSystemOut("Shutting down log manager..."); LogManager.shutdown(); logToSystemOut("The log manager has been shut down."); logToSystemOut("The servlet context has been destroyed."); } }