From b7510fa355be518a419d3bd69d3503ff946b27f3 Mon Sep 17 00:00:00 2001 From: jeb228 Date: Fri, 25 Feb 2011 16:49:21 +0000 Subject: [PATCH] NIHVIVO-1261 Add the new ConfigurationProperties classes, and add the setup to web.xml --- webapp/config/web.xml | 5 + .../config/ConfigurationProperties.java | 145 ++++++++++++++ .../config/ConfigurationPropertiesImpl.java | 90 +++++++++ .../config/ConfigurationPropertiesSetup.java | 186 ++++++++++++++++++ .../config/DummyConfigurationProperties.java | 41 ++++ .../config/ConfigurationPropertiesStub.java | 66 +++++++ 6 files changed, 533 insertions(+) create mode 100644 webapp/src/edu/cornell/mannlib/vitro/webapp/config/ConfigurationProperties.java create mode 100644 webapp/src/edu/cornell/mannlib/vitro/webapp/config/ConfigurationPropertiesImpl.java create mode 100644 webapp/src/edu/cornell/mannlib/vitro/webapp/config/ConfigurationPropertiesSetup.java create mode 100644 webapp/src/edu/cornell/mannlib/vitro/webapp/config/DummyConfigurationProperties.java create mode 100644 webapp/test/stubs/edu/cornell/mannlib/vitro/webapp/config/ConfigurationPropertiesStub.java diff --git a/webapp/config/web.xml b/webapp/config/web.xml index ff1b6f32c..4301cfa2a 100644 --- a/webapp/config/web.xml +++ b/webapp/config/web.xml @@ -39,6 +39,11 @@ + + + edu.cornell.mannlib.vitro.webapp.config.ConfigurationPropertiesSetup + + edu.cornell.mannlib.vitro.webapp.config.RevisionInfoSetup diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/config/ConfigurationProperties.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/config/ConfigurationProperties.java new file mode 100644 index 000000000..0109c9286 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/config/ConfigurationProperties.java @@ -0,0 +1,145 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.config; + +import java.util.Map; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletRequest; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Provides an mechanism for modules to read the configuration properties that + * are attached to the servlet context. + * + * The customary behavior is for ConfigurationPropertiesSetup to create a + * ConfigurationPropertiesImpl, which will parse the deploy.properties file for + * these properties. + */ +public abstract class ConfigurationProperties { + private static final Log log = LogFactory + .getLog(ConfigurationProperties.class); + + /** The bean is attached to the session by this name. */ + private static final String ATTRIBUTE_NAME = ConfigurationProperties.class + .getName(); + + /** If they ask for a bean before one has been set, they get this. */ + private static final ConfigurationProperties DUMMY_PROPERTIES = new DummyConfigurationProperties(); + + // ---------------------------------------------------------------------- + // static methods + // ---------------------------------------------------------------------- + + public static ConfigurationProperties getBean(ServletRequest request) { + if (request == null) { + throw new NullPointerException("request may not be null."); + } + if (!(request instanceof HttpServletRequest)) { + throw new IllegalArgumentException( + "request must be an HttpServletRequest"); + } + HttpServletRequest httpRequest = (HttpServletRequest) request; + return getBean(httpRequest.getSession()); + } + + public static ConfigurationProperties getBean(HttpSession session) { + if (session == null) { + throw new NullPointerException("session may not be null."); + } + return getBean(session.getServletContext()); + } + + public static ConfigurationProperties getBean(HttpServlet servlet) { + if (servlet == null) { + throw new NullPointerException("servlet may not be null."); + } + return getBean(servlet.getServletContext()); + } + + public static ConfigurationProperties getBean(ServletContextEvent sce) { + if (sce == null) { + throw new NullPointerException("sce may not be null."); + } + return getBean(sce.getServletContext()); + } + + public static ConfigurationProperties getBean(ServletConfig servletConfig) { + if (servletConfig == null) { + throw new NullPointerException("servletConfig may not be null."); + } + return getBean(servletConfig.getServletContext()); + } + + public static ConfigurationProperties getBean(ServletContext context) { + if (context == null) { + throw new NullPointerException("context may not be null."); + } + + Object o = context.getAttribute(ATTRIBUTE_NAME); + if (o == null) { + log.error("ConfigurationProperties bean has not been set."); + return DUMMY_PROPERTIES; + } else if (!(o instanceof ConfigurationProperties)) { + log.error("Error: ConfigurationProperties was set to an " + + "invalid object: " + o); + return DUMMY_PROPERTIES; + } + + return (ConfigurationProperties) o; + } + + /** + * Protected access, so the Stub class can call it for unit tests. + * Otherwise, this should only be called by ConfigurationPropertiesSetup. + */ + protected static void setBean(ServletContext context, + ConfigurationProperties bean) { + if (context == null) { + throw new NullPointerException("context may not be null."); + } + if (bean == null) { + throw new NullPointerException("bean may not be null."); + } + context.setAttribute(ATTRIBUTE_NAME, bean); + log.info(bean); + } + + /** Package access, so unit tests can call it. */ + static void removeBean(ServletContext context) { + if (context == null) { + throw new NullPointerException("context may not be null."); + } + context.removeAttribute(ATTRIBUTE_NAME); + } + + // ---------------------------------------------------------------------- + // The interface + // ---------------------------------------------------------------------- + + /** + * Get the value of the property, or null if the property has + * not been assigned a value. + */ + public abstract String getProperty(String key); + + /** + * Get the value of the property, or use the default value if the property + * has not been assigned a value. + */ + public abstract String getProperty(String key, String defaultValue); + + /** + * Get a copy of the map of the configuration properties and their settings. + * Because this is a copy, it cannot be used to modify the settings. + */ + public abstract Map getPropertyMap(); + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/config/ConfigurationPropertiesImpl.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/config/ConfigurationPropertiesImpl.java new file mode 100644 index 000000000..f0440b5de --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/config/ConfigurationPropertiesImpl.java @@ -0,0 +1,90 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.config; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * The basic implementation of ConfigurationProperties. It loads the + * configuration properties from a properties file and stores them in a map. + * + * Leading and trailing white space are trimmed from the property values. + * + * Once the properties have been parsed and stored, they are immutable. + */ +public class ConfigurationPropertiesImpl extends ConfigurationProperties { + private static final Log log = LogFactory + .getLog(ConfigurationPropertiesImpl.class); + + private final Map propertyMap; + + public ConfigurationPropertiesImpl(InputStream stream) { + Properties props = loadFromPropertiesFile(stream); + Map map = copyPropertiesToMap(props); + trimWhiteSpaceFromValues(map); + this.propertyMap = Collections.unmodifiableMap(map); + + log.debug("Configuration properties are: " + map); + } + + private Properties loadFromPropertiesFile(InputStream stream) { + Properties props = new Properties(); + try { + props.load(stream); + } catch (IOException e) { + throw new IllegalStateException( + "Failed to parse the configuration properties file.", e); + } + return props; + } + + private Map copyPropertiesToMap(Properties props) { + Map map = new HashMap(); + for (Enumeration keys = props.keys(); keys.hasMoreElements();) { + String key = (String) keys.nextElement(); + String value = props.getProperty(key); + map.put(key, value); + } + return map; + } + + private void trimWhiteSpaceFromValues(Map map) { + for (String key : map.keySet()) { + map.put(key, map.get(key).trim()); + } + } + + @Override + public String getProperty(String key) { + return propertyMap.get(key); + } + + @Override + public String getProperty(String key, String defaultValue) { + if (propertyMap.containsKey(key)) { + return propertyMap.get(key); + } else { + return defaultValue; + } + } + + @Override + public Map getPropertyMap() { + return new HashMap(propertyMap); + } + + @Override + public String toString() { + return "ConfigurationPropertiesImpl[propertyMap=" + propertyMap + "]"; + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/config/ConfigurationPropertiesSetup.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/config/ConfigurationPropertiesSetup.java new file mode 100644 index 000000000..4cdfe55bc --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/config/ConfigurationPropertiesSetup.java @@ -0,0 +1,186 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.config; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; + +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import edu.cornell.mannlib.vitro.webapp.servlet.setup.AbortStartup; + +/** + * Reads the configuration properties from a file and stores them in the servlet + * context. + * + * This must be invoked before any listener that requires configuration + * properties. + * + * The path to the file can be specified by an Environment name in the Context, + * like this: + * + *
+ * 
+ * 
+ *     
+ * 
+ * 
+ * 
+ * + * We look in this environment variable to find the path to the properties file. + * If there is no such environment variable, the default path is used. + * + * Once the path has been determined, we will use it to look for a resource in + * the classpath. So if the path is "deploy.properties", it might be found in + * "tomcat/webapps/vivo/WEB-INF/classes/deploy.properties". Of course, it might + * also be found in any other portion of the classpath as well. + * + * If we can't find the resource in the classpath, we will use it to look for an + * external file. So, one might reasonably set this value to something like + * "/usr/local/vitro/stuff/my.deploy.properties". + * + * If neither a resource nor an external file can be found, we throw an + * exception and set AbortStartup. + */ +public class ConfigurationPropertiesSetup implements ServletContextListener { + private static final Log log = LogFactory + .getLog(ConfigurationPropertiesSetup.class); + + /** + * The JNDI naming context where Tomcat stores environment attributes. + */ + static final String JNDI_BASE = "java:comp/env"; + + /** + * The name of the JNDI environment mapping for the path to the + * configuration file (or resource). + */ + static final String PATH_CONFIGURATION = "path.configuration"; + + /** + * If we don't find the path to the config properties from a JNDI mapping, + * use this. Not final, so we can jigger it for unit tests. + */ + private static String DEFAULT_CONFIG_PATH = "deploy.properties"; + + @Override + public void contextInitialized(ServletContextEvent sce) { + if (AbortStartup.isStartupAborted(sce.getServletContext())) { + return; + } + + ServletContext ctx = sce.getServletContext(); + + try { + InputStream stream = null; + try { + stream = locatePropertiesFile(); + ConfigurationProperties.setBean(ctx, + new ConfigurationPropertiesImpl(stream)); + } finally { + if (stream != null) { + try { + stream.close(); + } catch (IOException e) { + log.error(e, e); + } + } + } + } catch (Exception e) { + log.error(e, e); + AbortStartup.abortStartup(ctx); + throw new RuntimeException(e); + } + } + + private InputStream locatePropertiesFile() { + String path = determinePathToProperties(); + log.debug("Configuration properties path is '" + path + "'"); + + if (resourceExists(path)) { + log.debug("Found configuration properties as a resource."); + return getResourceStream(path); + } + + if (externalFileExists(path)) { + log.debug("Found configuration properties as an external file."); + return getExternalFileStream(path); + } + + throw new IllegalStateException("Can't find the properties file at '" + + path + "'"); + } + + /** + * If we can't find it with JNDI, use the default. + */ + private String determinePathToProperties() { + try { + Context envCtx = (Context) new InitialContext().lookup(JNDI_BASE); + if (envCtx == null) { + log.debug("JNDI Lookup on '" + JNDI_BASE + "' failed."); + return DEFAULT_CONFIG_PATH; + } + + String configPath = (String) envCtx.lookup(PATH_CONFIGURATION); + if (configPath == null) { + log.debug("JNDI Lookup on '" + PATH_CONFIGURATION + "' failed."); + return DEFAULT_CONFIG_PATH; + } + + log.debug("deploy.property as specified by JNDI: " + configPath); + return configPath; + } catch (NamingException e) { + log.warn("JNDI lookup failed. " + + "Using default path for config properties.", e); + return DEFAULT_CONFIG_PATH; + } + } + + private boolean resourceExists(String path) { + return getResourceStream(path) != null; + } + + private InputStream getResourceStream(String path) { + return getClass().getClassLoader().getResourceAsStream(path); + } + + private boolean externalFileExists(String path) { + File file = new File(path); + return file.isFile(); + } + + private InputStream getExternalFileStream(String path) { + InputStream stream = null; + File file = new File(path); + if (file.isFile()) { + try { + stream = new FileInputStream(file); + } catch (FileNotFoundException e) { + // testing file.isFile() should have prevented this + log.error(e, e); + } + } + return stream; + } + + @Override + public void contextDestroyed(ServletContextEvent sce) { + ConfigurationProperties.removeBean(sce.getServletContext()); + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/config/DummyConfigurationProperties.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/config/DummyConfigurationProperties.java new file mode 100644 index 000000000..434c39efe --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/config/DummyConfigurationProperties.java @@ -0,0 +1,41 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.config; + +import java.util.Collections; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * If somebody asks for ConfigurationProperties before it has been initialized, + * they get this. It doesn't stop them from proceeding, it just yields no + * properties while logging warning messages for each request. + */ +class DummyConfigurationProperties extends ConfigurationProperties { + private static final Log log = LogFactory + .getLog(DummyConfigurationProperties.class); + + @Override + public String getProperty(String key) { + log.warn("ConfigurationProperties has not been initialized: getProperty(\"" + + key + "\")"); + return null; + } + + @Override + public String getProperty(String key, String defaultValue) { + log.warn("ConfigurationProperties has not been initialized: getProperty(\"" + + key + "\", \"" + defaultValue + "\")"); + return defaultValue; + } + + @Override + public Map getPropertyMap() { + log.warn("ConfigurationProperties has not been initialized: " + + "getPropertyMap()"); + return Collections.emptyMap(); + } + +} diff --git a/webapp/test/stubs/edu/cornell/mannlib/vitro/webapp/config/ConfigurationPropertiesStub.java b/webapp/test/stubs/edu/cornell/mannlib/vitro/webapp/config/ConfigurationPropertiesStub.java new file mode 100644 index 000000000..f259542da --- /dev/null +++ b/webapp/test/stubs/edu/cornell/mannlib/vitro/webapp/config/ConfigurationPropertiesStub.java @@ -0,0 +1,66 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package stubs.edu.cornell.mannlib.vitro.webapp.config; + +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.ServletContext; + +import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties; + +/** + * A version of ConfigurationProperties that we can use for unit tests. Unlike + * the basic implementation, this starts as an empty map, and allows the user to + * add properties as desired. + * + * Call setBean() to store these properties in the ServletContext. + */ +public class ConfigurationPropertiesStub extends ConfigurationProperties { + // ---------------------------------------------------------------------- + // Stub infrastructure + // ---------------------------------------------------------------------- + + private final Map propertyMap = new HashMap(); + + public void setProperty(String key, String value) { + propertyMap.put(key, value); + } + + public void setBean(ServletContext ctx) { + setBean(ctx, this); + } + + @Override + public String toString() { + return "ConfigurationPropertiesStub[map=" + propertyMap + "]"; + } + + // ---------------------------------------------------------------------- + // Stub methods + // ---------------------------------------------------------------------- + + @Override + public String getProperty(String key) { + return propertyMap.get(key); + } + + @Override + public String getProperty(String key, String defaultValue) { + if (propertyMap.containsKey(key)) { + return propertyMap.get(key); + } else { + return defaultValue; + } + } + + @Override + public Map getPropertyMap() { + return new HashMap(propertyMap); + } + + // ---------------------------------------------------------------------- + // Un-implemented methods + // ---------------------------------------------------------------------- + +}