NIHVIVO-1261 Add the new ConfigurationProperties classes, and add the setup to web.xml

This commit is contained in:
jeb228 2011-02-25 16:49:21 +00:00
parent 3134054f7b
commit b7510fa355
6 changed files with 533 additions and 0 deletions

View file

@ -39,6 +39,11 @@
<!-- Listeners ****************************************************** --> <!-- Listeners ****************************************************** -->
<!-- Reads deploy.properties. Needs to run before almost all other listeners. -->
<listener>
<listener-class>edu.cornell.mannlib.vitro.webapp.config.ConfigurationPropertiesSetup</listener-class>
</listener>
<listener> <listener>
<listener-class>edu.cornell.mannlib.vitro.webapp.config.RevisionInfoSetup</listener-class> <listener-class>edu.cornell.mannlib.vitro.webapp.config.RevisionInfoSetup</listener-class>
</listener> </listener>

View file

@ -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 <code>null</code> 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<String, String> getPropertyMap();
}

View file

@ -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<String, String> propertyMap;
public ConfigurationPropertiesImpl(InputStream stream) {
Properties props = loadFromPropertiesFile(stream);
Map<String, String> 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<String, String> copyPropertiesToMap(Properties props) {
Map<String, String> map = new HashMap<String, String>();
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<String, String> 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<String, String> getPropertyMap() {
return new HashMap<String, String>(propertyMap);
}
@Override
public String toString() {
return "ConfigurationPropertiesImpl[propertyMap=" + propertyMap + "]";
}
}

View file

@ -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:
*
* <pre>
*
* <Context override="true">
* <Environment name="path.configuration"
* value="/wherever/the/file/lives/deploy.properties"
* type="java.lang.String"
* override="false" />
* </Context>
*
* </pre>
*
* 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());
}
}

View file

@ -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<String, String> getPropertyMap() {
log.warn("ConfigurationProperties has not been initialized: "
+ "getPropertyMap()");
return Collections.emptyMap();
}
}

View file

@ -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<String, String> propertyMap = new HashMap<String, String>();
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<String, String> getPropertyMap() {
return new HashMap<String, String>(propertyMap);
}
// ----------------------------------------------------------------------
// Un-implemented methods
// ----------------------------------------------------------------------
}