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
+ // ----------------------------------------------------------------------
+
+}