Commits

Stephen McKamey  committed 39104b0

many outstanding changes; overhaul tomcat to streamline a bit if JSP not needed

  • Participants
  • Parent commits 7a9a208

Comments (0)

Files changed (17)

 
 - Java SE JDK 1.6
 	http://www.oracle.com/technetwork/java/javase/downloads/
-- Maven 3.0.3
+- Maven 3.0.x
 	http://maven.apache.org/download.html
 
 Usage
 	mvn archetype:generate \
 		-DarchetypeGroupId=org.duelengine \
 		-DarchetypeArtifactId=war-bootstrap-archetype \
-		-DarchetypeVersion=0.4.0
+		-DarchetypeVersion=0.5.0
 
 	# build your boostrap
 	# NOTE: replace "bootstrap" with your chosen project name

File war-bootstrap-archetype/pom.xml

 
 	<groupId>org.duelengine</groupId>
 	<artifactId>war-bootstrap-archetype</artifactId>
-	<version>0.4.0</version>
+	<version>0.5.0</version>
 	<packaging>maven-archetype</packaging>
 
 	<name>WAR Bootstrap Archetype</name>

File war-bootstrap-archetype/src/main/resources/archetype-resources/pom.xml

 
 		<glassfish.version>3.1.1</glassfish.version>
 		<jetty.version>8.1.5.v20120716</jetty.version>
-		<tomcat.version>7.0.29</tomcat.version>
-		<slf4j.version>1.6.6</slf4j.version>
+		<tomcat.version>7.0.33</tomcat.version>
+		<slf4j.version>1.7.5</slf4j.version>
 		<javac.version>1.6</javac.version>
 	</properties>
 
 	<dependencies>
