Add support for JSPs in DevMode; this also enables annotations scanning.

Bug: #9292
Bug-Link: https://github.com/gwtproject/gwt/issues/9292
Change-Id: Ibd8704a74292088857f344a25a073ba03771696c
diff --git a/dev/build.xml b/dev/build.xml
index 1d8bf84..a864a2b 100755
--- a/dev/build.xml
+++ b/dev/build.xml
@@ -75,12 +75,13 @@
           <include name="jsr305/jsr305.jar"/>
           <include name="protobuf/protobuf-2.5.0/protobuf-java-rebased-2.5.0.jar"/>
           <!-- dependencies needed for JSP support in DevMode: BEGIN -->
-          <include name="tomcat/commons-el-1.0.jar"/>
-          <include name="tomcat/jasper-compiler-1.0.jar"/>
-          <include name="tomcat/jasper-runtime-1.0.jar"/>
-          <include name="tomcat/jsp-api-2.0.jar"/>
+          <include name="jetty/jetty-9.2.14.v20151106/mortbay-apache-jsp-8.0.9.M3.jar"/>
+          <include name="jetty/jetty-9.2.14.v20151106/mortbay-apache-el-8.0.9.M3.jar"/>
+          <include name="jetty/jetty-9.2.14.v20151106/jetty-apache-jsp-9.2.14.v20151106.jar"/>
           <!-- dependencies needed for JSP support in DevMode: END -->
           <include name="tomcat/tomcat-servlet-api-8.0.28.jar"/>
+          <include name="tomcat/tomcat-websocket-api-8.0.28.jar"/>
+          <include name="tomcat/tomcat-annotations-api-8.0.28.jar"/>
           <include name="apache/commons/commons-collections-3.2.2.jar"/>
           <!-- htmlunit dependencies not already included: BEGIN -->
           <include name="apache/http/httpclient-4.5.1.jar"/>
@@ -111,6 +112,10 @@
       </targetfiles>
       <sequential>
         <gwt.jar destfile="${alldeps.jar}" duplicate="preserve">
+          <service type="javax.servlet.ServletContainerInitializer">
+            <provider classname="org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer"/>
+            <provider classname="org.eclipse.jetty.apache.jsp.JettyJasperInitializer"/>
+          </service>
           <zipfileset src="${gwt.tools.lib}/objectweb/asm-5.0.3/lib/asm-all-5.0.3.jar"/>
           <zipfileset src="${gwt.tools.lib}/apache/tapestry-util-text-4.0.2.jar"/>
           <zipfileset src="${gwt.tools.lib}/apache/ant-1.6.5.jar"/>
@@ -130,12 +135,13 @@
           <zipfileset
               src="${gwt.tools.lib}/protobuf/protobuf-2.5.0/protobuf-java-rebased-2.5.0.jar"/>
           <!-- dependencies needed for JSP support in DevMode: BEGIN -->
-          <zipfileset src="${gwt.tools.lib}/tomcat/commons-el-1.0.jar"/>
-          <zipfileset src="${gwt.tools.lib}/tomcat/jasper-compiler-1.0.jar"/>
-          <zipfileset src="${gwt.tools.lib}/tomcat/jasper-runtime-1.0.jar"/>
-          <zipfileset src="${gwt.tools.lib}/tomcat/jsp-api-2.0.jar"/>
+          <zipfileset src="${gwt.tools.lib}/jetty/jetty-9.2.14.v20151106/mortbay-apache-jsp-8.0.9.M3.jar"/>
+          <zipfileset src="${gwt.tools.lib}/jetty/jetty-9.2.14.v20151106/mortbay-apache-el-8.0.9.M3.jar"/>
+          <zipfileset src="${gwt.tools.lib}/jetty/jetty-9.2.14.v20151106/jetty-apache-jsp-9.2.14.v20151106.jar"/>
           <!-- dependencies needed for JSP support in DevMode: END -->
           <zipfileset src="${gwt.tools.lib}/tomcat/tomcat-servlet-api-8.0.28.jar"/>
+          <zipfileset src="${gwt.tools.lib}/tomcat/tomcat-websocket-api-8.0.28.jar"/>
+          <zipfileset src="${gwt.tools.lib}/tomcat/tomcat-annotations-api-8.0.28.jar"/>
           <zipfileset
               src="${gwt.tools.lib}/apache/commons/commons-collections-3.2.2.jar"/>
           <!-- htmlunit dependencies not already included: BEGIN -->
diff --git a/dev/core/src/com/google/gwt/dev/shell/jetty/JettyLauncher.java b/dev/core/src/com/google/gwt/dev/shell/jetty/JettyLauncher.java
index 5a7b37c..317159c 100644
--- a/dev/core/src/com/google/gwt/dev/shell/jetty/JettyLauncher.java
+++ b/dev/core/src/com/google/gwt/dev/shell/jetty/JettyLauncher.java
@@ -21,9 +21,9 @@
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.dev.util.InstalledHelpInfo;
 import com.google.gwt.dev.util.Util;
+import com.google.gwt.thirdparty.guava.common.collect.Iterators;
+import com.google.gwt.thirdparty.guava.common.collect.Lists;
 
-import org.apache.tools.ant.taskdefs.Javac;
-import org.eclipse.jdt.core.JDTCompilerAdapter;
 import org.eclipse.jetty.http.HttpField;
 import org.eclipse.jetty.http.HttpFields;
 import org.eclipse.jetty.server.HttpConfiguration;
@@ -41,6 +41,7 @@
 import org.eclipse.jetty.util.log.Logger;
 import org.eclipse.jetty.util.ssl.SslContextFactory;
 import org.eclipse.jetty.webapp.ClasspathPattern;
+import org.eclipse.jetty.webapp.Configuration;
 import org.eclipse.jetty.webapp.WebAppClassLoader;
 import org.eclipse.jetty.webapp.WebAppContext;
 
@@ -51,6 +52,10 @@
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.URLConnection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
 
 import javax.imageio.ImageIO;
 import javax.xml.parsers.DocumentBuilderFactory;
@@ -62,23 +67,6 @@
 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
@@ -363,13 +351,17 @@
 
       private final ClasspathPattern systemClassesFromWebappFirst = new ClasspathPattern(new String[] {
           "-javax.servlet.",
+          "-javax.el.",
           "javax.",
       });
       private final ClasspathPattern allowedFromSystemClassLoader = new ClasspathPattern(new String[] {
           "org.eclipse.jetty.",
+          "javax.websocket.",
           // Jasper
           "org.apache.jasper.",
-          "org.apache.commons.logging.",
+          "org.apache.juli.logging.",
+          "org.apache.tomcat.",
+          "org.apache.el.",
           // Xerces
           "org.apache.xerces.",
           "javax.xml.", // Used by Jetty for jetty-web.xml parsing
@@ -380,6 +372,18 @@
       }
 
       @Override
+      public Enumeration<URL> getResources(String name) throws IOException {
+        // Logic copied from Jetty's WebAppClassLoader
+        List<URL> fromParent = isServerClass(name)
+            ? Collections.<URL>emptyList()
+            : Lists.newArrayList(Iterators.forEnumeration(systemClassLoader.getResources(name)));
+        Iterator<URL> fromWebapp = isSystemClass(name) && !fromParent.isEmpty()
+            ? Collections.<URL>emptyIterator()
+            : Iterators.forEnumeration(findResources(name));
+        return Iterators.asEnumeration(Iterators.concat(fromWebapp, fromParent.iterator()));
+      }
+
+      @Override
       public URL findResource(String name) {
         // Specifically for META-INF/services/javax.xml.parsers.SAXParserFactory
         String checkName = name;
@@ -390,7 +394,7 @@
 
         // 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).
+        // tried from the webapp first (except for javax.servlet and javax.el).
         URL found;
         if (isSystemClass(checkName) && !systemClassesFromWebappFirst.match(checkName)) {
           found = systemClassLoader.getResource(name);
@@ -413,12 +417,8 @@
 
         // 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)) {
+            // Jetty-plus reads jndi.properties
+            "jndi.properties".equals(name)) {
           return found;
         }
 
@@ -460,6 +460,12 @@
           return null;
         }
 
+        // Special-case JDBCUnloader; it should always be loaded in the webapp classloader
+        if (JDBCUnloader.class.getName().equals(name)) {
+          byte[] jdbcUnloader = Util.readURLAsBytes(found);
+          return defineClass(name, jdbcUnloader, 0, jdbcUnloader.length);
+        }
+
         // 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)) {
@@ -574,16 +580,6 @@
    */
   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.
    *
@@ -735,8 +731,28 @@
     setupConnector(connector, bindAddress, port);
     server.addConnector(connector);
 
+    Configuration.ClassList cl = Configuration.ClassList.setServerDefault(server);
+    try {
+      // from jetty-plus.xml
+      Thread.currentThread().getContextClassLoader().loadClass("org.eclipse.jetty.plus.webapp.PlusConfiguration");
+      cl.addAfter("org.eclipse.jetty.webapp.FragmentConfiguration",
+          "org.eclipse.jetty.plus.webapp.EnvConfiguration",
+          "org.eclipse.jetty.plus.webapp.PlusConfiguration");
+    } catch (ClassNotFoundException cnfe) {
+      logger.log(TreeLogger.Type.DEBUG, "jetty-plus isn't on the classpath, JNDI won't work. This might also affect annotations scanning and JSP.");
+    }
+    try {
+      // from jetty-annotations.xml
+      Thread.currentThread().getContextClassLoader()
+          .loadClass("org.eclipse.jetty.annotations.AnnotationConfiguration");
+      cl.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration",
+          "org.eclipse.jetty.annotations.AnnotationConfiguration");
+    } catch (ClassNotFoundException cnfe) {
+      logger.log(TreeLogger.Type.DEBUG, "jetty-annotations isn't on the classpath, annotation scanning won't work. This might also affect annotations scanning.");
+    }
+
     // Create a new web app in the war directory.
