Make ServletValidator not use Jetty.
Jetty's just been too flaky, since this is only for user guidance, just use a loose parser.
http://gwt-code-reviews.appspot.com/284801/show
Fixes: 4760
Review by: bobv,tbroyer@gmail.com
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7823 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/ServletValidator.java b/dev/core/src/com/google/gwt/dev/ServletValidator.java
index 1fc93da..1d4af91 100644
--- a/dev/core/src/com/google/gwt/dev/ServletValidator.java
+++ b/dev/core/src/com/google/gwt/dev/ServletValidator.java
@@ -16,24 +16,24 @@
package com.google.gwt.dev;
import com.google.gwt.core.ext.TreeLogger;
-import com.google.gwt.dev.shell.jetty.JettyNullLogger;
+import com.google.gwt.dev.util.collect.HashSet;
-import org.mortbay.jetty.servlet.ServletHandler;
-import org.mortbay.jetty.servlet.ServletHolder;
-import org.mortbay.jetty.servlet.ServletMapping;
-import org.mortbay.jetty.webapp.WebAppContext;
-import org.mortbay.jetty.webapp.WebXmlConfiguration;
-import org.mortbay.log.Log;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
-import java.util.Arrays;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
+import java.util.Stack;
+import java.util.Map.Entry;
+
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
/**
* Validates that <code><servlet></code> tags in a GWT module match ones
@@ -41,10 +41,130 @@
*/
class ServletValidator {
- static {
- // Suppress spammy Jetty log initialization.
- System.setProperty("org.mortbay.log.class", JettyNullLogger.class.getName());
- Log.getLog();
+ /**
+ * Collect servlet information from a web xml.
+ */
+ private static final class WebXmlDataCollector extends DefaultHandler {
+ /*
+ * TODO(scottb): this should have been implemented as a Schema.
+ */
+
+ private static class ElementStack {
+ private final Stack<String> stack = new Stack<String>();
+
+ public boolean exactly(String elementName, int depth) {
+ return (depth == stack.size() - 1)
+ && elementName.equals(stack.get(depth));
+ }
+
+ public String peek() {
+ return stack.peek();
+ }
+
+ public String pop() {
+ return stack.pop();
+ }
+
+ public void push(String elementName) {
+ stack.push(elementName);
+ }
+
+ public boolean within(String elementName, int depth) {
+ return (depth < stack.size()) && elementName.equals(stack.get(depth));
+ }
+ }
+
+ private Set<String> accumulateClasses = new HashSet<String>();
+ private Set<String> accumulatePaths = new HashSet<String>();
+ private final TreeLogger branch;
+ private final Stack<StringBuilder> cdataStack = new Stack<StringBuilder>();
+ private final Map<String, String> classNameToServletName;
+ private final ElementStack context = new ElementStack();
+ private String currentServletName;
+ private String indent = "";
+ private final Map<String, Set<String>> servletNameToPaths;
+
+ private WebXmlDataCollector(TreeLogger branch,
+ Map<String, String> classNameToServletName,
+ Map<String, Set<String>> servletNameToPaths) {
+ this.branch = branch;
+ this.classNameToServletName = classNameToServletName;
+ this.servletNameToPaths = servletNameToPaths;
+ }
+
+ @Override
+ public void characters(char[] ch, int start, int length)
+ throws SAXException {
+ cdataStack.peek().append(ch, start, length);
+ }
+
+ @Override
+ public void endElement(String uri, String localName, String qName)
+ throws SAXException {
+ String cdata = cdataStack.pop().toString().trim();
+ if (context.within("web-app", 0)) {
+ if (context.within("servlet", 1)) {
+ if (context.exactly("servlet-name", 2)) {
+ currentServletName = cdata;
+ } else if (context.exactly("servlet-class", 2)) {
+ accumulateClasses.add(cdata);
+ } else if (context.exactly("servlet", 1)) {
+ if (currentServletName != null) {
+ for (String className : accumulateClasses) {
+ classNameToServletName.put(className, currentServletName);
+ }
+ currentServletName = null;
+ }
+ accumulateClasses.clear();
+ }
+ } else if (context.within("servlet-mapping", 1)) {
+ if (context.exactly("servlet-name", 2)) {
+ currentServletName = cdata;
+ } else if (context.exactly("url-pattern", 2)) {
+ accumulatePaths.add(cdata);
+ } else if (context.exactly("servlet-mapping", 1)) {
+ if (currentServletName != null) {
+ Set<String> servletPaths = servletNameToPaths.get(currentServletName);
+ if (servletPaths == null) {
+ servletPaths = new HashSet<String>();
+ servletNameToPaths.put(currentServletName, servletPaths);
+ }
+ servletPaths.addAll(accumulatePaths);
+ }
+ currentServletName = null;
+ accumulatePaths.clear();
+ }
+ }
+ }
+
+ assert qName.equals(context.peek());
+ context.pop();
+
+ if (cdata.length() > 0) {
+ branch.log(TreeLogger.DEBUG, " characters: " + indent + cdata);
+ }
+ indent = indent.substring(2);
+ branch.log(TreeLogger.DEBUG, " endElement: " + indent + qName);
+ }
+
+ @Override
+ public void startElement(String uri, String localName, String qName,
+ Attributes attributes) throws SAXException {
+ context.push(qName);
+ cdataStack.push(new StringBuilder());
+
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < attributes.getLength(); ++i) {
+ sb.append(' ');
+ sb.append(attributes.getQName(i));
+ sb.append("=\"");
+ sb.append(attributes.getValue(i));
+ sb.append('"');
+ }
+ branch.log(TreeLogger.DEBUG, "startElement: " + indent + qName
+ + sb.toString());
+ indent += " ";
+ }
}
public static ServletValidator create(TreeLogger logger, File webXml) {
@@ -59,39 +179,35 @@
public static ServletValidator create(TreeLogger logger, URL webXmlUrl) {
String webXmlUrlString = webXmlUrl.toExternalForm();
- String oldProp = System.getProperty("org.mortbay.xml.XmlParser.Validating",
- "false");
try {
- System.setProperty("org.mortbay.xml.XmlParser.Validating", "false");
- WebXmlConfiguration wxc = new WebXmlConfiguration();
- ServletHandler myServletHandler = new ServletHandler();
- wxc.setWebAppContext(new WebAppContext(null, null, myServletHandler, null));
- wxc.configure(webXmlUrlString);
- ServletMapping[] mappings = myServletHandler.getServletMappings();
- ServletHolder[] servlets = myServletHandler.getServlets();
- Map<String, String> servletNameToClassName = new HashMap<String, String>();
- Map<String, Set<String>> classNameToPaths = new HashMap<String, Set<String>>();
+ final TreeLogger branch = logger.branch(TreeLogger.DEBUG, "Parsing "
+ + webXmlUrlString);
+ SAXParserFactory fac = SAXParserFactory.newInstance();
+ fac.setValidating(false);
+ fac.setNamespaceAware(false);
+ SAXParser parser = fac.newSAXParser();
+ parser.getXMLReader().setFeature(
+ "http://xml.org/sax/features/validation", false);
+ parser.getXMLReader().setFeature(
+ "http://xml.org/sax/features/namespaces", false);
+ parser.getXMLReader().setFeature(
+ "http://xml.org/sax/features/namespace-prefixes", false);
+
Map<String, String> classNameToServletName = new HashMap<String, String>();
- for (ServletHolder servlet : servlets) {
- servletNameToClassName.put(servlet.getName(), servlet.getClassName());
- classNameToServletName.put(servlet.getClassName(), servlet.getName());
- classNameToPaths.put(servlet.getClassName(), new HashSet<String>());
- }
- for (ServletMapping mapping : mappings) {
- String servletName = mapping.getServletName();
- String className = servletNameToClassName.get(servletName);
- assert (className != null);
- Set<String> paths = classNameToPaths.get(className);
- assert (paths != null);
- paths.addAll(Arrays.asList(mapping.getPathSpecs()));
+ Map<String, Set<String>> servletNameToPaths = new HashMap<String, Set<String>>();
+ parser.parse(webXmlUrlString, new WebXmlDataCollector(branch,
+ classNameToServletName, servletNameToPaths));
+
+ Map<String, Set<String>> classNameToPaths = new HashMap<String, Set<String>>();
+ for (Entry<String, String> entry : classNameToServletName.entrySet()) {
+ classNameToPaths.put(entry.getKey(),
+ servletNameToPaths.get(entry.getValue()));
}
return new ServletValidator(classNameToServletName, classNameToPaths);
} catch (Exception e) {
logger.log(TreeLogger.WARN, "Unable to process '" + webXmlUrlString
+ "' for servlet validation", e);
return null;
- } finally {
- System.setProperty("org.mortbay.xml.XmlParser.Validating", oldProp);
}
}
diff --git a/dev/core/test/com/google/gwt/dev/ServletValidatorTest.java b/dev/core/test/com/google/gwt/dev/ServletValidatorTest.java
index e9e30a3..7bf4c1a 100644
--- a/dev/core/test/com/google/gwt/dev/ServletValidatorTest.java
+++ b/dev/core/test/com/google/gwt/dev/ServletValidatorTest.java
@@ -20,7 +20,6 @@
import junit.framework.TestCase;
-import org.mortbay.log.Logger;
import org.xml.sax.SAXParseException;
import java.io.File;
@@ -32,39 +31,6 @@
*/
public class ServletValidatorTest extends TestCase {
- public static class DummyJettyLogger implements Logger {
- public void debug(String msg, Object arg0, Object arg1) {
- }
-
- public void debug(String msg, Throwable th) {
- }
-
- public Logger getLogger(String name) {
- return this;
- }
-
- public void info(String msg, Object arg0, Object arg1) {
- }
-
- public boolean isDebugEnabled() {
- return false;
- }
-
- public void setDebugEnabled(boolean enabled) {
- }
-
- public void warn(String msg, Object arg0, Object arg1) {
- }
-
- public void warn(String msg, Throwable th) {
- }
- }
-
- static {
- System.setProperty("org.mortbay.log.class",
- DummyJettyLogger.class.getName());
- }
-
public void testBadUrl() throws Exception {
UnitTestTreeLogger.Builder builder = new UnitTestTreeLogger.Builder();
builder.setLowestLogLevel(TreeLogger.WARN);