diff --git a/utilities/buildutils/src/edu/cornell/mannlib/vitro/utilities/containerneutral/CheckContainerNeutrality.java b/utilities/buildutils/src/edu/cornell/mannlib/vitro/utilities/containerneutral/CheckContainerNeutrality.java
new file mode 100644
index 000000000..9475d792e
--- /dev/null
+++ b/utilities/buildutils/src/edu/cornell/mannlib/vitro/utilities/containerneutral/CheckContainerNeutrality.java
@@ -0,0 +1,401 @@
+/* $This file is distributed under the terms of the license in /doc/license.txt$ */
+
+package edu.cornell.mannlib.vitro.utilities.containerneutral;
+
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.servlet.Filter;
+import javax.servlet.ServletContextListener;
+import javax.servlet.http.HttpServlet;
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpression;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+
+import org.apache.commons.lang.StringUtils;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+/**
+ * Look at web.xml, and check for conditions that violate the Servlet 2.4 spec,
+ * but that might not be noticed because Tomcat doesn't complain.
+ *
+ * ------
+ *
+ * Values of the tag:
+ *
+ * The spec permits only these values: "FORWARD", "REQUEST", "INCLUDE", "ERROR",
+ * but Tomcat also allows the lower-case equivalents. GlassFish or WebLogic will
+ * barf on lower-case.
+ *
+ * Check to see that only the upper-case values are used.
+ *
+ * ------
+ *
+ * Existence of Servlet classes:
+ *
+ * The spec allows the container to either load all servlets at startup, or to
+ * load them when requested. Since Tomcat only loads servlet when requested, it
+ * doesn't notice or complain if web.xml cites a that doesn't
+ * exist, as long as it is never invoked. On the other hand, WebLogic loads all
+ * serlvets at startup, and will barf if the class is not found.
+ *
+ * Check each to insure that the class can be loaded and
+ * instantiated and assigned to HttpServlet.
+ *
+ * ------
+ *
+ * Embedded URIs in taglibs.
+ *
+ * I can't find this definitively in the JSP spec, but some containers complain
+ * if web.xml specifies a that conflicts with the embedded
+ * in the taglib itself. As far as I can see in the spec, the embedded
+ * tag is not required or referenced unless we are using
+ * "Implicit Map Entries From TLDs", which in turn is only relevant for TLDs
+ * packaged in JAR files. So, I can't find support for this complaint, but it
+ * seems a reasonable one.
+ *
+ * Check each specified in web.xml. If the taglib has an embedded
+ * tag, it should match the from web.xml.
+ *
+ * ------
+ *
+ * Existence of Listener and Filter classes.
+ *
+ * As far as I can tell, there is no ambiguity here, and every container will
+ * complain if any of the or entries are
+ * unsuitable. I check them anyway, since the mechanism was already assembled
+ * for checking entries.
+ *
+ * Check each to insure that the class can be loaded and
+ * instantiated and assigned to ServletContextListener.
+ *
+ * Check each to insure that the class can be loaded and
+ * instantiated and assigned to Filter.
+ *
+ * ---------------------------------------------------------------------
+ *
+ * Although this class is executed as a JUnit test, it doesn't have the usual
+ * structure for a unit test.
+ *
+ * In order to produce the most diagnostic information, the test does not abort
+ * on the first failure. Rather, failure messages are accumulated until all
+ * checks have been performed, and the test list all such messages on failure.
+ *
+ * ---------------------------------------------------------------------
+ *
+ * Since this is not executed as part of the standard Vitro unit tests, it also
+ * cannot use the standard logging mechanism. Log4J has not been initialized.
+ *
+ */
+public class CheckContainerNeutrality {
+ private static final String PROPERTY_WEBAPP_DIR = "CheckContainerNeutrality.webapp.dir";
+
+ private static DocumentBuilder docBuilder;
+ private static XPath xpath;
+
+ @BeforeClass
+ public static void createDocBuilder() {
+ try {
+ DocumentBuilderFactory factory = DocumentBuilderFactory
+ .newInstance();
+ factory.setNamespaceAware(true); // never forget this!
+ docBuilder = factory.newDocumentBuilder();
+ } catch (ParserConfigurationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @BeforeClass
+ public static void createXPath() {
+ xpath = XPathFactory.newInstance().newXPath();
+ xpath.setNamespaceContext(new StupidNamespaceContext());
+ }
+
+ private File webappDir;
+ private File webXmlFile;
+ private Document webXmlDoc;
+ private List messages;
+
+ @Before
+ public void setup() throws SAXException, IOException {
+ String webappDirPath = System.getProperty(PROPERTY_WEBAPP_DIR);
+ if (webappDirPath == null) {
+ fail("System property '" + PROPERTY_WEBAPP_DIR
+ + "' was not provided.");
+ }
+ webappDir = new File(webappDirPath);
+ if (!webappDir.isDirectory()) {
+ fail("'" + webappDirPath + "' is not a directory");
+ }
+ webXmlFile = new File(webappDir, "WEB-INF/web.xml");
+ if (!webXmlFile.isFile()) {
+ fail("Can't find '" + webXmlFile.getAbsolutePath() + "'");
+ }
+
+ webXmlDoc = docBuilder.parse(webXmlFile);
+
+ messages = new ArrayList();
+ }
+
+ // ----------------------------------------------------------------------
+ // Tests
+ // ----------------------------------------------------------------------
+
+ @Test
+ public void checkAll() throws IOException {
+ checkDispatcherValues();
+ checkServletClasses();
+ checkListenerClasses();
+ checkFilterClasses();
+ checkTaglibLocations();
+
+ if (!messages.isEmpty()) {
+ fail("Found these problems with '" + webXmlFile.getCanonicalPath()
+ + "'\n " + StringUtils.join(messages, "\n "));
+ }
+ }
+
+ private void checkDispatcherValues() {
+ List okValues = Arrays.asList(new String[] { "FORWARD",
+ "REQUEST", "INCLUDE", "ERROR" });
+ for (Node n : findNodes("//j2ee:dispatcher")) {
+ String text = n.getTextContent();
+ if (!okValues.contains(text)) {
+ messages.add("" + text
+ + " is not valid. Acceptable values are "
+ + okValues);
+ }
+ }
+ }
+
+ private void checkServletClasses() {
+ for (Node n : findNodes("//j2ee:servlet-class")) {
+ String text = n.getTextContent();
+ String problem = confirmClassNameIsValid(text, HttpServlet.class);
+ if (problem != null) {
+ messages.add("" + text
+ + " is not valid: " + problem);
+ }
+ }
+ }
+
+ private void checkListenerClasses() {
+ for (Node n : findNodes("//j2ee:listener-class")) {
+ String text = n.getTextContent();
+ String problem = confirmClassNameIsValid(text,
+ ServletContextListener.class);
+ if (problem != null) {
+ messages.add("" + text
+ + " is not valid: " + problem);
+ }
+ }
+ }
+
+ private void checkFilterClasses() {
+ for (Node n : findNodes("//j2ee:filter-class")) {
+ String text = n.getTextContent();
+ String problem = confirmClassNameIsValid(text, Filter.class);
+ if (problem != null) {
+ messages.add("" + text
+ + " is not valid: " + problem);
+ }
+ }
+ }
+
+ private void checkTaglibLocations() {
+ for (Node n : findNodes("//j2ee:jsp-config/j2ee:taglib")) {
+ String taglibUri = findNode("j2ee:taglib-uri", n).getTextContent();
+ String taglibLocation = findNode("j2ee:taglib-location", n)
+ .getTextContent();
+ // System.out.println("taglibUri='" + taglibUri
+ // + "', taglibLocation='" + taglibLocation + "'");
+ String message = checkTaglibUri(taglibUri, taglibLocation);
+ if (message != null) {
+ messages.add(message);
+ }
+ }
+ }
+
+ private String checkTaglibUri(String taglibUri, String taglibLocation) {
+ File taglibFile = new File(webappDir, taglibLocation);
+ if (!taglibFile.isFile()) {
+ return "File '" + taglibLocation + "' can't be found ('"
+ + taglibFile.getAbsolutePath() + "')";
+ }
+
+ Document taglibDoc;
+ try {
+ taglibDoc = docBuilder.parse(taglibFile);
+ } catch (SAXException e) {
+ return "Failed to parse the taglib file '" + taglibFile + "': " + e;
+ } catch (IOException e) {
+ return "Failed to parse the taglib file '" + taglibFile + "': " + e;
+ }
+
+ List uriNodes = findNodes("/j2ee:taglib/j2ee:uri",
+ taglibDoc.getDocumentElement());
+ // System.out.println("uriNodes: " + uriNodes);
+ if (uriNodes.isEmpty()) {
+ return null;
+ }
+ if (uriNodes.size() > 1) {
+ return "taglib '" + taglibLocation + "' contains "
+ + uriNodes.size()
+ + " nodes. Expecting no more than 1";
+ }
+
+ String embeddedUri = uriNodes.get(0).getTextContent();
+ if (taglibUri.equals(embeddedUri)) {
+ return null;
+ } else {
+ return "URI in taglib doesn't match the one in web.xml: taglib='"
+ + taglibLocation + "', internal URI='"
+ + uriNodes.get(0).getTextContent()
+ + "', URI from web.xml='" + taglibUri + "'";
+ }
+ }
+
+ // ----------------------------------------------------------------------
+ // Helper methods
+ // ----------------------------------------------------------------------
+
+ /**
+ * Search for an Xpath in web.xml, returning a handy list.
+ */
+ private List findNodes(String pattern) {
+ return findNodes(pattern, webXmlDoc.getDocumentElement());
+ }
+
+ /**
+ * Search for an Xpath within a node of web.xml, returning a handy list.
+ */
+ private List findNodes(String pattern, Node context) {
+ try {
+ XPathExpression xpe = xpath.compile(pattern);
+ NodeList nodes = (NodeList) xpe.evaluate(context,
+ XPathConstants.NODESET);
+ List list = new ArrayList();
+ for (int i = 0; i < nodes.getLength(); i++) {
+ list.add(nodes.item(i));
+ }
+ return list;
+ } catch (XPathExpressionException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Search for an Xpath within a node of web.xml, returning a single node or
+ * throwing an exception.
+ */
+ private Node findNode(String pattern, Node context) {
+ List list = findNodes(pattern, context);
+ if (list.size() != 1) {
+ throw new RuntimeException("Expecting 1 node, but found "
+ + list.size() + " nodes using '" + pattern + "'");
+ } else {
+ return list.get(0);
+ }
+ }
+
+ /**
+ * Check that the supplied className can be instantiated with a
+ * zero-argument constructor, and assigned to a variable of the target
+ * class.
+ */
+ private String confirmClassNameIsValid(String className,
+ Class> targetClass) {
+ try {
+ Class> specifiedClass = Class.forName(className);
+ Object o = specifiedClass.newInstance();
+ if (!targetClass.isInstance(o)) {
+ return specifiedClass.getSimpleName()
+ + " is not a subclass of "
+ + targetClass.getSimpleName() + ".";
+ }
+ } catch (ClassNotFoundException e) {
+ return "The class does not exist.";
+ } catch (InstantiationException e) {
+ return "The class does not have a public constructor "
+ + "that takes zero arguments.";
+ } catch (IllegalAccessException e) {
+ return "The class does not have a public constructor "
+ + "that takes zero arguments.";
+ }
+ return null;
+ }
+
+ /**
+ * Dump the first 20 nodes of an XML context, excluding comments and blank
+ * text nodes.
+ */
+ @SuppressWarnings("unused")
+ private int dumpXml(Node xmlNode, int... parms) {
+ int remaining = (parms.length == 0) ? 20 : parms[0];
+ int level = (parms.length < 2) ? 1 : parms[1];
+
+ Node n = xmlNode;
+
+ if (Node.COMMENT_NODE == n.getNodeType()) {
+ return 0;
+ }
+ if (Node.TEXT_NODE == n.getNodeType()) {
+ if (StringUtils.isBlank(n.getTextContent())) {
+ return 0;
+ }
+ }
+
+ int used = 1;
+
+ System.out.println(StringUtils.repeat("-->", level) + n);
+ NodeList nl = n.getChildNodes();
+ for (int i = 0; (i < nl.getLength() && remaining > used); i++) {
+ used += dumpXml(nl.item(i), remaining - used, level + 1);
+ }
+ return used;
+ }
+
+ // ----------------------------------------------------------------------
+ // Helper classes
+ // ----------------------------------------------------------------------
+
+ private static class StupidNamespaceContext implements NamespaceContext {
+ @Override
+ public String getNamespaceURI(String prefix) {
+ if ("j2ee".equals(prefix)) {
+ return "http://java.sun.com/xml/ns/j2ee";
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ @Override
+ public String getPrefix(String namespaceURI) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Iterator> getPrefixes(String namespaceURI) {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+}
diff --git a/webapp/build.xml b/webapp/build.xml
index b522e1531..e9cda0354 100644
--- a/webapp/build.xml
+++ b/webapp/build.xml
@@ -84,8 +84,13 @@
+
+
+
+
+
-
+
-
+
@@ -160,7 +165,7 @@
-
+
@@ -210,7 +215,7 @@
-
+
-
+
@@ -422,6 +427,21 @@ deploy - Deploy the application directly into the Tomcat webapps directory.
+
+
+
+
+
+
+
+
+
+
+
+
+
", level) + n);
- NodeList nl = n.getChildNodes();
- for (int i = 0; (i < nl.getLength() && remaining > used); i++) {
- used += dumpXml(nl.item(i), remaining - used, level + 1);
- }
- return used;
- }
-
- // ----------------------------------------------------------------------
- // Helper classes
- // ----------------------------------------------------------------------
-
- private static class StupidNamespaceContext implements NamespaceContext {
- @Override
- public String getNamespaceURI(String prefix) {
- if ("j2ee".equals(prefix)) {
- return "http://java.sun.com/xml/ns/j2ee";
- } else {
- throw new UnsupportedOperationException();
- }
- }
-
- @Override
- public String getPrefix(String namespaceURI) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public Iterator> getPrefixes(String namespaceURI) {
- throw new UnsupportedOperationException();
- }
- }
-
-}