Author: [email protected]
Date: Thu Mar 5 15:59:42 2009
New Revision: 4944
Added:
releases/1.6/distro-source/core/src/doc/helpInfo/webAppClassPath.html
(contents, props changed)
Modified:
releases/1.6/dev/core/src/com/google/gwt/dev/shell/jetty/JettyLauncher.java
Log:
Embedded Jetty WebAppClassLoader no longer completely blocks attempts to
load from the system classpath.
This was just causing too much pain for users, integration problems, etc.
Now we issue a warning and copy classpath entries from the system classpath
into the web app classpath. Also adds an explanatory error doc.
Review by: toby
Issue: 3435
Modified:
releases/1.6/dev/core/src/com/google/gwt/dev/shell/jetty/JettyLauncher.java
==============================================================================
---
releases/1.6/dev/core/src/com/google/gwt/dev/shell/jetty/JettyLauncher.java
(original)
+++
releases/1.6/dev/core/src/com/google/gwt/dev/shell/jetty/JettyLauncher.java
Thu Mar 5 15:59:42 2009
@@ -20,6 +20,7 @@
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.TreeLogger.Type;
+import com.google.gwt.dev.util.InstalledHelpInfo;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.nio.SelectChannelConnector;
@@ -29,6 +30,8 @@
import org.mortbay.log.Logger;
import java.io.File;
+import java.io.IOException;
+import java.net.URL;
/**
* A launcher for an embedded Jetty server.
@@ -188,7 +191,6 @@
branch.log(TreeLogger.INFO, "Stopped successfully");
}
}
-
/**
* A {...@link WebAppContext} tailored to GWT hosted mode. Features
hot-reload
* with a new {...@link WebAppClassLoader} to pick up disk changes. The
default
@@ -200,50 +202,179 @@
* hosting environment.
*/
private final class WebAppContextWithReload extends WebAppContext {
+
/**
- * Ensures that only Jetty and other server classes can be loaded into
the
- * {...@link WebAppClassLoader}. This forces the user to put any necessary
- * dependencies into WEB-INF/lib.
+ * Specialized {...@link WebAppClassLoader} that allows outside resources
to be
+ * brought in dynamically from the system path. A warning is issued
when
+ * this occurs.
*/
- private final ClassLoader parentClassLoader = new ClassLoader(null) {
- private final ClassLoader delegateTo =
Thread.currentThread().getContextClassLoader();
+ private class WebAppClassLoaderExtension extends WebAppClassLoader {
+
+ public WebAppClassLoaderExtension() throws IOException {
+ super(bootStrapOnlyClassLoader, WebAppContextWithReload.this);
+ }
+
+ @Override
+ public URL findResource(String name) {
+ // Always check this ClassLoader first.
+ URL 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;
+ }
+
+ // 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 system/server path, just return it quietly.
+ if (isServerPath(checkName) || isSystemPath(checkName)) {
+ 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 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 {
- if (webAppClassLoader != null
- && (webAppClassLoader.isServerPath(name) ||
webAppClassLoader.isSystemPath(name))) {
- return delegateTo.loadClass(name);
+ try {
+ return super.findClass(name);
+ } catch (ClassNotFoundException e) {
+ }
+
+ // For system/server path, just try the outside world quietly.
+ if (isServerPath(name) || isSystemPath(name)) {
+ return systemClassLoader.loadClass(name);
}
- throw new ClassNotFoundException();
+
+ // 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 WebAppClassLoader webAppClassLoader;
+ private final TreeLogger logger;
+
+ /**
+ * In the usual case of launching {...@link
com.google.gwt.dev.HostedMode},
+ * this will always by the system app ClassLoader.
+ */
+ private final ClassLoader systemClassLoader =
Thread.currentThread().getContextClassLoader();
@SuppressWarnings("unchecked")
- private WebAppContextWithReload(String webApp, String contextPath) {
+ 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 {
- webAppClassLoader = new WebAppClassLoader(parentClassLoader, this);
- setClassLoader(webAppClassLoader);
+ setClassLoader(new WebAppClassLoaderExtension());
super.doStart();
}
@Override
protected void doStop() throws Exception {
super.doStop();
- webAppClassLoader = null;
setClassLoader(null);
}
}
+ /**
+ * 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";
+
public ServletContainer start(TreeLogger logger, int port, File
appRootDir)
throws Exception {
checkStartParams(logger, port, appRootDir);
@@ -279,7 +410,7 @@
server.addConnector(connector);
// Create a new web app in the war directory.
- WebAppContext wac = new WebAppContextWithReload(
+ WebAppContext wac = new WebAppContextWithReload(logger,
appRootDir.getAbsolutePath(), "/");
server.setHandler(wac);
Added: releases/1.6/distro-source/core/src/doc/helpInfo/webAppClassPath.html
==============================================================================
--- (empty file)
+++ releases/1.6/distro-source/core/src/doc/helpInfo/webAppClassPath.html
Thu Mar 5 15:59:42 2009
@@ -0,0 +1,38 @@
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+<title>Web App Classpath Problem</title>
+</head>
+<body>
+
+<h1>Web App Classpath Problem</h1>
+
+<p>You were directed to this doc because your server code needed a class or
+resource that was not found on <i>web app classpath</i>, but <b>was</b>
found
+on the <i>system classpath</i>. The <i>system classpath</i> is the
classpath
+you specify when launching the Java VM to run hosted mode. The <i>web app
+classpath</i> is different-- it consists of classes that live in your web
+application's <i>war directory</i>. All server classes and dependencies
should
+to be placed in your war directory-- libraries (jars) should be placed in
+<code>war/WEB-INF/lib/</code>; classes that don't live in jars should be
placed
+in <code>war/WEB-INF/classes/</code>.
+
+<p>GWT hosted mode helpfully works around this problem by mapping these
outside
+resources into your web app classpath. A warning is issued because
failing to
+address the issue can lead to problems when you actually deploy your web
app to
+a real server.</p>
+
+<h2>Tips</h2>
+
+<ul>
+<li>The most common reason to encounter this problem with a new project is
+trying to load <code>com.google.gwt.user.client.rpc.RemoteService</code>.
The
+solution to is copy <code>gwt-servlet.jar</code> from the GWT install
directory
+into your web app's <code>war/WEB-INF/lib/</code> directory.</li>
+<li>If you have a good reason for not following the recommended
configuration,
+you can suppress warning by setting the Java system property
+<code>gwt.nowarn.webapp.classpath</code>. Specify
+<code>-Dgwt.nowarn.webapp.classpath</code> as a JVM argument when launching
+hosted mode.
+</li>
+</ul>
--~--~---------~--~----~------------~-------~--~----~
http://groups.google.com/group/Google-Web-Toolkit-Contributors
-~----------~----~----~----~------~----~------~--~---