Revert GWTShell and GWTCompiler change due to internal breakage.
Review by: andrewwarner@google.com
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@11409 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/build.xml b/dev/build.xml
index 08677ef..0ea5528 100755
--- a/dev/build.xml
+++ b/dev/build.xml
@@ -34,6 +34,7 @@
excludes="**/super/**">
<classpath>
<pathelement location="${javac.out}" />
+ <pathelement location="${gwt.tools.lib}/tomcat/servlet-api-2.5.jar" />
<pathelement location="${gwt.tools.lib}/junit/junit-4.8.2.jar" />
<pathelement location="${gwt.tools.lib}/jfreechart/jfreechart-1.0.3.jar" />
<pathelement location="${gwt.tools.lib}/selenium/selenium-java-client-driver.jar" />
@@ -62,13 +63,33 @@
<include name="jetty/jetty-6.1.11.jar" />
<include name="icu4j/4.4.2/icu4j.jar" />
<include name="protobuf/protobuf-2.2.0/protobuf-java-rebased-2.2.0.jar" />
- <!-- dependencies needed for JSP support in DevMode: BEGIN -->
+ <include name="tomcat/ant-launcher-1.6.5.jar" />
+ <include name="tomcat/catalina-1.0.jar" />
+ <include name="tomcat/catalina-optional-1.0.jar" />
+ <include name="tomcat/commons-beanutils-1.6.jar" />
+ <include name="tomcat/commons-collections-3.1.jar" />
+ <include name="tomcat/commons-digester-1.5.jar" />
<include name="tomcat/commons-el-1.0.jar" />
+ <include name="tomcat/commons-logging-1.0.jar" />
+ <include name="tomcat/commons-modeler-1.1.jar" />
+ <include name="tomcat/jakarta-regexp-1.3.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" />
- <!-- dependencies needed for JSP support in DevMode: END -->
- <include name="apache/commons/commons-collections-3.2.1.jar" />
+ <include name="tomcat/mx4j-jmx-1.1.jar" />
+ <include name="tomcat/naming-common-1.0.jar" />
+ <include name="tomcat/naming-factory-1.0.jar" />
+ <include name="tomcat/naming-java-1.0.jar" />
+ <include name="tomcat/naming-resources-1.0.jar" />
+ <include name="tomcat/servlet-api-2.5.jar" />
+ <include name="tomcat/servlet-api-2.4.jar" />
+ <include name="tomcat/servlets-common-1.0.jar" />
+ <include name="tomcat/servlets-default-1.0.jar" />
+ <include name="tomcat/servlets-invoker-1.0.jar" />
+ <include name="tomcat/tomcat-coyote-1.0.jar" />
+ <include name="tomcat/tomcat-http11-1.0.jar" />
+ <include name="tomcat/tomcat-jk2-2.1.jar" />
+ <include name="tomcat/tomcat-util-5.1.jar" />
<!-- htmlunit dependencies not already included: BEGIN -->
<include name="apache/http/httpclient-4.1.2.jar" />
<include name="apache/http/httpcore-4.1.2.jar" />
@@ -110,13 +131,33 @@
<zipfileset src="${gwt.tools.lib}/jetty/jetty-6.1.11.jar" />
<zipfileset src="${gwt.tools.lib}/icu4j/4.4.2/icu4j.jar" />
<zipfileset src="${gwt.tools.lib}/protobuf/protobuf-2.2.0/protobuf-java-rebased-2.2.0.jar" />
- <!-- dependencies needed for JSP support in DevMode: BEGIN -->
+ <zipfileset src="${gwt.tools.lib}/tomcat/ant-launcher-1.6.5.jar" />
+ <zipfileset src="${gwt.tools.lib}/tomcat/catalina-1.0.jar" />
+ <zipfileset src="${gwt.tools.lib}/tomcat/catalina-optional-1.0.jar" />
+ <zipfileset src="${gwt.tools.lib}/tomcat/commons-beanutils-1.6.jar" />
+ <zipfileset src="${gwt.tools.lib}/tomcat/commons-collections-3.1.jar" />
+ <zipfileset src="${gwt.tools.lib}/tomcat/commons-digester-1.5.jar" />
<zipfileset src="${gwt.tools.lib}/tomcat/commons-el-1.0.jar" />
+ <zipfileset src="${gwt.tools.lib}/tomcat/commons-logging-1.0.jar" />
+ <zipfileset src="${gwt.tools.lib}/tomcat/commons-modeler-1.1.jar" />
+ <zipfileset src="${gwt.tools.lib}/tomcat/jakarta-regexp-1.3.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" />
- <!-- dependencies needed for JSP support in DevMode: END -->
- <zipfileset src="${gwt.tools.lib}/apache/commons/commons-collections-3.2.1.jar" />
+ <zipfileset src="${gwt.tools.lib}/tomcat/mx4j-jmx-1.1.jar" />
+ <zipfileset src="${gwt.tools.lib}/tomcat/naming-common-1.0.jar" />
+ <zipfileset src="${gwt.tools.lib}/tomcat/naming-factory-1.0.jar" />
+ <zipfileset src="${gwt.tools.lib}/tomcat/naming-java-1.0.jar" />
+ <zipfileset src="${gwt.tools.lib}/tomcat/naming-resources-1.0.jar" />
+ <zipfileset src="${gwt.tools.lib}/tomcat/servlet-api-2.5.jar" />
+ <zipfileset src="${gwt.tools.lib}/tomcat/servlet-api-2.4.jar" />
+ <zipfileset src="${gwt.tools.lib}/tomcat/servlets-common-1.0.jar" />
+ <zipfileset src="${gwt.tools.lib}/tomcat/servlets-default-1.0.jar" />
+ <zipfileset src="${gwt.tools.lib}/tomcat/servlets-invoker-1.0.jar" />
+ <zipfileset src="${gwt.tools.lib}/tomcat/tomcat-coyote-1.0.jar" />
+ <zipfileset src="${gwt.tools.lib}/tomcat/tomcat-http11-1.0.jar" />
+ <zipfileset src="${gwt.tools.lib}/tomcat/tomcat-jk2-2.1.jar" />
+ <zipfileset src="${gwt.tools.lib}/tomcat/tomcat-util-5.1.jar" />
<!-- htmlunit dependencies not already included: BEGIN -->
<zipfileset src="${gwt.tools.lib}/apache/http/httpclient-4.1.2.jar" />
<zipfileset src="${gwt.tools.lib}/apache/http/httpcore-4.1.2.jar" />
@@ -179,7 +220,7 @@
<classpath>
<pathelement location="${gwt.tools.lib}/apache/ant-1.6.5.jar" />
<pathelement location="${gwt.tools.lib}/eclipse/jdt-3.4.2_r894.jar" />
- <pathelement location="${gwt.tools.lib}/apache/commons/commons-collections-3.2.1.jar" />
+ <pathelement location="${gwt.tools.lib}/tomcat/commons-collections-3.1.jar" />
<pathelement location="${gwt.tools.lib}/guava/guava-10.0.1/guava-10.0.1-rebased.jar" />
<pathelement location="${gwt.tools.lib}/jscomp/r1649/compiler-rebased.jar" />
</classpath>
diff --git a/dev/core/src/com/google/gwt/dev/CompileTaskRunner.java b/dev/core/src/com/google/gwt/dev/CompileTaskRunner.java
index 914713d..7c65f6e 100644
--- a/dev/core/src/com/google/gwt/dev/CompileTaskRunner.java
+++ b/dev/core/src/com/google/gwt/dev/CompileTaskRunner.java
@@ -63,7 +63,7 @@
}
});
- compilerThread.setName("GWT Compiler Thread");
+ compilerThread.setName("GWTCompiler Thread");
compilerThread.start();
// TODO(jat): create an app frame for loggerWindow
diff --git a/dev/core/src/com/google/gwt/dev/GWTCompiler.java b/dev/core/src/com/google/gwt/dev/GWTCompiler.java
new file mode 100644
index 0000000..ef2e4ea
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/GWTCompiler.java
@@ -0,0 +1,239 @@
+/*
+ * 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;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.linker.ArtifactSet;
+import com.google.gwt.dev.CompileTaskRunner.CompileTask;
+import com.google.gwt.dev.cfg.ModuleDef;
+import com.google.gwt.dev.cfg.ModuleDefLoader;
+import com.google.gwt.dev.jjs.JJSOptions;
+import com.google.gwt.dev.jjs.PermutationResult;
+import com.google.gwt.dev.shell.CheckForUpdates;
+import com.google.gwt.dev.shell.CheckForUpdates.UpdateResult;
+import com.google.gwt.dev.util.FileBackedObject;
+import com.google.gwt.dev.util.Util;
+import com.google.gwt.dev.util.arg.ArgHandlerLocalWorkers;
+import com.google.gwt.dev.util.arg.ArgHandlerOutDir;
+import com.google.gwt.dev.util.arg.ArgHandlerWorkDirOptional;
+import com.google.gwt.dev.util.log.speedtracer.CompilerEventType;
+import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger;
+import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event;
+import com.google.gwt.util.tools.ToolBase;
+import com.google.gwt.util.tools.Utility;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.FutureTask;
+
+/**
+ * The main executable entry point for the GWT Java to JavaScript compiler.
+ *
+ * @deprecated Use {@link Compiler} instead
+ */
+@Deprecated
+public class GWTCompiler {
+
+ static final class ArgProcessor extends PrecompileTaskArgProcessor {
+ public ArgProcessor(LegacyCompilerOptions options) {
+ super(options);
+
+ registerHandler(new ArgHandlerOutDir(options));
+
+ // Override the ArgHandlerWorkDirRequired in the super class.
+ registerHandler(new ArgHandlerWorkDirOptional(options));
+
+ registerHandler(new ArgHandlerLocalWorkers(options));
+ }
+
+ @Override
+ protected String getName() {
+ return GWTCompiler.class.getName();
+ }
+ }
+
+ /**
+ * Simple implementation of {@link LegacyCompilerOptions}.
+ */
+ public static class GWTCompilerOptionsImpl extends PrecompileTaskOptionsImpl
+ implements LegacyCompilerOptions {
+
+ private int localWorkers;
+ private File outDir;
+
+ public GWTCompilerOptionsImpl() {
+ }
+
+ public GWTCompilerOptionsImpl(LegacyCompilerOptions other) {
+ copyFrom(other);
+ }
+
+ public void copyFrom(LegacyCompilerOptions other) {
+ super.copyFrom(other);
+ setLocalWorkers(other.getLocalWorkers());
+ setOutDir(other.getOutDir());
+ }
+
+ @Override
+ public int getLocalWorkers() {
+ return localWorkers;
+ }
+
+ @Override
+ public File getOutDir() {
+ return outDir;
+ }
+
+ @Override
+ public void setLocalWorkers(int localWorkers) {
+ this.localWorkers = localWorkers;
+ }
+
+ @Override
+ public void setOutDir(File outDir) {
+ this.outDir = outDir;
+ }
+ }
+
+ public static void main(String[] args) {
+ ToolBase.legacyWarn(GWTCompiler.class, Compiler.class);
+ SpeedTracerLogger.init();
+ Event compileEvent = SpeedTracerLogger.start(CompilerEventType.COMPILE);
+
+ /*
+ * NOTE: main always exits with a call to System.exit to terminate any
+ * non-daemon threads that were started in Generators. Typically, this is to
+ * shutdown AWT related threads, since the contract for their termination is
+ * still implementation-dependent.
+ */
+ final LegacyCompilerOptions options = new GWTCompilerOptionsImpl();
+ boolean success = false;
+ if (new ArgProcessor(options).processArgs(args)) {
+ CompileTask task = new CompileTask() {
+ @Override
+ public boolean run(TreeLogger logger) throws UnableToCompleteException {
+ FutureTask<UpdateResult> updater = null;
+ if (!options.isUpdateCheckDisabled()) {
+ updater = CheckForUpdates.checkForUpdatesInBackgroundThread(logger,
+ CheckForUpdates.ONE_DAY);
+ }
+ boolean success = new GWTCompiler(options).run(logger);
+ if (success) {
+ CheckForUpdates.logUpdateAvailable(logger, updater);
+ }
+ return success;
+ }
+ };
+ if (CompileTaskRunner.runWithAppropriateLogger(options, task)) {
+ // Exit w/ success code.
+ success = true;
+ }
+ }
+
+ compileEvent.end();
+ // Exit w/ non-success code.
+ System.exit(success ? 0 : 1);
+ }
+
+ private final GWTCompilerOptionsImpl options;
+
+ public GWTCompiler(LegacyCompilerOptions options) {
+ this.options = new GWTCompilerOptionsImpl(options);
+ }
+
+ /**
+ * Compiles the set of modules specified in the options.
+ */
+ public boolean run(TreeLogger logger) throws UnableToCompleteException {
+ ModuleDef[] modules = new ModuleDef[options.getModuleNames().size()];
+ int i = 0;
+ for (String moduleName : options.getModuleNames()) {
+ modules[i++] = ModuleDefLoader.loadFromClassPath(logger, moduleName, true);
+ }
+ return run(logger, modules);
+ }
+
+ /**
+ * Compiles a specific set of modules.
+ */
+ public boolean run(TreeLogger logger, ModuleDef... modules)
+ throws UnableToCompleteException {
+ Event compileEvent = SpeedTracerLogger.start(CompilerEventType.COMPILE);
+ boolean tempWorkDir = false;
+ try {
+ if (options.getWorkDir() == null) {
+ options.setWorkDir(Utility.makeTemporaryDirectory(null, "gwtc"));
+ tempWorkDir = true;
+ }
+
+ for (ModuleDef module : modules) {
+ String moduleName = module.getName();
+
+ if (options.isValidateOnly()) {
+ if (!Precompile.validate(logger, options, module, options.getGenDir())) {
+ return false;
+ }
+ } else {
+ long compileStart = System.currentTimeMillis();
+ logger = logger.branch(TreeLogger.INFO, "Compiling module "
+ + moduleName);
+
+ // Optimize early since permutation compiles will run in process.
+ options.setOptimizePrecompile(true);
+ Precompilation precompilation = Precompile.precompile(logger,
+ options, module, options.getGenDir());
+
+ if (precompilation == null) {
+ return false;
+ }
+ Permutation[] allPerms = precompilation.getPermutations();
+ List<FileBackedObject<PermutationResult>> resultFiles = CompilePerms.makeResultFiles(
+ options.getCompilerWorkDir(moduleName), allPerms);
+ CompilePerms.compile(logger, precompilation, allPerms,
+ options.getLocalWorkers(), resultFiles);
+
+ ArtifactSet generatedArtifacts = precompilation.getGeneratedArtifacts();
+ JJSOptions precompileOptions = precompilation.getUnifiedAst().getOptions();
+
+ precompilation = null; // No longer needed, so save the memory
+
+ Link.legacyLink(logger.branch(TreeLogger.TRACE, "Linking into "
+ + options.getOutDir().getPath()), module, generatedArtifacts,
+ allPerms, resultFiles, options.getOutDir(), precompileOptions);
+
+ long compileDone = System.currentTimeMillis();
+ long delta = compileDone - compileStart;
+ if (logger.isLoggable(TreeLogger.INFO)) {
+ logger.log(TreeLogger.INFO, "Compilation succeeded -- "
+ + String.format("%.3f", delta / 1000d) + "s");
+ }
+ }
+ }
+ } catch (IOException e) {
+ logger.log(TreeLogger.ERROR, "Unable to create compiler work directory",
+ e);
+ return false;
+ } finally {
+ compileEvent.end();
+ if (tempWorkDir) {
+ Util.recursiveDelete(options.getWorkDir(), false);
+ }
+ }
+ return true;
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/GWTShell.java b/dev/core/src/com/google/gwt/dev/GWTShell.java
new file mode 100644
index 0000000..77339ff
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/GWTShell.java
@@ -0,0 +1,252 @@
+/*
+ * 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;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.linker.ArtifactSet;
+import com.google.gwt.core.ext.linker.EmittedArtifact.Visibility;
+import com.google.gwt.core.ext.linker.impl.StandardLinkerContext;
+import com.google.gwt.dev.cfg.ModuleDef;
+import com.google.gwt.dev.shell.WorkDirs;
+import com.google.gwt.dev.shell.tomcat.EmbeddedTomcatServer;
+import com.google.gwt.dev.util.OutputFileSetOnDirectory;
+import com.google.gwt.dev.util.arg.ArgHandlerDisableUpdateCheck;
+import com.google.gwt.dev.util.arg.ArgHandlerOutDir;
+import com.google.gwt.util.tools.ArgHandlerExtra;
+
+import java.io.File;
+import java.util.Locale;
+import java.util.Set;
+
+/**
+ * The main executable class for the hosted mode shell.
+ */
+@Deprecated
+public class GWTShell extends DevModeBase {
+
+ /**
+ * Handles the list of startup urls that can be passed at the end of the
+ * command line.
+ */
+ protected static class ArgHandlerStartupURLsExtra extends ArgHandlerExtra {
+
+ private final OptionStartupURLs options;
+
+ public ArgHandlerStartupURLsExtra(OptionStartupURLs options) {
+ this.options = options;
+ }
+
+ @Override
+ public boolean addExtraArg(String arg) {
+ options.addStartupURL(arg);
+ return true;
+ }
+
+ @Override
+ public String getPurpose() {
+ return "Automatically launches the specified URL";
+ }
+
+ @Override
+ public String[] getTagArgs() {
+ return new String[] {"url"};
+ }
+ }
+
+ /**
+ * The GWTShell argument processor.
+ */
+ protected static class ArgProcessor extends DevModeBase.ArgProcessor {
+ public ArgProcessor(ShellOptionsImpl options, boolean forceServer,
+ boolean noURLs) {
+ super(options, forceServer);
+ if (!noURLs) {
+ registerHandler(new ArgHandlerStartupURLsExtra(options));
+ }
+ registerHandler(new ArgHandlerOutDir(options));
+ registerHandler(new ArgHandlerDisableUpdateCheck(options));
+ }
+
+ @Override
+ protected String getName() {
+ return GWTShell.class.getName();
+ }
+ }
+
+ /**
+ * Concrete class to implement all shell options.
+ */
+ protected static class ShellOptionsImpl extends HostedModeBaseOptionsImpl
+ implements WorkDirs, LegacyCompilerOptions {
+ private int localWorkers;
+ private File outDir;
+
+ public File getCompilerOutputDir(ModuleDef moduleDef) {
+ return new File(getOutDir(), moduleDef.getName());
+ }
+
+ public int getLocalWorkers() {
+ return localWorkers;
+ }
+
+ public File getOutDir() {
+ return outDir;
+ }
+
+ public File getShellPublicGenDir(ModuleDef moduleDef) {
+ File moduleWorkDir = new File(getWorkDir(), moduleDef.getName());
+ return new File(moduleWorkDir, "public");
+ }
+
+ @Override
+ public File getWorkDir() {
+ return new File(getOutDir(), ".gwt-tmp");
+ }
+
+ public void setLocalWorkers(int localWorkers) {
+ this.localWorkers = localWorkers;
+ }
+
+ public void setOutDir(File outDir) {
+ this.outDir = outDir;
+ }
+ }
+
+ public static String checkHost(String hostUnderConsideration,
+ Set<String> hosts) {
+ hostUnderConsideration = hostUnderConsideration.toLowerCase(Locale.ENGLISH);
+ for (String rule : hosts) {
+ // match on lowercased regex
+ if (hostUnderConsideration.matches(".*" + rule + ".*")) {
+ return rule;
+ }
+ }
+ return null;
+ }
+
+ public static String computeHostRegex(String url) {
+ // the entire URL up to the first slash not prefixed by a slash or colon.
+ String raw = url.split("(?<![:/])/")[0];
+ // escape the dots and put a begin line specifier on the result
+ return "^" + raw.replaceAll("[.]", "[.]");
+ }
+
+ public static String formatRules(Set<String> invalidHttpHosts) {
+ StringBuffer out = new StringBuffer();
+ for (String rule : invalidHttpHosts) {
+ out.append(rule);
+ out.append(" ");
+ }
+ return out.toString();
+ }
+
+ public static void main(String[] args) {
+ /*
+ * NOTE: main always exits with a call to System.exit to terminate any
+ * non-daemon threads that were started in Generators. Typically, this is to
+ * shutdown AWT related threads, since the contract for their termination is
+ * still implementation-dependent.
+ */
+ GWTShell gwtShell = new GWTShell();
+ ArgProcessor argProcessor = new ArgProcessor(gwtShell.options, false, false);
+ if (argProcessor.processArgs(args)) {
+ gwtShell.run();
+ // Exit w/ success code.
+ System.exit(0);
+ }
+ // Exit w/ non-success code.
+ System.exit(-1);
+ }
+
+ /**
+ * Hiding super field because it's actually the same object, just with a
+ * stronger type.
+ */
+ @SuppressWarnings("hiding")
+ protected final ShellOptionsImpl options = (ShellOptionsImpl) super.options;
+
+ protected File outDir;
+
+ @SuppressWarnings("unused")
+ public void restartServer(TreeLogger logger) throws UnableToCompleteException {
+ // Unimplemented.
+ }
+
+ @Override
+ protected HostedModeBaseOptions createOptions() {
+ return new ShellOptionsImpl();
+ }
+
+ @Override
+ protected void doShutDownServer() {
+ // Stop the HTTP server.
+ //
+ EmbeddedTomcatServer.stop();
+ }
+
+ @Override
+ protected boolean doStartup() {
+ File persistentCacheDir = new File(options.getWorkDir(), "gwt-unitCache");
+ return super.doStartup(persistentCacheDir);
+ }
+
+ @Override
+ protected int doStartUpServer() {
+ // TODO(jat): find a safe way to get an icon for Tomcat
+ TreeLogger logger = ui.getWebServerLogger("Tomcat", null);
+ // TODO(bruce): make tomcat work in terms of the modular launcher
+ String whyFailed = EmbeddedTomcatServer.start(isHeadless() ? getTopLogger()
+ : logger, getPort(), options, shouldAutoGenerateResources());
+
+ if (whyFailed != null) {
+ getTopLogger().log(TreeLogger.ERROR, "Starting Tomcat: " + whyFailed);
+ return -1;
+ }
+ return EmbeddedTomcatServer.getPort();
+ }
+
+ @Override
+ protected synchronized void produceOutput(TreeLogger logger,
+ StandardLinkerContext linkerStack, ArtifactSet artifacts,
+ ModuleDef module, boolean isRelink) throws UnableToCompleteException {
+ /*
+ * Legacy: in GWTShell we only copy generated artifacts into the public gen
+ * folder. Public files and "autogen" files have special handling (that
+ * needs to die).
+ */
+ if (isRelink) {
+ File outputDir = options.getShellPublicGenDir(module);
+ outputDir.mkdirs();
+ OutputFileSetOnDirectory outFileSet = new OutputFileSetOnDirectory(
+ outputDir, "");
+ linkerStack.produceOutput(logger, artifacts, Visibility.Public,
+ outFileSet);
+ outFileSet.close();
+ }
+ }
+
+ protected boolean shouldAutoGenerateResources() {
+ return true;
+ }
+
+ @Override
+ protected void warnAboutNoStartupUrls() {
+ getTopLogger().log(TreeLogger.WARN,
+ "No startup URLs were supplied -- add them to the end of the GWTShell"
+ + " command line");
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java b/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java
index 378ab1a..8d972e9 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java
@@ -466,9 +466,9 @@
}
/**
- * For convenience in unit tests, servlets can be automatically loaded and
- * mapped in the embedded web server. If a servlet is already mapped to the
- * specified path, it is replaced.
+ * For convenience in hosted mode, servlets can be automatically loaded and
+ * delegated to via {@link com.google.gwt.dev.shell.GWTShellServlet}. If a
+ * servlet is already mapped to the specified path, it is replaced.
*
* @param path the url path at which the servlet resides
* @param servletClassName the name of the servlet to publish
diff --git a/dev/core/src/com/google/gwt/dev/etc/tomcat/conf/web.xml b/dev/core/src/com/google/gwt/dev/etc/tomcat/conf/web.xml
new file mode 100644
index 0000000..fee85f3
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/etc/tomcat/conf/web.xml
@@ -0,0 +1,566 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!-- A tweaked version of the default Tomcat web.xml to remove everything except the stuff we want to use -->
+<web-app version="2.4">
+
+ <session-config>
+ <session-timeout>30</session-timeout>
+ </session-config>
+
+ <mime-mapping>
+ <extension>abs</extension>
+ <mime-type>audio/x-mpeg</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>ai</extension>
+ <mime-type>application/postscript</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>aif</extension>
+ <mime-type>audio/x-aiff</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>aifc</extension>
+ <mime-type>audio/x-aiff</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>aiff</extension>
+ <mime-type>audio/x-aiff</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>aim</extension>
+ <mime-type>application/x-aim</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>art</extension>
+ <mime-type>image/x-jg</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>asf</extension>
+ <mime-type>video/x-ms-asf</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>asx</extension>
+ <mime-type>video/x-ms-asf</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>au</extension>
+ <mime-type>audio/basic</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>avi</extension>
+ <mime-type>video/x-msvideo</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>avx</extension>
+ <mime-type>video/x-rad-screenplay</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>bcpio</extension>
+ <mime-type>application/x-bcpio</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>bin</extension>
+ <mime-type>application/octet-stream</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>bmp</extension>
+ <mime-type>image/bmp</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>body</extension>
+ <mime-type>text/html</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>cdf</extension>
+ <mime-type>application/x-cdf</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>cer</extension>
+ <mime-type>application/x-x509-ca-cert</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>class</extension>
+ <mime-type>application/java</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>cpio</extension>
+ <mime-type>application/x-cpio</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>csh</extension>
+ <mime-type>application/x-csh</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>css</extension>
+ <mime-type>text/css</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>dib</extension>
+ <mime-type>image/bmp</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>doc</extension>
+ <mime-type>application/msword</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>dtd</extension>
+ <mime-type>text/plain</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>dv</extension>
+ <mime-type>video/x-dv</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>dvi</extension>
+ <mime-type>application/x-dvi</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>eps</extension>
+ <mime-type>application/postscript</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>etx</extension>
+ <mime-type>text/x-setext</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>exe</extension>
+ <mime-type>application/octet-stream</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>gif</extension>
+ <mime-type>image/gif</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>gtar</extension>
+ <mime-type>application/x-gtar</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>gz</extension>
+ <mime-type>application/x-gzip</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>hdf</extension>
+ <mime-type>application/x-hdf</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>hqx</extension>
+ <mime-type>application/mac-binhex40</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>htc</extension>
+ <mime-type>text/x-component</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>htm</extension>
+ <mime-type>text/html</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>html</extension>
+ <mime-type>text/html</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>hqx</extension>
+ <mime-type>application/mac-binhex40</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>ief</extension>
+ <mime-type>image/ief</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>jad</extension>
+ <mime-type>text/vnd.sun.j2me.app-descriptor</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>jar</extension>
+ <mime-type>application/java-archive</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>java</extension>
+ <mime-type>text/plain</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>jnlp</extension>
+ <mime-type>application/x-java-jnlp-file</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>jpe</extension>
+ <mime-type>image/jpeg</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>jpeg</extension>
+ <mime-type>image/jpeg</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>jpg</extension>
+ <mime-type>image/jpeg</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>js</extension>
+ <mime-type>text/javascript</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>jsf</extension>
+ <mime-type>text/plain</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>jspf</extension>
+ <mime-type>text/plain</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>kar</extension>
+ <mime-type>audio/x-midi</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>latex</extension>
+ <mime-type>application/x-latex</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>m3u</extension>
+ <mime-type>audio/x-mpegurl</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>mac</extension>
+ <mime-type>image/x-macpaint</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>man</extension>
+ <mime-type>application/x-troff-man</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>me</extension>
+ <mime-type>application/x-troff-me</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>mid</extension>
+ <mime-type>audio/x-midi</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>midi</extension>
+ <mime-type>audio/x-midi</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>mif</extension>
+ <mime-type>application/x-mif</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>mov</extension>
+ <mime-type>video/quicktime</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>movie</extension>
+ <mime-type>video/x-sgi-movie</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>mp1</extension>
+ <mime-type>audio/x-mpeg</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>mp2</extension>
+ <mime-type>audio/x-mpeg</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>mp3</extension>
+ <mime-type>audio/x-mpeg</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>mpa</extension>
+ <mime-type>audio/x-mpeg</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>mpe</extension>
+ <mime-type>video/mpeg</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>mpeg</extension>
+ <mime-type>video/mpeg</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>mpega</extension>
+ <mime-type>audio/x-mpeg</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>mpg</extension>
+ <mime-type>video/mpeg</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>mpv2</extension>
+ <mime-type>video/mpeg2</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>ms</extension>
+ <mime-type>application/x-wais-source</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>nc</extension>
+ <mime-type>application/x-netcdf</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>oda</extension>
+ <mime-type>application/oda</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>pbm</extension>
+ <mime-type>image/x-portable-bitmap</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>pct</extension>
+ <mime-type>image/pict</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>pdf</extension>
+ <mime-type>application/pdf</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>pgm</extension>
+ <mime-type>image/x-portable-graymap</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>pic</extension>
+ <mime-type>image/pict</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>pict</extension>
+ <mime-type>image/pict</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>pls</extension>
+ <mime-type>audio/x-scpls</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>png</extension>
+ <mime-type>image/png</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>pnm</extension>
+ <mime-type>image/x-portable-anymap</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>pnt</extension>
+ <mime-type>image/x-macpaint</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>ppm</extension>
+ <mime-type>image/x-portable-pixmap</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>ppt</extension>
+ <mime-type>application/powerpoint</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>ps</extension>
+ <mime-type>application/postscript</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>psd</extension>
+ <mime-type>image/x-photoshop</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>qt</extension>
+ <mime-type>video/quicktime</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>qti</extension>
+ <mime-type>image/x-quicktime</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>qtif</extension>
+ <mime-type>image/x-quicktime</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>ras</extension>
+ <mime-type>image/x-cmu-raster</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>rgb</extension>
+ <mime-type>image/x-rgb</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>rm</extension>
+ <mime-type>application/vnd.rn-realmedia</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>roff</extension>
+ <mime-type>application/x-troff</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>rtf</extension>
+ <mime-type>application/rtf</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>rtx</extension>
+ <mime-type>text/richtext</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>sh</extension>
+ <mime-type>application/x-sh</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>shar</extension>
+ <mime-type>application/x-shar</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>smf</extension>
+ <mime-type>audio/x-midi</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>sit</extension>
+ <mime-type>application/x-stuffit</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>snd</extension>
+ <mime-type>audio/basic</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>src</extension>
+ <mime-type>application/x-wais-source</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>sv4cpio</extension>
+ <mime-type>application/x-sv4cpio</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>sv4crc</extension>
+ <mime-type>application/x-sv4crc</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>swf</extension>
+ <mime-type>application/x-shockwave-flash</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>t</extension>
+ <mime-type>application/x-troff</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>tar</extension>
+ <mime-type>application/x-tar</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>tcl</extension>
+ <mime-type>application/x-tcl</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>tex</extension>
+ <mime-type>application/x-tex</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>texi</extension>
+ <mime-type>application/x-texinfo</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>texinfo</extension>
+ <mime-type>application/x-texinfo</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>tif</extension>
+ <mime-type>image/tiff</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>tiff</extension>
+ <mime-type>image/tiff</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>tr</extension>
+ <mime-type>application/x-troff</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>tsv</extension>
+ <mime-type>text/tab-separated-values</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>txt</extension>
+ <mime-type>text/plain</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>ulw</extension>
+ <mime-type>audio/basic</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>ustar</extension>
+ <mime-type>application/x-ustar</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>xbm</extension>
+ <mime-type>image/x-xbitmap</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>xht</extension>
+ <mime-type>application/xhtml+xml</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>xhtml</extension>
+ <mime-type>application/xhtml+xml</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>xml</extension>
+ <mime-type>text/xml</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>xpm</extension>
+ <mime-type>image/x-xpixmap</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>xsl</extension>
+ <mime-type>text/xml</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>xwd</extension>
+ <mime-type>image/x-xwindowdump</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>wav</extension>
+ <mime-type>audio/x-wav</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>svg</extension>
+ <mime-type>image/svg+xml</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>svgz</extension>
+ <mime-type>image/svg+xml</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>vsd</extension>
+ <mime-type>application/x-visio</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <!-- Wireless Bitmap -->
+ <extension>wbmp</extension>
+ <mime-type>image/vnd.wap.wbmp</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <!-- WML Source -->
+ <extension>wml</extension>
+ <mime-type>text/vnd.wap.wml</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <!-- Compiled WML -->
+ <extension>wmlc</extension>
+ <mime-type>application/vnd.wap.wmlc</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <!-- WML Script Source -->
+ <extension>wmls</extension>
+ <mime-type>text/vnd.wap.wmlscript</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <!-- Compiled WML Script -->
+ <extension>wmlscriptc</extension>
+ <mime-type>application/vnd.wap.wmlscriptc</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>wrl</extension>
+ <mime-type>x-world/x-vrml</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>Z</extension>
+ <mime-type>application/x-compress</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>z</extension>
+ <mime-type>application/x-compress</mime-type>
+ </mime-mapping>
+ <mime-mapping>
+ <extension>zip</extension>
+ <mime-type>application/zip</mime-type>
+ </mime-mapping>
+</web-app>
diff --git a/dev/core/src/com/google/gwt/dev/etc/tomcat/webapps/ROOT/WEB-INF/web.xml b/dev/core/src/com/google/gwt/dev/etc/tomcat/webapps/ROOT/WEB-INF/web.xml
new file mode 100644
index 0000000..e600b2f
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/etc/tomcat/webapps/ROOT/WEB-INF/web.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app>
+
+ <servlet>
+ <servlet-name>shell</servlet-name>
+ <servlet-class>com.google.gwt.dev.shell.GWTShellServlet</servlet-class>
+ </servlet>
+
+ <servlet-mapping>
+ <servlet-name>shell</servlet-name>
+ <url-pattern>/*</url-pattern>
+ </servlet-mapping>
+
+</web-app>
diff --git a/dev/core/src/com/google/gwt/dev/shell/GWTShellServlet.java b/dev/core/src/com/google/gwt/dev/shell/GWTShellServlet.java
new file mode 100644
index 0000000..9e7ead7
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/shell/GWTShellServlet.java
@@ -0,0 +1,991 @@
+/*
+ * 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;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.linker.ArtifactSet;
+import com.google.gwt.core.ext.linker.impl.HostedModeLinker;
+import com.google.gwt.core.ext.linker.impl.StandardLinkerContext;
+import com.google.gwt.dev.cfg.ModuleDef;
+import com.google.gwt.dev.cfg.ModuleDefLoader;
+import com.google.gwt.dev.jjs.JJSOptionsImpl;
+import com.google.gwt.dev.resource.Resource;
+import com.google.gwt.dev.shell.log.ServletContextTreeLogger;
+import com.google.gwt.dev.util.HttpHeaders;
+import com.google.gwt.dev.util.Util;
+import com.google.gwt.thirdparty.guava.common.collect.MapMaker;
+import com.google.gwt.util.tools.Utility;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Built-in servlet for convenient access to the public path of a specified
+ * module.
+ */
+public class GWTShellServlet extends HttpServlet {
+
+ private static class RequestParts {
+ public final String moduleName;
+
+ public final String partialPath;
+
+ public RequestParts(HttpServletRequest request)
+ throws UnableToCompleteException {
+ String pathInfo = request.getPathInfo();
+ if (pathInfo != null) {
+ int slash = pathInfo.indexOf('/', 1);
+ if (slash != -1) {
+ moduleName = pathInfo.substring(1, slash);
+ partialPath = pathInfo.substring(slash + 1);
+ return;
+ } else {
+ moduleName = pathInfo.substring(1);
+ partialPath = null;
+ return;
+ }
+ }
+ throw new UnableToCompleteException();
+ }
+ }
+
+ /**
+ * This the default cache time in seconds for files that aren't either
+ * *.cache.*, *.nocache.*.
+ */
+ private static final int DEFAULT_CACHE_SECONDS = 5;
+
+ private static final String XHTML_MIME_TYPE = "application/xhtml+xml";
+
+ /**
+ * Must keep only weak references to ModuleDefs else we permanently pin them.
+ */
+ private final Map<String, ModuleDef> loadedModulesByName = new MapMaker().weakValues().makeMap();
+
+ /**
+ * The lifetime of the module pins the lifetime of the associated servlet;
+ * this is because the loaded servlet has a weak backRef to its live module
+ * through its context. When the module dies, the servlet needs to die also.
+ */
+ private final Map<ModuleDef, Map<String, HttpServlet>> loadedServletsByModuleAndClassName =
+ new MapMaker().weakKeys().makeMap();
+
+ private final Map<String, String> mimeTypes = new HashMap<String, String>();
+
+ /**
+ * Only for backwards compatibility. Shouldn't we remove this now?
+ */
+ private final Map<String, ModuleDef> modulesByServletPath = new MapMaker().weakValues().makeMap();
+
+ private int nextRequestId;
+
+ private final Object requestIdLock = new Object();
+
+ private TreeLogger topLogger;
+
+ private WorkDirs workDirs;
+
+ public GWTShellServlet() {
+ initMimeTypes();
+ }
+
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ processFileRequest(request, response);
+ }
+
+ @Override
+ protected void doPost(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ processFileRequest(request, response);
+ }
+
+ protected void processFileRequest(HttpServletRequest request,
+ HttpServletResponse response) throws IOException {
+
+ String pathInfo = request.getPathInfo();
+ if (pathInfo.length() == 0 || pathInfo.equals("/")) {
+ response.setContentType("text/html");
+ PrintWriter writer = response.getWriter();
+ writer.println("<html><body><basefont face='arial'>");
+ writer.println("To launch an application, specify a URL of the form <code>/<i>module</i>/<i>file.html</i></code>");
+ writer.println("</body></html>");
+ return;
+ }
+
+ TreeLogger logger = getLogger();
+
+ // Parse the request assuming it is module/resource.
+ //
+ RequestParts parts;
+ try {
+ parts = new RequestParts(request);
+ } catch (UnableToCompleteException e) {
+ sendErrorResponse(response, HttpServletResponse.SC_NOT_FOUND,
+ "Don't know what to do with this URL: '" + pathInfo + "'");
+ return;
+ }
+
+ String partialPath = parts.partialPath;
+ String moduleName = parts.moduleName;
+
+ // If the module is renamed, substitute the renamed module name
+ ModuleDef moduleDef = loadedModulesByName.get(moduleName);
+ if (moduleDef != null) {
+ moduleName = moduleDef.getName();
+ }
+
+ if (partialPath == null) {
+ // Redir back to the same URL but ending with a slash.
+ //
+ response.sendRedirect(moduleName + "/");
+ return;
+ } else if (partialPath.length() > 0) {
+ // Both the module name and a resource.
+ //
+ doGetPublicFile(request, response, logger, partialPath, moduleName);
+ return;
+ } else {
+ // Was just the module name, ending with a slash.
+ //
+ doGetModule(request, response, logger, parts);
+ return;
+ }
+ }
+
+ @Override
+ protected void service(HttpServletRequest request,
+ HttpServletResponse response) throws ServletException, IOException {
+
+ TreeLogger logger = getLogger();
+ int id = allocateRequestId();
+ if (logger.isLoggable(TreeLogger.TRACE)) {
+ StringBuffer url = request.getRequestURL();
+
+ // Branch the logger in case we decide to log more below.
+ logger = logger.branch(TreeLogger.TRACE, "Request " + id + ": " + url,
+ null);
+ }
+
+ String servletClassName = null;
+ ModuleDef moduleDef = null;
+
+ try {
+ // Attempt to split the URL into module/path, which we'll use to see
+ // if we can map the request to a module's servlet.
+ RequestParts parts = new RequestParts(request);
+
+ if ("favicon.ico".equalsIgnoreCase(parts.moduleName)) {
+ sendErrorResponse(response, HttpServletResponse.SC_NOT_FOUND,
+ "Icon not available");
+ return;
+ }
+
+ // See if the request references a module we know.
+ moduleDef = getModuleDef(logger, parts.moduleName);
+ if (moduleDef != null) {
+ // Okay, we know this module. Do we know this servlet path?
+ // It is right to prepend the slash because (1) ModuleDefSchema requires
+ // every servlet path to begin with a slash and (2) RequestParts always
+ // rips off the leading slash.
+ String servletPath = "/" + parts.partialPath;
+ servletClassName = moduleDef.findServletForPath(servletPath);
+
+ // Fall-through below, where we check servletClassName.
+ } else {
+ // Fall-through below, where we check servletClassName.
+ }
+ } catch (UnableToCompleteException e) {
+ // Do nothing, since it was speculative anyway.
+ }
+
+ // BEGIN BACKWARD COMPATIBILITY
+ if (servletClassName == null) {
+ // Try to map a bare path that isn't preceded by the module name.
+ // This is no longer the recommended practice, so we warn.
+ String path = request.getPathInfo();
+ moduleDef = modulesByServletPath.get(path);
+ if (moduleDef != null) {
+ // See if there is a servlet we can delegate to for the given url.
+ servletClassName = moduleDef.findServletForPath(path);
+
+ if (servletClassName != null) {
+ TreeLogger branch = logger.branch(TreeLogger.WARN,
+ "Use of deprecated hosted mode servlet path mapping", null);
+ branch.log(
+ TreeLogger.WARN,
+ "The client code is invoking the servlet with a URL that is not module-relative: "
+ + path, null);
+ branch.log(
+ TreeLogger.WARN,
+ "Prepend GWT.getModuleBaseURL() to the URL in client code to create a module-relative URL: /"
+ + moduleDef.getName() + path, null);
+ branch.log(
+ TreeLogger.WARN,
+ "Using module-relative URLs ensures correct URL-independent behavior in external servlet containers",
+ null);
+ }
+
+ // Fall-through below, where we check servletClassName.
+ } else {
+ // Fall-through below, where we check servletClassName.
+ }
+ }
+ // END BACKWARD COMPATIBILITY
+
+ // Load/get the servlet if we found one.
+ if (servletClassName != null) {
+ HttpServlet delegatee = tryGetOrLoadServlet(logger, moduleDef,
+ servletClassName);
+ if (delegatee == null) {
+ logger.log(TreeLogger.ERROR, "Unable to dispatch request", null);
+ sendErrorResponse(response,
+ HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+ "Unable to find/load mapped servlet class '" + servletClassName
+ + "'");
+ return;
+ }
+
+ // Delegate everything to the downstream servlet and we're done.
+ delegatee.service(request, response);
+ } else {
+ // Use normal default processing on this request, since we couldn't
+ // recognize it as anything special.
+ super.service(request, response);
+ }
+ }
+
+ private int allocateRequestId() {
+ synchronized (requestIdLock) {
+ return nextRequestId++;
+ }
+ }
+
+ /**
+ * Handle auto-generated resources.
+ *
+ * @return <code>true</code> if a resource was generated
+ */
+ private boolean autoGenerateResources(HttpServletRequest request,
+ HttpServletResponse response, TreeLogger logger, String partialPath,
+ String moduleName) throws IOException {
+
+ if (partialPath.equals(moduleName + ".nocache.js")) {
+ if (request.getParameter("compiled") == null) {
+ // Generate the .js file.
+ try {
+ String js = genSelectionScript(logger, moduleName);
+ setResponseCacheHeaders(response, 0); // do not cache selection script
+ response.setStatus(HttpServletResponse.SC_OK);
+ response.setContentType("text/javascript");
+ response.getWriter().println(js);
+ return true;
+ } catch (UnableToCompleteException e) {
+ // The error will have already been logged. Continue, since this could
+ // actually be a request for a static file that happens to have an
+ // unfortunately confusing name.
+ }
+ }
+ } else if (partialPath.equals("hosted.html")) {
+ String html = HostedModeLinker.getHostedHtml();
+ setResponseCacheHeaders(response, DEFAULT_CACHE_SECONDS);
+ response.setStatus(HttpServletResponse.SC_OK);
+ response.setContentType("text/html");
+ response.getWriter().println(html);
+ return true;
+ }
+
+ return false;
+ }
+
+ private void doGetModule(HttpServletRequest request,
+ HttpServletResponse response, TreeLogger logger, RequestParts parts)
+ throws IOException {
+
+ // Generate a generic empty host page.
+ //
+ String msg = "The development shell servlet received a request to generate a host page for module '"
+ + parts.moduleName + "' ";
+
+ logger = logger.branch(TreeLogger.TRACE, msg, null);
+
+ try {
+ // Try to load the module just to make sure it'll work.
+ getModuleDef(logger, parts.moduleName);
+ } catch (UnableToCompleteException e) {
+ sendErrorResponse(response, HttpServletResponse.SC_NOT_FOUND,
+ "Unable to find/load module '" + Util.escapeXml(parts.moduleName)
+ + "' (see server log for details)");
+ return;
+ }
+
+ response.setContentType("text/html");
+ PrintWriter writer = response.getWriter();
+ writer.println("<html><head>");
+ writer.print("<script language='javascript' src='");
+ writer.print(parts.moduleName);
+ writer.println(".nocache.js'></script>");
+
+ // Create a property for each query param.
+ Map<String, String[]> params = getParameterMap(request);
+ for (Map.Entry<String, String[]> entry : params.entrySet()) {
+ String[] values = entry.getValue();
+ if (values.length > 0) {
+ writer.print("<meta name='gwt:property' content='");
+ writer.print(entry.getKey());
+ writer.print("=");
+ writer.print(values[values.length - 1]);
+ writer.println("'>");
+ }
+ }
+
+ writer.println("</head><body>");
+ writer.println("<iframe src=\"javascript:''\" id='__gwt_historyFrame' "
+ + "style='position:absolute;width:0;height:0;border:0'></iframe>");
+ writer.println("<noscript>");
+ writer.println(" <div style=\"width: 22em; position: absolute; left: 50%; margin-left: -11em; color: red; background-color: white; border: 1px solid red; padding: 4px; font-family: sans-serif\">");
+ writer.println(" Your web browser must have JavaScript enabled");
+ writer.println(" in order for this application to display correctly.");
+ writer.println(" </div>");
+ writer.println("</noscript>");
+ writer.println("</body></html>");
+
+ // Done.
+ }
+
+ /**
+ * Fetch a file and return it as the HTTP response, setting the cache-related
+ * headers according to the name of the file (see
+ * {@link #getCacheTime(String)}). This function honors If-Modified-Since to
+ * minimize the impact of limiting caching of files for development.
+ *
+ * @param request the HTTP request
+ * @param response the HTTP response
+ * @param logger a TreeLogger to use for debug output
+ * @param partialPath the path within the module
+ * @param moduleName the name of the module
+ * @throws IOException
+ */
+ @SuppressWarnings("deprecation")
+ private void doGetPublicFile(HttpServletRequest request,
+ HttpServletResponse response, TreeLogger logger, String partialPath,
+ String moduleName) throws IOException {
+
+ // Create a logger branch for this request.
+ logger = logger.branch(TreeLogger.TRACE,
+ "The development shell servlet received a request for '"
+ + partialPath + "' in module '" + moduleName + ".gwt.xml' ", null);
+
+ // Handle auto-generation of resources.
+ if (shouldAutoGenerateResources()) {
+ if (autoGenerateResources(request, response, logger, partialPath,
+ moduleName)) {
+ return;
+ }
+ }
+
+ URL foundResource = null;
+ try {
+ // Look for the requested file on the public path.
+ //
+ ModuleDef moduleDef = getModuleDef(logger, moduleName);
+ if (shouldAutoGenerateResources()) {
+ Resource publicResource = moduleDef.findPublicFile(partialPath);
+ if (publicResource != null) {
+ foundResource = publicResource.getURL();
+ }
+
+ if (foundResource == null) {
+ // Look for public generated files
+ File shellDir = getShellWorkDirs().getShellPublicGenDir(moduleDef);
+ File requestedFile = new File(shellDir, partialPath);
+ if (requestedFile.exists()) {
+ try {
+ foundResource = requestedFile.toURI().toURL();
+ } catch (MalformedURLException e) {
+ // ignore since it was speculative anyway
+ }
+ }
+ }
+ }
+
+ /*
+ * If the user is coming from compiled web-mode, check the linker output
+ * directory for the real bootstrap file.
+ */
+ if (foundResource == null) {
+ File moduleDir = getShellWorkDirs().getCompilerOutputDir(moduleDef);
+ File requestedFile = new File(moduleDir, partialPath);
+ if (requestedFile.exists()) {
+ try {
+ foundResource = requestedFile.toURI().toURL();
+ } catch (MalformedURLException e) {
+ // ignore since it was speculative anyway
+ }
+ }
+ }
+
+ if (foundResource == null) {
+ String msg;
+ if ("gwt.js".equals(partialPath)) {
+ msg = "Loading the old 'gwt.js' bootstrap script is no longer supported; please load '"
+ + moduleName + ".nocache.js' directly";
+ } else {
+ msg = "Resource not found: " + partialPath + "; "
+ + "(could a file be missing from the public path or a <servlet> "
+ + "tag misconfigured in module " + moduleName + ".gwt.xml ?)";
+ }
+ logger.log(TreeLogger.WARN, msg, null);
+ throw new UnableToCompleteException();
+ }
+ } catch (UnableToCompleteException e) {
+ sendErrorResponse(response, HttpServletResponse.SC_NOT_FOUND,
+ "Cannot find resource '" + partialPath
+ + "' in the public path of module '" + moduleName + "'");
+ return;
+ }
+
+ // Get the MIME type.
+ String path = foundResource.toExternalForm();
+ String mimeType = null;
+ try {
+ mimeType = getServletContext().getMimeType(path);
+ } catch (UnsupportedOperationException e) {
+ // Certain minimalist servlet containers throw this.
+ // Fall through to guess the type.
+ }
+
+ if (mimeType == null) {
+ mimeType = guessMimeType(path);
+ if (logger.isLoggable(TreeLogger.TRACE)) {
+ logger.log(TreeLogger.TRACE, "Guessed MIME type '" + mimeType + "'", null);
+ }
+ }
+
+ maybeIssueXhtmlWarning(logger, mimeType, partialPath);
+
+ long cacheSeconds = getCacheTime(path);
+
+ InputStream is = null;
+ try {
+ // Check for up-to-datedness.
+ URLConnection conn = foundResource.openConnection();
+ long lastModified = conn.getLastModified();
+ if (isNotModified(request, lastModified)) {
+ response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+ setResponseCacheHeaders(response, cacheSeconds);
+ return;
+ }
+
+ // Set up headers to really send it.
+ response.setStatus(HttpServletResponse.SC_OK);
+ long now = new Date().getTime();
+ response.setHeader(HttpHeaders.DATE,
+ HttpHeaders.toInternetDateFormat(now));
+ response.setContentType(mimeType);
+ String lastModifiedStr = HttpHeaders.toInternetDateFormat(lastModified);
+ response.setHeader(HttpHeaders.LAST_MODIFIED, lastModifiedStr);
+
+ // Expiration header. Either immediately stale (requiring an
+ // "If-Modified-Since") or infinitely cacheable (not requiring even a
+ // freshness check).
+ setResponseCacheHeaders(response, cacheSeconds);
+
+ // Content length.
+ int contentLength = conn.getContentLength();
+ if (contentLength >= 0) {
+ response.setHeader(HttpHeaders.CONTENT_LENGTH,
+ Integer.toString(contentLength));
+ }
+
+ // Send the bytes.
+ is = conn.getInputStream();
+ streamOut(is, response.getOutputStream(), 1024 * 8);
+ } finally {
+ Utility.close(is);
+ }
+ }
+
+ /**
+ * Generates a module.js file on the fly. Note that the nocache file that is
+ * generated that can only be used for hosted mode. It cannot produce a web
+ * mode version, since this servlet doesn't know strong names, since by
+ * definition of "hosted mode" JavaScript hasn't been compiled at this point.
+ */
+ private String genSelectionScript(TreeLogger logger, String moduleName)
+ throws UnableToCompleteException {
+ if (logger.isLoggable(TreeLogger.TRACE)) {
+ logger.log(TreeLogger.TRACE,
+ "Generating a script selection script for module " + moduleName);
+ }
+ ModuleDef module = getModuleDef(logger, moduleName);
+ StandardLinkerContext context = new StandardLinkerContext(logger, module,
+ new JJSOptionsImpl());
+ ArtifactSet artifacts = context.getArtifactsForPublicResources(logger,
+ module);
+ HostedModeLinker linker = new HostedModeLinker();
+ return linker.generateSelectionScript(logger, context, artifacts);
+ }
+
+ /**
+ * Get the length of time a given file should be cacheable. If the path
+ * contains *.nocache.*, it is never cacheable; if it contains *.cache.*, it
+ * is infinitely cacheable; anything else gets a default time.
+ *
+ * @return cache time in seconds, or 0 if the file is not cacheable at all
+ */
+ private long getCacheTime(String path) {
+ int lastDot = path.lastIndexOf('.');
+ if (lastDot >= 0) {
+ String prefix = path.substring(0, lastDot);
+ if (prefix.endsWith(".cache")) {
+ // RFC2616 says to never give a cache time of more than a year
+ // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.21
+ return HttpHeaders.SEC_YR;
+ } else if (prefix.endsWith(".nocache")) {
+ return 0;
+ }
+ }
+ return DEFAULT_CACHE_SECONDS;
+ }
+
+ private synchronized TreeLogger getLogger() {
+ if (topLogger == null) {
+ ServletContext servletContext = getServletContext();
+ final String attr = "com.google.gwt.dev.shell.logger";
+ topLogger = (TreeLogger) servletContext.getAttribute(attr);
+ if (topLogger == null) {
+ // No shell available, so wrap the regular servlet context logger.
+ //
+ topLogger = new ServletContextTreeLogger(servletContext);
+ }
+ }
+ return topLogger;
+ }
+
+ /**
+ * We don't actually log this on purpose since the client does anyway.
+ */
+ private ModuleDef getModuleDef(TreeLogger logger, String moduleName)
+ throws UnableToCompleteException {
+ synchronized (loadedModulesByName) {
+ ModuleDef moduleDef = loadedModulesByName.get(moduleName);
+ if (moduleDef == null) {
+ moduleDef = ModuleDefLoader.loadFromClassPath(logger, moduleName, false);
+ loadedModulesByName.put(moduleName, moduleDef);
+ loadedModulesByName.put(moduleDef.getName(), moduleDef);
+
+ // BEGIN BACKWARD COMPATIBILITY
+ // The following map of servlet path to module is included only
+ // for backward-compatibility. We are going to remove this functionality
+ // when we go out of beta. The new behavior is that the client should
+ // specify the module name as part of the URL and construct it using
+ // getModuleBaseURL().
+ String[] servletPaths = moduleDef.getServletPaths();
+ for (int i = 0; i < servletPaths.length; i++) {
+ modulesByServletPath.put(servletPaths[i], moduleDef);
+ }
+ // END BACKWARD COMPATIBILITY
+ }
+ return moduleDef;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private Map<String, String[]> getParameterMap(HttpServletRequest request) {
+ return request.getParameterMap();
+ }
+
+ private synchronized WorkDirs getShellWorkDirs() {
+ if (workDirs == null) {
+ ServletContext servletContext = getServletContext();
+ final String attr = "com.google.gwt.dev.shell.workdirs";
+ workDirs = (WorkDirs) servletContext.getAttribute(attr);
+ assert (workDirs != null);
+ }
+ return workDirs;
+ }
+
+ private String guessMimeType(String fullPath) {
+ int dot = fullPath.lastIndexOf('.');
+ if (dot != -1) {
+ String ext = fullPath.substring(dot + 1);
+ String mimeType = mimeTypes.get(ext);
+ if (mimeType != null) {
+ return mimeType;
+ }
+
+ // Otherwise, fall through.
+ //
+ }
+
+ // Last resort.
+ //
+ return "application/octet-stream";
+ }
+
+ private void initMimeTypes() {
+ mimeTypes.put("abs", "audio/x-mpeg");
+ mimeTypes.put("ai", "application/postscript");
+ mimeTypes.put("aif", "audio/x-aiff");
+ mimeTypes.put("aifc", "audio/x-aiff");
+ mimeTypes.put("aiff", "audio/x-aiff");
+ mimeTypes.put("aim", "application/x-aim");
+ mimeTypes.put("art", "image/x-jg");
+ mimeTypes.put("asf", "video/x-ms-asf");
+ mimeTypes.put("asx", "video/x-ms-asf");
+ mimeTypes.put("au", "audio/basic");
+ mimeTypes.put("avi", "video/x-msvideo");
+ mimeTypes.put("avx", "video/x-rad-screenplay");
+ mimeTypes.put("bcpio", "application/x-bcpio");
+ mimeTypes.put("bin", "application/octet-stream");
+ mimeTypes.put("bmp", "image/bmp");
+ mimeTypes.put("body", "text/html");
+ mimeTypes.put("cdf", "application/x-cdf");
+ mimeTypes.put("cer", "application/x-x509-ca-cert");
+ mimeTypes.put("class", "application/java");
+ mimeTypes.put("cpio", "application/x-cpio");
+ mimeTypes.put("csh", "application/x-csh");
+ mimeTypes.put("css", "text/css");
+ mimeTypes.put("dib", "image/bmp");
+ mimeTypes.put("doc", "application/msword");
+ mimeTypes.put("dtd", "text/plain");
+ mimeTypes.put("dv", "video/x-dv");
+ mimeTypes.put("dvi", "application/x-dvi");
+ mimeTypes.put("eps", "application/postscript");
+ mimeTypes.put("etx", "text/x-setext");
+ mimeTypes.put("exe", "application/octet-stream");
+ mimeTypes.put("gif", "image/gif");
+ mimeTypes.put("gtar", "application/x-gtar");
+ mimeTypes.put("gz", "application/x-gzip");
+ mimeTypes.put("hdf", "application/x-hdf");
+ mimeTypes.put("hqx", "application/mac-binhex40");
+ mimeTypes.put("htc", "text/x-component");
+ mimeTypes.put("htm", "text/html");
+ mimeTypes.put("html", "text/html");
+ mimeTypes.put("hqx", "application/mac-binhex40");
+ mimeTypes.put("ief", "image/ief");
+ mimeTypes.put("jad", "text/vnd.sun.j2me.app-descriptor");
+ mimeTypes.put("jar", "application/java-archive");
+ mimeTypes.put("java", "text/plain");
+ mimeTypes.put("jnlp", "application/x-java-jnlp-file");
+ mimeTypes.put("jpe", "image/jpeg");
+ mimeTypes.put("jpeg", "image/jpeg");
+ mimeTypes.put("jpg", "image/jpeg");
+ mimeTypes.put("js", "text/javascript");
+ mimeTypes.put("jsf", "text/plain");
+ mimeTypes.put("jspf", "text/plain");
+ mimeTypes.put("kar", "audio/x-midi");
+ mimeTypes.put("latex", "application/x-latex");
+ mimeTypes.put("m3u", "audio/x-mpegurl");
+ mimeTypes.put("mac", "image/x-macpaint");
+ mimeTypes.put("man", "application/x-troff-man");
+ mimeTypes.put("me", "application/x-troff-me");
+ mimeTypes.put("mid", "audio/x-midi");
+ mimeTypes.put("midi", "audio/x-midi");
+ mimeTypes.put("mif", "application/x-mif");
+ mimeTypes.put("mov", "video/quicktime");
+ mimeTypes.put("movie", "video/x-sgi-movie");
+ mimeTypes.put("mp1", "audio/x-mpeg");
+ mimeTypes.put("mp2", "audio/x-mpeg");
+ mimeTypes.put("mp3", "audio/x-mpeg");
+ mimeTypes.put("mpa", "audio/x-mpeg");
+ mimeTypes.put("mpe", "video/mpeg");
+ mimeTypes.put("mpeg", "video/mpeg");
+ mimeTypes.put("mpega", "audio/x-mpeg");
+ mimeTypes.put("mpg", "video/mpeg");
+ mimeTypes.put("mpv2", "video/mpeg2");
+ mimeTypes.put("ms", "application/x-wais-source");
+ mimeTypes.put("nc", "application/x-netcdf");
+ mimeTypes.put("oda", "application/oda");
+ mimeTypes.put("pbm", "image/x-portable-bitmap");
+ mimeTypes.put("pct", "image/pict");
+ mimeTypes.put("pdf", "application/pdf");
+ mimeTypes.put("pgm", "image/x-portable-graymap");
+ mimeTypes.put("pic", "image/pict");
+ mimeTypes.put("pict", "image/pict");
+ mimeTypes.put("pls", "audio/x-scpls");
+ mimeTypes.put("png", "image/png");
+ mimeTypes.put("pnm", "image/x-portable-anymap");
+ mimeTypes.put("pnt", "image/x-macpaint");
+ mimeTypes.put("ppm", "image/x-portable-pixmap");
+ mimeTypes.put("ppt", "application/powerpoint");
+ mimeTypes.put("ps", "application/postscript");
+ mimeTypes.put("psd", "image/x-photoshop");
+ mimeTypes.put("qt", "video/quicktime");
+ mimeTypes.put("qti", "image/x-quicktime");
+ mimeTypes.put("qtif", "image/x-quicktime");
+ mimeTypes.put("ras", "image/x-cmu-raster");
+ mimeTypes.put("rgb", "image/x-rgb");
+ mimeTypes.put("rm", "application/vnd.rn-realmedia");
+ mimeTypes.put("roff", "application/x-troff");
+ mimeTypes.put("rtf", "application/rtf");
+ mimeTypes.put("rtx", "text/richtext");
+ mimeTypes.put("sh", "application/x-sh");
+ mimeTypes.put("shar", "application/x-shar");
+ mimeTypes.put("smf", "audio/x-midi");
+ mimeTypes.put("sit", "application/x-stuffit");
+ mimeTypes.put("snd", "audio/basic");
+ mimeTypes.put("src", "application/x-wais-source");
+ mimeTypes.put("sv4cpio", "application/x-sv4cpio");
+ mimeTypes.put("sv4crc", "application/x-sv4crc");
+ mimeTypes.put("swf", "application/x-shockwave-flash");
+ mimeTypes.put("t", "application/x-troff");
+ mimeTypes.put("tar", "application/x-tar");
+ mimeTypes.put("tcl", "application/x-tcl");
+ mimeTypes.put("tex", "application/x-tex");
+ mimeTypes.put("texi", "application/x-texinfo");
+ mimeTypes.put("texinfo", "application/x-texinfo");
+ mimeTypes.put("tif", "image/tiff");
+ mimeTypes.put("tiff", "image/tiff");
+ mimeTypes.put("tr", "application/x-troff");
+ mimeTypes.put("tsv", "text/tab-separated-values");
+ mimeTypes.put("txt", "text/plain");
+ mimeTypes.put("ulw", "audio/basic");
+ mimeTypes.put("ustar", "application/x-ustar");
+ mimeTypes.put("xbm", "image/x-xbitmap");
+ mimeTypes.put("xht", "application/xhtml+xml");
+ mimeTypes.put("xhtml", "application/xhtml+xml");
+ mimeTypes.put("xml", "text/xml");
+ mimeTypes.put("xpm", "image/x-xpixmap");
+ mimeTypes.put("xsl", "text/xml");
+ mimeTypes.put("xwd", "image/x-xwindowdump");
+ mimeTypes.put("wav", "audio/x-wav");
+ mimeTypes.put("svg", "image/svg+xml");
+ mimeTypes.put("svgz", "image/svg+xml");
+ mimeTypes.put("vsd", "application/x-visio");
+ mimeTypes.put("wbmp", "image/vnd.wap.wbmp");
+ mimeTypes.put("wml", "text/vnd.wap.wml");
+ mimeTypes.put("wmlc", "application/vnd.wap.wmlc");
+ mimeTypes.put("wmls", "text/vnd.wap.wmlscript");
+ mimeTypes.put("wmlscriptc", "application/vnd.wap.wmlscriptc");
+ mimeTypes.put("wrl", "x-world/x-vrml");
+ mimeTypes.put("Z", "application/x-compress");
+ mimeTypes.put("z", "application/x-compress");
+ mimeTypes.put("zip", "application/zip");
+ }
+
+ /**
+ * Checks to see whether or not a client's file is out of date relative to the
+ * original.
+ */
+ private boolean isNotModified(HttpServletRequest request, long ageOfServerCopy) {
+ // The age of the server copy *must* have the milliseconds truncated.
+ // Since milliseconds isn't part of the GMT format, failure to truncate
+ // will leave the file in a state where it appears constantly out of date
+ // and yet it can never get in sync because the Last-Modified date keeps
+ // truncating off the milliseconds part on its way out.
+ //
+ ageOfServerCopy -= (ageOfServerCopy % 1000);
+
+ long ageOfClientCopy = 0;
+ String ifModifiedSince = request.getHeader("If-Modified-Since");
+ if (ifModifiedSince != null) {
+ // Rip off any additional stuff at the end, such as "; length="
+ // (IE does add this).
+ //
+ int lastSemi = ifModifiedSince.lastIndexOf(';');
+ if (lastSemi != -1) {
+ ifModifiedSince = ifModifiedSince.substring(0, lastSemi);
+ }
+ ageOfClientCopy = HttpHeaders.fromInternetDateFormat(ifModifiedSince);
+ }
+
+ if (ageOfClientCopy >= ageOfServerCopy) {
+ // The client already has a good copy.
+ //
+ return true;
+ } else {
+ // The client needs a fresh copy of the requested file.
+ //
+ return false;
+ }
+ }
+
+ private void maybeIssueXhtmlWarning(TreeLogger logger, String mimeType,
+ String path) {
+ if (!XHTML_MIME_TYPE.equals(mimeType)) {
+ return;
+ }
+
+ String msg = "File was returned with content-type of \"" + mimeType
+ + "\". GWT requires browser features that are not available to "
+ + "documents with this content-type.";
+
+ int ix = path.lastIndexOf('.');
+ if (ix >= 0 && ix < path.length()) {
+ String base = path.substring(0, ix);
+ msg += " Consider renaming \"" + path + "\" to \"" + base + ".html\".";
+ }
+
+ logger.log(TreeLogger.WARN, msg, null);
+ }
+
+ private void sendErrorResponse(HttpServletResponse response, int statusCode,
+ String msg) throws IOException {
+ response.setContentType("text/html");
+ response.getWriter().println(msg);
+ response.setStatus(statusCode);
+ }
+
+ /**
+ * Sets the Cache-control and Expires headers in the response based on the
+ * supplied cache time.
+ *
+ * Expires is used in addition to Cache-control for older clients or proxies
+ * which may not properly understand Cache-control.
+ *
+ * @param response the HttpServletResponse to update
+ * @param cacheTime non-negative number of seconds to cache the response; 0
+ * means specifically do not allow caching at all.
+ * @throws IllegalArgumentException if cacheTime is negative
+ */
+ private void setResponseCacheHeaders(HttpServletResponse response,
+ long cacheTime) {
+ long expires;
+ if (cacheTime < 0) {
+ throw new IllegalArgumentException("cacheTime of " + cacheTime
+ + " is negative");
+ }
+ if (cacheTime > 0) {
+ // Expire the specified seconds in the future.
+ expires = new Date().getTime() + cacheTime * HttpHeaders.MS_SEC;
+ } else {
+ // Prevent caching by using a time in the past for cache expiration.
+ // Use January 2, 1970 00:00:00, to account for timezone changes
+ // in case a browser tries to convert to a local timezone first
+ // 0=Jan 1, so add 1 day's worth of milliseconds to get Jan 2
+ expires = HttpHeaders.SEC_DAY * HttpHeaders.MS_SEC;
+ }
+ response.setHeader(HttpHeaders.CACHE_CONTROL,
+ HttpHeaders.CACHE_CONTROL_MAXAGE + cacheTime);
+ String expiresString = HttpHeaders.toInternetDateFormat(expires);
+ response.setHeader(HttpHeaders.EXPIRES, expiresString);
+ }
+
+ private boolean shouldAutoGenerateResources() {
+ ServletContext servletContext = getServletContext();
+ final String attr = "com.google.gwt.dev.shell.shouldAutoGenerateResources";
+ Boolean attrValue = (Boolean) servletContext.getAttribute(attr);
+ if (attrValue == null) {
+ return true;
+ }
+ return attrValue;
+ }
+
+ private void streamOut(InputStream in, OutputStream out, int bufferSize)
+ throws IOException {
+ assert (bufferSize >= 0);
+
+ byte[] buffer = new byte[bufferSize];
+ int bytesRead = 0;
+ while (true) {
+ bytesRead = in.read(buffer);
+ if (bytesRead >= 0) {
+ // Copy the bytes out.
+ out.write(buffer, 0, bytesRead);
+ } else {
+ // End of input stream.
+ out.flush();
+ return;
+ }
+ }
+ }
+
+ private HttpServlet tryGetOrLoadServlet(TreeLogger logger,
+ ModuleDef moduleDef, String className) {
+
+ // Maps className to live servlet for this module.
+ Map<String, HttpServlet> moduleServlets;
+ synchronized (loadedServletsByModuleAndClassName) {
+ moduleServlets = loadedServletsByModuleAndClassName.get(moduleDef);
+ if (moduleServlets == null) {
+ moduleServlets = new HashMap<String, HttpServlet>();
+ loadedServletsByModuleAndClassName.put(moduleDef, moduleServlets);
+ }
+ }
+
+ synchronized (moduleServlets) {
+ HttpServlet servlet = moduleServlets.get(className);
+ if (servlet != null) {
+ // Found it.
+ //
+ return servlet;
+ }
+
+ // Try to load and instantiate it.
+ //
+ Throwable caught = null;
+ try {
+ Class<?> servletClass = Class.forName(className);
+ Object newInstance = servletClass.newInstance();
+ if (!(newInstance instanceof HttpServlet)) {
+ logger.log(TreeLogger.ERROR,
+ "Not compatible with HttpServlet: " + className
+ + " (does your service extend RemoteServiceServlet?)", null);
+ return null;
+ }
+
+ // Success. Hang onto the instance so we can reuse it.
+ //
+ servlet = (HttpServlet) newInstance;
+
+ // We create proxies for ServletContext and ServletConfig to enable
+ // RemoteServiceServlets to load public and generated resources via
+ // ServletContext.getResourceAsStream()
+ //
+ ServletContext context = new HostedModeServletContextProxy(
+ getServletContext(), moduleDef, getShellWorkDirs());
+ ServletConfig config = new HostedModeServletConfigProxy(
+ getServletConfig(), context);
+
+ servlet.init(config);
+
+ moduleServlets.put(className, servlet);
+ return servlet;
+ } catch (ClassNotFoundException e) {
+ caught = e;
+ } catch (InstantiationException e) {
+ caught = e;
+ } catch (IllegalAccessException e) {
+ caught = e;
+ } catch (ServletException e) {
+ caught = e;
+ }
+ String msg = "Unable to instantiate '" + className + "'";
+ logger.log(TreeLogger.ERROR, msg, caught);
+ return null;
+ }
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/shell/HostedModeServletContextProxy.java b/dev/core/src/com/google/gwt/dev/shell/HostedModeServletContextProxy.java
new file mode 100644
index 0000000..d6b321e
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/shell/HostedModeServletContextProxy.java
@@ -0,0 +1,338 @@
+/*
+ * Copyright 2007 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;
+
+import com.google.gwt.dev.cfg.ModuleDef;
+import com.google.gwt.dev.resource.Resource;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.ref.WeakReference;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.Set;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.Servlet;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+
+/**
+ * ServletContext proxy that implements the getResource and getResourceAsStream
+ * members so that they can work with the {@link GWTShellServlet}.
+ */
+class HostedModeServletContextProxy implements ServletContext {
+ private final ServletContext context;
+ /**
+ * Avoid pinning my moduleDef.
+ */
+ private final WeakReference<ModuleDef> moduleDefRef;
+ private final WorkDirs workDirs;
+
+ HostedModeServletContextProxy(ServletContext context, ModuleDef moduleDef,
+ WorkDirs workDirs) {
+ this.context = context;
+ this.moduleDefRef = new WeakReference<ModuleDef>(moduleDef);
+ this.workDirs = workDirs;
+ }
+
+ /**
+ * @param arg0
+ * @return
+ * @see javax.servlet.ServletContext#getAttribute(java.lang.String)
+ */
+ public Object getAttribute(String arg0) {
+ return context.getAttribute(arg0);
+ }
+
+ /**
+ * @return
+ * @see javax.servlet.ServletContext#getAttributeNames()
+ */
+ @SuppressWarnings("unchecked")
+ public Enumeration<String> getAttributeNames() {
+ return context.getAttributeNames();
+ }
+
+ /**
+ * @param arg0
+ * @return
+ * @see javax.servlet.ServletContext#getContext(java.lang.String)
+ */
+ public ServletContext getContext(String arg0) {
+ return context.getContext(arg0);
+ }
+
+ public String getContextPath() {
+ return context.getContextPath();
+ }
+
+ /**
+ * @param arg0
+ * @return
+ * @see javax.servlet.ServletContext#getInitParameter(java.lang.String)
+ */
+ public String getInitParameter(String arg0) {
+ return context.getInitParameter(arg0);
+ }
+
+ /**
+ * @return
+ * @see javax.servlet.ServletContext#getInitParameterNames()
+ */
+ public Enumeration<?> getInitParameterNames() {
+ return context.getInitParameterNames();
+ }
+
+ /**
+ * @return
+ * @see javax.servlet.ServletContext#getMajorVersion()
+ */
+ public int getMajorVersion() {
+ return context.getMajorVersion();
+ }
+
+ /**
+ * @param arg0
+ * @return
+ * @see javax.servlet.ServletContext#getMimeType(java.lang.String)
+ */
+ public String getMimeType(String arg0) {
+ return context.getMimeType(arg0);
+ }
+
+ /**
+ * @return
+ * @see javax.servlet.ServletContext#getMinorVersion()
+ */
+ public int getMinorVersion() {
+ return context.getMinorVersion();
+ }
+
+ /**
+ * @param arg0
+ * @return
+ * @see javax.servlet.ServletContext#getNamedDispatcher(java.lang.String)
+ */
+ public RequestDispatcher getNamedDispatcher(String arg0) {
+ return context.getNamedDispatcher(arg0);
+ }
+
+ /**
+ * @param arg0
+ * @return
+ * @see javax.servlet.ServletContext#getRealPath(java.lang.String)
+ */
+ public String getRealPath(String arg0) {
+ return context.getRealPath(arg0);
+ }
+
+ /**
+ * @param arg0
+ * @return
+ * @see javax.servlet.ServletContext#getRequestDispatcher(java.lang.String)
+ */
+ public RequestDispatcher getRequestDispatcher(String arg0) {
+ return context.getRequestDispatcher(arg0);
+ }
+
+ /**
+ * @param arg0
+ * @return
+ * @throws MalformedURLException
+ * @see javax.servlet.ServletContext#getResource(java.lang.String)
+ */
+ @SuppressWarnings("deprecation")
+ public URL getResource(String path) throws MalformedURLException {
+ ModuleDef moduleDef = moduleDefRef.get();
+ assert (moduleDef != null) : "GWTShellServlet should have guaranteed that a"
+ + " live servlet will never process a request for a dead module; if you"
+ + " are using this servlet outside the context of processing a call,"
+ + " then don't do that";
+
+ String moduleContext = "/" + moduleDef.getName() + "/";
+ if (!path.startsWith(moduleContext)) {
+ // Check for a renamed module
+ moduleContext = "/" + moduleDef.getCanonicalName() + "/";
+ if (!path.startsWith(moduleContext)) {
+ // This path is in a different context; just return null
+ return null;
+ }
+ }
+
+ String partialPath = path.substring(moduleContext.length());
+
+ // Try to get the resource from the application's public path
+ Resource publicResource = moduleDef.findPublicFile(partialPath);
+ if (publicResource != null) {
+ return publicResource.getURL();
+ }
+
+ // Otherwise try the path in the shell's public generated directory
+ File shellDir = workDirs.getShellPublicGenDir(moduleDef);
+ File requestedFile = new File(shellDir, partialPath);
+ if (requestedFile.exists()) {
+ return requestedFile.toURI().toURL();
+ }
+
+ /*
+ * If the user is coming from compiled web-mode, check the linker output
+ * directory for the file. We'll default to using the output directory of
+ * the first linker defined in the <set-linker> tab.
+ */
+ File linkDir = workDirs.getCompilerOutputDir(moduleDef);
+ requestedFile = new File(linkDir, partialPath);
+ if (requestedFile.exists()) {
+ try {
+ return requestedFile.toURI().toURL();
+ } catch (MalformedURLException e) {
+ // ignore since it was speculative anyway
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @param arg0
+ * @return
+ * @see javax.servlet.ServletContext#getResourceAsStream(java.lang.String)
+ */
+ public InputStream getResourceAsStream(String arg0) {
+ URL url;
+ try {
+ url = getResource(arg0);
+ if (url != null) {
+ return url.openStream();
+ }
+ } catch (MalformedURLException e) {
+ // Ignore the exception; return null
+ } catch (IOException e) {
+ // Ignore the exception; return null
+ }
+
+ return null;
+ }
+
+ /**
+ *
+ * @param path
+ * @return
+ * @see javax.servlet.ServletContext#getResourcePaths(java.lang.String)
+ */
+ @SuppressWarnings("unchecked")
+ public Set<String> getResourcePaths(String path) {
+ return context.getResourcePaths(path);
+ }
+
+ /**
+ * @return
+ * @see javax.servlet.ServletContext#getServerInfo()
+ */
+ public String getServerInfo() {
+ return context.getServerInfo();
+ }
+
+ /**
+ * @param arg0
+ * @return
+ * @throws ServletException
+ * @deprecated
+ * @see javax.servlet.ServletContext#getServlet(java.lang.String)
+ */
+ @Deprecated
+ public Servlet getServlet(String arg0) throws ServletException {
+ return context.getServlet(arg0);
+ }
+
+ /**
+ * @return
+ * @see javax.servlet.ServletContext#getServletContextName()
+ */
+ public String getServletContextName() {
+ return context.getServletContextName();
+ }
+
+ /**
+ * @return
+ * @deprecated
+ * @see javax.servlet.ServletContext#getServletNames()
+ */
+ @Deprecated
+ @SuppressWarnings("unchecked")
+ public Enumeration<String> getServletNames() {
+ return context.getServletNames();
+ }
+
+ /**
+ * @return
+ * @deprecated
+ * @see javax.servlet.ServletContext#getServlets()
+ */
+ @Deprecated
+ @SuppressWarnings("unchecked")
+ public Enumeration<Servlet> getServlets() {
+ return context.getServlets();
+ }
+
+ /**
+ * @param arg0
+ * @param arg1
+ * @deprecated
+ * @see javax.servlet.ServletContext#log(java.lang.Exception,
+ * java.lang.String)
+ */
+ @Deprecated
+ public void log(Exception arg0, String arg1) {
+ context.log(arg0, arg1);
+ }
+
+ /**
+ * @param arg0
+ * @see javax.servlet.ServletContext#log(java.lang.String)
+ */
+ public void log(String arg0) {
+ context.log(arg0);
+ }
+
+ /**
+ * @param arg0
+ * @param arg1
+ * @see javax.servlet.ServletContext#log(java.lang.String,java.lang.Throwable)
+ */
+ public void log(String arg0, Throwable arg1) {
+ context.log(arg0, arg1);
+ }
+
+ /**
+ * @param arg0
+ * @see javax.servlet.ServletContext#removeAttribute(java.lang.String)
+ */
+ public void removeAttribute(String arg0) {
+ context.removeAttribute(arg0);
+ }
+
+ /**
+ * @param arg0
+ * @param arg1
+ * @see javax.servlet.ServletContext#setAttribute(java.lang.String,java.lang.Object)
+ */
+ public void setAttribute(String arg0, Object arg1) {
+ context.setAttribute(arg0, arg1);
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/shell/tomcat/CatalinaLoggerAdapter.java b/dev/core/src/com/google/gwt/dev/shell/tomcat/CatalinaLoggerAdapter.java
new file mode 100644
index 0000000..9cf2d95
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/shell/tomcat/CatalinaLoggerAdapter.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2006 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.tomcat;
+
+import com.google.gwt.core.ext.TreeLogger;
+
+import org.apache.catalina.logger.LoggerBase;
+
+class CatalinaLoggerAdapter extends LoggerBase {
+
+ private final TreeLogger logger;
+
+ public CatalinaLoggerAdapter(TreeLogger logger) {
+ this.logger = logger;
+ }
+
+ @Override
+ public void log(Exception exception, String msg) {
+ logger.log(TreeLogger.WARN, msg, exception);
+ }
+
+ @Override
+ public void log(String msg) {
+ logger.log(TreeLogger.INFO, msg, null);
+ }
+
+ @Override
+ public void log(String message, int verbosity) {
+ TreeLogger.Type type = mapVerbosityToLogType(verbosity);
+ logger.log(type, message, null);
+ }
+
+ @Override
+ public void log(String msg, Throwable throwable) {
+ logger.log(TreeLogger.WARN, msg, throwable);
+ }
+
+ @Override
+ public void log(String message, Throwable throwable, int verbosity) {
+ TreeLogger.Type type = mapVerbosityToLogType(verbosity);
+ logger.log(type, message, throwable);
+ }
+
+ private TreeLogger.Type mapVerbosityToLogType(int verbosity) {
+ switch (verbosity) {
+ case LoggerBase.FATAL:
+ case LoggerBase.ERROR:
+ case LoggerBase.WARNING:
+ return TreeLogger.WARN;
+
+ case LoggerBase.INFORMATION:
+ return TreeLogger.DEBUG;
+ case LoggerBase.DEBUG:
+ return TreeLogger.SPAM;
+
+ default:
+ // really, this was an unexpected type
+ return TreeLogger.WARN;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/dev/shell/tomcat/CommonsLoggerAdapter.java b/dev/core/src/com/google/gwt/dev/shell/tomcat/CommonsLoggerAdapter.java
new file mode 100644
index 0000000..d071d2a
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/shell/tomcat/CommonsLoggerAdapter.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2006 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.tomcat;
+
+import com.google.gwt.core.ext.TreeLogger;
+
+/**
+ * Maps Tomcat's commons logger onto the GWT shell's tree logger.
+ */
+public class CommonsLoggerAdapter implements org.apache.commons.logging.Log {
+
+ private TreeLogger log;
+
+ /**
+ * @param name unused
+ */
+ public CommonsLoggerAdapter(String name) {
+ // NOTE: this is ugly, but I don't know of any other way to get a
+ // non-static log to which we can delegate.
+ //
+ log = EmbeddedTomcatServer.sTomcat.getLogger();
+ }
+
+ public void debug(Object message) {
+ doLog(TreeLogger.SPAM, message, null);
+ }
+
+ public void debug(Object message, Throwable t) {
+ doLog(TreeLogger.SPAM, message, t);
+ }
+
+ public void error(Object message) {
+ doLog(TreeLogger.WARN, message, null);
+ }
+
+ public void error(Object message, Throwable t) {
+ doLog(TreeLogger.WARN, message, t);
+ }
+
+ public void fatal(Object message) {
+ doLog(TreeLogger.WARN, message, null);
+ }
+
+ public void fatal(Object message, Throwable t) {
+ doLog(TreeLogger.WARN, message, t);
+ }
+
+ public void info(Object message) {
+ // Intentionally low-level to us.
+ doLog(TreeLogger.TRACE, message, null);
+ }
+
+ public void info(Object message, Throwable t) {
+ // Intentionally low-level to us.
+ doLog(TreeLogger.TRACE, message, t);
+ }
+
+ public boolean isDebugEnabled() {
+ return log.isLoggable(TreeLogger.SPAM);
+ }
+
+ public boolean isErrorEnabled() {
+ return log.isLoggable(TreeLogger.WARN);
+ }
+
+ public boolean isFatalEnabled() {
+ return log.isLoggable(TreeLogger.WARN);
+ }
+
+ public boolean isInfoEnabled() {
+ // Intentionally low-level to us.
+ return log.isLoggable(TreeLogger.TRACE);
+ }
+
+ public boolean isTraceEnabled() {
+ // Intentionally low-level to us.
+ return log.isLoggable(TreeLogger.SPAM);
+ }
+
+ public boolean isWarnEnabled() {
+ return log.isLoggable(TreeLogger.WARN);
+ }
+
+ public void trace(Object message) {
+ // Intentionally low-level to us.
+ doLog(TreeLogger.DEBUG, message, null);
+ }
+
+ public void trace(Object message, Throwable t) {
+ // Intentionally low-level to us.
+ doLog(TreeLogger.DEBUG, message, t);
+ }
+
+ public void warn(Object message) {
+ doLog(TreeLogger.WARN, message, null);
+ }
+
+ public void warn(Object message, Throwable t) {
+ doLog(TreeLogger.WARN, message, t);
+ }
+
+ private void doLog(TreeLogger.Type type, Object message, Throwable t) {
+ String msg = message.toString();
+ log.log(type, msg, t);
+ }
+}
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/dev/shell/tomcat/EmbeddedTomcatServer.java b/dev/core/src/com/google/gwt/dev/shell/tomcat/EmbeddedTomcatServer.java
new file mode 100644
index 0000000..e7d27ac
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/shell/tomcat/EmbeddedTomcatServer.java
@@ -0,0 +1,436 @@
+/*
+ * Copyright 2006 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.tomcat;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.dev.resource.Resource;
+import com.google.gwt.dev.resource.impl.ClassPathEntry;
+import com.google.gwt.dev.resource.impl.PathPrefix;
+import com.google.gwt.dev.resource.impl.PathPrefixSet;
+import com.google.gwt.dev.resource.impl.ResourceOracleImpl;
+import com.google.gwt.dev.shell.WorkDirs;
+import com.google.gwt.dev.util.Util;
+
+import org.apache.catalina.Connector;
+import org.apache.catalina.ContainerEvent;
+import org.apache.catalina.ContainerListener;
+import org.apache.catalina.Engine;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.Logger;
+import org.apache.catalina.core.StandardContext;
+import org.apache.catalina.core.StandardHost;
+import org.apache.catalina.startup.Embedded;
+import org.apache.catalina.startup.HostConfig;
+import org.apache.coyote.tomcat5.CoyoteConnector;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.Collections;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * Wraps an instance of the Tomcat web server used in hosted mode.
+ */
+public class EmbeddedTomcatServer {
+
+ static EmbeddedTomcatServer sTomcat;
+
+ public static int getPort() {
+ return sTomcat.port;
+ }
+
+ public static String start(TreeLogger topLogger, int port, WorkDirs workDirs) {
+ return start(topLogger, port, workDirs, true);
+ }
+
+ public static synchronized String start(TreeLogger topLogger, int port,
+ WorkDirs workDirs, boolean shouldAutoGenerateResources) {
+ if (sTomcat != null) {
+ throw new IllegalStateException("Embedded Tomcat is already running");
+ }
+
+ try {
+ new EmbeddedTomcatServer(topLogger, port, workDirs,
+ shouldAutoGenerateResources);
+ return null;
+ } catch (LifecycleException e) {
+ String msg = e.getMessage();
+ if (msg != null && msg.indexOf("already in use") != -1) {
+ msg = "Port "
+ + port
+ + " is already is use; you probably still have another session active";
+ } else {
+ msg = "Unable to start the embedded Tomcat server; double-check that your configuration is valid";
+ }
+ return msg;
+ }
+ }
+
+ // Stop the embedded Tomcat server.
+ //
+ public static synchronized void stop() {
+ if (sTomcat != null) {
+ try {
+ sTomcat.catEmbedded.stop();
+ } catch (LifecycleException e) {
+ // There's nothing we can really do about this and the logger is
+ // gone in many scenarios, so we just ignore it.
+ //
+ } finally {
+ sTomcat = null;
+ }
+ }
+ }
+
+ /**
+ * Returns what local port the Tomcat connector is running on.
+ *
+ * When starting Tomcat with port 0 (i.e. choose an open port), there is just
+ * no way to figure out what port it actually chose. So we're using pure
+ * hackery to steal the port via reflection. The only works because we bundle
+ * Tomcat with GWT and know exactly what version it is.
+ */
+ private static int computeLocalPort(Connector connector) {
+ Throwable caught = null;
+ try {
+ Field phField = CoyoteConnector.class.getDeclaredField("protocolHandler");
+ phField.setAccessible(true);
+ Object protocolHandler = phField.get(connector);
+
+ Field epField = protocolHandler.getClass().getDeclaredField("ep");
+ epField.setAccessible(true);
+ Object endPoint = epField.get(protocolHandler);
+
+ Field ssField = endPoint.getClass().getDeclaredField("serverSocket");
+ ssField.setAccessible(true);
+ ServerSocket serverSocket = (ServerSocket) ssField.get(endPoint);
+
+ return serverSocket.getLocalPort();
+ } catch (SecurityException e) {
+ caught = e;
+ } catch (NoSuchFieldException e) {
+ caught = e;
+ } catch (IllegalArgumentException e) {
+ caught = e;
+ } catch (IllegalAccessException e) {
+ caught = e;
+ }
+ throw new RuntimeException(
+ "Failed to retrieve the startup port from Embedded Tomcat", caught);
+ }
+
+ private Embedded catEmbedded;
+
+ private Engine catEngine;
+
+ private StandardHost catHost = null;
+
+ private int port;
+
+ private final TreeLogger startupBranchLogger;
+
+ private EmbeddedTomcatServer(final TreeLogger topLogger, int listeningPort,
+ final WorkDirs workDirs, final boolean shouldAutoGenerateResources)
+ throws LifecycleException {
+ if (topLogger == null) {
+ throw new NullPointerException("No logger specified");
+ }
+
+ final TreeLogger logger = topLogger.branch(TreeLogger.INFO,
+ "Starting HTTP on port " + listeningPort, null);
+
+ startupBranchLogger = logger;
+
+ // Make myself the one static instance.
+ // NOTE: there is only one small implementation reason that this has
+ // to be a singleton, which is that the commons logger LogFactory insists
+ // on creating your logger class which must have a constructor with
+ // exactly one String argument, and since we want LoggerAdapter to delegate
+ // to the logger instance available through instance host, there is no
+ // way I can think of to delegate without accessing a static field.
+ // An inner class is almost right, except there's no outer instance.
+ //
+ sTomcat = this;
+
+ // Assume the working directory is simply the user's current directory.
+ //
+ File topWorkDir = new File(System.getProperty("user.dir"));
+
+ // Tell Tomcat its base directory so that it won't complain.
+ //
+ String catBase = System.getProperty("catalina.base");
+ if (catBase == null) {
+ // we (briefly) supported catalina.base.create, so let's not cut support
+ // until the deprecated sunset
+ catBase = System.getProperty("catalina.base.create");
+ if (catBase != null) {
+ logger.log(TreeLogger.WARN, "catalina.base.create is deprecated. " +
+ "Use catalina.base, and it will be created if necessary.");
+ topWorkDir = new File(catBase);
+ }
+ catBase = generateDefaultCatalinaBase(logger, topWorkDir);
+ System.setProperty("catalina.base", catBase);
+ }
+
+ // Some debug messages for ourselves.
+ //
+ if (logger.isLoggable(TreeLogger.DEBUG)) {
+ logger.log(TreeLogger.DEBUG, "catalina.base = " + catBase, null);
+ }
+
+ // Set up the logger that will be returned by the Commons logging factory.
+ //
+ String adapterClassName = CommonsLoggerAdapter.class.getName();
+ System.setProperty("org.apache.commons.logging.Log", adapterClassName);
+
+ // And set up an adapter that will work with the Catalina logger family.
+ //
+ Logger catalinaLogger = new CatalinaLoggerAdapter(topLogger);
+
+ // Create an embedded server.
+ //
+ catEmbedded = new Embedded();
+ catEmbedded.setDebug(0);
+ catEmbedded.setLogger(catalinaLogger);
+
+ // The embedded engine is called "gwt".
+ //
+ catEngine = catEmbedded.createEngine();
+ catEngine.setName("gwt");
+ catEngine.setDefaultHost("localhost");
+ catEngine.setParentClassLoader(this.getClass().getClassLoader());
+
+ // It answers localhost requests.
+ //
+ // String appBase = fCatalinaBaseDir.getAbsolutePath();
+ String appBase = catBase + "/webapps";
+ catHost = (StandardHost) catEmbedded.createHost("localhost", appBase);
+
+ // Hook up a host config to search for and pull in webapps.
+ //
+ HostConfig hostConfig = new HostConfig();
+ catHost.addLifecycleListener(hostConfig);
+
+ // Hook pre-install events so that we can add attributes to allow loaded
+ // instances to find their development instance host.
+ //
+ catHost.addContainerListener(new ContainerListener() {
+ public void containerEvent(ContainerEvent event) {
+ if (StandardHost.PRE_INSTALL_EVENT.equals(event.getType())) {
+ StandardContext webapp = (StandardContext) event.getData();
+ publishShellLoggerAttribute(logger, topLogger, webapp);
+ publishShellWorkDirsAttribute(logger, workDirs, webapp);
+ publishShouldAutoGenerateResourcesAttribute(logger,
+ shouldAutoGenerateResources, webapp);
+ }
+ }
+ });
+
+ // Tell the engine about the host.
+ //
+ catEngine.addChild(catHost);
+ catEngine.setDefaultHost(catHost.getName());
+
+ // Tell the embedded manager about the engine.
+ //
+ catEmbedded.addEngine(catEngine);
+ InetAddress nullAddr = null;
+ Connector connector = catEmbedded.createConnector(nullAddr, listeningPort,
+ false);
+ catEmbedded.addConnector(connector);
+
+ // start up!
+ catEmbedded.start();
+ port = computeLocalPort(connector);
+
+ if (port != listeningPort) {
+ if (logger.isLoggable(TreeLogger.INFO)) {
+ logger.log(TreeLogger.INFO, "HTTP listening on port " + port, null);
+ }
+ }
+ }
+
+ public TreeLogger getLogger() {
+ return startupBranchLogger;
+ }
+
+ /*
+ * Assumes that the leaf is a file (not a directory).
+ */
+ private void copyFileNoOverwrite(TreeLogger logger, String srcResName,
+ Resource srcRes, File catBase) {
+
+ File dest = new File(catBase, srcResName);
+ try {
+ // Only copy if src is newer than desc.
+ long srcLastModified = srcRes.getLastModified();
+ long dstLastModified = dest.lastModified();
+
+ if (srcLastModified < dstLastModified) {
+ // Don't copy over it.
+ if (logger.isLoggable(TreeLogger.SPAM)) {
+ logger.log(TreeLogger.SPAM, "Source is older than existing: "
+ + dest.getAbsolutePath(), null);
+ }
+ return;
+ } else if (srcLastModified == dstLastModified) {
+ // Exact same time; quietly don't overwrite.
+ return;
+ } else if (dest.exists()) {
+ // Warn about the overwrite
+ logger.log(TreeLogger.WARN, "Overwriting existing file '"
+ + dest.getAbsolutePath() + "' with '" + srcRes.getLocation()
+ + "', which has a newer timestamp");
+ }
+
+ // Make dest directories as required.
+ File destParent = dest.getParentFile();
+ if (destParent != null) {
+ // No need to check mkdirs result because IOException later anyway.
+ destParent.mkdirs();
+ }
+
+ Util.copy(srcRes.openContents(), new FileOutputStream(dest));
+ dest.setLastModified(srcLastModified);
+
+ if (logger.isLoggable(TreeLogger.TRACE)) {
+ logger.log(TreeLogger.TRACE, "Wrote: " + dest.getAbsolutePath(), null);
+ }
+ } catch (IOException e) {
+ logger.log(TreeLogger.WARN, "Failed to write: " + dest.getAbsolutePath(),
+ e);
+ }
+ }
+
+ /**
+ * Extracts a valid catalina base instance from the classpath. Does not
+ * overwrite any existing files.
+ */
+ private String generateDefaultCatalinaBase(TreeLogger logger, File workDir) {
+ logger = logger.branch(
+ TreeLogger.TRACE,
+ "Property 'catalina.base' not specified; checking for a standard catalina base image instead",
+ null);
+
+ // Recursively copies out files and directories
+ String tomcatEtcDir = "com/google/gwt/dev/etc/tomcat/";
+ Map<String, Resource> resourceMap = null;
+ Throwable caught = null;
+ try {
+ resourceMap = getResourcesFor(logger, tomcatEtcDir);
+ } catch (URISyntaxException e) {
+ caught = e;
+ } catch (IOException e) {
+ caught = e;
+ }
+
+ File catBase = new File(workDir, "tomcat");
+ if (resourceMap == null || resourceMap.isEmpty()) {
+ logger.log(TreeLogger.WARN, "Could not find " + tomcatEtcDir, caught);
+ } else {
+ for (Entry<String, Resource> entry : resourceMap.entrySet()) {
+ copyFileNoOverwrite(logger, entry.getKey(), entry.getValue(), catBase);
+ }
+ }
+
+ return catBase.getAbsolutePath();
+ }
+
+ /**
+ * Hacky, but fast.
+ */
+ private Map<String, Resource> getResourcesFor(TreeLogger logger,
+ String tomcatEtcDir) throws URISyntaxException, IOException {
+ ClassLoader contextClassLoader = this.getClass().getClassLoader();
+ URL url = contextClassLoader.getResource(tomcatEtcDir);
+ if (url == null) {
+ return null;
+ }
+ String prefix = "";
+ String urlString = url.toString();
+ if (urlString.startsWith("jar:")) {
+ assert urlString.toLowerCase(Locale.ENGLISH).contains(".jar!/"
+ + tomcatEtcDir);
+ urlString = urlString.substring(4, urlString.indexOf('!'));
+ url = new URL(urlString);
+ prefix = tomcatEtcDir;
+ } else if (urlString.startsWith("zip:")) {
+ assert urlString.toLowerCase(Locale.ENGLISH).contains(".zip!/"
+ + tomcatEtcDir);
+ urlString = urlString.substring(4, urlString.indexOf('!'));
+ url = new URL(urlString);
+ prefix = tomcatEtcDir;
+ }
+ ClassPathEntry entry = ResourceOracleImpl.createEntryForUrl(logger, url);
+ assert (entry != null);
+ ResourceOracleImpl resourceOracle = new ResourceOracleImpl(
+ Collections.singletonList(entry));
+ PathPrefixSet pathPrefixSet = new PathPrefixSet();
+ PathPrefix pathPrefix = new PathPrefix(prefix, null, true);
+ pathPrefixSet.add(pathPrefix);
+ resourceOracle.setPathPrefixes(pathPrefixSet);
+ ResourceOracleImpl.refresh(logger, resourceOracle);
+ Map<String, Resource> resourceMap = resourceOracle.getResourceMap();
+ return resourceMap;
+ }
+
+ private void publishAttributeToWebApp(TreeLogger logger,
+ StandardContext webapp, String attrName, Object attrValue) {
+ if (logger.isLoggable(TreeLogger.TRACE)) {
+ logger.log(TreeLogger.TRACE, "Adding attribute '" + attrName
+ + "' to web app '" + webapp.getName() + "'", null);
+ }
+ webapp.getServletContext().setAttribute(attrName, attrValue);
+ }
+
+ /**
+ * Publish the shell's tree logger as an attribute. This attribute is used to
+ * find the logger out of the thin air within the shell servlet.
+ */
+ private void publishShellLoggerAttribute(TreeLogger logger,
+ TreeLogger loggerToPublish, StandardContext webapp) {
+ final String attr = "com.google.gwt.dev.shell.logger";
+ publishAttributeToWebApp(logger, webapp, attr, loggerToPublish);
+ }
+
+ /**
+ * Publish the shell's work dir as an attribute. This attribute is used to
+ * find it out of the thin air within the shell servlet.
+ */
+ private void publishShellWorkDirsAttribute(TreeLogger logger,
+ WorkDirs workDirs, StandardContext webapp) {
+ final String attr = "com.google.gwt.dev.shell.workdirs";
+ publishAttributeToWebApp(logger, webapp, attr, workDirs);
+ }
+
+ /**
+ * Publish to the web app whether it should automatically generate resources.
+ */
+ private void publishShouldAutoGenerateResourcesAttribute(TreeLogger logger,
+ boolean shouldAutoGenerateResources, StandardContext webapp) {
+ publishAttributeToWebApp(logger, webapp,
+ "com.google.gwt.dev.shell.shouldAutoGenerateResources",
+ shouldAutoGenerateResources);
+ }
+}
diff --git a/dev/core/src/org/apache/COPYING b/dev/core/src/org/apache/COPYING
new file mode 100644
index 0000000..d9a10c0
--- /dev/null
+++ b/dev/core/src/org/apache/COPYING
@@ -0,0 +1,176 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
diff --git a/dev/core/src/org/apache/catalina/loader/WebappClassLoader.java b/dev/core/src/org/apache/catalina/loader/WebappClassLoader.java
new file mode 100644
index 0000000..f941fad
--- /dev/null
+++ b/dev/core/src/org/apache/catalina/loader/WebappClassLoader.java
@@ -0,0 +1,2169 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ *
+ * 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.
+ */
+// Modified by Google.
+
+package org.apache.catalina.loader;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FilePermission;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.AccessControlException;
+import java.security.AccessController;
+import java.security.CodeSource;
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.security.Policy;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Vector;
+import java.util.jar.Attributes;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+import java.util.jar.Attributes.Name;
+
+import javax.naming.NameClassPair;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.DirContext;
+
+import org.apache.catalina.Lifecycle;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleListener;
+import org.apache.catalina.util.StringManager;
+import org.apache.naming.JndiPermission;
+import org.apache.naming.resources.Resource;
+import org.apache.naming.resources.ResourceAttributes;
+import org.apache.tomcat.util.compat.JdkCompat;
+
+/**
+ * Specialized web application class loader.
+ * <p>
+ * This class loader is a full reimplementation of the
+ * <code>URLClassLoader</code> from the JDK. It is desinged to be fully
+ * compatible with a normal <code>URLClassLoader</code>, although its internal
+ * behavior may be completely different.
+ * <p>
+ * <strong>IMPLEMENTATION NOTE</strong> - This class loader faithfully follows
+ * the delegation model recommended in the specification. The system class
+ * loader will be queried first, then the local repositories, and only then
+ * delegation to the parent class loader will occur. This allows the web
+ * application to override any shared class except the classes from J2SE.
+ * Special handling is provided from the JAXP XML parser interfaces, the JNDI
+ * interfaces, and the classes from the servlet API, which are never loaded
+ * from the webapp repository.
+ * <p>
+ * <strong>IMPLEMENTATION NOTE</strong> - Due to limitations in Jasper
+ * compilation technology, any repository which contains classes from
+ * the servlet API will be ignored by the class loader.
+ * <p>
+ * <strong>IMPLEMENTATION NOTE</strong> - The class loader generates source
+ * URLs which include the full JAR URL when a class is loaded from a JAR file,
+ * which allows setting security permission at the class level, even when a
+ * class is contained inside a JAR.
+ * <p>
+ * <strong>IMPLEMENTATION NOTE</strong> - Local repositories are searched in
+ * the order they are added via the initial constructor and/or any subsequent
+ * calls to <code>addRepository()</code> or <code>addJar()</code>.
+ * <p>
+ * <strong>IMPLEMENTATION NOTE</strong> - No check for sealing violations or
+ * security is made unless a security manager is present.
+ *
+ * @author Remy Maucherat
+ * @author Craig R. McClanahan
+ * @version $Revision: 1.34 $ $Date: 2004/05/26 15:47:40 $
+ */
+public class WebappClassLoader
+ extends URLClassLoader
+ implements Reloader, Lifecycle
+ {
+
+ private static org.apache.commons.logging.Log log=
+ org.apache.commons.logging.LogFactory.getLog( WebappClassLoader.class );
+
+ protected class PrivilegedFindResource
+ implements PrivilegedAction {
+
+ private File file;
+ private String path;
+
+ PrivilegedFindResource(File file, String path) {
+ this.file = file;
+ this.path = path;
+ }
+
+ public Object run() {
+ return findResourceInternal(file, path);
+ }
+
+ }
+
+
+ // ------------------------------------------------------- Static Variables
+
+
+ /**
+ * The set of trigger classes that will cause a proposed repository not
+ * to be added if this class is visible to the class loader that loaded
+ * this factory class. Typically, trigger classes will be listed for
+ * components that have been integrated into the JDK for later versions,
+ * but where the corresponding JAR files are required to run on
+ * earlier versions.
+ */
+ private static final String[] triggers = {
+ "javax.servlet.Servlet" // Servlet API
+ };
+
+ /**
+ * Jdk Compatibility Support.
+ */
+ private static JdkCompat jdkCompat = JdkCompat.getJdkCompat();
+
+ /**
+ * Set of package names which are not allowed to be loaded from a webapp
+ * class loader without delegating first.
+ */
+ private static final String[] packageTriggers = {
+ "javax", // Java extensions
+ "org.xml.sax", // SAX 1 & 2
+ "org.w3c.dom", // DOM 1 & 2
+ "org.apache.xerces", // Xerces 1 & 2
+ "org.apache.xalan" // Xalan
+ };
+
+
+ /**
+ * The string manager for this package.
+ */
+ protected static final StringManager sm =
+ StringManager.getManager(Constants.Package);
+
+
+ // ----------------------------------------------------------- Constructors
+
+
+ /**
+ * Construct a new ClassLoader with no defined repositories and no
+ * parent ClassLoader.
+ */
+ public WebappClassLoader() {
+
+ super(new URL[0]);
+ this.parent = getParent();
+ system = getSystemClassLoader();
+ securityManager = System.getSecurityManager();
+
+ if (securityManager != null) {
+ refreshPolicy();
+ }
+
+ }
+
+
+ /**
+ * Construct a new ClassLoader with no defined repositories and no
+ * parent ClassLoader.
+ */
+ public WebappClassLoader(ClassLoader parent) {
+
+ super(new URL[0], parent);
+
+ this.parent = getParent();
+
+ system = getSystemClassLoader();
+ securityManager = System.getSecurityManager();
+
+ if (securityManager != null) {
+ refreshPolicy();
+ }
+ }
+
+
+ // ----------------------------------------------------- Instance Variables
+
+
+ /**
+ * Associated directory context giving access to the resources in this
+ * webapp.
+ */
+ protected DirContext resources = null;
+
+
+ /**
+ * The cache of ResourceEntry for classes and resources we have loaded,
+ * keyed by resource name.
+ */
+ protected HashMap resourceEntries = new HashMap();
+
+
+ /**
+ * The list of not found resources.
+ */
+ protected HashMap notFoundResources = new HashMap();
+
+
+ /**
+ * The debugging detail level of this component.
+ */
+ protected int debug = 0;
+
+
+ /**
+ * Should this class loader delegate to the parent class loader
+ * <strong>before</strong> searching its own repositories (i.e. the
+ * usual Java2 delegation model)? If set to <code>false</code>,
+ * this class loader will search its own repositories first, and
+ * delegate to the parent only if the class or resource is not
+ * found locally.
+ */
+ protected boolean delegate = false;
+
+
+ /**
+ * Last time a JAR was accessed.
+ */
+ protected long lastJarAccessed = 0L;
+
+
+ /**
+ * The list of local repositories, in the order they should be searched
+ * for locally loaded classes or resources.
+ */
+ protected String[] repositories = new String[0];
+
+
+ /**
+ * Repositories URLs, used to cache the result of getURLs.
+ */
+ protected URL[] repositoryURLs = null;
+
+
+ /**
+ * Repositories translated as path in the work directory (for Jasper
+ * originally), but which is used to generate fake URLs should getURLs be
+ * called.
+ */
+ protected File[] files = new File[0];
+
+
+ /**
+ * The list of JARs, in the order they should be searched
+ * for locally loaded classes or resources.
+ */
+ protected JarFile[] jarFiles = new JarFile[0];
+
+
+ /**
+ * The list of JARs, in the order they should be searched
+ * for locally loaded classes or resources.
+ */
+ protected File[] jarRealFiles = new File[0];
+
+
+ /**
+ * The path which will be monitored for added Jar files.
+ */
+ protected String jarPath = null;
+
+
+ /**
+ * The list of JARs, in the order they should be searched
+ * for locally loaded classes or resources.
+ */
+ protected String[] jarNames = new String[0];
+
+
+ /**
+ * The list of JARs last modified dates, in the order they should be
+ * searched for locally loaded classes or resources.
+ */
+ protected long[] lastModifiedDates = new long[0];
+
+
+ /**
+ * The list of resources which should be checked when checking for
+ * modifications.
+ */
+ protected String[] paths = new String[0];
+
+
+ /**
+ * A list of read File and Jndi Permission's required if this loader
+ * is for a web application context.
+ */
+ private ArrayList permissionList = new ArrayList();
+
+
+ /**
+ * Path where resources loaded from JARs will be extracted.
+ */
+ private File loaderDir = null;
+
+
+ /**
+ * The PermissionCollection for each CodeSource for a web
+ * application context.
+ */
+ private HashMap loaderPC = new HashMap();
+
+
+ /**
+ * Instance of the SecurityManager installed.
+ */
+ private SecurityManager securityManager = null;
+
+
+ /**
+ * The parent class loader.
+ */
+ private ClassLoader parent = null;
+
+
+ /**
+ * The system class loader.
+ */
+ private ClassLoader system = null;
+
+
+ /**
+ * Has this component been started?
+ */
+ protected boolean started = false;
+
+
+ /**
+ * Has external repositories.
+ */
+ protected boolean hasExternalRepositories = false;
+
+
+ /**
+ * All permission.
+ */
+ private Permission allPermission = new java.security.AllPermission();
+
+
+ // ------------------------------------------------------------- Properties
+
+
+ /**
+ * Get associated resources.
+ */
+ public DirContext getResources() {
+
+ return this.resources;
+
+ }
+
+
+ /**
+ * Set associated resources.
+ */
+ public void setResources(DirContext resources) {
+
+ this.resources = resources;
+
+ }
+
+
+ /**
+ * Return the debugging detail level for this component.
+ */
+ public int getDebug() {
+
+ return (this.debug);
+
+ }
+
+
+ /**
+ * Set the debugging detail level for this component.
+ *
+ * @param debug The new debugging detail level
+ */
+ public void setDebug(int debug) {
+
+ this.debug = debug;
+
+ }
+
+
+ /**
+ * Return the "delegate first" flag for this class loader.
+ */
+ public boolean getDelegate() {
+
+ return (this.delegate);
+
+ }
+
+
+ /**
+ * Set the "delegate first" flag for this class loader.
+ *
+ * @param delegate The new "delegate first" flag
+ */
+ public void setDelegate(boolean delegate) {
+
+ this.delegate = delegate;
+
+ }
+
+
+ /**
+ * If there is a Java SecurityManager create a read FilePermission
+ * or JndiPermission for the file directory path.
+ *
+ * @param path file directory path
+ */
+ public void addPermission(String path) {
+ if (path == null) {
+ return;
+ }
+
+ if (securityManager != null) {
+ Permission permission = null;
+ if( path.startsWith("jndi:") || path.startsWith("jar:jndi:") ) {
+ if (!path.endsWith("/")) {
+ path = path + "/";
+ }
+ permission = new JndiPermission(path + "*");
+ addPermission(permission);
+ } else {
+ if (!path.endsWith(File.separator)) {
+ permission = new FilePermission(path, "read");
+ addPermission(permission);
+ path = path + File.separator;
+ }
+ permission = new FilePermission(path + "-", "read");
+ addPermission(permission);
+ }
+ }
+ }
+
+
+ /**
+ * If there is a Java SecurityManager create a read FilePermission
+ * or JndiPermission for URL.
+ *
+ * @param url URL for a file or directory on local system
+ */
+ public void addPermission(URL url) {
+ if (url != null) {
+ addPermission(url.toString());
+ }
+ }
+
+
+ /**
+ * If there is a Java SecurityManager create a Permission.
+ *
+ * @param url URL for a file or directory on local system
+ */
+ public void addPermission(Permission permission) {
+ if ((securityManager != null) && (permission != null)) {
+ permissionList.add(permission);
+ }
+ }
+
+
+ /**
+ * Return the JAR path.
+ */
+ public String getJarPath() {
+
+ return this.jarPath;
+
+ }
+
+
+ /**
+ * Change the Jar path.
+ */
+ public void setJarPath(String jarPath) {
+
+ this.jarPath = jarPath;
+
+ }
+
+
+ /**
+ * Change the work directory.
+ */
+ public void setWorkDir(File workDir) {
+ this.loaderDir = new File(workDir, "loader");
+ }
+
+
+ // ------------------------------------------------------- Reloader Methods
+
+
+ /**
+ * Add a new repository to the set of places this ClassLoader can look for
+ * classes to be loaded.
+ *
+ * @param repository Name of a source of classes to be loaded, such as a
+ * directory pathname, a JAR file pathname, or a ZIP file pathname
+ *
+ * @exception IllegalArgumentException if the specified repository is
+ * invalid or does not exist
+ */
+ public void addRepository(String repository) {
+
+ // Ignore any of the standard repositories, as they are set up using
+ // either addJar or addRepository
+ if (repository.startsWith("/WEB-INF/lib")
+ || repository.startsWith("/WEB-INF/classes"))
+ return;
+
+ // Add this repository to our underlying class loader
+ try {
+ URL url = new URL(repository);
+ super.addURL(url);
+ hasExternalRepositories = true;
+ repositoryURLs = null;
+ } catch (MalformedURLException e) {
+ IllegalArgumentException iae = new IllegalArgumentException
+ ("Invalid repository: " + repository);
+ jdkCompat.chainException(iae, e);
+ throw iae;
+ }
+
+ }
+
+
+ /**
+ * Add a new repository to the set of places this ClassLoader can look for
+ * classes to be loaded.
+ *
+ * @param repository Name of a source of classes to be loaded, such as a
+ * directory pathname, a JAR file pathname, or a ZIP file pathname
+ *
+ * @exception IllegalArgumentException if the specified repository is
+ * invalid or does not exist
+ */
+ synchronized void addRepository(String repository, File file) {
+
+ // Note : There should be only one (of course), but I think we should
+ // keep this a bit generic
+
+ if (repository == null)
+ return;
+
+ if (log.isDebugEnabled())
+ log.debug("addRepository(" + repository + ")");
+
+ int i;
+
+ // Add this repository to our internal list
+ String[] result = new String[repositories.length + 1];
+ for (i = 0; i < repositories.length; i++) {
+ result[i] = repositories[i];
+ }
+ result[repositories.length] = repository;
+ repositories = result;
+
+ // Add the file to the list
+ File[] result2 = new File[files.length + 1];
+ for (i = 0; i < files.length; i++) {
+ result2[i] = files[i];
+ }
+ result2[files.length] = file;
+ files = result2;
+
+ }
+
+
+ synchronized void addJar(String jar, JarFile jarFile, File file)
+ throws IOException {
+
+ if (jar == null)
+ return;
+ if (jarFile == null)
+ return;
+ if (file == null)
+ return;
+
+ if (log.isDebugEnabled())
+ log.debug("addJar(" + jar + ")");
+
+ int i;
+
+ if ((jarPath != null) && (jar.startsWith(jarPath))) {
+
+ String jarName = jar.substring(jarPath.length());
+ while (jarName.startsWith("/"))
+ jarName = jarName.substring(1);
+
+ String[] result = new String[jarNames.length + 1];
+ for (i = 0; i < jarNames.length; i++) {
+ result[i] = jarNames[i];
+ }
+ result[jarNames.length] = jarName;
+ jarNames = result;
+
+ }
+
+ try {
+
+ // Register the JAR for tracking
+
+ long lastModified =
+ ((ResourceAttributes) resources.getAttributes(jar))
+ .getLastModified();
+
+ String[] result = new String[paths.length + 1];
+ for (i = 0; i < paths.length; i++) {
+ result[i] = paths[i];
+ }
+ result[paths.length] = jar;
+ paths = result;
+
+ long[] result3 = new long[lastModifiedDates.length + 1];
+ for (i = 0; i < lastModifiedDates.length; i++) {
+ result3[i] = lastModifiedDates[i];
+ }
+ result3[lastModifiedDates.length] = lastModified;
+ lastModifiedDates = result3;
+
+ } catch (NamingException e) {
+ // Ignore
+ }
+
+ // If the JAR currently contains invalid classes, don't actually use it
+ // for classloading
+ if (!validateJarFile(file))
+ return;
+
+ JarFile[] result2 = new JarFile[jarFiles.length + 1];
+ for (i = 0; i < jarFiles.length; i++) {
+ result2[i] = jarFiles[i];
+ }
+ result2[jarFiles.length] = jarFile;
+ jarFiles = result2;
+
+ // Add the file to the list
+ File[] result4 = new File[jarRealFiles.length + 1];
+ for (i = 0; i < jarRealFiles.length; i++) {
+ result4[i] = jarRealFiles[i];
+ }
+ result4[jarRealFiles.length] = file;
+ jarRealFiles = result4;
+ }
+
+
+ /**
+ * Return a String array of the current repositories for this class
+ * loader. If there are no repositories, a zero-length array is
+ * returned.For security reason, returns a clone of the Array (since
+ * String are immutable).
+ */
+ public String[] findRepositories() {
+
+ return ((String[])repositories.clone());
+
+ }
+
+
+ /**
+ * Have one or more classes or resources been modified so that a reload
+ * is appropriate?
+ */
+ public boolean modified() {
+
+ if (log.isDebugEnabled())
+ log.debug("modified()");
+
+ // Checking for modified loaded resources
+ int length = paths.length;
+
+ // A rare race condition can occur in the updates of the two arrays
+ // It's totally ok if the latest class added is not checked (it will
+ // be checked the next time
+ int length2 = lastModifiedDates.length;
+ if (length > length2)
+ length = length2;
+
+ for (int i = 0; i < length; i++) {
+ try {
+ long lastModified =
+ ((ResourceAttributes) resources.getAttributes(paths[i]))
+ .getLastModified();
+ if (lastModified != lastModifiedDates[i]) {
+ if( log.isDebugEnabled() )
+ log.debug(" Resource '" + paths[i]
+ + "' was modified; Date is now: "
+ + new java.util.Date(lastModified) + " Was: "
+ + new java.util.Date(lastModifiedDates[i]));
+ return (true);
+ }
+ } catch (NamingException e) {
+ log.error(" Resource '" + paths[i] + "' is missing");
+ return (true);
+ }
+ }
+
+ length = jarNames.length;
+
+ // Check if JARs have been added or removed
+ if (getJarPath() != null) {
+
+ try {
+ NamingEnumeration enum_ = resources.listBindings(getJarPath());
+ int i = 0;
+ while (enum_.hasMoreElements() && (i < length)) {
+ NameClassPair ncPair = (NameClassPair) enum_.nextElement();
+ String name = ncPair.getName();
+ // Ignore non JARs present in the lib folder
+ if (!name.endsWith(".jar"))
+ continue;
+ if (!name.equals(jarNames[i])) {
+ // Missing JAR
+ log.info(" Additional JARs have been added : '"
+ + name + "'");
+ return (true);
+ }
+ i++;
+ }
+ if (enum_.hasMoreElements()) {
+ while (enum_.hasMoreElements()) {
+ NameClassPair ncPair =
+ (NameClassPair) enum_.nextElement();
+ String name = ncPair.getName();
+ // Additional non-JAR files are allowed
+ if (name.endsWith(".jar")) {
+ // There was more JARs
+ log.info(" Additional JARs have been added");
+ return (true);
+ }
+ }
+ } else if (i < jarNames.length) {
+ // There was less JARs
+ log.info(" Additional JARs have been added");
+ return (true);
+ }
+ } catch (NamingException e) {
+ if (log.isDebugEnabled())
+ log.debug(" Failed tracking modifications of '"
+ + getJarPath() + "'");
+ } catch (ClassCastException e) {
+ log.error(" Failed tracking modifications of '"
+ + getJarPath() + "' : " + e.getMessage());
+ }
+
+ }
+
+ // No classes have been modified
+ return (false);
+
+ }
+
+
+ /**
+ * Render a String representation of this object.
+ */
+ public String toString() {
+
+ StringBuffer sb = new StringBuffer("WebappClassLoader\r\n");
+ sb.append(" delegate: ");
+ sb.append(delegate);
+ sb.append("\r\n");
+ sb.append(" repositories:\r\n");
+ if (repositories != null) {
+ for (int i = 0; i < repositories.length; i++) {
+ sb.append(" ");
+ sb.append(repositories[i]);
+ sb.append("\r\n");
+ }
+ }
+ if (this.parent != null) {
+ sb.append("----------> Parent Classloader:\r\n");
+ sb.append(this.parent.toString());
+ sb.append("\r\n");
+ }
+ return (sb.toString());
+
+ }
+
+
+ // ---------------------------------------------------- ClassLoader Methods
+
+
+ /**
+ * Add the specified URL to the classloader.
+ */
+ protected void addURL(URL url) {
+ super.addURL(url);
+ hasExternalRepositories = true;
+ repositoryURLs = null;
+ }
+
+
+ /**
+ * Find the specified class in our local repositories, if possible. If
+ * not found, throw <code>ClassNotFoundException</code>.
+ *
+ * @param name Name of the class to be loaded
+ *
+ * @exception ClassNotFoundException if the class was not found
+ */
+ public Class findClass(String name) throws ClassNotFoundException {
+
+ if (log.isDebugEnabled())
+ log.debug(" findClass(" + name + ")");
+
+ // (1) Permission to define this class when using a SecurityManager
+ if (securityManager != null) {
+ int i = name.lastIndexOf('.');
+ if (i >= 0) {
+ try {
+ if (log.isTraceEnabled())
+ log.trace(" securityManager.checkPackageDefinition");
+ securityManager.checkPackageDefinition(name.substring(0,i));
+ } catch (Exception se) {
+ if (log.isTraceEnabled())
+ log.trace(" -->Exception-->ClassNotFoundException", se);
+ throw new ClassNotFoundException(name, se);
+ }
+ }
+ }
+
+ // Ask our superclass to locate this class, if possible
+ // (throws ClassNotFoundException if it is not found)
+ Class clazz = null;
+ try {
+ if (log.isTraceEnabled())
+ log.trace(" findClassInternal(" + name + ")");
+ try {
+ clazz = findClassInternal(name);
+ } catch(ClassNotFoundException cnfe) {
+ if (!hasExternalRepositories) {
+ throw cnfe;
+ }
+ } catch(AccessControlException ace) {
+ throw new ClassNotFoundException(name, ace);
+ } catch (RuntimeException e) {
+ if (log.isTraceEnabled())
+ log.trace(" -->RuntimeException Rethrown", e);
+ throw e;
+ }
+ if ((clazz == null) && hasExternalRepositories) {
+ try {
+ clazz = super.findClass(name);
+ } catch(AccessControlException ace) {
+ throw new ClassNotFoundException(name, ace);
+ } catch (RuntimeException e) {
+ if (log.isTraceEnabled())
+ log.trace(" -->RuntimeException Rethrown", e);
+ throw e;
+ }
+ }
+ if (clazz == null) {
+ if (log.isDebugEnabled())
+ log.debug(" --> Returning ClassNotFoundException");
+ throw new ClassNotFoundException(name);
+ }
+ } catch (ClassNotFoundException e) {
+ if (log.isTraceEnabled())
+ log.trace(" --> Passing on ClassNotFoundException");
+ throw e;
+ }
+
+ // Return the class we have located
+ if (log.isTraceEnabled())
+ log.debug(" Returning class " + clazz);
+ if ((log.isTraceEnabled()) && (clazz != null))
+ log.debug(" Loaded by " + clazz.getClassLoader());
+ return (clazz);
+
+ }
+
+
+ /**
+ * Find the specified resource in our local repository, and return a
+ * <code>URL</code> refering to it, or <code>null</code> if this resource
+ * cannot be found.
+ *
+ * @param name Name of the resource to be found
+ */
+ public URL findResource(final String name) {
+
+ if (log.isDebugEnabled())
+ log.debug(" findResource(" + name + ")");
+
+ URL url = null;
+
+ ResourceEntry entry = (ResourceEntry) resourceEntries.get(name);
+ if (entry == null) {
+ entry = findResourceInternal(name, name);
+ }
+ if (entry != null) {
+ url = entry.source;
+ }
+
+ if ((url == null) && hasExternalRepositories)
+ url = super.findResource(name);
+
+ if (log.isDebugEnabled()) {
+ if (url != null)
+ log.debug(" --> Returning '" + url.toString() + "'");
+ else
+ log.debug(" --> Resource not found, returning null");
+ }
+ return (url);
+
+ }
+
+
+ /**
+ * Return an enumeration of <code>URLs</code> representing all of the
+ * resources with the given name. If no resources with this name are
+ * found, return an empty enumeration.
+ *
+ * @param name Name of the resources to be found
+ *
+ * @exception IOException if an input/output error occurs
+ */
+ public Enumeration findResources(String name) throws IOException {
+
+ if (log.isDebugEnabled())
+ log.debug(" findResources(" + name + ")");
+
+ Vector result = new Vector();
+
+ int jarFilesLength = jarFiles.length;
+ int repositoriesLength = repositories.length;
+
+ int i;
+
+ // Looking at the repositories
+ for (i = 0; i < repositoriesLength; i++) {
+ try {
+ String fullPath = repositories[i] + name;
+ resources.lookup(fullPath);
+ // Note : Not getting an exception here means the resource was
+ // found
+ try {
+ result.addElement(getURI(new File(files[i], name)));
+ } catch (MalformedURLException e) {
+ // Ignore
+ }
+ } catch (NamingException e) {
+ }
+ }
+
+ // Looking at the JAR files
+ synchronized (jarFiles) {
+ openJARs();
+ for (i = 0; i < jarFilesLength; i++) {
+ JarEntry jarEntry = jarFiles[i].getJarEntry(name);
+ if (jarEntry != null) {
+ try {
+ String jarFakeUrl = getURI(jarRealFiles[i]).toString();
+ jarFakeUrl = "jar:" + jarFakeUrl + "!/" + name;
+ result.addElement(new URL(jarFakeUrl));
+ } catch (MalformedURLException e) {
+ // Ignore
+ }
+ }
+ }
+ }
+
+ // Adding the results of a call to the superclass
+ if (hasExternalRepositories) {
+
+ Enumeration otherResourcePaths = super.findResources(name);
+
+ while (otherResourcePaths.hasMoreElements()) {
+ result.addElement(otherResourcePaths.nextElement());
+ }
+
+ }
+
+ return result.elements();
+
+ }
+
+
+ /**
+ * Find the resource with the given name. A resource is some data
+ * (images, audio, text, etc.) that can be accessed by class code in a
+ * way that is independent of the location of the code. The name of a
+ * resource is a "/"-separated path name that identifies the resource.
+ * If the resource cannot be found, return <code>null</code>.
+ * <p>
+ * This method searches according to the following algorithm, returning
+ * as soon as it finds the appropriate URL. If the resource cannot be
+ * found, returns <code>null</code>.
+ * <ul>
+ * <li>If the <code>delegate</code> property is set to <code>true</code>,
+ * call the <code>getResource()</code> method of the parent class
+ * loader, if any.</li>
+ * <li>Call <code>findResource()</code> to find this resource in our
+ * locally defined repositories.</li>
+ * <li>Call the <code>getResource()</code> method of the parent class
+ * loader, if any.</li>
+ * </ul>
+ *
+ * @param name Name of the resource to return a URL for
+ */
+ public URL getResource(String name) {
+
+ if (log.isDebugEnabled())
+ log.debug("getResource(" + name + ")");
+ URL url = null;
+
+ // (1) Delegate to parent if requested
+ if (delegate) {
+ if (log.isDebugEnabled())
+ log.debug(" Delegating to parent classloader " + parent);
+ ClassLoader loader = parent;
+ if (loader == null)
+ loader = system;
+ url = loader.getResource(name);
+ if (url != null) {
+ if (log.isDebugEnabled())
+ log.debug(" --> Returning '" + url.toString() + "'");
+ return (url);
+ }
+ }
+
+ // (2) Search local repositories
+ url = findResource(name);
+ if (url != null) {
+ // Locating the repository for special handling in the case
+ // of a JAR
+ ResourceEntry entry = (ResourceEntry) resourceEntries.get(name);
+ try {
+ String repository = entry.codeBase.toString();
+ if ((repository.endsWith(".jar"))
+ && (!(name.endsWith(".class")))) {
+ // Copy binary content to the work directory if not present
+ File resourceFile = new File(loaderDir, name);
+ url = resourceFile.toURL();
+ }
+ } catch (Exception e) {
+ // Ignore
+ }
+ if (log.isDebugEnabled())
+ log.debug(" --> Returning '" + url.toString() + "'");
+ return (url);
+ }
+
+ // (3) Delegate to parent unconditionally if not already attempted
+ if( !delegate ) {
+ ClassLoader loader = parent;
+ if (loader == null)
+ loader = system;
+ url = loader.getResource(name);
+ if (url != null) {
+ if (log.isDebugEnabled())
+ log.debug(" --> Returning '" + url.toString() + "'");
+ return (url);
+ }
+ }
+
+ // (4) Resource was not found
+ if (log.isDebugEnabled())
+ log.debug(" --> Resource not found, returning null");
+ return (null);
+
+ }
+
+
+ /**
+ * Find the resource with the given name, and return an input stream
+ * that can be used for reading it. The search order is as described
+ * for <code>getResource()</code>, after checking to see if the resource
+ * data has been previously cached. If the resource cannot be found,
+ * return <code>null</code>.
+ *
+ * @param name Name of the resource to return an input stream for
+ */
+ public InputStream getResourceAsStream(String name) {
+
+ if (log.isDebugEnabled())
+ log.debug("getResourceAsStream(" + name + ")");
+ InputStream stream = null;
+
+ // (0) Check for a cached copy of this resource
+ stream = findLoadedResource(name);
+ if (stream != null) {
+ if (log.isDebugEnabled())
+ log.debug(" --> Returning stream from cache");
+ return (stream);
+ }
+
+ // (1) Delegate to parent if requested
+ if (delegate) {
+ if (log.isDebugEnabled())
+ log.debug(" Delegating to parent classloader " + parent);
+ ClassLoader loader = parent;
+ if (loader == null)
+ loader = system;
+ stream = loader.getResourceAsStream(name);
+ if (stream != null) {
+ // FIXME - cache???
+ if (log.isDebugEnabled())
+ log.debug(" --> Returning stream from parent");
+ return (stream);
+ }
+ }
+
+ // (2) Search local repositories
+ if (log.isDebugEnabled())
+ log.debug(" Searching local repositories");
+ URL url = findResource(name);
+ if (url != null) {
+ // FIXME - cache???
+ if (log.isDebugEnabled())
+ log.debug(" --> Returning stream from local");
+ stream = findLoadedResource(name);
+ try {
+ if (hasExternalRepositories && (stream == null))
+ stream = url.openStream();
+ } catch (IOException e) {
+ ; // Ignore
+ }
+ if (stream != null)
+ return (stream);
+ }
+
+ // (3) Delegate to parent unconditionally
+ if (!delegate) {
+ if (log.isDebugEnabled())
+ log.debug(" Delegating to parent classloader unconditionally " + parent);
+ ClassLoader loader = parent;
+ if (loader == null)
+ loader = system;
+ stream = loader.getResourceAsStream(name);
+ if (stream != null) {
+ // FIXME - cache???
+ if (log.isDebugEnabled())
+ log.debug(" --> Returning stream from parent");
+ return (stream);
+ }
+ }
+
+ // (4) Resource was not found
+ if (log.isDebugEnabled())
+ log.debug(" --> Resource not found, returning null");
+ return (null);
+
+ }
+
+
+ /**
+ * Load the class with the specified name. This method searches for
+ * classes in the same manner as <code>loadClass(String, boolean)</code>
+ * with <code>false</code> as the second argument.
+ *
+ * @param name Name of the class to be loaded
+ *
+ * @exception ClassNotFoundException if the class was not found
+ */
+ public Class loadClass(String name) throws ClassNotFoundException {
+
+ return (loadClass(name, false));
+
+ }
+
+
+ /**
+ * Load the class with the specified name, searching using the following
+ * algorithm until it finds and returns the class. If the class cannot
+ * be found, returns <code>ClassNotFoundException</code>.
+ * <ul>
+ * <li>Call <code>findLoadedClass(String)</code> to check if the
+ * class has already been loaded. If it has, the same
+ * <code>Class</code> object is returned.</li>
+ * <li>If the <code>delegate</code> property is set to <code>true</code>,
+ * call the <code>loadClass()</code> method of the parent class
+ * loader, if any.</li>
+ * <li>Call <code>findClass()</code> to find this class in our locally
+ * defined repositories.</li>
+ * <li>Call the <code>loadClass()</code> method of our parent
+ * class loader, if any.</li>
+ * </ul>
+ * If the class was found using the above steps, and the
+ * <code>resolve</code> flag is <code>true</code>, this method will then
+ * call <code>resolveClass(Class)</code> on the resulting Class object.
+ *
+ * @param name Name of the class to be loaded
+ * @param resolve If <code>true</code> then resolve the class
+ *
+ * @exception ClassNotFoundException if the class was not found
+ */
+ public Class loadClass(String name, boolean resolve)
+ throws ClassNotFoundException {
+
+ if (log.isDebugEnabled())
+ log.debug("loadClass(" + name + ", " + resolve + ")");
+ Class clazz = null;
+
+ // Don't load classes if class loader is stopped
+ if (!started) {
+ log.info(sm.getString("webappClassLoader.stopped"));
+ throw new ThreadDeath();
+ }
+
+ // (0) Check our previously loaded local class cache
+ clazz = findLoadedClass0(name);
+ if (clazz != null) {
+ if (log.isDebugEnabled())
+ log.debug(" Returning class from cache");
+ if (resolve)
+ resolveClass(clazz);
+ return (clazz);
+ }
+
+ // (0.1) Check our previously loaded class cache
+ clazz = findLoadedClass(name);
+ if (clazz != null) {
+ if (log.isDebugEnabled())
+ log.debug(" Returning class from cache");
+ if (resolve)
+ resolveClass(clazz);
+ return (clazz);
+ }
+
+ // (0.2) Try loading the class with the system class loader, to prevent
+ // the webapp from overriding J2SE classes
+ // GOOGLE: use the bootstrap loader, not the system loader; it breaks
+ // embedding.
+ try {
+ // clazz = system.loadClass(name);
+ clazz = Class.forName(name, false, null);
+ if (clazz != null) {
+ if (resolve)
+ resolveClass(clazz);
+ return (clazz);
+ }
+ } catch (ClassNotFoundException e) {
+ // Ignore
+ }
+
+ // (0.5) Permission to access this class when using a SecurityManager
+ if (securityManager != null) {
+ int i = name.lastIndexOf('.');
+ if (i >= 0) {
+ try {
+ securityManager.checkPackageAccess(name.substring(0,i));
+ } catch (SecurityException se) {
+ String error = "Security Violation, attempt to use " +
+ "Restricted Class: " + name;
+ log.info(error, se);
+ throw new ClassNotFoundException(error, se);
+ }
+ }
+ }
+
+ boolean delegateLoad = delegate || filter(name);
+
+ // (1) Delegate to our parent if requested
+ if (delegateLoad) {
+ if (log.isDebugEnabled())
+ log.debug(" Delegating to parent classloader1 " + parent);
+ ClassLoader loader = parent;
+ if (loader == null)
+ loader = system;
+ try {
+ clazz = loader.loadClass(name);
+ if (clazz != null) {
+ if (log.isDebugEnabled())
+ log.debug(" Loading class from parent");
+ if (resolve)
+ resolveClass(clazz);
+ return (clazz);
+ }
+ } catch (ClassNotFoundException e) {
+ ;
+ }
+ }
+
+ // (2) Search local repositories
+ if (log.isDebugEnabled())
+ log.debug(" Searching local repositories");
+ try {
+ clazz = findClass(name);
+ if (clazz != null) {
+ if (log.isDebugEnabled())
+ log.debug(" Loading class from local repository");
+ if (resolve)
+ resolveClass(clazz);
+ return (clazz);
+ }
+ } catch (ClassNotFoundException e) {
+ ;
+ }
+
+ // (3) Delegate to parent unconditionally
+ if (!delegateLoad) {
+ if (log.isDebugEnabled())
+ log.debug(" Delegating to parent classloader at end: " + parent);
+ ClassLoader loader = parent;
+ if (loader == null)
+ loader = system;
+ try {
+ clazz = loader.loadClass(name);
+ if (clazz != null) {
+ if (log.isDebugEnabled())
+ log.debug(" Loading class from parent");
+ if (resolve)
+ resolveClass(clazz);
+ return (clazz);
+ }
+ } catch (ClassNotFoundException e) {
+ ;
+ }
+ }
+
+ throw new ClassNotFoundException(name);
+ }
+
+
+ /**
+ * Get the Permissions for a CodeSource. If this instance
+ * of WebappClassLoader is for a web application context,
+ * add read FilePermission or JndiPermissions for the base
+ * directory (if unpacked),
+ * the context URL, and jar file resources.
+ *
+ * @param codeSource where the code was loaded from
+ * @return PermissionCollection for CodeSource
+ */
+ protected PermissionCollection getPermissions(CodeSource codeSource) {
+
+ String codeUrl = codeSource.getLocation().toString();
+ PermissionCollection pc;
+ if ((pc = (PermissionCollection)loaderPC.get(codeUrl)) == null) {
+ pc = super.getPermissions(codeSource);
+ if (pc != null) {
+ Iterator perms = permissionList.iterator();
+ while (perms.hasNext()) {
+ Permission p = (Permission)perms.next();
+ pc.add(p);
+ }
+ loaderPC.put(codeUrl,pc);
+ }
+ }
+ return (pc);
+
+ }
+
+
+ /**
+ * Returns the search path of URLs for loading classes and resources.
+ * This includes the original list of URLs specified to the constructor,
+ * along with any URLs subsequently appended by the addURL() method.
+ * @return the search path of URLs for loading classes and resources.
+ */
+ public URL[] getURLs() {
+
+ if (repositoryURLs != null) {
+ return repositoryURLs;
+ }
+
+ URL[] external = super.getURLs();
+
+ int filesLength = files.length;
+ int jarFilesLength = jarRealFiles.length;
+ int length = filesLength + jarFilesLength + external.length;
+ int i;
+
+ try {
+
+ URL[] urls = new URL[length];
+ for (i = 0; i < length; i++) {
+ if (i < filesLength) {
+ urls[i] = getURL(files[i]);
+ } else if (i < filesLength + jarFilesLength) {
+ urls[i] = getURL(jarRealFiles[i - filesLength]);
+ } else {
+ urls[i] = external[i - filesLength - jarFilesLength];
+ }
+ }
+
+ repositoryURLs = urls;
+
+ } catch (MalformedURLException e) {
+ repositoryURLs = new URL[0];
+ }
+
+ return repositoryURLs;
+
+ }
+
+
+ // ------------------------------------------------------ Lifecycle Methods
+
+
+ /**
+ * Add a lifecycle event listener to this component.
+ *
+ * @param listener The listener to add
+ */
+ public void addLifecycleListener(LifecycleListener listener) {
+ }
+
+
+ /**
+ * Get the lifecycle listeners associated with this lifecycle. If this
+ * Lifecycle has no listeners registered, a zero-length array is returned.
+ */
+ public LifecycleListener[] findLifecycleListeners() {
+ return new LifecycleListener[0];
+ }
+
+
+ /**
+ * Remove a lifecycle event listener from this component.
+ *
+ * @param listener The listener to remove
+ */
+ public void removeLifecycleListener(LifecycleListener listener) {
+ }
+
+
+ /**
+ * Start the class loader.
+ *
+ * @exception LifecycleException if a lifecycle error occurs
+ */
+ public void start() throws LifecycleException {
+
+ started = true;
+
+ }
+
+
+ /**
+ * Stop the class loader.
+ *
+ * @exception LifecycleException if a lifecycle error occurs
+ */
+ public void stop() throws LifecycleException {
+
+ started = false;
+
+ int length = files.length;
+ for (int i = 0; i < length; i++) {
+ files[i] = null;
+ }
+
+ length = jarFiles.length;
+ for (int i = 0; i < length; i++) {
+ try {
+ if (jarFiles[i] != null) {
+ jarFiles[i].close();
+ }
+ } catch (IOException e) {
+ // Ignore
+ }
+ jarFiles[i] = null;
+ }
+
+ notFoundResources.clear();
+ resourceEntries.clear();
+ resources = null;
+ repositories = null;
+ repositoryURLs = null;
+ files = null;
+ jarFiles = null;
+ jarRealFiles = null;
+ jarPath = null;
+ jarNames = null;
+ lastModifiedDates = null;
+ paths = null;
+ hasExternalRepositories = false;
+ parent = null;
+
+ permissionList.clear();
+ loaderPC.clear();
+
+ if (loaderDir != null) {
+ deleteDir(loaderDir);
+ }
+
+ org.apache.commons.logging.LogFactory.release(this);
+
+ }
+
+
+ /**
+ * Used to periodically signal to the classloader to release
+ * JAR resources.
+ */
+ public void closeJARs(boolean force) {
+ if (jarFiles.length > 0) {
+ try {
+ synchronized (jarFiles) {
+ if (force || (System.currentTimeMillis()
+ > (lastJarAccessed + 90000))) {
+ for (int i = 0; i < jarFiles.length; i++) {
+ if (jarFiles[i] != null) {
+ jarFiles[i].close();
+ jarFiles[i] = null;
+ }
+ }
+ }
+ }
+ } catch (IOException e) {
+ log("Failed to close JAR", e);
+ }
+ }
+ }
+
+
+ // ------------------------------------------------------ Protected Methods
+
+
+ /**
+ * Used to periodically signal to the classloader to release JAR resources.
+ */
+ protected void openJARs() {
+ if (started && (jarFiles.length > 0)) {
+ lastJarAccessed = System.currentTimeMillis();
+ if (jarFiles[0] == null) {
+ try {
+ for (int i = 0; i < jarFiles.length; i++) {
+ jarFiles[i] = new JarFile(jarRealFiles[i]);
+ }
+ } catch (IOException e) {
+ log("Failed to open JAR", e);
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Find specified class in local repositories.
+ *
+ * @return the loaded class, or null if the class isn't found
+ */
+ protected Class findClassInternal(String name)
+ throws ClassNotFoundException {
+
+ if (!validate(name))
+ throw new ClassNotFoundException(name);
+
+ String tempPath = name.replace('.', '/');
+ String classPath = tempPath + ".class";
+
+ ResourceEntry entry = null;
+
+ entry = findResourceInternal(name, classPath);
+
+ if ((entry == null) || (entry.binaryContent == null))
+ throw new ClassNotFoundException(name);
+
+ Class clazz = entry.loadedClass;
+ if (clazz != null)
+ return clazz;
+
+ // Looking up the package
+ String packageName = null;
+ int pos = name.lastIndexOf('.');
+ if (pos != -1)
+ packageName = name.substring(0, pos);
+
+ Package pkg = null;
+
+ if (packageName != null) {
+
+ pkg = getPackage(packageName);
+
+ // Define the package (if null)
+ if (pkg == null) {
+ if (entry.manifest == null) {
+ definePackage(packageName, null, null, null, null, null,
+ null, null);
+ } else {
+ definePackage(packageName, entry.manifest, entry.codeBase);
+ }
+ }
+
+ }
+
+ // Create the code source object
+ CodeSource codeSource =
+ new CodeSource(entry.codeBase, entry.certificates);
+
+ if (securityManager != null) {
+
+ // Checking sealing
+ if (pkg != null) {
+ boolean sealCheck = true;
+ if (pkg.isSealed()) {
+ sealCheck = pkg.isSealed(entry.codeBase);
+ } else {
+ sealCheck = (entry.manifest == null)
+ || !isPackageSealed(packageName, entry.manifest);
+ }
+ if (!sealCheck)
+ throw new SecurityException
+ ("Sealing violation loading " + name + " : Package "
+ + packageName + " is sealed.");
+ }
+
+ }
+
+ if (entry.loadedClass == null) {
+ synchronized (this) {
+ if (entry.loadedClass == null) {
+ clazz = defineClass(name, entry.binaryContent, 0,
+ entry.binaryContent.length,
+ codeSource);
+ entry.loadedClass = clazz;
+ entry.binaryContent = null;
+ entry.source = null;
+ entry.codeBase = null;
+ entry.manifest = null;
+ entry.certificates = null;
+ } else {
+ clazz = entry.loadedClass;
+ }
+ }
+ } else {
+ clazz = entry.loadedClass;
+ }
+
+ return clazz;
+
+ }
+
+ /**
+ * Find specified resource in local repositories. This block
+ * will execute under an AccessControl.doPrivilege block.
+ *
+ * @return the loaded resource, or null if the resource isn't found
+ */
+ private ResourceEntry findResourceInternal(File file, String path){
+ ResourceEntry entry = new ResourceEntry();
+ try {
+ entry.source = getURI(new File(file, path));
+ entry.codeBase = getURL(new File(file, path));
+ } catch (MalformedURLException e) {
+ return null;
+ }
+ return entry;
+ }
+
+
+ /**
+ * Find specified resource in local repositories.
+ *
+ * @return the loaded resource, or null if the resource isn't found
+ */
+ protected ResourceEntry findResourceInternal(String name, String path) {
+
+ if (!started) {
+ log.info(sm.getString("webappClassLoader.stopped"));
+ return null;
+ }
+
+ if ((name == null) || (path == null))
+ return null;
+
+ ResourceEntry entry = (ResourceEntry) resourceEntries.get(name);
+ if (entry != null)
+ return entry;
+
+ int contentLength = -1;
+ InputStream binaryStream = null;
+
+ int jarFilesLength = jarFiles.length;
+ int repositoriesLength = repositories.length;
+
+ int i;
+
+ Resource resource = null;
+
+ for (i = 0; (entry == null) && (i < repositoriesLength); i++) {
+ try {
+
+ String fullPath = repositories[i] + path;
+
+ Object lookupResult = resources.lookup(fullPath);
+ if (lookupResult instanceof Resource) {
+ resource = (Resource) lookupResult;
+ }
+
+ // Note : Not getting an exception here means the resource was
+ // found
+ if (securityManager != null) {
+ PrivilegedAction dp =
+ new PrivilegedFindResource(files[i], path);
+ entry = (ResourceEntry)AccessController.doPrivileged(dp);
+ } else {
+ entry = findResourceInternal(files[i], path);
+ }
+
+ ResourceAttributes attributes =
+ (ResourceAttributes) resources.getAttributes(fullPath);
+ contentLength = (int) attributes.getContentLength();
+ entry.lastModified = attributes.getLastModified();
+
+ if (resource != null) {
+
+ try {
+ binaryStream = resource.streamContent();
+ } catch (IOException e) {
+ return null;
+ }
+
+ // Register the full path for modification checking
+ // Note: Only syncing on a 'constant' object is needed
+ synchronized (allPermission) {
+
+ int j;
+
+ long[] result2 =
+ new long[lastModifiedDates.length + 1];
+ for (j = 0; j < lastModifiedDates.length; j++) {
+ result2[j] = lastModifiedDates[j];
+ }
+ result2[lastModifiedDates.length] = entry.lastModified;
+ lastModifiedDates = result2;
+
+ String[] result = new String[paths.length + 1];
+ for (j = 0; j < paths.length; j++) {
+ result[j] = paths[j];
+ }
+ result[paths.length] = fullPath;
+ paths = result;
+
+ }
+
+ }
+
+ } catch (NamingException e) {
+ }
+ }
+
+ if ((entry == null) && (notFoundResources.containsKey(name)))
+ return null;
+
+ JarEntry jarEntry = null;
+
+ synchronized (jarFiles) {
+
+ openJARs();
+ for (i = 0; (entry == null) && (i < jarFilesLength); i++) {
+
+ jarEntry = jarFiles[i].getJarEntry(path);
+
+ if (jarEntry != null) {
+
+ entry = new ResourceEntry();
+ try {
+ entry.codeBase = getURL(jarRealFiles[i]);
+ String jarFakeUrl = getURI(jarRealFiles[i]).toString();
+ jarFakeUrl = "jar:" + jarFakeUrl + "!/" + path;
+ entry.source = new URL(jarFakeUrl);
+ entry.lastModified = jarRealFiles[i].lastModified();
+ } catch (MalformedURLException e) {
+ return null;
+ }
+ contentLength = (int) jarEntry.getSize();
+ try {
+ entry.manifest = jarFiles[i].getManifest();
+ binaryStream = jarFiles[i].getInputStream(jarEntry);
+ } catch (IOException e) {
+ return null;
+ }
+
+ // Extract resources contained in JAR to the workdir
+ if (!(path.endsWith(".class"))) {
+ byte[] buf = new byte[1024];
+ File resourceFile = new File
+ (loaderDir, jarEntry.getName());
+ if (!resourceFile.exists()) {
+ Enumeration entries = jarFiles[i].entries();
+ while (entries.hasMoreElements()) {
+ JarEntry jarEntry2 =
+ (JarEntry) entries.nextElement();
+ if (!(jarEntry2.isDirectory())
+ && (!jarEntry2.getName().endsWith
+ (".class"))) {
+ resourceFile = new File
+ (loaderDir, jarEntry2.getName());
+ // No need to check mkdirs result because an
+ // IOException will occur anyway
+ resourceFile.getParentFile().mkdirs();
+ FileOutputStream os = null;
+ InputStream is = null;
+ try {
+ is = jarFiles[i].getInputStream
+ (jarEntry2);
+ os = new FileOutputStream
+ (resourceFile);
+ while (true) {
+ int n = is.read(buf);
+ if (n <= 0) {
+ break;
+ }
+ os.write(buf, 0, n);
+ }
+ } catch (IOException e) {
+ // Ignore
+ } finally {
+ try {
+ if (is != null) {
+ is.close();
+ }
+ } catch (IOException e) {
+ }
+ try {
+ if (os != null) {
+ os.close();
+ }
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+ }
+ }
+
+ }
+
+ }
+
+ if (entry == null) {
+ synchronized (notFoundResources) {
+ notFoundResources.put(name, name);
+ }
+ return null;
+ }
+
+ if (binaryStream != null) {
+
+ byte[] binaryContent = new byte[contentLength];
+
+ try {
+ int pos = 0;
+
+ while (true) {
+ int n = binaryStream.read(binaryContent, pos,
+ binaryContent.length - pos);
+ if (n <= 0)
+ break;
+ pos += n;
+ }
+ binaryStream.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ return null;
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+
+ entry.binaryContent = binaryContent;
+
+ // The certificates are only available after the JarEntry
+ // associated input stream has been fully read
+ if (jarEntry != null) {
+ entry.certificates = jarEntry.getCertificates();
+ }
+
+ }
+
+ }
+
+ // Add the entry in the local resource repository
+ synchronized (resourceEntries) {
+ // Ensures that all the threads which may be in a race to load
+ // a particular class all end up with the same ResourceEntry
+ // instance
+ ResourceEntry entry2 = (ResourceEntry) resourceEntries.get(name);
+ if (entry2 == null) {
+ resourceEntries.put(name, entry);
+ } else {
+ entry = entry2;
+ }
+ }
+
+ return entry;
+
+ }
+
+
+ /**
+ * Returns true if the specified package name is sealed according to the
+ * given manifest.
+ */
+ protected boolean isPackageSealed(String name, Manifest man) {
+
+ String path = name + "/";
+ Attributes attr = man.getAttributes(path);
+ String sealed = null;
+ if (attr != null) {
+ sealed = attr.getValue(Name.SEALED);
+ }
+ if (sealed == null) {
+ if ((attr = man.getMainAttributes()) != null) {
+ sealed = attr.getValue(Name.SEALED);
+ }
+ }
+ return "true".equalsIgnoreCase(sealed);
+
+ }
+
+
+ /**
+ * Finds the resource with the given name if it has previously been
+ * loaded and cached by this class loader, and return an input stream
+ * to the resource data. If this resource has not been cached, return
+ * <code>null</code>.
+ *
+ * @param name Name of the resource to return
+ */
+ protected InputStream findLoadedResource(String name) {
+
+ ResourceEntry entry = (ResourceEntry) resourceEntries.get(name);
+ if (entry != null) {
+ if (entry.binaryContent != null)
+ return new ByteArrayInputStream(entry.binaryContent);
+ }
+ return (null);
+
+ }
+
+
+ /**
+ * Finds the class with the given name if it has previously been
+ * loaded and cached by this class loader, and return the Class object.
+ * If this class has not been cached, return <code>null</code>.
+ *
+ * @param name Name of the resource to return
+ */
+ protected Class findLoadedClass0(String name) {
+
+ ResourceEntry entry = (ResourceEntry) resourceEntries.get(name);
+ if (entry != null) {
+ return entry.loadedClass;
+ }
+ return (null); // FIXME - findLoadedResource()
+
+ }
+
+
+ /**
+ * Refresh the system policy file, to pick up eventual changes.
+ */
+ protected void refreshPolicy() {
+
+ try {
+ // The policy file may have been modified to adjust
+ // permissions, so we're reloading it when loading or
+ // reloading a Context
+ Policy policy = Policy.getPolicy();
+ policy.refresh();
+ } catch (AccessControlException e) {
+ // Some policy files may restrict this, even for the core,
+ // so this exception is ignored
+ }
+
+ }
+
+
+ /**
+ * Filter classes.
+ *
+ * @param name class name
+ * @return true if the class should be filtered
+ */
+ protected boolean filter(String name) {
+
+ if (name == null)
+ return false;
+
+ // Looking up the package
+ String packageName = null;
+ int pos = name.lastIndexOf('.');
+ if (pos != -1)
+ packageName = name.substring(0, pos);
+ else
+ return false;
+
+ for (int i = 0; i < packageTriggers.length; i++) {
+ if (packageName.startsWith(packageTriggers[i]))
+ return true;
+ }
+
+ return false;
+
+ }
+
+
+ /**
+ * Validate a classname. As per SRV.9.7.2, we must restict loading of
+ * classes from J2SE (java.*) and classes of the servlet API
+ * (javax.servlet.*). That should enhance robustness and prevent a number
+ * of user error (where an older version of servlet.jar would be present
+ * in /WEB-INF/lib).
+ *
+ * @param name class name
+ * @return true if the name is valid
+ */
+ protected boolean validate(String name) {
+
+ if (name == null)
+ return false;
+ if (name.startsWith("java."))
+ return false;
+
+ return true;
+
+ }
+
+
+ /**
+ * Check the specified JAR file, and return <code>true</code> if it does
+ * not contain any of the trigger classes.
+ *
+ * @param jarfile The JAR file to be checked
+ *
+ * @exception IOException if an input/output error occurs
+ */
+ private boolean validateJarFile(File jarfile)
+ throws IOException {
+
+ if (triggers == null)
+ return (true);
+ JarFile jarFile = new JarFile(jarfile);
+ for (int i = 0; i < triggers.length; i++) {
+ Class clazz = null;
+ try {
+ if (parent != null) {
+ clazz = parent.loadClass(triggers[i]);
+ } else {
+ clazz = Class.forName(triggers[i]);
+ }
+ } catch (Throwable t) {
+ clazz = null;
+ }
+ if (clazz == null)
+ continue;
+ String name = triggers[i].replace('.', '/') + ".class";
+ if (log.isDebugEnabled())
+ log.debug(" Checking for " + name);
+ JarEntry jarEntry = jarFile.getJarEntry(name);
+ if (jarEntry != null) {
+ log.info("validateJarFile(" + jarfile +
+ ") - jar not loaded. See Servlet Spec 2.3, "
+ + "section 9.7.2. Offending class: " + name);
+ jarFile.close();
+ return (false);
+ }
+ }
+ jarFile.close();
+ return (true);
+
+ }
+
+
+ /**
+ * Get URL.
+ */
+ protected URL getURL(File file)
+ throws MalformedURLException {
+
+ File realFile = file;
+ try {
+ realFile = realFile.getCanonicalFile();
+ } catch (IOException e) {
+ // Ignore
+ }
+ return realFile.toURL();
+
+ }
+
+
+ /**
+ * Get URL.
+ */
+ protected URL getURI(File file)
+ throws MalformedURLException {
+
+ return jdkCompat.getURI(file);
+
+ }
+
+
+ /**
+ * Delete the specified directory, including all of its contents and
+ * subdirectories recursively.
+ *
+ * @param dir File object representing the directory to be deleted
+ */
+ protected static void deleteDir(File dir) {
+
+ String files[] = dir.list();
+ if (files == null) {
+ files = new String[0];
+ }
+ for (int i = 0; i < files.length; i++) {
+ File file = new File(dir, files[i]);
+ if (file.isDirectory()) {
+ deleteDir(file);
+ } else {
+ file.delete();
+ }
+ }
+ dir.delete();
+
+ }
+
+
+ /**
+ * Log a debugging output message.
+ *
+ * @param message Message to be logged
+ */
+ private void log(String message) {
+
+ System.out.println("WebappClassLoader: " + message);
+
+ }
+
+
+ /**
+ * Log a debugging output message with an exception.
+ *
+ * @param message Message to be logged
+ * @param throwable Exception to be logged
+ */
+ private void log(String message, Throwable throwable) {
+
+ System.out.println("WebappClassLoader: " + message);
+ throwable.printStackTrace(System.out);
+
+ }
+
+}
diff --git a/dev/core/test/com/google/gwt/dev/GWTCompilerTest.java b/dev/core/test/com/google/gwt/dev/GWTCompilerTest.java
new file mode 100644
index 0000000..8955714
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/GWTCompilerTest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2009 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;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.dev.GWTCompiler.GWTCompilerOptionsImpl;
+import com.google.gwt.dev.jjs.JsOutputOption;
+
+import java.io.File;
+
+/**
+ * Test for deprecated {@link GWTShell}.
+ */
+@SuppressWarnings("deprecation")
+public class GWTCompilerTest extends ArgProcessorTestBase {
+
+ private final GWTCompiler.ArgProcessor argProcessor;
+ private final GWTCompilerOptionsImpl options = new GWTCompilerOptionsImpl();
+
+ public GWTCompilerTest() {
+ argProcessor = new GWTCompiler.ArgProcessor(options);
+ }
+
+ public void testAllValidArgs() {
+ assertProcessSuccess(argProcessor, "-logLevel", "DEBUG", "-style",
+ "PRETTY", "-ea", "-XdisableAggressiveOptimization", "-out", "myWww",
+ "-gen", "myGen", "c.g.g.h.H", "my.Module");
+
+ assertEquals(new File("myGen").getAbsoluteFile(),
+ options.getGenDir().getAbsoluteFile());
+ assertEquals(new File("myWww"), options.getOutDir());
+
+ assertEquals(TreeLogger.DEBUG, options.getLogLevel());
+ assertEquals(JsOutputOption.PRETTY, options.getOutput());
+ assertTrue(options.isEnableAssertions());
+ assertFalse(options.isAggressivelyOptimize());
+
+ assertEquals(2, options.getModuleNames().size());
+ assertEquals("c.g.g.h.H", options.getModuleNames().get(0));
+ assertEquals("my.Module", options.getModuleNames().get(1));
+ }
+
+ public void testDefaultArgs() {
+ assertProcessSuccess(argProcessor, "c.g.g.h.H");
+
+ assertEquals(null, options.getGenDir());
+ assertEquals(new File("").getAbsoluteFile(),
+ options.getOutDir().getAbsoluteFile());
+
+ assertEquals(TreeLogger.INFO, options.getLogLevel());
+ assertEquals(JsOutputOption.OBFUSCATED, options.getOutput());
+ assertFalse(options.isEnableAssertions());
+ assertTrue(options.isAggressivelyOptimize());
+
+ assertEquals(1, options.getModuleNames().size());
+ assertEquals("c.g.g.h.H", options.getModuleNames().get(0));
+ }
+
+ public void testForbiddenArgs() {
+ assertProcessFailure(argProcessor, "-localWorkers", "2");
+ assertProcessFailure(argProcessor, "-extra", "extra");
+ assertProcessFailure(argProcessor, "-war", "war");
+ assertProcessFailure(argProcessor, "-work", "work");
+ }
+}
diff --git a/dev/core/test/com/google/gwt/dev/GWTShellTest.java b/dev/core/test/com/google/gwt/dev/GWTShellTest.java
new file mode 100644
index 0000000..557189e
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/GWTShellTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2009 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;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.dev.GWTShell.ShellOptionsImpl;
+import com.google.gwt.dev.HostedModeTest.MySCL;
+import com.google.gwt.dev.jjs.JsOutputOption;
+import com.google.gwt.dev.shell.BrowserWidgetHostChecker;
+
+import java.io.File;
+
+/**
+ * Test for deprecated {@link GWTShell}.
+ */
+@SuppressWarnings("deprecation")
+public class GWTShellTest extends ArgProcessorTestBase {
+
+ private final GWTShell.ArgProcessor argProcessor;
+ private final ShellOptionsImpl options = new ShellOptionsImpl();
+
+ public GWTShellTest() {
+ argProcessor = new GWTShell.ArgProcessor(options, false, false);
+ }
+
+ public void testAllValidArgs() {
+ assertProcessSuccess(argProcessor, "-port", "8080", "-whitelist", "white",
+ "-blacklist", "black", "-logLevel", "DEBUG", "-noserver", "-out",
+ "myWww", "-gen", "myGen", "http://www.google.com/", "foo");
+
+ assertNotNull(BrowserWidgetHostChecker.matchWhitelisted("white"));
+ assertNotNull(BrowserWidgetHostChecker.matchBlacklisted("black"));
+ assertFalse(BrowserWidgetHostChecker.isAlwaysWhitelisted("http://127.0.0.1.40"));
+ assertFalse(BrowserWidgetHostChecker.isAlwaysWhitelisted("http://127.0.0.1.40:88"));
+ assertFalse(BrowserWidgetHostChecker.isAlwaysWhitelisted("http://127.0.0.1.40:88/"));
+ assertFalse(BrowserWidgetHostChecker.isAlwaysWhitelisted("http://127.0.0.1.40:88/foo"));
+ assertFalse(BrowserWidgetHostChecker.isAlwaysWhitelisted("http://localhost.evildomain.org"));
+ assertFalse(BrowserWidgetHostChecker.isAlwaysWhitelisted("http://localhost.evildomain.org:88"));
+ assertFalse(BrowserWidgetHostChecker.isAlwaysWhitelisted("http://localhost.evildomain.org:88/"));
+ assertFalse(BrowserWidgetHostChecker.isAlwaysWhitelisted("http://localhost.evildomain.org:88/foo"));
+ assertFalse(BrowserWidgetHostChecker.isAlwaysWhitelisted("http://localhost.evildomain.org/"));
+ assertFalse(BrowserWidgetHostChecker.isAlwaysWhitelisted("http://localhost.evildomain.org/foo"));
+ assertFalse(BrowserWidgetHostChecker.isAlwaysWhitelisted("http://www.evildomain.org/foo?http://localhost"));
+ assertTrue(BrowserWidgetHostChecker.isAlwaysWhitelisted("http://127.0.0.1"));
+ assertTrue(BrowserWidgetHostChecker.isAlwaysWhitelisted("http://127.0.0.1:88"));
+ assertTrue(BrowserWidgetHostChecker.isAlwaysWhitelisted("http://127.0.0.1:88/"));
+ assertTrue(BrowserWidgetHostChecker.isAlwaysWhitelisted("http://127.0.0.1:88/foo"));
+
+ assertEquals(new File("myGen").getAbsoluteFile(),
+ options.getGenDir().getAbsoluteFile());
+ assertEquals(new File("myWww"), options.getOutDir());
+
+ assertEquals(TreeLogger.DEBUG, options.getLogLevel());
+
+ assertEquals(8080, options.getPort());
+ assertTrue(options.isNoServer());
+ assertEquals(2, options.getStartupURLs().size());
+ assertEquals("http://www.google.com/", options.getStartupURLs().get(0));
+ assertEquals("foo", options.getStartupURLs().get(1));
+ }
+
+ public void testDefaultArgs() {
+ assertProcessSuccess(argProcessor);
+
+ assertEquals(null, options.getGenDir());
+ assertEquals(new File("").getAbsoluteFile(),
+ options.getOutDir().getAbsoluteFile());
+
+ assertEquals(TreeLogger.INFO, options.getLogLevel());
+ assertEquals(JsOutputOption.OBFUSCATED, options.getOutput());
+ assertFalse(options.isEnableAssertions());
+ assertTrue(options.isAggressivelyOptimize());
+
+ assertEquals(8888, options.getPort());
+ assertFalse(options.isNoServer());
+ assertEquals(0, options.getStartupURLs().size());
+ }
+
+ public void testForbiddenArgs() {
+ assertProcessFailure(argProcessor, "-localWorkers", "2");
+ assertProcessFailure(argProcessor, "-extra", "extra");
+ assertProcessFailure(argProcessor, "-war", "war");
+ assertProcessFailure(argProcessor, "-work", "work");
+ assertProcessFailure(argProcessor, "-server", MySCL.class.getName());
+ }
+}
diff --git a/eclipse/dev/.classpath b/eclipse/dev/.classpath
index 4b1f5b4..fa17a0d 100644
--- a/eclipse/dev/.classpath
+++ b/eclipse/dev/.classpath
@@ -12,16 +12,35 @@
<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-beanutils-1.6.jar"/>
+ <classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/commons-collections-3.1.jar" sourcepath="/GWT_TOOLS/lib/tomcat/commons-collections-3.1-src.zip"/>
+ <classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/commons-digester-1.5.jar"/>
<classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/commons-el-1.0.jar"/>
+ <classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/commons-logging-1.0.jar"/>
+ <classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/commons-modeler-1.1.jar"/>
+ <classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/jakarta-regexp-1.3.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/tomcat/mx4j-jmx-1.1.jar"/>
+ <classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/naming-common-1.0.jar"/>
+ <classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/naming-factory-1.0.jar"/>
+ <classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/naming-java-1.0.jar"/>
+ <classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/naming-resources-1.0.jar"/>
+ <classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/servlet-api-2.5.jar"/>
+ <classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/servlet-api-2.4.jar"/>
+ <classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/servlets-common-1.0.jar"/>
+ <classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/servlets-default-1.0.jar"/>
+ <classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/servlets-invoker-1.0.jar"/>
+ <classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/tomcat-coyote-1.0.jar"/>
+ <classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/tomcat-http11-1.0.jar" sourcepath="/GWT_TOOLS/lib/tomcat/tomcat-http11-1.0.jar"/>
+ <classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/tomcat-jk2-2.1.jar"/>
+ <classpathentry kind="var" path="GWT_TOOLS/lib/tomcat/tomcat-util-5.1.jar"/>
<classpathentry kind="var" path="GWT_TOOLS/lib/htmlunit/htmlunit-2.9/htmlunit-core-js-2.9.jar" sourcepath="/GWT_TOOLS/lib/htmlunit/htmlunit-2.9/htmlunit-core-js-2.9-sources.jar"/>
<classpathentry kind="var" path="GWT_TOOLS/lib/htmlunit/htmlunit-2.9/htmlunit-2.9.jar" sourcepath="/GWT_TOOLS/lib/htmlunit/htmlunit-2.9/htmlunit-2.9-sources.jar"/>
<classpathentry kind="var" path="GWT_TOOLS/lib/protobuf/protobuf-2.2.0/protobuf-java-rebased-2.2.0.jar"/>
<classpathentry kind="var" path="GWT_TOOLS/lib/guava/guava-10.0.1/guava-10.0.1-rebased.jar"/>
<classpathentry kind="var" path="GWT_TOOLS/lib/jscomp/sourcemap-rebased.jar"/>
<classpathentry kind="var" path="GWT_TOOLS/lib/jscomp/r1649/compiler-rebased.jar"/>
- <classpathentry kind="var" path="GWT_TOOLS/lib/apache/commons/commons-collections-3.2.1.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>