NIHVIVO-336 Create a StartupManager to run all of the context listeners, and accumulate the results in a StartupStatus bean.
This commit is contained in:
parent
5a64431b71
commit
65bba272df
6 changed files with 721 additions and 146 deletions
|
@ -155,6 +155,10 @@ deploy - Deploy the application directly into the Tomcat webapps directory.
|
||||||
<fileset file="${webapp.dir}/config/dwr.xml" />
|
<fileset file="${webapp.dir}/config/dwr.xml" />
|
||||||
</copy>
|
</copy>
|
||||||
|
|
||||||
|
<copy todir="${war-resources.dir}">
|
||||||
|
<fileset file="${webapp.dir}/config/startup_listeners.txt" />
|
||||||
|
</copy>
|
||||||
|
|
||||||
<!-- copy the ontologies and the filegraphs into the war directory. -->
|
<!-- copy the ontologies and the filegraphs into the war directory. -->
|
||||||
<copy todir="${war-webinf.dir}">
|
<copy todir="${war-webinf.dir}">
|
||||||
<fileset dir="${webapp.dir}" includes="ontologies" />
|
<fileset dir="${webapp.dir}" includes="ontologies" />
|
||||||
|
|
60
webapp/config/startup_listeners.txt
Normal file
60
webapp/config/startup_listeners.txt
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
#
|
||||||
|
# ServletContextListeners for Vitro, to be instantiated and run by the StartupManager.
|
||||||
|
#
|
||||||
|
|
||||||
|
edu.cornell.mannlib.vitro.webapp.config.ConfigurationPropertiesSetup
|
||||||
|
|
||||||
|
edu.cornell.mannlib.vitro.webapp.config.RevisionInfoSetup
|
||||||
|
|
||||||
|
edu.cornell.mannlib.vitro.webapp.email.FreemarkerEmailFactory$Setup
|
||||||
|
|
||||||
|
# DefaultThemeSetup needs to run before the JenaDataSourceSetup to allow creation of default portal and tab
|
||||||
|
edu.cornell.mannlib.vitro.webapp.servlet.setup.DefaultThemeSetup
|
||||||
|
|
||||||
|
# Comment out this listener to run Vitro without a database
|
||||||
|
# If used, this listener must be run before JenaDataSourceSetup
|
||||||
|
edu.cornell.mannlib.vitro.webapp.servlet.setup.JenaPersistentDataSourceSetup
|
||||||
|
|
||||||
|
# This listener is required in order to use a Jena triple store (currently the only option)
|
||||||
|
edu.cornell.mannlib.vitro.webapp.servlet.setup.JenaDataSourceSetup
|
||||||
|
|
||||||
|
edu.cornell.mannlib.vitro.webapp.servlet.setup.UpdateKnowledgeBase
|
||||||
|
|
||||||
|
edu.cornell.mannlib.vitro.webapp.filestorage.backend.FileStorageSetup
|
||||||
|
|
||||||
|
# Invokes a process to move any uploaded files into the new file storage system.
|
||||||
|
# Needs to run after FileStorageSetup and JenaDataSourceSetup.
|
||||||
|
# Should run before Pellet is set up.
|
||||||
|
edu.cornell.mannlib.vitro.webapp.servlet.setup.UpdateUploadedFiles
|
||||||
|
|
||||||
|
# Update to the new UserAccounts model (1.3). Needs to run after JenaDataSourceSetup.
|
||||||
|
edu.cornell.mannlib.vitro.webapp.servlet.setup.UpdateUserAccounts
|
||||||
|
|
||||||
|
# Attaching submodels permits extra RDF files to be made visible without storing the data in the DB.
|
||||||
|
edu.cornell.mannlib.vitro.webapp.servlet.setup.AttachSubmodels
|
||||||
|
|
||||||
|
# Pellet setup enables reasoning. Inferences are cached in a separate DB graph.
|
||||||
|
# Changing the class name sets the kinds of inferences that are materialized.
|
||||||
|
# See documentation for details.
|
||||||
|
# If used, must be run after JenaDataSourceSetup
|
||||||
|
edu.cornell.mannlib.vitro.webapp.servlet.setup.PelletReasonerSetup
|
||||||
|
|
||||||
|
edu.cornell.mannlib.vitro.webapp.auth.permissions.PermissionSetsLoader
|
||||||
|
|
||||||
|
edu.cornell.mannlib.vitro.webapp.auth.policy.bean.PropertyRestrictionPolicyHelper$Setup
|
||||||
|
|
||||||
|
edu.cornell.mannlib.vitro.webapp.auth.policy.setup.CommonPolicyFamilySetup
|
||||||
|
|
||||||
|
edu.cornell.mannlib.vitro.webapp.auth.policy.RootUserPolicy$Setup
|
||||||
|
|
||||||
|
edu.cornell.mannlib.vitro.webapp.auth.policy.RestrictHomeMenuItemEditingPolicy$Setup
|
||||||
|
|
||||||
|
# The Solr index uses a "public" filter, so the PropertyRestrictionPolicyHelper must already be set up.
|
||||||
|
edu.cornell.mannlib.vitro.webapp.search.solr.SolrSetup
|
||||||
|
|
||||||
|
edu.cornell.mannlib.vitro.webapp.controller.freemarker.FreemarkerSetup
|
||||||
|
|
||||||
|
# On shutdown, this will kill the background thread started by Apache Commons File Upload
|
||||||
|
org.apache.commons.fileupload.servlet.FileCleanerCleanup
|
||||||
|
|
||||||
|
edu.cornell.mannlib.vitro.webapp.dao.jena.VClassGroupCache$Setup
|
|
@ -41,155 +41,11 @@
|
||||||
|
|
||||||
<!-- Listeners ****************************************************** -->
|
<!-- Listeners ****************************************************** -->
|
||||||
|
|
||||||
<!-- Reads deploy.properties. Needs to run before almost all other listeners. -->
|
<!-- StartupManager instantiates and runs the listeners from startup_listeners.txt -->
|
||||||
<listener>
|
<listener>
|
||||||
<listener-class>edu.cornell.mannlib.vitro.webapp.config.ConfigurationPropertiesSetup</listener-class>
|
<listener-class>edu.cornell.mannlib.vitro.webapp.startup.StartupManager</listener-class>
|
||||||
</listener>
|
</listener>
|
||||||
|
|
||||||
<listener>
|
|
||||||
<listener-class>edu.cornell.mannlib.vitro.webapp.config.RevisionInfoSetup</listener-class>
|
|
||||||
</listener>
|
|
||||||
|
|
||||||
<listener>
|
|
||||||
<listener-class>edu.cornell.mannlib.vitro.webapp.email.FreemarkerEmailFactory$Setup</listener-class>
|
|
||||||
</listener>
|
|
||||||
|
|
||||||
<!-- DefaultThemeSetup needs to run before the JenaDataSourceSetup
|
|
||||||
to allow creation of default portal and tab -->
|
|
||||||
<listener>
|
|
||||||
<listener-class>edu.cornell.mannlib.vitro.webapp.servlet.setup.DefaultThemeSetup</listener-class>
|
|
||||||
</listener>
|
|
||||||
|
|
||||||
<!-- Comment out this listener to run Vitro without a database -->
|
|
||||||
<!-- If used, this listener must be run before JenaDataSourceSetup -->
|
|
||||||
<listener>
|
|
||||||
<listener-class>edu.cornell.mannlib.vitro.webapp.servlet.setup.JenaPersistentDataSourceSetup</listener-class>
|
|
||||||
</listener>
|
|
||||||
|
|
||||||
<!-- experimental JenaMulgaraBridge, replaces JenaPersistentDataSourceSetup. -->
|
|
||||||
<!-- <listener>
|
|
||||||
-->
|
|
||||||
<!-- <listener-class>
|
|
||||||
-->
|
|
||||||
<!-- edu.cornell.mannlib.vitro.webapp.servlet.setup.JenaMulgaraBridgeSetup
|
|
||||||
-->
|
|
||||||
<!-- </listener-class>
|
|
||||||
-->
|
|
||||||
<!-- </listener> -->
|
|
||||||
|
|
||||||
<!-- This listener is required in order to use a Jena triple store (currently the only option) -->
|
|
||||||
<listener>
|
|
||||||
<listener-class>edu.cornell.mannlib.vitro.webapp.servlet.setup.JenaDataSourceSetup</listener-class>
|
|
||||||
</listener>
|
|
||||||
|
|
||||||
<!-- Invokes process to perform updates to align with ontology changes if needed -->
|
|
||||||
<!-- Needs to run before submodels are attached and Pellet is set up -->
|
|
||||||
|
|
||||||
<listener>
|
|
||||||
<listener-class>edu.cornell.mannlib.vitro.webapp.servlet.setup.UpdateKnowledgeBase</listener-class>
|
|
||||||
</listener>
|
|
||||||
|
|
||||||
<listener>
|
|
||||||
<listener-class>
|
|
||||||
edu.cornell.mannlib.vitro.webapp.filestorage.backend.FileStorageSetup
|
|
||||||
</listener-class>
|
|
||||||
</listener>
|
|
||||||
|
|
||||||
<!-- Invokes a process to move any uploaded files into the new file storage system. -->
|
|
||||||
<!-- Needs to run after FileStorageSetup and JenaDataSourceSetup. -->
|
|
||||||
<!-- Should run before Pellet is set up. -->
|
|
||||||
<listener>
|
|
||||||
<listener-class>
|
|
||||||
edu.cornell.mannlib.vitro.webapp.servlet.setup.UpdateUploadedFiles
|
|
||||||
</listener-class>
|
|
||||||
</listener>
|
|
||||||
|
|
||||||
<!-- Update to the new UserAccounts model (1.3). Needs to run after JenaDataSourceSetup. -->
|
|
||||||
<listener>
|
|
||||||
<listener-class>
|
|
||||||
edu.cornell.mannlib.vitro.webapp.servlet.setup.UpdateUserAccounts
|
|
||||||
</listener-class>
|
|
||||||
</listener>
|
|
||||||
|
|
||||||
<!-- Attaching submodels permits extra RDF files to be made visible without storing the data in the DB. -->
|
|
||||||
<listener>
|
|
||||||
<listener-class>edu.cornell.mannlib.vitro.webapp.servlet.setup.AttachSubmodels</listener-class>
|
|
||||||
</listener>
|
|
||||||
|
|
||||||
<!-- Pellet setup enables reasoning. Inferences are cached in a separate DB graph. -->
|
|
||||||
<!-- Changing the class name sets the kinds of inferences that are materialized. -->
|
|
||||||
<!-- See documentation for details. -->
|
|
||||||
<!-- If used, must be run after JenaDataSourceSetup -->
|
|
||||||
|
|
||||||
<listener>
|
|
||||||
<listener-class>edu.cornell.mannlib.vitro.webapp.servlet.setup.PelletReasonerSetup</listener-class>
|
|
||||||
</listener>
|
|
||||||
|
|
||||||
<!-- The followng listener records all edit changes, in reified form, to another database model -->
|
|
||||||
<!-- still at an experimental stage -->
|
|
||||||
<!-- if used, this listener should be run after all other Jena-related listeners, to avoid logging unnecessary data and slowing the context startup process -->
|
|
||||||
|
|
||||||
<!--
|
|
||||||
<listener>
|
|
||||||
<listener-class>
|
|
||||||
edu.cornell.mannlib.vitro.webapp.servlet.setup.ModelAuditorSetup
|
|
||||||
</listener-class>
|
|
||||||
</listener>
|
|
||||||
-->
|
|
||||||
|
|
||||||
<listener>
|
|
||||||
<listener-class>edu.cornell.mannlib.vitro.webapp.auth.permissions.PermissionSetsLoader</listener-class>
|
|
||||||
</listener>
|
|
||||||
|
|
||||||
<listener>
|
|
||||||
<listener-class> edu.cornell.mannlib.vitro.webapp.auth.policy.bean.PropertyRestrictionPolicyHelper$Setup
|
|
||||||
</listener-class>
|
|
||||||
</listener>
|
|
||||||
|
|
||||||
<listener>
|
|
||||||
<listener-class> edu.cornell.mannlib.vitro.webapp.auth.policy.setup.CommonPolicyFamilySetup
|
|
||||||
</listener-class>
|
|
||||||
</listener>
|
|
||||||
|
|
||||||
<listener>
|
|
||||||
<listener-class> edu.cornell.mannlib.vitro.webapp.auth.policy.RootUserPolicy$Setup</listener-class>
|
|
||||||
</listener>
|
|
||||||
|
|
||||||
<listener>
|
|
||||||
<listener-class> edu.cornell.mannlib.vitro.webapp.auth.policy.RestrictHomeMenuItemEditingPolicy$Setup</listener-class>
|
|
||||||
</listener>
|
|
||||||
|
|
||||||
<!-- The Solr index uses a "public" filter, so the PropertyRestrictionPolicyHelper must already be set up. -->
|
|
||||||
<listener>
|
|
||||||
<listener-class>
|
|
||||||
edu.cornell.mannlib.vitro.webapp.search.solr.SolrSetup
|
|
||||||
</listener-class>
|
|
||||||
</listener>
|
|
||||||
|
|
||||||
<listener>
|
|
||||||
<listener-class>
|
|
||||||
edu.cornell.mannlib.vitro.webapp.controller.freemarker.FreemarkerSetup
|
|
||||||
</listener-class>
|
|
||||||
</listener>
|
|
||||||
|
|
||||||
<!-- On shutdown, this will kill the background thread started by Apache Commons File Upload -->
|
|
||||||
<listener>
|
|
||||||
<listener-class>
|
|
||||||
org.apache.commons.fileupload.servlet.FileCleanerCleanup
|
|
||||||
</listener-class>
|
|
||||||
</listener>
|
|
||||||
|
|
||||||
<listener>
|
|
||||||
<listener-class>edu.cornell.mannlib.vitro.webapp.dao.jena.VClassGroupCache$Setup</listener-class>
|
|
||||||
</listener>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
<listener>
|
|
||||||
<listener-class>
|
|
||||||
edu.cornell.mannlib.vitro.webapp.auth.policy.setup.AlwaysAuthorizePolicySetup
|
|
||||||
</listener-class>
|
|
||||||
</listener>
|
|
||||||
-->
|
|
||||||
|
|
||||||
<!-- Filters ********************************************************** -->
|
<!-- Filters ********************************************************** -->
|
||||||
<!-- in 2.4 spec, filter chain order is first by filter-mapping <url-pattern> order in web.xml,
|
<!-- in 2.4 spec, filter chain order is first by filter-mapping <url-pattern> order in web.xml,
|
||||||
|
|
|
@ -0,0 +1,230 @@
|
||||||
|
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||||
|
|
||||||
|
package edu.cornell.mannlib.vitro.webapp.startup;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.servlet.ServletContext;
|
||||||
|
import javax.servlet.ServletContextEvent;
|
||||||
|
import javax.servlet.ServletContextListener;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO
|
||||||
|
*/
|
||||||
|
public class StartupManager implements ServletContextListener {
|
||||||
|
private static final Log log = LogFactory.getLog(StartupManager.class);
|
||||||
|
|
||||||
|
public static final String FILE_OF_STARTUP_LISTENERS = "/WEB-INF/resources/startup_listeners.txt";
|
||||||
|
|
||||||
|
private final List<ServletContextListener> initializeList = new ArrayList<ServletContextListener>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These can be instance variables without risk, since contextInitialized()
|
||||||
|
* will only be called once per instance.
|
||||||
|
*/
|
||||||
|
private ServletContext ctx;
|
||||||
|
private StartupStatus ss;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a list of the listeners, and run contextInitialized() on each of
|
||||||
|
* them, at least until we get a fatal error.
|
||||||
|
*
|
||||||
|
* Each step of this should handle its own exceptions, but we'll wrap the
|
||||||
|
* whole thing in a try/catch just in case.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void contextInitialized(ServletContextEvent sce) {
|
||||||
|
ctx = sce.getServletContext();
|
||||||
|
ss = StartupStatus.getBean(ctx);
|
||||||
|
|
||||||
|
try {
|
||||||
|
findAndInstantiateListeners();
|
||||||
|
|
||||||
|
for (ServletContextListener listener : initializeList) {
|
||||||
|
if (ss.isStartupAborted()) {
|
||||||
|
ss.listenerNotExecuted(listener);
|
||||||
|
} else {
|
||||||
|
initialize(listener, sce);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
recordFatal("Startup threw an unexpected exception.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the file and instantiate build a list of listener instances.
|
||||||
|
*
|
||||||
|
* If there is a problem, it will occur and be handled in a sub-method.
|
||||||
|
*/
|
||||||
|
private void findAndInstantiateListeners() {
|
||||||
|
List<String> classNames = readFileOfListeners();
|
||||||
|
|
||||||
|
for (String className : classNames) {
|
||||||
|
ServletContextListener listener = instantiateListener(className);
|
||||||
|
if (listener != null) {
|
||||||
|
initializeList.add(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkForDuplicateListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the names of the listener classes.
|
||||||
|
*
|
||||||
|
* If there is a problem, set a fatal error, and return an empty list.
|
||||||
|
*/
|
||||||
|
private List<String> readFileOfListeners() {
|
||||||
|
List<String> list = new ArrayList<String>();
|
||||||
|
|
||||||
|
InputStream is = null;
|
||||||
|
BufferedReader br = null;
|
||||||
|
try {
|
||||||
|
is = ctx.getResourceAsStream(FILE_OF_STARTUP_LISTENERS);
|
||||||
|
br = new BufferedReader(new InputStreamReader(is));
|
||||||
|
|
||||||
|
String line;
|
||||||
|
while (null != (line = br.readLine())) {
|
||||||
|
String trimmed = line.trim();
|
||||||
|
if (!trimmed.isEmpty() && !trimmed.startsWith("#")) {
|
||||||
|
list.add(trimmed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
recordFatal("Unable to locate the list of startup listeners: "
|
||||||
|
+ FILE_OF_STARTUP_LISTENERS);
|
||||||
|
} catch (IOException e) {
|
||||||
|
recordFatal(
|
||||||
|
"Failed while processing the list of startup listeners: "
|
||||||
|
+ FILE_OF_STARTUP_LISTENERS, e);
|
||||||
|
} finally {
|
||||||
|
if (br != null) {
|
||||||
|
try {
|
||||||
|
br.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is != null) {
|
||||||
|
try {
|
||||||
|
is.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("Classnames of listeners = " + list);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate a context listener from this class name.
|
||||||
|
*
|
||||||
|
* If there is a problem, set a fatal error, and return null.
|
||||||
|
*/
|
||||||
|
private ServletContextListener instantiateListener(String className) {
|
||||||
|
try {
|
||||||
|
Class<?> c = Class.forName(className);
|
||||||
|
Object o = c.newInstance();
|
||||||
|
return (ServletContextListener) o;
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
recordFatal("Failed to instantiate listener: '" + className + "'",
|
||||||
|
e);
|
||||||
|
return null;
|
||||||
|
} catch (InstantiationException e) {
|
||||||
|
recordFatal("Failed to instantiate listener: '" + className + "'",
|
||||||
|
e);
|
||||||
|
return null;
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
recordFatal("Failed to instantiate listener: '" + className + "'",
|
||||||
|
e);
|
||||||
|
return null;
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
recordFatal("Instance of '" + className
|
||||||
|
+ "' is not a ServletContextListener", e);
|
||||||
|
return null;
|
||||||
|
} catch (Exception e) {
|
||||||
|
recordFatal("Failed to instantiate listener: '" + className + "'",
|
||||||
|
e);
|
||||||
|
return null;
|
||||||
|
} catch (ExceptionInInitializerError e) {
|
||||||
|
recordFatal("Failed to instantiate listener: '" + className + "'",
|
||||||
|
e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call contextInitialized() on the listener.
|
||||||
|
*
|
||||||
|
* If there is an unexpected exception, set a fatal error.
|
||||||
|
*/
|
||||||
|
private void initialize(ServletContextListener listener,
|
||||||
|
ServletContextEvent sce) {
|
||||||
|
try {
|
||||||
|
log.debug("Initializing '" + listener.getClass().getName() + "'");
|
||||||
|
listener.contextInitialized(sce);
|
||||||
|
} catch (Exception e) {
|
||||||
|
ss.fatal(listener, "Threw unexpected exception", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If we have more than one listener from the same class, set a fatal error.
|
||||||
|
*/
|
||||||
|
private void checkForDuplicateListeners() {
|
||||||
|
for (int i = 0; i < initializeList.size(); i++) {
|
||||||
|
for (int j = i + 1; j < initializeList.size(); j++) {
|
||||||
|
ServletContextListener iListener = initializeList.get(i);
|
||||||
|
ServletContextListener jListener = initializeList.get(j);
|
||||||
|
if (iListener.getClass().equals(jListener.getClass())) {
|
||||||
|
recordFatal("File contains duplicate listener classes: '"
|
||||||
|
+ iListener.getClass().getName() + "'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void recordFatal(String message) {
|
||||||
|
ss.fatal(this, message);
|
||||||
|
log.error(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void recordFatal(String message, Throwable cause) {
|
||||||
|
ss.fatal(this, message, cause);
|
||||||
|
log.error(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify the listeners that the context is being destroyed, in the reverse
|
||||||
|
* order from how they were notified at initialization.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void contextDestroyed(ServletContextEvent sce) {
|
||||||
|
List<ServletContextListener> destroyList = new ArrayList<ServletContextListener>(
|
||||||
|
initializeList);
|
||||||
|
Collections.reverse(destroyList);
|
||||||
|
|
||||||
|
for (ServletContextListener listener : destroyList) {
|
||||||
|
try {
|
||||||
|
log.debug("Destroying '" + listener.getClass().getName() + "'");
|
||||||
|
listener.contextDestroyed(sce);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Unexpected exception from contextDestroyed() on '"
|
||||||
|
+ listener.getClass().getName() + "'", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||||
|
|
||||||
|
package edu.cornell.mannlib.vitro.webapp.startup;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.servlet.ServletContext;
|
||||||
|
import javax.servlet.ServletContextListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO
|
||||||
|
*/
|
||||||
|
public class StartupStatus {
|
||||||
|
private static final String ATTRIBUTE_NAME = "STARTUP_STATUS";
|
||||||
|
|
||||||
|
public static StartupStatus getBean(ServletContext ctx) {
|
||||||
|
StartupStatus ss;
|
||||||
|
|
||||||
|
Object o = ctx.getAttribute(ATTRIBUTE_NAME);
|
||||||
|
if (o instanceof StartupStatus) {
|
||||||
|
ss = (StartupStatus) o;
|
||||||
|
} else {
|
||||||
|
ss = new StartupStatus();
|
||||||
|
ctx.setAttribute(ATTRIBUTE_NAME, ss);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ss;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<StatusItem> itemList = new ArrayList<StatusItem>();
|
||||||
|
|
||||||
|
public void info(ServletContextListener listener, String message) {
|
||||||
|
addItem(StatusItem.Level.INFO, listener, message, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void info(ServletContextListener listener, String message,
|
||||||
|
Throwable cause) {
|
||||||
|
addItem(StatusItem.Level.INFO, listener, message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void warning(ServletContextListener listener, String message) {
|
||||||
|
addItem(StatusItem.Level.WARNING, listener, message, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void warning(ServletContextListener listener, String message,
|
||||||
|
Throwable cause) {
|
||||||
|
addItem(StatusItem.Level.WARNING, listener, message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fatal(ServletContextListener listener, String message) {
|
||||||
|
addItem(StatusItem.Level.FATAL, listener, message, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fatal(ServletContextListener listener, String message,
|
||||||
|
Throwable cause) {
|
||||||
|
addItem(StatusItem.Level.FATAL, listener, message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void listenerNotExecuted(ServletContextListener listener) {
|
||||||
|
addItem(StatusItem.Level.NOT_EXECUTED, listener, "Not executed", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isStartupAborted() {
|
||||||
|
for (StatusItem item : itemList) {
|
||||||
|
if (item.level == StatusItem.Level.FATAL) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<StatusItem> getStatusItems() {
|
||||||
|
return Collections.unmodifiableList(itemList);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addItem(StatusItem.Level level, ServletContextListener source,
|
||||||
|
String message, Throwable cause) {
|
||||||
|
itemList.add(new StatusItem(level, source, message, cause));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class StatusItem {
|
||||||
|
public enum Level {
|
||||||
|
INFO, WARNING, FATAL, NOT_EXECUTED
|
||||||
|
}
|
||||||
|
|
||||||
|
final Level level;
|
||||||
|
final String sourceName;
|
||||||
|
final String message;
|
||||||
|
final Throwable cause;
|
||||||
|
|
||||||
|
public StatusItem(Level level, ServletContextListener source,
|
||||||
|
String message, Throwable cause) {
|
||||||
|
this.level = level;
|
||||||
|
this.sourceName = source.getClass().getName();
|
||||||
|
this.message = message;
|
||||||
|
this.cause = cause;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,322 @@
|
||||||
|
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||||
|
|
||||||
|
package edu.cornell.mannlib.vitro.webapp.startup;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.servlet.ServletContextEvent;
|
||||||
|
import javax.servlet.ServletContextListener;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.apache.log4j.Level;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import stubs.javax.servlet.ServletContextStub;
|
||||||
|
import edu.cornell.mannlib.vitro.testing.AbstractTestClass;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.startup.StartupStatus.StatusItem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO
|
||||||
|
*/
|
||||||
|
public class StartupManagerTest extends AbstractTestClass {
|
||||||
|
private static final Log log = LogFactory.getLog(StartupManagerTest.class);
|
||||||
|
|
||||||
|
private ServletContextStub ctx;
|
||||||
|
private ServletContextEvent sce;
|
||||||
|
|
||||||
|
private StartupManager sm;
|
||||||
|
private StartupStatus ss;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() {
|
||||||
|
ctx = new ServletContextStub();
|
||||||
|
sce = new ServletContextEvent(ctx);
|
||||||
|
|
||||||
|
sm = new StartupManager();
|
||||||
|
ss = StartupStatus.getBean(ctx);
|
||||||
|
|
||||||
|
// setLoggerLevel(this.getClass(), Level.DEBUG);
|
||||||
|
// setLoggerLevel(StartupManager.class, Level.DEBUG);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void dumpForDebug() {
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
dumpStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void noSuchFile() {
|
||||||
|
setLoggerLevel(StartupManager.class, Level.OFF);
|
||||||
|
assertStartupFails((String) null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void emptyFile() {
|
||||||
|
assertStartupSucceeds();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void blankLine() {
|
||||||
|
assertStartupSucceeds(" \n");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void commentLines() {
|
||||||
|
assertStartupSucceeds("# comment line \n"
|
||||||
|
+ " # comment line starting with spaces\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void classDoesNotExist() {
|
||||||
|
setLoggerLevel(StartupManager.class, Level.OFF);
|
||||||
|
assertStartupFails("no.such.class\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void classThrowsExceptionWhenLoading() {
|
||||||
|
setLoggerLevel(StartupManager.class, Level.OFF);
|
||||||
|
assertStartupFails(ThrowsExceptionWhenLoading.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void classIsPrivate() {
|
||||||
|
setLoggerLevel(StartupManager.class, Level.OFF);
|
||||||
|
assertStartupFails(PrivateClass.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void noDefaultConstructor() {
|
||||||
|
setLoggerLevel(StartupManager.class, Level.OFF);
|
||||||
|
assertStartupFails(NoDefaultConstructor.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorIsPrivate() {
|
||||||
|
setLoggerLevel(StartupManager.class, Level.OFF);
|
||||||
|
assertStartupFails(PrivateConstructor.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorThrowsException() {
|
||||||
|
setLoggerLevel(StartupManager.class, Level.OFF);
|
||||||
|
assertStartupFails(ConstructorThrowsException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void notAServletContextListener() {
|
||||||
|
setLoggerLevel(StartupManager.class, Level.OFF);
|
||||||
|
assertStartupFails(NotAListener.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void listenerThrowsException() {
|
||||||
|
assertStartupFails(InitThrowsException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void listenerSetsFatalStatus() {
|
||||||
|
assertStartupFails(InitSetsFatalStatus.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void success() {
|
||||||
|
String listener1Name = SucceedsWithInfo.class.getName();
|
||||||
|
String listener2Name = SucceedsWithWarning.class.getName();
|
||||||
|
|
||||||
|
assertStartupSucceeds(SucceedsWithInfo.class, SucceedsWithWarning.class);
|
||||||
|
|
||||||
|
// Did they initialize in the correct order?
|
||||||
|
List<StatusItem> items = ss.getStatusItems();
|
||||||
|
assertEquals("how many", 2, items.size());
|
||||||
|
assertEquals("init order 1", listener1Name, items.get(0).sourceName);
|
||||||
|
assertEquals("init order 2", listener2Name, items.get(1).sourceName);
|
||||||
|
|
||||||
|
sm.contextDestroyed(sce);
|
||||||
|
|
||||||
|
// Did they destroy in reverse order?
|
||||||
|
items = ss.getStatusItems();
|
||||||
|
assertEquals("how many", 4, items.size());
|
||||||
|
assertEquals("destroy order 1", listener2Name, items.get(2).sourceName);
|
||||||
|
assertEquals("destroy order 2", listener1Name, items.get(3).sourceName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void duplicateListeners() {
|
||||||
|
setLoggerLevel(StartupManager.class, Level.OFF);
|
||||||
|
assertStartupFails(SucceedsWithInfo.class, SucceedsWithWarning.class,
|
||||||
|
SucceedsWithInfo.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void dontExecuteAfterFailure() {
|
||||||
|
assertStartupFails(InitThrowsException.class, SucceedsWithInfo.class);
|
||||||
|
|
||||||
|
for (StatusItem item : ss.getStatusItems()) {
|
||||||
|
if (item.sourceName.equals(SucceedsWithInfo.class.getName())
|
||||||
|
&& (item.level == StatusItem.Level.NOT_EXECUTED)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fail("'" + SucceedsWithInfo.class.getName()
|
||||||
|
+ "' should not have been run after '"
|
||||||
|
+ PrivateConstructor.class.getName() + "' failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
// Helper classes
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
public static class BasicListener implements ServletContextListener {
|
||||||
|
@Override
|
||||||
|
public void contextDestroyed(ServletContextEvent sce) {
|
||||||
|
// does nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void contextInitialized(ServletContextEvent sce) {
|
||||||
|
// does nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ThrowsExceptionWhenLoading extends BasicListener {
|
||||||
|
static {
|
||||||
|
if (true) {
|
||||||
|
throw new IllegalStateException("can't load me.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class PrivateClass extends BasicListener {
|
||||||
|
// no methods
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class NoDefaultConstructor extends BasicListener {
|
||||||
|
public NoDefaultConstructor(String bogus) {
|
||||||
|
bogus.length();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class PrivateConstructor extends BasicListener {
|
||||||
|
private PrivateConstructor() {
|
||||||
|
// does nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ConstructorThrowsException extends BasicListener {
|
||||||
|
public ConstructorThrowsException() {
|
||||||
|
if (true) {
|
||||||
|
throw new IllegalStateException("can't load me.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class NotAListener {
|
||||||
|
// no methods
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class InitThrowsException extends BasicListener {
|
||||||
|
@Override
|
||||||
|
public void contextInitialized(ServletContextEvent sce) {
|
||||||
|
throw new IllegalStateException("Initialization failed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class InitSetsFatalStatus extends BasicListener {
|
||||||
|
@Override
|
||||||
|
public void contextInitialized(ServletContextEvent sce) {
|
||||||
|
StartupStatus.getBean(sce.getServletContext()).fatal(this,
|
||||||
|
"Set fatal status");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SucceedsWithInfo implements ServletContextListener {
|
||||||
|
@Override
|
||||||
|
public void contextInitialized(ServletContextEvent sce) {
|
||||||
|
StartupStatus.getBean(sce.getServletContext()).info(this,
|
||||||
|
"Set info message on init.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void contextDestroyed(ServletContextEvent sce) {
|
||||||
|
StartupStatus.getBean(sce.getServletContext()).info(this,
|
||||||
|
"Set info message on destroy.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SucceedsWithWarning implements ServletContextListener {
|
||||||
|
@Override
|
||||||
|
public void contextInitialized(ServletContextEvent sce) {
|
||||||
|
StartupStatus.getBean(sce.getServletContext()).warning(this,
|
||||||
|
"Set warning message on init.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void contextDestroyed(ServletContextEvent sce) {
|
||||||
|
StartupStatus.getBean(sce.getServletContext()).warning(this,
|
||||||
|
"Set warning message on destroy.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
// Helper methods
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
private void assertStartupFails(String fileContents) {
|
||||||
|
if (fileContents != null) {
|
||||||
|
ctx.setMockResource(StartupManager.FILE_OF_STARTUP_LISTENERS,
|
||||||
|
fileContents);
|
||||||
|
}
|
||||||
|
sm.contextInitialized(sce);
|
||||||
|
assertTrue("expecting abort", ss.isStartupAborted());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertStartupFails(Class<?>... classes) {
|
||||||
|
assertStartupFails(joinClassNames(classes));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertStartupSucceeds(String fileContents) {
|
||||||
|
if (fileContents != null) {
|
||||||
|
ctx.setMockResource(StartupManager.FILE_OF_STARTUP_LISTENERS,
|
||||||
|
fileContents);
|
||||||
|
}
|
||||||
|
sm.contextInitialized(sce);
|
||||||
|
assertFalse("expecting success", ss.isStartupAborted());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertStartupSucceeds(Class<?>... classes) {
|
||||||
|
assertStartupSucceeds(joinClassNames(classes));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String joinClassNames(Class<?>[] classes) {
|
||||||
|
if (classes == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (classes.length == 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
for (int i = 0; i < classes.length; i++) {
|
||||||
|
result.append(classes[i].getName()).append('\n');
|
||||||
|
}
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void dumpStatus() {
|
||||||
|
List<StatusItem> items = ss.getStatusItems();
|
||||||
|
log.debug("-------------- " + items.size() + " items");
|
||||||
|
for (StatusItem item : items) {
|
||||||
|
log.debug(String.format("%8s %s \n %s \n %s", item.level,
|
||||||
|
item.sourceName, item.message, item.cause));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue