Java tutorial
/* * 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.google.gwt.dev.shell.jetty; 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.apache.tools.ant.taskdefs.Javac; import org.eclipse.jdt.core.JDTCompilerAdapter; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpFields.Field; import org.eclipse.jetty.server.AbstractConnector; import org.eclipse.jetty.server.AbstractHttpConnection; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.RequestLog; import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.RequestLogHandler; import org.eclipse.jetty.server.nio.SelectChannelConnector; import org.eclipse.jetty.server.ssl.SslSocketConnector; import org.eclipse.jetty.util.component.AbstractLifeCycle; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.webapp.ClasspathPattern; import org.eclipse.jetty.webapp.WebAppClassLoader; import org.eclipse.jetty.webapp.WebAppContext; 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 javax.imageio.ImageIO; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; /** * A {@link ServletContainerLauncher} for an embedded Jetty server. */ public class JettyLauncher extends ServletContainerLauncher { /** * Ant compiler adapter for Eclipse Java compiler, but with default * target and source compatibility set to Java 6. */ public static final class JDTCompiler16 extends JDTCompilerAdapter { @Override public void setJavac(Javac attributes) { if (attributes.getTarget() == null) { attributes.setTarget("1.6"); } if (attributes.getSource() == null) { attributes.setSource("1.6"); } super.setJavac(attributes); } } /** * 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. */ 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)) { AbstractHttpConnection connection = request.getConnection(); logHeaders(branch.branch(logHeaders, "Request headers"), logHeaders, connection.getRequestFields()); logHeaders(branch.branch(logHeaders, "Response headers"), logHeaders, connection.getResponseFields()); } } } private void logHeaders(TreeLogger logger, TreeLogger.Type logLevel, HttpFields fields) { for (int i = 0; i < fields.size(); ++i) { Field field = fields.getField(i); logger.log(logLevel, field.getName() + ": " + field.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... args) { if (logger.isLoggable(TreeLogger.SPAM)) { logger.log(TreeLogger.SPAM, format(msg, args)); } } public void debug(String msg, Throwable th) { logger.log(TreeLogger.SPAM, msg, th); } public void debug(Throwable th) { logger.log(TreeLogger.SPAM, "", th); } public Logger getLogger(String name) { return this; } public String getName() { return ""; } public void info(String msg, Object... args) { if (logger.isLoggable(TreeLogger.TRACE)) { logger.log(TreeLogger.TRACE, format(msg, args)); } } public void info(String msg, Throwable th) { logger.log(TreeLogger.TRACE, msg, th); } public void info(Throwable th) { logger.log(TreeLogger.TRACE, "", th); } public boolean isDebugEnabled() { return logger.isLoggable(TreeLogger.SPAM); } public void setDebugEnabled(boolean enabled) { // ignored } public void warn(String msg, Object... args) { if (logger.isLoggable(TreeLogger.WARN)) { logger.log(TreeLogger.WARN, format(msg, args)); } } public void warn(String msg, Throwable th) { logger.log(TreeLogger.WARN, msg, th); } public void warn(Throwable th) { logger.log(TreeLogger.WARN, "", th); } public void ignore(Throwable th) { logger.log(TreeLogger.SPAM, "IGNORE", th); } /** * Copied from org.eclipse.log.StdErrLog. */ private String format(String msg, Object... args) { if (msg == null) { msg = ""; for (int i = 0; i < args.length; i++) { msg += "{} "; } } String braces = "{}"; int start = 0; StringBuilder builder = new StringBuilder(); for (Object arg : args) { int bracesIndex = msg.indexOf(braces, start); if (bracesIndex < 0) { builder.append(msg.substring(start)); builder.append(" "); builder.append(arg); start = msg.length(); } else { builder.append(msg.substring(start, bracesIndex)); builder.append(String.valueOf(arg)); start = bracesIndex + braces.length(); } } builder.append(msg.substring(start)); return builder.toString(); } } /** * 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/"; private final ClasspathPattern systemClassesFromWebappFirst = new ClasspathPattern( new String[] { "-javax.servlet.", "javax.", }); private final ClasspathPattern allowedFromSystemClassLoader = new ClasspathPattern( new String[] { "org.eclipse.jetty.", // Jasper "org.apache.jasper.", "org.apache.commons.logging.", // Xerces "org.apache.xerces.", "javax.xml.", // Used by Jetty for jetty-web.xml parsing }); 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()); } checkName = checkName.replace('/', '.'); // For a system path, load from the outside world. // Note: bootstrap has already been searched, so javax. classes should be // tried from the webapp first (except for javax.servlet). URL found; if (isSystemClass(checkName) && !systemClassesFromWebappFirst.match(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 || isServerClass(checkName)) { return null; } // Special-case Jetty/Jasper/etc. resources if (allowedFromSystemClassLoader.match(checkName) || // Jasper uses Log4j (via Commons Logging), which will try // to load those. // We have a log4j.properties in user/test and don't want // to add gwt-user when using a "Developer SDK" in Eclipse. "log4j.xml".equals(name) || "log4j.properties".equals(name)) { return found; } // 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 protected Class<?> findClass(String name) throws ClassNotFoundException { // For system path, always prefer the outside world. // Note: bootstrap has already been searched, so javax. classes should be // tried from the webapp first (except for javax.servlet). if (isSystemClass(name) && !systemClassesFromWebappFirst.match(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 (isServerClass(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; } // Those classes are allowed to be loaded right from the systemClassLoader // Note: Jetty classes here are not "server classes", handled above. if (allowedFromSystemClassLoader.match(name)) { return systemClassLoader.loadClass(name); } // 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 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.eclipse.jetty.servlet.Default.useFileMappedBuffer", "false"); // Since the parent class loader is bootstrap-only, prefer it first. setParentLoaderPriority(true); } @Override protected void doStart() throws Exception { setClassLoader(new WebAppClassLoaderExtension()); super.doStart(); } @Override protected void doStop() throws Exception { super.doStop(); Class<?> jdbcUnloader = getClassLoader().loadClass("com.google.gwt.dev.shell.jetty.JDBCUnloader"); java.lang.reflect.Method unload = jdbcUnloader.getMethod("unload"); unload.invoke(null); setClassLoader(null); } } /** * 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 { /* * 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", JDTCompiler16.class.getName()); 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); // Allow binding to a port even if it's still in state TIME_WAIT. connector.setReuseAddress(true); // 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 " + JettyLauncher.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.eclipse.jetty.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); 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(), "/"); } @SuppressWarnings("deprecation") 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); } } }