+		<!-- SLF4J runtime -->
+		<dependency>
+			<groupId>com.pseudocode</groupId>
+			<artifactId>slf4j-compact</artifactId>
+			<version>${slf4j.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>slf4j-api</artifactId>
+			<version>${slf4j.version}</version>
+		</dependency>
+
 		<!-- NOTE: GlassFish and Tomcat appear to be conflicting.
 			 order appears to matter and also
 			 must set scope to provided if not used -->
 			<version>${glassfish.version}</version>
 			<scope>compile</scope><!-- comment this for GF3 -->
 		</dependency>
-
-		<!-- SLF4J runtime -->
-		<dependency>
-			<groupId>com.pseudocode</groupId>
-			<artifactId>slf4j-compact</artifactId>
-			<version>${slf4j.version}</version>
-		</dependency>
-		<dependency>
-			<groupId>org.slf4j</groupId>
-			<artifactId>slf4j-api</artifactId>
-			<version>${slf4j.version}</version>
-		</dependency>
 	</dependencies>
 
 	<build>

File war-bootstrap-archetype/src/main/resources/archetype-resources/src/main/java/Bootstrap.java

 package ${package};
 
 import java.io.File;
+import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 public class Bootstrap {
 
-	private static final String SEPARATOR = "========================================";
+	private static final Logger log = LoggerFactory.getLogger(Bootstrap.class);
+
+	private static final long MS_PER_NANO = (long)1e6;
+	private static final String SEPARATOR = "========================================================================";
 	private static final String HELP = "java -jar ${artifactId}.jar${symbol_escape}n"+
 			"  --help                 : this help text${symbol_escape}n"+
 			"  -war <context>=<path>  : set the context of war (e.g., /=./root.war)${symbol_escape}n" +
 			"  -p <port>              : set the HTTP listening port (default: 8080)${symbol_escape}n"+
 			"  -s <port>              : set the HTTPS listening port (default: none)${symbol_escape}n" +
+			"  -ks <keystore=file>    : set the path to the keystore file (default: null)${symbol_escape}n" +
+			"  -kp <keystore-pass>    : set the password to the keystore (default: null)${symbol_escape}n" +
 			"  --tomcat               : use Tomcat as servlet container (default)${symbol_escape}n" +
 			"  --jetty                : use Jetty as servlet container${symbol_escape}n" +
 			"  --glassfish            : use GlassFish as servlet container";
 
 	public static void main(String[] args) {
 
+		ServletServer server = null;
+		try {
+			server = serverStart(args);
+
+		} catch (Exception ex) {
+			ex.printStackTrace();
+			log.error(ex.getMessage(), ex);
+		}
+
+		long start = System.nanoTime();
+
+		try {
+			if (server != null) {
+				waitForKillSignal();
+			}
+
+		} catch (Exception ex) {
+			ex.printStackTrace();
+			log.error(ex.getMessage(), ex);
+		}
+
+		log.info("Killing webhead, uptime: %d ms", (System.nanoTime() - start) / MS_PER_NANO);
+
+		try {
+			if (server != null) {
+				serverStop(server);
+			}
+
+		} catch (Exception ex) {
+			ex.printStackTrace();
+			log.error(ex.getMessage(), ex);
+		}
+	}
+
+	private static void waitForKillSignal()
+			throws IOException {
+
+			log.info("Press ENTER to exit.");
+			log.info(SEPARATOR);
+
+			System.in.read();
+	}
+
+	private static ServletServer serverStart(String[] args)
+			throws IOException, Exception {
+
+		log.info(SEPARATOR);
+		log.info("WAR Bootstrap");
+		log.info(SEPARATOR);
+
 		Map<String, String> contexts = new HashMap<String, String>();
 		int port = 8080;
 		int https = -1;
 		ServletServer server = null;
-		try {
-			System.out.println(SEPARATOR);
-			System.out.println("WAR Bootstrap\n");
-			for (int i=0; i<args.length; i++) {
-				String arg = args[i];
-				if ("-p".equals(arg)) {
-					port = Integer.parseInt(args[++i]);
+		String keystoreFile = null;
+		String keystorePass = null;
 
-				} else if ("-s".equals(arg)) {
-					https = Integer.parseInt(args[++i]);
+		for (int i=0; i<args.length; i++) {
+			String arg = args[i];
+			if ("-p".equals(arg)) {
+				port = Integer.parseInt(args[++i]);
 
-				} else if ("-war".equals(arg)) {
-					String contextPath;
-					String warPath = args[++i];
-					int delim = warPath.indexOf('=');
-					if (delim < 1) {
-						contextPath = "/";
-					} else {
-						contextPath = warPath.substring(0, delim);
-					}
-					warPath = new File(warPath.substring(delim+1)).getCanonicalPath();
-					contexts.put(contextPath, warPath);
+			} else if ("-s".equals(arg)) {
+				https = Integer.parseInt(args[++i]);
 
-				} else if ("--glassfish".equalsIgnoreCase(arg)) {
-					server = new GlassFishServletServer();
+			} else if ("-ks".equals(arg)) {
+				keystoreFile = args[++i];
 
-				} else if ("--jetty".equalsIgnoreCase(arg)) {
-					server = new JettyServletServer();
+			} else if ("-kp".equals(arg)) {
+				keystorePass = args[++i];
 
-				} else if ("--tomcat".equalsIgnoreCase(arg)) {
-					server = new TomcatServletServer();
+			} else if ("-war".equals(arg)) {
+				String contextPath;
+				String warPath = args[++i];
+				int delim = warPath.indexOf('=');
+				if (delim < 1) {
+					contextPath = "/";
+				} else {
+					contextPath = warPath.substring(0, delim);
+				}
+				warPath = new File(warPath.substring(delim+1)).getCanonicalPath();
+				contexts.put(contextPath, warPath);
 
-				} else if ("--help".equalsIgnoreCase(arg)) {
-					System.out.println(HELP);
-					System.out.println(SEPARATOR);
-					return;
+			} else if ("--glassfish".equalsIgnoreCase(arg)) {
+				server = new GlassFishServletServer();
 
-				} else {
-					System.out.println(HELP);
-					System.out.println(SEPARATOR);
-					return;
-				}
+			} else if ("--jetty".equalsIgnoreCase(arg)) {
+				server = new JettyServletServer();
+
+			} else if ("--tomcat".equalsIgnoreCase(arg)) {
+				server = new TomcatServletServer();
+
+			} else if ("--help".equalsIgnoreCase(arg)) {
+				log.info(HELP);
+				log.info(SEPARATOR);
+				return null;
+
+			} else {
+				log.info(HELP);
+				log.info(SEPARATOR);
+				return null;
 			}
+		}
 
-			if (server == null) {
-				server = new TomcatServletServer();
-			}
+		if (server == null) {
+			server = new TomcatServletServer();
+		}
 
-			System.out.println(" - servlet container: "+server.getName());
+		log.info(" - servlet container: "+server.getName());
 
-			for (String contextPath : contexts.keySet()) {
-				System.out.println(" - context root: "+contextPath);
-				System.out.println(" - war path: "+contexts.get(contextPath));
-			}
-			if (port > 0) {
-				System.out.println(" - Listening to HTTP on port: "+port);
-			} else {
-				System.out.println(" - Not listening to HTTP");
-			}
-			if (https > 0) {
-				System.out.println(" - Listening to HTTPS on port: "+https);
-			} else {
-				System.out.println(" - Not listening to HTTPS");
-			}
+		for (String contextPath : contexts.keySet()) {
+			log.info(" - context root: "+contextPath);
+			log.info(" - war path: "+contexts.get(contextPath));
+		}
+		if (port > 0) {
+			log.info(" - Listening to HTTP on port: "+port);
+		} else {
+			log.info(" - Not listening to HTTP");
+		}
+		if (https > 0) {
+			log.info(" - Listening to HTTPS on port: "+https);
+		} else {
+			log.info(" - Not listening to HTTPS");
+		}
 
-			System.out.println("${symbol_escape}nPress ENTER to exit.");
-			System.out.println(SEPARATOR);
-			System.out.println();
+		log.info(SEPARATOR);
+		server.start(contexts, port, https, keystoreFile, keystorePass);
+		log.info(SEPARATOR);
 
-			server.start(contexts, port, https);
+		return server;
+	}
 
-			System.in.read();
+	private static void serverStop(ServletServer server)
+			throws Exception {
 
-			System.out.println(SEPARATOR);
-			System.out.println("WAR Bootstrap exiting...");
-			System.out.println(SEPARATOR);
-			System.out.println();
+		log.info(SEPARATOR);
+		log.info("WAR Bootstrap exiting...");
+		log.info(SEPARATOR);
 
-			server.stop();
-
-		} catch (Exception ex) {
-			ex.printStackTrace();
-		}
+		server.stop();
 	}
 }

File war-bootstrap-archetype/src/main/resources/archetype-resources/src/main/java/GlassFishServletServer.java

 		return "GlassFish";
 	}
 
-	public void start(Map<String, String> contexts, int httpPort, int httpsPort) throws Exception {
+	public void start(Map<String, String> contexts, int httpPort, int httpsPort, String keystoreFile, String keystorePass)
+		throws Exception {
+
 		if (server != null) {
 			throw new IllegalStateException("Web server is already running.");
 		}
 		if (httpPort > 0) {
 			gfProps.setPort("http-listener", httpPort);
 		}
-//		if (httpsPort > 0) {
-//			gfProps.setPort("https-listener", httpsPort);
-//		}
+		if (httpsPort > 0) {
+			gfProps.setPort("https-listener", httpsPort);
+		}
 
 		server = GlassFishRuntime.bootstrap().newGlassFish(gfProps);
 		server.start();

File war-bootstrap-archetype/src/main/resources/archetype-resources/src/main/java/JettyServletServer.java

 		return "Jetty";
 	}
 
-	public void start(Map<String, String> contexts, int httpPort, int httpsPort) throws Exception {
+	public void start(Map<String, String> contexts, int httpPort, int httpsPort, String keystoreFile, String keystorePass)
+		throws Exception {
+
 		if (server != null) {
 			throw new IllegalStateException("Web server is already running.");
 		}

File war-bootstrap-archetype/src/main/resources/archetype-resources/src/main/java/MinimalContextConfig.java

+#set( $symbol_pound = '#' )
+#set( $symbol_dollar = '$' )
+#set( $symbol_escape = '\' )
+package ${package};
+
+import java.net.URL;
+
+import org.apache.catalina.core.StandardContext;
+import org.apache.catalina.deploy.ErrorPage;
+import org.apache.catalina.startup.Constants;
+import org.apache.catalina.startup.ContextConfig;
+import org.apache.catalina.startup.DigesterFactory;
+import org.apache.catalina.startup.WebRuleSet;
+import org.apache.catalina.util.SchemaResolver;
+import org.apache.tomcat.util.digester.Digester;
+import org.apache.tomcat.util.digester.RuleSet;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class overrides ContextConfig (unfortunately requiring a bunch of cut-n-paste)
+ * in order to remove registering JSP/TLD namespaces which cause a ton of warnings on startup
+ */
+public class MinimalContextConfig extends ContextConfig {
+	private static final Logger log = LoggerFactory.getLogger(MinimalContextConfig.class);
+	private final StandardContext cx;
+
+	MinimalContextConfig(StandardContext cx) {
+		this.cx = cx;
+	}
+
+	@Override
+	protected synchronized void configureStart() {
+		super.configureStart();
+
+		// clear out all default error content
+		for (ErrorPage page : cx.findErrorPages()) {
+			cx.removeErrorPage(page);
+		}
+	}
+
+	/**
+	 * Taken from org.apache.catalina.startup.ContextConfig
+	 * ====================================================
+	 * Create and return a Digester configured to process the
+	 * web application deployment descriptor (web.xml).
+	 */
+	@Override
+	public void createWebXmlDigester(boolean namespaceAware, boolean validation) {
+
+		webRuleSet = new WebRuleSet(false);
+		webDigester = newDigester(validation, namespaceAware, webRuleSet);
+		webDigester.getParser();
+
+		webFragmentRuleSet = new WebRuleSet(true);
+		webFragmentDigester = newDigester(validation, namespaceAware, webFragmentRuleSet);
+		webFragmentDigester.getParser();
+	}
+
+	/**
+	 * Taken from org.apache.catalina.startup.DigesterFactory
+	 * ======================================================
+	 * Create a <code>Digester</code> parser.
+	 * @param xmlValidation turn on/off xml validation
+	 * @param xmlNamespaceAware turn on/off namespace validation
+	 * @param rule an instance of <code>RuleSet</code> used for parsing the xml.
+	 */
+	protected static Digester newDigester(boolean xmlValidation,
+								boolean xmlNamespaceAware,
+								RuleSet rule) {
+		Digester digester = new Digester();
+		digester.setNamespaceAware(xmlNamespaceAware);
+		digester.setValidating(xmlValidation);
+		digester.setUseContextClassLoader(true);
+
+		SchemaResolver schemaResolver = new SchemaResolver(digester);
+		registerLocalSchema(schemaResolver);
+		
+		digester.setEntityResolver(schemaResolver);
+		if ( rule != null ) {
+			digester.addRuleSet(rule);
+		}
+
+		return (digester);
+	}
+
+	/**
+	 * Taken from org.apache.catalina.startup.DigesterFactory
+	 * ======================================================
+	 * Utilities used to force the parser to use local schema, when available,
+	 * instead of the <code>schemaLocation</code> XML element.
+	 */
+	protected static void registerLocalSchema(SchemaResolver schemaResolver){
+		// J2EE
+		register(Constants.J2eeSchemaResourcePath_14,
+				 Constants.J2eeSchemaPublicId_14,
+				 schemaResolver);
+
+		register(Constants.JavaeeSchemaResourcePath_5,
+				Constants.JavaeeSchemaPublicId_5,
+				schemaResolver);
+
+		register(Constants.JavaeeSchemaResourcePath_6,
+				Constants.JavaeeSchemaPublicId_6,
+				schemaResolver);
+
+		// W3C
+		register(Constants.W3cSchemaResourcePath_10,
+				 Constants.W3cSchemaPublicId_10,
+				 schemaResolver);
+
+		register(Constants.W3cSchemaDTDResourcePath_10,
+				Constants.W3cSchemaDTDPublicId_10,
+				schemaResolver);
+
+		register(Constants.W3cDatatypesDTDResourcePath_10,
+				Constants.W3cDatatypesDTDPublicId_10,
+				schemaResolver);
+
+		// web.xml	  
+		register(Constants.WebDtdResourcePath_22,
+				 Constants.WebDtdPublicId_22,
+				 schemaResolver);
+
+		register(Constants.WebDtdResourcePath_23,
+				 Constants.WebDtdPublicId_23,
+				 schemaResolver);
+
+		register(Constants.WebSchemaResourcePath_24,
+				 Constants.WebSchemaPublicId_24,
+				 schemaResolver);
+
+		register(Constants.WebSchemaResourcePath_25,
+				Constants.WebSchemaPublicId_25,
+				schemaResolver);
+
+		register(Constants.WebSchemaResourcePath_30,
+				Constants.WebSchemaPublicId_30,
+				schemaResolver);
+
+		register(Constants.WebCommonSchemaResourcePath_30,
+				Constants.WebCommonSchemaPublicId_30,
+				schemaResolver);
+		
+		register(Constants.WebFragmentSchemaResourcePath_30,
+				Constants.WebFragmentSchemaPublicId_30,
+				schemaResolver);
+
+		// Web Service
+		register(Constants.J2eeWebServiceSchemaResourcePath_11,
+				 Constants.J2eeWebServiceSchemaPublicId_11,
+				 schemaResolver);
+
+		register(Constants.J2eeWebServiceClientSchemaResourcePath_11,
+				 Constants.J2eeWebServiceClientSchemaPublicId_11,
+				 schemaResolver);
+
+		register(Constants.JavaeeWebServiceSchemaResourcePath_12,
+				Constants.JavaeeWebServiceSchemaPublicId_12,
+				schemaResolver);
+
+		register(Constants.JavaeeWebServiceClientSchemaResourcePath_12,
+				Constants.JavaeeWebServiceClientSchemaPublicId_12,
+				schemaResolver);
+
+		register(Constants.JavaeeWebServiceSchemaResourcePath_13,
+				Constants.JavaeeWebServiceSchemaPublicId_13,
+				schemaResolver);
+
+		register(Constants.JavaeeWebServiceClientSchemaResourcePath_13,
+				Constants.JavaeeWebServiceClientSchemaPublicId_13,
+				schemaResolver);
+	}
+
+	/**
+	 * Taken from org.apache.catalina.startup.DigesterFactory
+	 * ======================================================
+	 * Load the resource and add it to the resolver.
+	 */
+	protected static void register(String resourceURL, String resourcePublicId,
+			SchemaResolver schemaResolver){
+		URL url = DigesterFactory.class.getResource(resourceURL);
+   
+		if (url == null) {
+			log.warn("Could not get URL [%s]", resourceURL);
+
+		} else {
+			schemaResolver.register(resourcePublicId , url.toString() );
+		}
+	}
+}

File war-bootstrap-archetype/src/main/resources/archetype-resources/src/main/java/MinimalErrorReportValve.java

+#set( $symbol_pound = '#' )
+#set( $symbol_dollar = '$' )
+#set( $symbol_escape = '\' )
+package ${package};
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.valves.ErrorReportValve;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Overrides the error message (while still passing through the status code).
+ */
+public class MinimalErrorReportValve extends ErrorReportValve {
+	private static final Logger log = LoggerFactory.getLogger(MinimalErrorReportValve.class);
+
+	@Override
+	protected void report(Request request, Response response, Throwable ex) {
+		if (!response.isError()) {
+			return;
+		}
+
+		String message = response.getMessage();
+		if (message == null || message.isEmpty()) {
+			message = "ERROR";
+		}
+
+		int status = response.getStatus();
+		String url = request.getRequestURI();
+		int slash = url.indexOf('/', 9);
+		if (slash > 0) {
+			url = url.substring(slash);
+		}
+
+		String info = url+": ("+status+") "+message;
+
+		if (status == 404) {
+			log.warn(info, ex);
+
+		} else {
+			log.error(info, ex);
+		}
+
+		try {
+			PrintWriter writer = response.getReporter();
+			writer.print(message);
+			if (ex != null) {
+				ex.printStackTrace(writer);
+			}
+			response.finishResponse();
+
+		} catch (IOException e) {
+			log.error(e.getMessage(), e);
+		}
+	}
+}

File war-bootstrap-archetype/src/main/resources/archetype-resources/src/main/java/MinimalLifecycleListener.java

 			ex.printStackTrace();
 		}
 	}
-	
+
 	@Override
 	public void lifecycleEvent(LifecycleEvent event) {
 		if (!Lifecycle.BEFORE_START_EVENT.equals(event.getType())) {
 		servlet.setName("default");
 		servlet.setServletClass("org.apache.catalina.servlets.DefaultServlet");
 		servlet.setLoadOnStartup(1);
+		servlet.setOverridable(true);
 		cx.addChild(servlet);
 
 		// servlet mappings

File war-bootstrap-archetype/src/main/resources/archetype-resources/src/main/java/ServletServer.java

 
 	String getName();
 
-	void start(Map<String, String> contexts, int httpPort, int httpsPort) throws Exception;
+	void start(Map<String, String> contexts, int httpPort, int httpsPort, String keystoreFile, String keystorePass)
+			throws Exception;
 
 	void stop() throws Exception;
 }

File war-bootstrap-archetype/src/main/resources/archetype-resources/src/main/java/TomcatServletServer.java

 import java.util.Deque;
 import java.util.Map;
 
+import org.apache.catalina.Host;
 import org.apache.catalina.LifecycleListener;
+import org.apache.catalina.connector.Connector;
 import org.apache.catalina.core.StandardContext;
+import org.apache.catalina.core.StandardHost;
 import org.apache.catalina.startup.ContextConfig;
 import org.apache.catalina.startup.Tomcat;
 
 		return "Tomcat";
 	}
 
-	public void start(Map<String, String> contexts, int httpPort, int httpsPort) throws Exception {
+	public void start(Map<String, String> contexts, int httpPort, int httpsPort, String keystoreFile, String keystorePass)
+			throws Exception {
+
 		if (server != null) {
 			throw new IllegalStateException("Web server is already running.");
 		}
 		tmpDir = new File("tomcat");
 
 		server = new Tomcat();
+		if (httpsPort > 0) {
+			Connector httpsConnector = new Connector();
+			httpsConnector.setPort(httpsPort);
+			httpsConnector.setSecure(true);
+			httpsConnector.setScheme("https");
+			httpsConnector.setAttribute("keystoreFile", keystoreFile);
+			httpsConnector.setAttribute("keystorePass", keystorePass);
+			httpsConnector.setAttribute("clientAuth", "false");
+			httpsConnector.setAttribute("sslProtocol", "TLS");
+			httpsConnector.setAttribute("SSLEnabled", true);
+			server.getService().addConnector(httpsConnector);
+		}
+
+		server.setPort(httpPort);
 		server.setBaseDir(tmpDir.getCanonicalPath());
-		server.setPort(httpPort);
 
 		boolean loadJSP = getClass().getResource("/javax/servlet/jsp/resources/jsp_2_0.xsd") != null;
 
+		Host host = server.getHost();
+		if (host instanceof StandardHost) {
+			((StandardHost)host).setErrorReportValveClass(MinimalErrorReportValve.class.getCanonicalName());
+		}
+
 		LifecycleListener minListener = loadJSP ? null : new MinimalLifecycleListener();
 
 		for (String contextPath : contexts.keySet()) {
 			String warPath = contexts.get(contextPath);
+			if (warPath == null || warPath.isEmpty()) {
+				continue;
+			}
 
 			if (loadJSP) {
 				// alternatively do this to include JSP
 				server.addWebapp(contextPath, warPath);
 
 			} else {
-				StandardContext cx = new StandardContext();
+
+				final StandardContext cx = new StandardContext();
+				cx.setName(contextPath);
 				cx.setPath(contextPath);
 				cx.setDocBase(warPath);
+				cx.setUnpackWAR(true);
+				cx.setProcessTlds(false);
 
 				cx.addLifecycleListener(minListener);
+			
+				ContextConfig config = new MinimalContextConfig(cx);
+				cx.addLifecycleListener(config);
 
-				ContextConfig cxCfg = new ContextConfig();
-				cx.addLifecycleListener(cxCfg);
+				// prevent it from looking ( if it finds one - it'll have dup error )
+				// "org/apache/catalin/startup/NO_DEFAULT_XML"
+				config.setDefaultWebXml(server.noDefaultWebXmlPath());
 
-				// prevent it from looking (if it finds one, it'll have dup error)
-				// "org/apache/catalin/startup/NO_DEFAULT_XML"
-				cxCfg.setDefaultWebXml(server.noDefaultWebXmlPath());
-
-				server.getHost().addChild(cx);
+				host.addChild(cx);
 			}
 		}
 

File war-bootstrap/pom.xml

 
 	<groupId>org.duelengine</groupId>
 	<artifactId>bootstrap</artifactId>
-	<version>0.4.0</version>
+	<version>0.5.0</version>
 	<packaging>jar</packaging>
 
 	<name>WAR Bootstrap</name>
 
 		<glassfish.version>3.1.1</glassfish.version>
 		<jetty.version>8.1.5.v20120716</jetty.version>
-		<tomcat.version>7.0.29</tomcat.version>
-		<slf4j.version>1.6.6</slf4j.version>
+		<tomcat.version>7.0.33</tomcat.version>
+		<slf4j.version>1.7.5</slf4j.version>
 		<javac.version>1.6</javac.version>
 	</properties>
 
 	<dependencies>
+		<!-- SLF4J runtime -->
+		<dependency>
+			<groupId>com.pseudocode</groupId>
+			<artifactId>slf4j-compact</artifactId>
+			<version>${slf4j.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>slf4j-api</artifactId>
+			<version>${slf4j.version}</version>
+		</dependency>
+
 		<!-- NOTE: GlassFish and Tomcat appear to be conflicting.
 			 order appears to matter and also
 			 must set scope to provided if not used -->
 			<version>${glassfish.version}</version>
 			<scope>compile</scope><!-- comment this for GF3 -->
 		</dependency>
-
-		<!-- SLF4J runtime -->
-		<dependency>
-			<groupId>com.pseudocode</groupId>
-			<artifactId>slf4j-compact</artifactId>
-			<version>${slf4j.version}</version>
-		</dependency>
-		<dependency>
-			<groupId>org.slf4j</groupId>
-			<artifactId>slf4j-api</artifactId>
-			<version>${slf4j.version}</version>
-		</dependency>
 	</dependencies>
 
 	<build>

File war-bootstrap/src/main/java/org/duelengine/bootstrap/Bootstrap.java

 package org.duelengine.bootstrap;
 
 import java.io.File;
+import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 public class Bootstrap {
 
-	private static final String SEPARATOR = "========================================";
+	private static final Logger log = LoggerFactory.getLogger(Bootstrap.class);
+
+	private static final long MS_PER_NANO = (long)1e6;
+	private static final String SEPARATOR = "========================================================================";
 	private static final String HELP = "java -jar bootstrap.jar\n"+
 			"  --help                 : this help text\n"+
 			"  -war <context>=<path>  : set the context of war (e.g., /=./root.war)\n" +
 
 	public static void main(String[] args) {
 
+		ServletServer server = null;
+		try {
+			server = serverStart(args);
+
+		} catch (Exception ex) {
+			ex.printStackTrace();
+			log.error(ex.getMessage(), ex);
+		}
+
+		long start = System.nanoTime();
+
+		try {
+			if (server != null) {
+				waitForKillSignal();
+			}
+
+		} catch (Exception ex) {
+			ex.printStackTrace();
+			log.error(ex.getMessage(), ex);
+		}
+
+		log.info("Killing webhead, uptime: %d ms", (System.nanoTime() - start) / MS_PER_NANO);
+
+		try {
+			if (server != null) {
+				serverStop(server);
+			}
+
+		} catch (Exception ex) {
+			ex.printStackTrace();
+			log.error(ex.getMessage(), ex);
+		}
+	}
+
+	private static void waitForKillSignal()
+			throws IOException {
+
+			log.info("Press ENTER to exit.");
+			log.info(SEPARATOR);
+
+			System.in.read();
+	}
+
+	private static ServletServer serverStart(String[] args)
+			throws IOException, Exception {
+
+		log.info(SEPARATOR);
+		log.info("WAR Bootstrap");
+		log.info(SEPARATOR);
+
 		Map<String, String> contexts = new HashMap<String, String>();
 		int port = 8080;
 		int https = -1;
 		ServletServer server = null;
 		String keystoreFile = null;
 		String keystorePass = null;
-		try {
-			System.out.println(SEPARATOR);
-			System.out.println("WAR Bootstrap\n");
-			for (int i=0; i<args.length; i++) {
-				String arg = args[i];
-				if ("-p".equals(arg)) {
-					port = Integer.parseInt(args[++i]);
 
-				} else if ("-s".equals(arg)) {
-					https = Integer.parseInt(args[++i]);
+		for (int i=0; i<args.length; i++) {
+			String arg = args[i];
+			if ("-p".equals(arg)) {
+				port = Integer.parseInt(args[++i]);
 
-				} else if ("-ks".equals(arg)) {
-					keystoreFile = args[++i];
+			} else if ("-s".equals(arg)) {
+				https = Integer.parseInt(args[++i]);
 
-				} else if ("-kp".equals(arg)) {
-					keystorePass = args[++i];
+			} else if ("-ks".equals(arg)) {
+				keystoreFile = args[++i];
 
-				} else if ("-war".equals(arg)) {
-					String contextPath;
-					String warPath = args[++i];
-					int delim = warPath.indexOf('=');
-					if (delim < 1) {
-						contextPath = "/";
-					} else {
-						contextPath = warPath.substring(0, delim);
-					}
-					warPath = new File(warPath.substring(delim+1)).getCanonicalPath();
-					contexts.put(contextPath, warPath);
+			} else if ("-kp".equals(arg)) {
+				keystorePass = args[++i];
 
-				} else if ("--glassfish".equalsIgnoreCase(arg)) {
-					server = new GlassFishServletServer();
+			} else if ("-war".equals(arg)) {
+				String contextPath;
+				String warPath = args[++i];
+				int delim = warPath.indexOf('=');
+				if (delim < 1) {
+					contextPath = "/";
+				} else {
+					contextPath = warPath.substring(0, delim);
+				}
+				warPath = new File(warPath.substring(delim+1)).getCanonicalPath();
+				contexts.put(contextPath, warPath);
 
-				} else if ("--jetty".equalsIgnoreCase(arg)) {
-					server = new JettyServletServer();
+			} else if ("--glassfish".equalsIgnoreCase(arg)) {
+				server = new GlassFishServletServer();
 
-				} else if ("--tomcat".equalsIgnoreCase(arg)) {
-					server = new TomcatServletServer();
+			} else if ("--jetty".equalsIgnoreCase(arg)) {
+				server = new JettyServletServer();
 
-				} else if ("--help".equalsIgnoreCase(arg)) {
-					System.out.println(HELP);
-					System.out.println(SEPARATOR);
-					return;
+			} else if ("--tomcat".equalsIgnoreCase(arg)) {
+				server = new TomcatServletServer();
 
-				} else {
-					System.out.println(HELP);
-					System.out.println(SEPARATOR);
-					return;
-				}
+			} else if ("--help".equalsIgnoreCase(arg)) {
+				log.info(HELP);
+				log.info(SEPARATOR);
+				return null;
+
+			} else {
+				log.info(HELP);
+				log.info(SEPARATOR);
+				return null;
 			}
+		}
 
-			if (server == null) {
-				server = new TomcatServletServer();
-			}
+		if (server == null) {
+			server = new TomcatServletServer();
+		}
 
-			System.out.println(" - servlet container: "+server.getName());
+		log.info(" - servlet container: "+server.getName());
 
-			for (String contextPath : contexts.keySet()) {
-				System.out.println(" - context root: "+contextPath);
-				System.out.println(" - war path: "+contexts.get(contextPath));
-			}
-			if (port > 0) {
-				System.out.println(" - Listening to HTTP on port: "+port);
-			} else {
-				System.out.println(" - Not listening to HTTP");
-			}
-			if (https > 0) {
-				System.out.println(" - Listening to HTTPS on port: "+https);
-			} else {
-				System.out.println(" - Not listening to HTTPS");
-			}
+		for (String contextPath : contexts.keySet()) {
+			log.info(" - context root: "+contextPath);
+			log.info(" - war path: "+contexts.get(contextPath));
+		}
+		if (port > 0) {
+			log.info(" - Listening to HTTP on port: "+port);
+		} else {
+			log.info(" - Not listening to HTTP");
+		}
+		if (https > 0) {
+			log.info(" - Listening to HTTPS on port: "+https);
+		} else {
+			log.info(" - Not listening to HTTPS");
+		}
 
-			System.out.println("\nPress ENTER to exit.");
-			System.out.println(SEPARATOR);
-			System.out.println();
+		log.info(SEPARATOR);
+		server.start(contexts, port, https, keystoreFile, keystorePass);
+		log.info(SEPARATOR);
 
-			server.start(contexts, port, https, keystoreFile, keystorePass);
+		return server;
+	}
 
-			System.in.read();
+	private static void serverStop(ServletServer server)
+			throws Exception {
 
-			System.out.println(SEPARATOR);
-			System.out.println("WAR Bootstrap exiting...");
-			System.out.println(SEPARATOR);
-			System.out.println();
+		log.info(SEPARATOR);
+		log.info("WAR Bootstrap exiting...");
+		log.info(SEPARATOR);
 
-			server.stop();
-
-		} catch (Exception ex) {
-			ex.printStackTrace();
-		}
+		server.stop();
 	}
 }

File war-bootstrap/src/main/java/org/duelengine/bootstrap/MinimalContextConfig.java

+package org.duelengine.bootstrap;
+
+import java.net.URL;
+
+import org.apache.catalina.core.StandardContext;
+import org.apache.catalina.deploy.ErrorPage;
+import org.apache.catalina.startup.Constants;
+import org.apache.catalina.startup.ContextConfig;
+import org.apache.catalina.startup.DigesterFactory;
+import org.apache.catalina.startup.WebRuleSet;
+import org.apache.catalina.util.SchemaResolver;
+import org.apache.tomcat.util.digester.Digester;
+import org.apache.tomcat.util.digester.RuleSet;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class overrides ContextConfig (unfortunately requiring a bunch of cut-n-paste)
+ * in order to remove registering JSP/TLD namespaces which cause a ton of warnings on startup
+ */
+public class MinimalContextConfig extends ContextConfig {
+	private static final Logger log = LoggerFactory.getLogger(MinimalContextConfig.class);
+	private final StandardContext cx;
+
+	MinimalContextConfig(StandardContext cx) {
+		this.cx = cx;
+	}
+
+	@Override
+	protected synchronized void configureStart() {
+		super.configureStart();
+
+		// clear out all default error content
+		for (ErrorPage page : cx.findErrorPages()) {
+			cx.removeErrorPage(page);
+		}
+	}
+
+	/**
+	 * Taken from org.apache.catalina.startup.ContextConfig
+	 * ====================================================
+	 * Create and return a Digester configured to process the
+	 * web application deployment descriptor (web.xml).
+	 */
+	@Override
+	public void createWebXmlDigester(boolean namespaceAware, boolean validation) {
+
+		webRuleSet = new WebRuleSet(false);
+		webDigester = newDigester(validation, namespaceAware, webRuleSet);
+		webDigester.getParser();
+
+		webFragmentRuleSet = new WebRuleSet(true);
+		webFragmentDigester = newDigester(validation, namespaceAware, webFragmentRuleSet);
+		webFragmentDigester.getParser();
+	}
+
+	/**
+	 * Taken from org.apache.catalina.startup.DigesterFactory
+	 * ======================================================
+	 * Create a <code>Digester</code> parser.
+	 * @param xmlValidation turn on/off xml validation
+	 * @param xmlNamespaceAware turn on/off namespace validation
+	 * @param rule an instance of <code>RuleSet</code> used for parsing the xml.
+	 */
+	protected static Digester newDigester(boolean xmlValidation,
+								boolean xmlNamespaceAware,
+								RuleSet rule) {
+		Digester digester = new Digester();
+		digester.setNamespaceAware(xmlNamespaceAware);
+		digester.setValidating(xmlValidation);
+		digester.setUseContextClassLoader(true);
+
+		SchemaResolver schemaResolver = new SchemaResolver(digester);
+		registerLocalSchema(schemaResolver);
+		
+		digester.setEntityResolver(schemaResolver);
+		if ( rule != null ) {
+			digester.addRuleSet(rule);
+		}
+
+		return (digester);
+	}
+
+	/**
+	 * Taken from org.apache.catalina.startup.DigesterFactory
+	 * ======================================================
+	 * Utilities used to force the parser to use local schema, when available,
+	 * instead of the <code>schemaLocation</code> XML element.
+	 */
+	protected static void registerLocalSchema(SchemaResolver schemaResolver){
+		// J2EE
+		register(Constants.J2eeSchemaResourcePath_14,
+				 Constants.J2eeSchemaPublicId_14,
+				 schemaResolver);
+
+		register(Constants.JavaeeSchemaResourcePath_5,
+				Constants.JavaeeSchemaPublicId_5,
+				schemaResolver);
+
+		register(Constants.JavaeeSchemaResourcePath_6,
+				Constants.JavaeeSchemaPublicId_6,
+				schemaResolver);
+
+		// W3C
+		register(Constants.W3cSchemaResourcePath_10,
+				 Constants.W3cSchemaPublicId_10,
+				 schemaResolver);
+
+		register(Constants.W3cSchemaDTDResourcePath_10,
+				Constants.W3cSchemaDTDPublicId_10,
+				schemaResolver);
+
+		register(Constants.W3cDatatypesDTDResourcePath_10,
+				Constants.W3cDatatypesDTDPublicId_10,
+				schemaResolver);
+
+		// web.xml	  
+		register(Constants.WebDtdResourcePath_22,
+				 Constants.WebDtdPublicId_22,
+				 schemaResolver);
+
+		register(Constants.WebDtdResourcePath_23,
+				 Constants.WebDtdPublicId_23,
+				 schemaResolver);
+
+		register(Constants.WebSchemaResourcePath_24,
+				 Constants.WebSchemaPublicId_24,
+				 schemaResolver);
+
+		register(Constants.WebSchemaResourcePath_25,
+				Constants.WebSchemaPublicId_25,
+				schemaResolver);
+
+		register(Constants.WebSchemaResourcePath_30,
+				Constants.WebSchemaPublicId_30,
+				schemaResolver);
+
+		register(Constants.WebCommonSchemaResourcePath_30,
+				Constants.WebCommonSchemaPublicId_30,
+				schemaResolver);
+		
+		register(Constants.WebFragmentSchemaResourcePath_30,
+				Constants.WebFragmentSchemaPublicId_30,
+				schemaResolver);
+
+		// Web Service
+		register(Constants.J2eeWebServiceSchemaResourcePath_11,
+				 Constants.J2eeWebServiceSchemaPublicId_11,
+				 schemaResolver);
+
+		register(Constants.J2eeWebServiceClientSchemaResourcePath_11,
+				 Constants.J2eeWebServiceClientSchemaPublicId_11,
+				 schemaResolver);
+
+		register(Constants.JavaeeWebServiceSchemaResourcePath_12,
+				Constants.JavaeeWebServiceSchemaPublicId_12,
+				schemaResolver);
+
+		register(Constants.JavaeeWebServiceClientSchemaResourcePath_12,
+				Constants.JavaeeWebServiceClientSchemaPublicId_12,
+				schemaResolver);
+
+		register(Constants.JavaeeWebServiceSchemaResourcePath_13,
+				Constants.JavaeeWebServiceSchemaPublicId_13,
+				schemaResolver);
+
+		register(Constants.JavaeeWebServiceClientSchemaResourcePath_13,
+				Constants.JavaeeWebServiceClientSchemaPublicId_13,
+				schemaResolver);
+	}
+
+	/**
+	 * Taken from org.apache.catalina.startup.DigesterFactory
+	 * ======================================================
+	 * Load the resource and add it to the resolver.
+	 */
+	protected static void register(String resourceURL, String resourcePublicId,
+			SchemaResolver schemaResolver){
+		URL url = DigesterFactory.class.getResource(resourceURL);
+   
+		if (url == null) {
+			log.warn("Could not get URL [%s]", resourceURL);
+
+		} else {
+			schemaResolver.register(resourcePublicId , url.toString() );
+		}
+	}
+}

File war-bootstrap/src/main/java/org/duelengine/bootstrap/MinimalErrorReportValve.java

+package org.duelengine.bootstrap;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.valves.ErrorReportValve;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Overrides the error message (while still passing through the status code).
+ */
+public class MinimalErrorReportValve extends ErrorReportValve {
+	private static final Logger log = LoggerFactory.getLogger(MinimalErrorReportValve.class);
+
+	@Override
+	protected void report(Request request, Response response, Throwable ex) {
+		if (!response.isError()) {
+			return;
+		}
+
+		String message = response.getMessage();
+		if (message == null || message.isEmpty()) {
+			message = "ERROR";
+		}
+
+		int status = response.getStatus();
+		String url = request.getRequestURI();
+		int slash = url.indexOf('/', 9);
+		if (slash > 0) {
+			url = url.substring(slash);
+		}
+
+		String info = url+": ("+status+") "+message;
+
+		if (status == 404) {
+			log.warn(info, ex);
+
+		} else {
+			log.error(info, ex);
+		}
+
+		try {
+			PrintWriter writer = response.getReporter();
+			writer.print(message);
+			if (ex != null) {
+				ex.printStackTrace(writer);
+			}
+			response.finishResponse();
+
+		} catch (IOException e) {
+			log.error(e.getMessage(), e);
+		}
+	}
+}

File war-bootstrap/src/main/java/org/duelengine/bootstrap/MinimalLifecycleListener.java

 			ex.printStackTrace();
 		}
 	}
-	
+
 	@Override
 	public void lifecycleEvent(LifecycleEvent event) {
 		if (!Lifecycle.BEFORE_START_EVENT.equals(event.getType())) {
 		servlet.setName("default");
 		servlet.setServletClass("org.apache.catalina.servlets.DefaultServlet");
 		servlet.setLoadOnStartup(1);
+		servlet.setOverridable(true);
 		cx.addChild(servlet);
 
 		// servlet mappings

File war-bootstrap/src/main/java/org/duelengine/bootstrap/TomcatServletServer.java

 import java.util.Deque;
 import java.util.Map;
 
+import org.apache.catalina.Host;
 import org.apache.catalina.LifecycleListener;
 import org.apache.catalina.connector.Connector;
 import org.apache.catalina.core.StandardContext;
+import org.apache.catalina.core.StandardHost;
 import org.apache.catalina.startup.ContextConfig;
 import org.apache.catalina.startup.Tomcat;
 
 	}
 
 	public void start(Map<String, String> contexts, int httpPort, int httpsPort, String keystoreFile, String keystorePass)
-		throws Exception {
+			throws Exception {
 
 		if (server != null) {
 			throw new IllegalStateException("Web server is already running.");
 
 		boolean loadJSP = getClass().getResource("/javax/servlet/jsp/resources/jsp_2_0.xsd") != null;
 
+		Host host = server.getHost();
+		if (host instanceof StandardHost) {
+			((StandardHost)host).setErrorReportValveClass(MinimalErrorReportValve.class.getCanonicalName());
+		}
+
 		LifecycleListener minListener = loadJSP ? null : new MinimalLifecycleListener();
 
 		for (String contextPath : contexts.keySet()) {
 			String warPath = contexts.get(contextPath);
+			if (warPath == null || warPath.isEmpty()) {
+				continue;
+			}
 
 			if (loadJSP) {
 				// alternatively do this to include JSP
 				server.addWebapp(contextPath, warPath);
 
 			} else {
-				StandardContext cx = new StandardContext();
+
+				final StandardContext cx = new StandardContext();
+				cx.setName(contextPath);
 				cx.setPath(contextPath);
 				cx.setDocBase(warPath);
+				cx.setUnpackWAR(true);
+				cx.setProcessTlds(false);
 
 				cx.addLifecycleListener(minListener);
+			
+				ContextConfig config = new MinimalContextConfig(cx);
+				cx.addLifecycleListener(config);
 
-				ContextConfig cxCfg = new ContextConfig();
-				cx.addLifecycleListener(cxCfg);
+				// prevent it from looking ( if it finds one - it'll have dup error )
+				// "org/apache/catalin/startup/NO_DEFAULT_XML"
+				config.setDefaultWebXml(server.noDefaultWebXmlPath());
 
-				// prevent it from looking (if it finds one, it'll have dup error)
-				// "org/apache/catalin/startup/NO_DEFAULT_XML"
-				cxCfg.setDefaultWebXml(server.noDefaultWebXmlPath());
-
-				server.getHost().addChild(cx);
+				host.addChild(cx);
 			}
 		}