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
-~----------~----~----~----~------~----~------~--~---

Reply via email to