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" />
|
||||
</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 todir="${war-webinf.dir}">
|
||||
<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 ****************************************************** -->
|
||||
|
||||
<!-- Reads deploy.properties. Needs to run before almost all other listeners. -->
|
||||
<!-- StartupManager instantiates and runs the listeners from startup_listeners.txt -->
|
||||
<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-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 ********************************************************** -->
|
||||
<!-- 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