-    WebAppContext wac = createWebAppContext(logger, appRootDir);
+      WebAppContext wac = createWebAppContext(logger, appRootDir);
 
     RequestLogHandler logHandler = new RequestLogHandler();
     logHandler.setRequestLog(new JettyRequestLogger(logger, getBaseLogLevel()));
@@ -766,7 +782,18 @@
   }
 
   protected WebAppContext createWebAppContext(TreeLogger logger, File appRootDir) {
-    return new WebAppContextWithReload(logger, appRootDir.getAbsolutePath(), "/");
+    WebAppContext context = new WebAppContextWithReload(logger, appRootDir.getAbsolutePath(), "/");
+    context.setConfigurationClasses(new String[] {
+        "org.eclipse.jetty.webapp.WebInfConfiguration",
+        "org.eclipse.jetty.webapp.WebXmlConfiguration",
+        "org.eclipse.jetty.webapp.MetaInfConfiguration",
+        "org.eclipse.jetty.webapp.FragmentConfiguration",
+        "org.eclipse.jetty.plus.webapp.EnvConfiguration",
+        "org.eclipse.jetty.plus.webapp.PlusConfiguration",
+        "org.eclipse.jetty.annotations.AnnotationConfiguration",
+        "org.eclipse.jetty.webapp.JettyWebXmlConfiguration"
+    });
+    return context;
   }
 
   protected ServerConnector getConnector(Server server, TreeLogger logger) {
diff --git a/eclipse/dev/.classpath b/eclipse/dev/.classpath
index 85c02a6..14118db 100644
--- a/eclipse/dev/.classpath
+++ b/eclipse/dev/.classpath
@@ -15,10 +15,9 @@
 	<classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/ant-launcher-1.6.5.jar"/>
 	<classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/catalina-1.0.jar"/>
 	<classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/catalina-optional-1.0.jar"/>
-	<classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/commons-el-1.0.jar"/>
-	<classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/jasper-compiler-1.0.jar"/>
-	<classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/jasper-runtime-1.0.jar"/>
-	<classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/jsp-api-2.0.jar"/>
+	<classpathentry kind="var" path="GWT_TOOLS/lib/jetty/jetty-9.2.14.v20151106/mortbay-apache-jsp-8.0.9.M3.jar"/>
+	<classpathentry kind="var" path="GWT_TOOLS/lib/jetty/jetty-9.2.14.v20151106/mortbay-apache-el-8.0.9.M3.jar"/>
+	<classpathentry kind="var" path="GWT_TOOLS/lib/jetty/jetty-9.2.14.v20151106/jetty-apache-jsp-9.2.14.v20151106.jar"/>
 	<classpathentry kind="var" path="GWT_TOOLS/lib/htmlunit/htmlunit-2.19/htmlunit-2.19.jar"/>
 	<classpathentry kind="var" path="GWT_TOOLS/lib/htmlunit/htmlunit-2.19/htmlunit-core-js-2.15.jar"/>
 	<classpathentry kind="var" path="GWT_TOOLS/lib/protobuf/protobuf-2.5.0/protobuf-java-rebased-2.5.0.jar"/>
@@ -29,6 +28,8 @@
 	<classpathentry kind="var" path="GWT_TOOLS/lib/json/android-sdk-19.1/json-android-rebased.jar"/>
 	<classpathentry kind="var" path="GWT_TOOLS/lib/apache/commons/commons-collections-3.2.2.jar"/>
 	<classpathentry exported="true" kind="var" path="GWT_TOOLS/lib/tomcat/tomcat-servlet-api-8.0.28.jar"/>
+	<classpathentry exported="true" kind="var" path="GWT_TOOLS/lib/tomcat/tomcat-websocket-api-8.0.28.jar"/>
+	<classpathentry exported="true" kind="var" path="GWT_TOOLS/lib/tomcat/tomcat-annotations-api-8.0.28.jar"/>
 	<classpathentry kind="var" path="GWT_TOOLS/lib/colt/colt-1.2.jar" sourcepath="/GWT_TOOLS/lib/colt/colt-1.2-src.jar"/>
 	<classpathentry kind="output" path="bin"/>
 </classpath>
diff --git a/user/test/com/google/gwt/dev/shell/jetty/JettyLauncherSuite.java b/user/test/com/google/gwt/dev/shell/jetty/JettyLauncherSuite.java
new file mode 100644
index 0000000..aebca32
--- /dev/null
+++ b/user/test/com/google/gwt/dev/shell/jetty/JettyLauncherSuite.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2016 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.dev.shell.jetty.client.JspTest;
+import com.google.gwt.junit.tools.GWTTestSuite;
+
+import junit.framework.Test;
+
+/**
+ * All JettyLauncher tests that use GWTTestCase.
+ */
+public class JettyLauncherSuite {
+  public static Test suite() {
+    GWTTestSuite suite = new GWTTestSuite("All JettyLauncher tests");
+
+    suite.addTestSuite(JspTest.class);
+
+    return suite;
+  }
+}
diff --git a/user/test/com/google/gwt/dev/shell/jetty/Jsp.gwt.xml b/user/test/com/google/gwt/dev/shell/jetty/Jsp.gwt.xml
new file mode 100644
index 0000000..684596f
--- /dev/null
+++ b/user/test/com/google/gwt/dev/shell/jetty/Jsp.gwt.xml
@@ -0,0 +1,17 @@
+<!--                                                                        -->
+<!-- Copyright 2016 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   -->
+<!-- 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. License for the specific language governing permissions and   -->
+<!-- limitations under the License.                                         -->
+
+<module>
+  <inherits name="com.google.gwt.http.HTTP" />
+</module>
diff --git a/user/test/com/google/gwt/dev/shell/jetty/client/JspTest.java b/user/test/com/google/gwt/dev/shell/jetty/client/JspTest.java
new file mode 100644
index 0000000..d6d0027
--- /dev/null
+++ b/user/test/com/google/gwt/dev/shell/jetty/client/JspTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2016 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.client;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.http.client.Request;
+import com.google.gwt.http.client.RequestBuilder;
+import com.google.gwt.http.client.RequestCallback;
+import com.google.gwt.http.client.Response;
+import com.google.gwt.junit.client.GWTTestCase;
+
+/**
+ * Checks that JSPs are supported in JettyLauncher (through JUnitShell)
+ */
+public class JspTest extends GWTTestCase {
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.dev.shell.jetty.Jsp";
+  }
+
+  public void testJsp() throws Exception {
+    delayTestFinish(5000);
+    new RequestBuilder(RequestBuilder.GET, GWT.getModuleBaseForStaticFiles() + "java7.jsp")
+        .sendRequest("", new RequestCallback() {
+          @Override
+          public void onResponseReceived(Request request, Response response) {
+            assertEquals(200, response.getStatusCode());
+            assertEquals("OK", response.getText().trim());
+            finishTest();
+          }
+
+          @Override
+          public void onError(Request request, Throwable exception) {
+            fail();
+          }
+        });
+  }
+}
diff --git a/user/test/com/google/gwt/dev/shell/jetty/public/java7.jsp b/user/test/com/google/gwt/dev/shell/jetty/public/java7.jsp
new file mode 100644
index 0000000..dbc65d3
--- /dev/null
+++ b/user/test/com/google/gwt/dev/shell/jetty/public/java7.jsp
@@ -0,0 +1,11 @@
+<%@page language="java" contentType="text/plain; charset=UTF-8" session="false" %>
+<%
+// Use a switch-on-string to check whether the page compiles as Java 7
+switch (request.getMethod()) {
+case "GET":
+    out.print("OK");
+    break;
+default:
+    out.print(request.getMethod());
+}
+%>