Created
June 1, 2012 19:47
-
-
Save pveller/2854726 to your computer and use it in GitHub Desktop.
GWT Jetty Launcher to work with JNDI in the hosted mode (dev)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright 2008 Google Inc. | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); you may not | |
* use this file except in compliance with the License. You may obtain a copy of | |
* the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |
* License for the specific language governing permissions and limitations under | |
* the License. | |
*/ | |
package com.mims.novonote.website.server; | |
import com.google.gwt.core.ext.ServletContainer; | |
import com.google.gwt.core.ext.ServletContainerLauncher; | |
import com.google.gwt.core.ext.TreeLogger; | |
import com.google.gwt.core.ext.UnableToCompleteException; | |
import com.google.gwt.dev.util.InstalledHelpInfo; | |
import com.google.gwt.dev.util.Util; | |
import org.mortbay.component.AbstractLifeCycle; | |
import org.mortbay.jetty.AbstractConnector; | |
import org.mortbay.jetty.Request; | |
import org.mortbay.jetty.RequestLog; | |
import org.mortbay.jetty.Response; | |
import org.mortbay.jetty.Server; | |
import org.mortbay.jetty.HttpFields.Field; | |
import org.mortbay.jetty.handler.RequestLogHandler; | |
import org.mortbay.jetty.nio.SelectChannelConnector; | |
import org.mortbay.jetty.security.SslSocketConnector; | |
import org.mortbay.jetty.webapp.WebAppClassLoader; | |
import org.mortbay.jetty.webapp.WebAppContext; | |
import org.mortbay.log.Log; | |
import org.mortbay.log.Logger; | |
import java.io.File; | |
import java.io.IOException; | |
import java.lang.reflect.InvocationTargetException; | |
import java.lang.reflect.Method; | |
import java.net.MalformedURLException; | |
import java.net.URL; | |
import java.net.URLConnection; | |
import java.util.Iterator; | |
import javax.imageio.ImageIO; | |
import javax.xml.parsers.DocumentBuilderFactory; | |
import javax.xml.parsers.ParserConfigurationException; | |
/** | |
* A {@link ServletContainerLauncher} for an embedded Jetty server. | |
*/ | |
public class CustomJettyLauncher extends ServletContainerLauncher { | |
public static final String[] DEFAULT_CONFIG_CLASSES = | |
{ | |
"org.mortbay.jetty.webapp.WebInfConfiguration", //init webapp structure | |
"org.mortbay.jetty.plus.webapp.EnvConfiguration", //process jetty-env | |
"org.mortbay.jetty.plus.webapp.Configuration", //process web.xml | |
"org.mortbay.jetty.webapp.JettyWebXmlConfiguration",//process jetty-web.xml | |
}; | |
/** | |
* Log jetty requests/responses to TreeLogger. | |
*/ | |
public static class JettyRequestLogger extends AbstractLifeCycle implements | |
RequestLog { | |
private final TreeLogger logger; | |
private final TreeLogger.Type normalLogLevel; | |
public JettyRequestLogger(TreeLogger logger, TreeLogger.Type normalLogLevel) { | |
this.logger = logger; | |
assert (normalLogLevel != null); | |
this.normalLogLevel = normalLogLevel; | |
} | |
/** | |
* Log an HTTP request/response to TreeLogger. | |
*/ | |
@SuppressWarnings("unchecked") | |
public void log(Request request, Response response) { | |
int status = response.getStatus(); | |
if (status < 0) { | |
// Copied from NCSARequestLog | |
status = 404; | |
} | |
TreeLogger.Type logStatus, logHeaders; | |
if (status >= 500) { | |
logStatus = TreeLogger.ERROR; | |
logHeaders = TreeLogger.INFO; | |
} else if (status == 404) { | |
if ("/favicon.ico".equals(request.getRequestURI()) | |
&& request.getQueryString() == null) { | |
/* | |
* We do not want to call the developer's attention to a 404 when | |
* requesting favicon.ico. This is a very common 404. | |
*/ | |
logStatus = TreeLogger.TRACE; | |
logHeaders = TreeLogger.DEBUG; | |
} else { | |
logStatus = TreeLogger.WARN; | |
logHeaders = TreeLogger.INFO; | |
} | |
} else if (status >= 400) { | |
logStatus = TreeLogger.WARN; | |
logHeaders = TreeLogger.INFO; | |
} else { | |
logStatus = normalLogLevel; | |
logHeaders = TreeLogger.DEBUG; | |
} | |
String userString = request.getRemoteUser(); | |
if (userString == null) { | |
userString = ""; | |
} else { | |
userString += "@"; | |
} | |
String bytesString = ""; | |
if (response.getContentCount() > 0) { | |
bytesString = " " + response.getContentCount() + " bytes"; | |
} | |
if (logger.isLoggable(logStatus)) { | |
TreeLogger branch = logger.branch(logStatus, String.valueOf(status) | |
+ " - " + request.getMethod() + ' ' + request.getUri() + " (" | |
+ userString + request.getRemoteHost() + ')' + bytesString); | |
if (branch.isLoggable(logHeaders)) { | |
// Request headers | |
TreeLogger headers = branch.branch(logHeaders, "Request headers"); | |
Iterator<Field> headerFields = request.getConnection().getRequestFields().getFields(); | |
while (headerFields.hasNext()) { | |
Field headerField = headerFields.next(); | |
headers.log(logHeaders, headerField.getName() + ": " | |
+ headerField.getValue()); | |
} | |
// Response headers | |
headers = branch.branch(logHeaders, "Response headers"); | |
headerFields = response.getHttpFields().getFields(); | |
while (headerFields.hasNext()) { | |
Field headerField = headerFields.next(); | |
headers.log(logHeaders, headerField.getName() + ": " | |
+ headerField.getValue()); | |
} | |
} | |
} | |
} | |
} | |
/** | |
* An adapter for the Jetty logging system to GWT's TreeLogger. This | |
* implementation class is only public to allow {@link Log} to instantiate it. | |
* | |
* The weird static data / default construction setup is a game we play with | |
* {@link Log}'s static initializer to prevent the initial log message from | |
* going to stderr. | |
*/ | |
public static class JettyTreeLogger implements Logger { | |
private final TreeLogger logger; | |
public JettyTreeLogger(TreeLogger logger) { | |
if (logger == null) { | |
throw new NullPointerException(); | |
} | |
this.logger = logger; | |
} | |
public void debug(String msg, Object arg0, Object arg1) { | |
if (logger.isLoggable(TreeLogger.SPAM)) { | |
logger.log(TreeLogger.SPAM, format(msg, arg0, arg1)); | |
} | |
} | |
public void debug(String msg, Throwable th) { | |
logger.log(TreeLogger.SPAM, msg, th); | |
} | |
public Logger getLogger(String name) { | |
return this; | |
} | |
public void info(String msg, Object arg0, Object arg1) { | |
if (logger.isLoggable(TreeLogger.TRACE)) { | |
logger.log(TreeLogger.TRACE, format(msg, arg0, arg1)); | |
} | |
} | |
public boolean isDebugEnabled() { | |
return logger.isLoggable(TreeLogger.SPAM); | |
} | |
public void setDebugEnabled(boolean enabled) { | |
// ignored | |
} | |
public void warn(String msg, Object arg0, Object arg1) { | |
if (logger.isLoggable(TreeLogger.WARN)) { | |
logger.log(TreeLogger.WARN, format(msg, arg0, arg1)); | |
} | |
} | |
public void warn(String msg, Throwable th) { | |
logger.log(TreeLogger.WARN, msg, th); | |
} | |
/** | |
* Copied from org.mortbay.log.StdErrLog. | |
*/ | |
private String format(String msg, Object arg0, Object arg1) { | |
int i0 = msg.indexOf("{}"); | |
int i1 = i0 < 0 ? -1 : msg.indexOf("{}", i0 + 2); | |
if (arg1 != null && i1 >= 0) { | |
msg = msg.substring(0, i1) + arg1 + msg.substring(i1 + 2); | |
} | |
if (arg0 != null && i0 >= 0) { | |
msg = msg.substring(0, i0) + arg0 + msg.substring(i0 + 2); | |
} | |
return msg; | |
} | |
} | |
/** | |
* The resulting {@link ServletContainer} this is launched. | |
*/ | |
protected static class JettyServletContainer extends ServletContainer { | |
private final int actualPort; | |
private final File appRootDir; | |
private final TreeLogger logger; | |
private final Server server; | |
private final WebAppContext wac; | |
public JettyServletContainer(TreeLogger logger, Server server, | |
WebAppContext wac, int actualPort, File appRootDir) { | |
this.logger = logger; | |
this.server = server; | |
this.wac = wac; | |
this.actualPort = actualPort; | |
this.appRootDir = appRootDir; | |
} | |
@Override | |
public int getPort() { | |
return actualPort; | |
} | |
@Override | |
public void refresh() throws UnableToCompleteException { | |
String msg = "Reloading web app to reflect changes in " | |
+ appRootDir.getAbsolutePath(); | |
TreeLogger branch = logger.branch(TreeLogger.INFO, msg); | |
// Temporarily log Jetty on the branch. | |
Log.setLog(new JettyTreeLogger(branch)); | |
try { | |
wac.stop(); | |
server.stop(); | |
wac.start(); | |
server.start(); | |
branch.log(TreeLogger.INFO, "Reload completed successfully"); | |
} catch (Exception e) { | |
branch.log(TreeLogger.ERROR, "Unable to restart embedded Jetty server", | |
e); | |
throw new UnableToCompleteException(); | |
} finally { | |
// Reset the top-level logger. | |
Log.setLog(new JettyTreeLogger(logger)); | |
} | |
} | |
@Override | |
public void stop() throws UnableToCompleteException { | |
TreeLogger branch = logger.branch(TreeLogger.INFO, | |
"Stopping Jetty server"); | |
// Temporarily log Jetty on the branch. | |
Log.setLog(new JettyTreeLogger(branch)); | |
try { | |
server.stop(); | |
server.setStopAtShutdown(false); | |
branch.log(TreeLogger.TRACE, "Stopped successfully"); | |
} catch (Exception e) { | |
branch.log(TreeLogger.ERROR, "Unable to stop embedded Jetty server", e); | |
throw new UnableToCompleteException(); | |
} finally { | |
// Reset the top-level logger. | |
Log.setLog(new JettyTreeLogger(logger)); | |
} | |
} | |
} | |
/** | |
* A {@link WebAppContext} tailored to GWT hosted mode. Features hot-reload | |
* with a new {@link WebAppClassLoader} to pick up disk changes. The default | |
* Jetty {@code WebAppContext} will create new instances of servlets, but it | |
* will not create a brand new {@link ClassLoader}. By creating a new {@code | |
* ClassLoader} each time, we re-read updated classes from disk. | |
* | |
* Also provides special class filtering to isolate the web app from the GWT | |
* hosting environment. | |
*/ | |
protected static final class WebAppContextWithReload extends WebAppContext { | |
/** | |
* Specialized {@link WebAppClassLoader} that allows outside resources to be | |
* brought in dynamically from the system path. A warning is issued when | |
* this occurs. | |
*/ | |
private class WebAppClassLoaderExtension extends WebAppClassLoader { | |
private static final String META_INF_SERVICES = "META-INF/services/"; | |
public WebAppClassLoaderExtension() throws IOException { | |
super(bootStrapOnlyClassLoader, WebAppContextWithReload.this); | |
} | |
@Override | |
public URL findResource(String name) { | |
// Specifically for META-INF/services/javax.xml.parsers.SAXParserFactory | |
String checkName = name; | |
if (checkName.startsWith(META_INF_SERVICES)) { | |
checkName = checkName.substring(META_INF_SERVICES.length()); | |
} | |
// For a system path, load from the outside world. | |
URL found; | |
if (isSystemPath(checkName)) { | |
found = systemClassLoader.getResource(name); | |
if (found != null) { | |
return found; | |
} | |
} | |
// Always check this ClassLoader first. | |
found = super.findResource(name); | |
if (found != null) { | |
return found; | |
} | |
// See if the outside world has it. | |
found = systemClassLoader.getResource(name); | |
if (found == null) { | |
return null; | |
} | |
// Warn, add containing URL to our own ClassLoader, and retry the call. | |
String warnMessage = "Server resource '" | |
+ name | |
+ "' could not be found in the web app, but was found on the system classpath"; | |
if (!addContainingClassPathEntry(warnMessage, found, name)) { | |
return null; | |
} | |
return super.findResource(name); | |
} | |
/** | |
* Override to additionally consider the most commonly available JSP and | |
* XML implementation as system resources. (In fact, Jasper is in gwt-dev | |
* via embedded Tomcat, so we always hit this case.) | |
*/ | |
@Override | |
public boolean isSystemPath(String name) { | |
name = name.replace('/', '.'); | |
return super.isSystemPath(name) | |
|| name.startsWith("org.apache.jasper.") | |
|| name.startsWith("org.apache.xerces."); | |
} | |
@Override | |
protected Class<?> findClass(String name) throws ClassNotFoundException { | |
// For system path, always prefer the outside world. | |
if (isSystemPath(name)) { | |
try { | |
return systemClassLoader.loadClass(name); | |
} catch (ClassNotFoundException e) { | |
} | |
} | |
try { | |
return super.findClass(name); | |
} catch (ClassNotFoundException e) { | |
// Don't allow server classes to be loaded from the outside. | |
if (isServerPath(name)) { | |
throw e; | |
} | |
} | |
// See if the outside world has a URL for it. | |
String resourceName = name.replace('.', '/') + ".class"; | |
URL found = systemClassLoader.getResource(resourceName); | |
if (found == null) { | |
return null; | |
} | |
// Warn, add containing URL to our own ClassLoader, and retry the call. | |
String warnMessage = "Server class '" | |
+ name | |
+ "' could not be found in the web app, but was found on the system classpath"; | |
if (!addContainingClassPathEntry(warnMessage, found, resourceName)) { | |
throw new ClassNotFoundException(name); | |
} | |
return super.findClass(name); | |
} | |
private boolean addContainingClassPathEntry(String warnMessage, | |
URL resource, String resourceName) { | |
TreeLogger.Type logLevel = (System.getProperty(PROPERTY_NOWARN_WEBAPP_CLASSPATH) == null) | |
? TreeLogger.WARN : TreeLogger.DEBUG; | |
TreeLogger branch = logger.branch(logLevel, warnMessage); | |
String classPathURL; | |
String foundStr = resource.toExternalForm(); | |
if (resource.getProtocol().equals("file")) { | |
assert foundStr.endsWith(resourceName); | |
classPathURL = foundStr.substring(0, foundStr.length() | |
- resourceName.length()); | |
} else if (resource.getProtocol().equals("jar")) { | |
assert foundStr.startsWith("jar:"); | |
assert foundStr.endsWith("!/" + resourceName); | |
classPathURL = foundStr.substring(4, foundStr.length() | |
- (2 + resourceName.length())); | |
} else { | |
branch.log(TreeLogger.ERROR, | |
"Found resouce but unrecognized URL format: '" + foundStr + '\''); | |
return false; | |
} | |
branch = branch.branch(logLevel, "Adding classpath entry '" | |
+ classPathURL + "' to the web app classpath for this session", | |
null, new InstalledHelpInfo("webAppClassPath.html")); | |
try { | |
addClassPath(classPathURL); | |
return true; | |
} catch (IOException e) { | |
branch.log(TreeLogger.ERROR, "Failed add container URL: '" | |
+ classPathURL + '\'', e); | |
return false; | |
} | |
} | |
} | |
/** | |
* Parent ClassLoader for the Jetty web app, which can only load JVM | |
* classes. We would just use <code>null</code> for the parent ClassLoader | |
* except this makes Jetty unhappy. | |
*/ | |
private final ClassLoader bootStrapOnlyClassLoader = new ClassLoader(null) { | |
}; | |
private final TreeLogger logger; | |
/** | |
* In the usual case of launching {@link com.google.gwt.dev.DevMode}, this | |
* will always by the system app ClassLoader. | |
*/ | |
private final ClassLoader systemClassLoader = Thread.currentThread().getContextClassLoader(); | |
private WebAppClassLoaderExtension classLoader; | |
@SuppressWarnings("unchecked") | |
private WebAppContextWithReload(TreeLogger logger, String webApp, | |
String contextPath) { | |
super(webApp, contextPath); | |
this.logger = logger; | |
// Prevent file locking on Windows; pick up file changes. | |
getInitParams().put( | |
"org.mortbay.jetty.servlet.Default.useFileMappedBuffer", "false"); | |
// Since the parent class loader is bootstrap-only, prefer it first. | |
setParentLoaderPriority(true); | |
} | |
@Override | |
protected void doStart() throws Exception { | |
classLoader = new WebAppClassLoaderExtension(); | |
setClassLoader(classLoader); | |
super.doStart(); | |
} | |
@Override | |
protected void doStop() throws Exception { | |
super.doStop(); | |
Class<?> jdbcUnloader = classLoader.loadClass("com.google.gwt.dev.shell.jetty.JDBCUnloader"); | |
java.lang.reflect.Method unload = jdbcUnloader.getMethod("unload"); | |
unload.invoke(null); | |
setClassLoader(null); | |
classLoader.destroy(); | |
} | |
} | |
/** | |
* Represents the type of SSL client certificate authentication desired. | |
*/ | |
private enum ClientAuth { | |
NONE, | |
WANT, | |
REQUIRE, | |
} | |
/** | |
* System property to suppress warnings about loading web app classes from the | |
* system classpath. | |
*/ | |
private static final String PROPERTY_NOWARN_WEBAPP_CLASSPATH = "gwt.nowarn.webapp.classpath"; | |
static { | |
// Suppress spammy Jetty log initialization. | |
//System.setProperty("org.mortbay.log.class", JettyNullLogger.class.getName()); | |
Log.getLog(); | |
/* | |
* Make JDT the default Ant compiler so that JSP compilation just works | |
* out-of-the-box. If we don't set this, it's very, very difficult to make | |
* JSP compilation work. | |
*/ | |
String antJavaC = System.getProperty("build.compiler", | |
"org.eclipse.jdt.core.JDTCompilerAdapter"); | |
System.setProperty("build.compiler", antJavaC); | |
} | |
/** | |
* Setup a connector for the bind address/port. | |
* | |
* @param connector | |
* @param bindAddress | |
* @param port | |
*/ | |
private static void setupConnector(AbstractConnector connector, | |
String bindAddress, int port) { | |
if (bindAddress != null) { | |
connector.setHost(bindAddress.toString()); | |
} | |
connector.setPort(port); | |
// Don't share ports with an existing process. | |
connector.setReuseAddress(false); | |
// Linux keeps the port blocked after shutdown if we don't disable this. | |
connector.setSoLingerTime(0); | |
} | |
// default value used if setBaseLogLevel isn't called | |
private TreeLogger.Type baseLogLevel = TreeLogger.INFO; | |
private String bindAddress = null; | |
private ClientAuth clientAuth; | |
private String keyStore; | |
private String keyStorePassword; | |
private final Object privateInstanceLock = new Object(); | |
private boolean useSsl; | |
@Override | |
public String getName() { | |
return "Jetty"; | |
} | |
@Override | |
public boolean isSecure() { | |
return useSsl; | |
} | |
@Override | |
public boolean processArguments(TreeLogger logger, String arguments) { | |
if (arguments != null && arguments.length() > 0) { | |
// TODO(jat): better parsing of the args | |
for (String arg : arguments.split(",")) { | |
int equals = arg.indexOf('='); | |
String tag; | |
String value = null; | |
if (equals < 0) { | |
tag = arg; | |
} else { | |
tag = arg.substring(0, equals); | |
value = arg.substring(equals + 1); | |
} | |
if ("ssl".equals(tag)) { | |
useSsl = true; | |
URL keyStoreUrl = getClass().getResource("localhost.keystore"); | |
if (keyStoreUrl == null) { | |
logger.log(TreeLogger.ERROR, "Default GWT keystore not found"); | |
return false; | |
} | |
keyStore = keyStoreUrl.toExternalForm(); | |
keyStorePassword = "localhost"; | |
} else if ("keystore".equals(tag)) { | |
useSsl = true; | |
keyStore = value; | |
} else if ("password".equals(tag)) { | |
useSsl = true; | |
keyStorePassword = value; | |
} else if ("pwfile".equals(tag)) { | |
useSsl = true; | |
keyStorePassword = Util.readFileAsString(new File(value)).trim(); | |
if (keyStorePassword == null) { | |
logger.log(TreeLogger.ERROR, | |
"Unable to read keystore password from '" + value + "'"); | |
return false; | |
} | |
} else if ("clientAuth".equals(tag)) { | |
useSsl = true; | |
try { | |
clientAuth = ClientAuth.valueOf(value); | |
} catch (IllegalArgumentException e) { | |
logger.log(TreeLogger.WARN, "Ignoring invalid clientAuth of '" | |
+ value + "'"); | |
} | |
} else { | |
logger.log(TreeLogger.ERROR, "Unexpected argument to " | |
+ CustomJettyLauncher.class.getSimpleName() + ": " + arg); | |
return false; | |
} | |
} | |
if (useSsl) { | |
if (keyStore == null) { | |
logger.log(TreeLogger.ERROR, "A keystore is required to use SSL"); | |
return false; | |
} | |
if (keyStorePassword == null) { | |
logger.log(TreeLogger.ERROR, | |
"A keystore password is required to use SSL"); | |
return false; | |
} | |
} | |
} | |
return true; | |
} | |
/* | |
* TODO: This is a hack to pass the base log level to the SCL. We'll have to | |
* figure out a better way to do this for SCLs in general. Please do not | |
* depend on this method, as it is subject to change. | |
*/ | |
public void setBaseRequestLogLevel(TreeLogger.Type baseLogLevel) { | |
synchronized (privateInstanceLock) { | |
this.baseLogLevel = baseLogLevel; | |
} | |
} | |
@Override | |
public void setBindAddress(String bindAddress) { | |
this.bindAddress = bindAddress; | |
} | |
@Override | |
public ServletContainer start(TreeLogger logger, int port, File appRootDir) | |
throws Exception { | |
TreeLogger branch = logger.branch(TreeLogger.TRACE, | |
"Starting Jetty on port " + port, null); | |
checkStartParams(branch, port, appRootDir); | |
// Setup our branch logger during startup. | |
Log.setLog(new JettyTreeLogger(branch)); | |
// Force load some JRE singletons that can pin the classloader. | |
jreLeakPrevention(logger); | |
// Turn off XML validation. | |
System.setProperty("org.mortbay.xml.XmlParser.Validating", "false"); | |
Server server = new Server(); | |
AbstractConnector connector = getConnector(logger); | |
setupConnector(connector, bindAddress, port); | |
server.addConnector(connector); | |
// Create a new web app in the war directory. | |
WebAppContext wac = createWebAppContext(logger, appRootDir); | |
wac.setConfigurationClasses(DEFAULT_CONFIG_CLASSES); | |
RequestLogHandler logHandler = new RequestLogHandler(); | |
logHandler.setRequestLog(new JettyRequestLogger(logger, getBaseLogLevel())); | |
logHandler.setHandler(wac); | |
server.setHandler(logHandler); | |
server.start(); | |
server.setStopAtShutdown(true); | |
// Now that we're started, log to the top level logger. | |
Log.setLog(new JettyTreeLogger(logger)); | |
// DevMode#doStartUpServer() fails from time to time (rarely) due | |
// to an unknown error. Adding some logging to pinpoint the problem. | |
int connectorPort = connector.getLocalPort(); | |
if (connector.getLocalPort() < 0) { | |
branch.log(TreeLogger.ERROR, String.format( | |
"Failed to connect to open channel with port %d (return value %d)", | |
port, connectorPort)); | |
if (connector.getConnection() == null) { | |
branch.log(TreeLogger.TRACE, "Connection is null"); | |
} | |
} | |
return createServletContainer(logger, appRootDir, server, wac, | |
connectorPort); | |
} | |
protected JettyServletContainer createServletContainer(TreeLogger logger, | |
File appRootDir, Server server, WebAppContext wac, int localPort) { | |
return new JettyServletContainer(logger, server, wac, localPort, appRootDir); | |
} | |
protected WebAppContext createWebAppContext(TreeLogger logger, File appRootDir) { | |
return new WebAppContextWithReload(logger, appRootDir.getAbsolutePath(), | |
"/"); | |
} | |
protected AbstractConnector getConnector(TreeLogger logger) { | |
if (useSsl) { | |
TreeLogger sslLogger = logger.branch(TreeLogger.INFO, | |
"Listening for SSL connections"); | |
if (sslLogger.isLoggable(TreeLogger.TRACE)) { | |
sslLogger.log(TreeLogger.TRACE, "Using keystore " + keyStore); | |
} | |
SslSocketConnector conn = new SslSocketConnector(); | |
if (clientAuth != null) { | |
switch (clientAuth) { | |
case NONE: | |
conn.setWantClientAuth(false); | |
conn.setNeedClientAuth(false); | |
break; | |
case WANT: | |
sslLogger.log(TreeLogger.TRACE, "Requesting client certificates"); | |
conn.setWantClientAuth(true); | |
conn.setNeedClientAuth(false); | |
break; | |
case REQUIRE: | |
sslLogger.log(TreeLogger.TRACE, "Requiring client certificates"); | |
conn.setWantClientAuth(true); | |
conn.setNeedClientAuth(true); | |
break; | |
} | |
} | |
conn.setKeystore(keyStore); | |
conn.setTruststore(keyStore); | |
conn.setKeyPassword(keyStorePassword); | |
conn.setTrustPassword(keyStorePassword); | |
return conn; | |
} | |
return new SelectChannelConnector(); | |
} | |
private void checkStartParams(TreeLogger logger, int port, File appRootDir) { | |
if (logger == null) { | |
throw new NullPointerException("logger cannot be null"); | |
} | |
if (port < 0 || port > 65535) { | |
throw new IllegalArgumentException( | |
"port must be either 0 (for auto) or less than 65536"); | |
} | |
if (appRootDir == null) { | |
throw new NullPointerException("app root direcotry cannot be null"); | |
} | |
} | |
/* | |
* TODO: This is a hack to pass the base log level to the SCL. We'll have to | |
* figure out a better way to do this for SCLs in general. | |
*/ | |
private TreeLogger.Type getBaseLogLevel() { | |
synchronized (privateInstanceLock) { | |
return this.baseLogLevel; | |
} | |
} | |
/** | |
* This is a modified version of JreMemoryLeakPreventionListener.java found | |
* in the Apache Tomcat project at | |
* | |
* http://svn.apache.org/repos/asf/tomcat/trunk/java/org/apache/catalina/core/ | |
* JreMemoryLeakPreventionListener.java | |
* | |
* Relevant part of the Tomcat NOTICE, retrieved from | |
* http://svn.apache.org/repos/asf/tomcat/trunk/NOTICE Apache Tomcat Copyright | |
* 1999-2010 The Apache Software Foundation | |
* | |
* This product includes software developed by The Apache Software Foundation | |
* (http://www.apache.org/). | |
*/ | |
private void jreLeakPrevention(TreeLogger logger) { | |
// Trigger a call to sun.awt.AppContext.getAppContext(). This will | |
// pin the common class loader in memory but that shouldn't be an | |
// issue. | |
ImageIO.getCacheDirectory(); | |
/* | |
* Several components end up calling: sun.misc.GC.requestLatency(long) | |
* | |
* Those libraries / components known to trigger memory leaks due to | |
* eventual calls to requestLatency(long) are: - | |
* javax.management.remote.rmi.RMIConnectorServer.start() | |
*/ | |
try { | |
Class<?> clazz = Class.forName("sun.misc.GC"); | |
Method method = clazz.getDeclaredMethod("requestLatency", | |
new Class[]{long.class}); | |
method.invoke(null, Long.valueOf(3600000)); | |
} catch (ClassNotFoundException e) { | |
logger.log(TreeLogger.ERROR, "jreLeakPrevention.gcDaemonFail", e); | |
} catch (SecurityException e) { | |
logger.log(TreeLogger.ERROR, "jreLeakPrevention.gcDaemonFail", e); | |
} catch (NoSuchMethodException e) { | |
logger.log(TreeLogger.ERROR, "jreLeakPrevention.gcDaemonFail", e); | |
} catch (IllegalArgumentException e) { | |
logger.log(TreeLogger.ERROR, "jreLeakPrevention.gcDaemonFail", e); | |
} catch (IllegalAccessException e) { | |
logger.log(TreeLogger.ERROR, "jreLeakPrevention.gcDaemonFail", e); | |
} catch (InvocationTargetException e) { | |
logger.log(TreeLogger.ERROR, "jreLeakPrevention.gcDaemonFail", e); | |
} | |
/* | |
* Calling getPolicy retains a static reference to the context class loader. | |
*/ | |
try { | |
// Policy.getPolicy(); | |
Class<?> policyClass = Class.forName("javax.security.auth.Policy"); | |
Method method = policyClass.getMethod("getPolicy"); | |
method.invoke(null); | |
} catch (ClassNotFoundException e) { | |
// Ignore. The class is deprecated. | |
} catch (SecurityException e) { | |
// Ignore. Don't need call to getPolicy() to be successful, | |
// just need to trigger static initializer. | |
} catch (NoSuchMethodException e) { | |
logger.log(TreeLogger.WARN, "jreLeakPrevention.authPolicyFail", e); | |
} catch (IllegalArgumentException e) { | |
logger.log(TreeLogger.WARN, "jreLeakPrevention.authPolicyFail", e); | |
} catch (IllegalAccessException e) { | |
logger.log(TreeLogger.WARN, "jreLeakPrevention.authPolicyFail", e); | |
} catch (InvocationTargetException e) { | |
logger.log(TreeLogger.WARN, "jreLeakPrevention.authPolicyFail", e); | |
} | |
/* | |
* Creating a MessageDigest during web application startup initializes the | |
* Java Cryptography Architecture. Under certain conditions this starts a | |
* Token poller thread with TCCL equal to the web application class loader. | |
* | |
* Instead we initialize JCA right now. | |
*/ | |
java.security.Security.getProviders(); | |
/* | |
* Several components end up opening JarURLConnections without first | |
* disabling caching. This effectively locks the file. Whilst more | |
* noticeable and harder to ignore on Windows, it affects all operating | |
* systems. | |
* | |
* Those libraries/components known to trigger this issue include: - log4j | |
* versions 1.2.15 and earlier - javax.xml.bind.JAXBContext.newInstance() | |
*/ | |
// Set the default URL caching policy to not to cache | |
try { | |
// Doesn't matter that this JAR doesn't exist - just as long as | |
// the URL is well-formed | |
URL url = new URL("jar:file://dummy.jar!/"); | |
URLConnection uConn = url.openConnection(); | |
uConn.setDefaultUseCaches(false); | |
} catch (MalformedURLException e) { | |
logger.log(TreeLogger.ERROR, "jreLeakPrevention.jarUrlConnCacheFail", e); | |
} catch (IOException e) { | |
logger.log(TreeLogger.ERROR, "jreLeakPrevention.jarUrlConnCacheFail", e); | |
} | |
/* | |
* Haven't got to the root of what is going on with this leak but if a web | |
* app is the first to make the calls below the web application class loader | |
* will be pinned in memory. | |
*/ | |
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); | |
try { | |
factory.newDocumentBuilder(); | |
} catch (ParserConfigurationException e) { | |
logger.log(TreeLogger.ERROR, "jreLeakPrevention.xmlParseFail", e); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment