blob: f22a94f33caacc940b93adb0108cd3c4e086b234 [file] [log] [blame]
/*
* 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.ServletContainer;
import com.google.gwt.core.ext.ServletContainerLauncher;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.linker.ArtifactSet;
import com.google.gwt.core.ext.linker.impl.StandardLinkerContext;
import com.google.gwt.dev.Compiler.CompilerOptionsImpl;
import com.google.gwt.dev.cfg.ModuleDef;
import com.google.gwt.dev.shell.ArtifactAcceptor;
import com.google.gwt.dev.shell.jetty.JettyLauncher;
import com.google.gwt.dev.util.InstalledHelpInfo;
import com.google.gwt.dev.util.Util;
import com.google.gwt.dev.util.arg.ArgHandlerExtraDir;
import com.google.gwt.dev.util.arg.ArgHandlerLocalWorkers;
import com.google.gwt.dev.util.arg.ArgHandlerModuleName;
import com.google.gwt.dev.util.arg.ArgHandlerWarDir;
import com.google.gwt.dev.util.arg.ArgHandlerWorkDirOptional;
import com.google.gwt.util.tools.ArgHandlerString;
import com.google.gwt.util.tools.Utility;
import java.io.File;
import java.io.IOException;
import java.net.BindException;
import java.util.HashMap;
import java.util.Map;
import javax.swing.ImageIcon;
/**
* The main executable class for the hosted mode shell. NOTE: the public API for
* this class is to be determined. Consider this class as having <b>no</b>
* public API other than {@link #main(String[])}.
*/
public class HostedMode extends OophmHostedModeBase {
/**
* Handles the -server command line flag.
*/
protected static class ArgHandlerServer extends ArgHandlerString {
private HostedModeOptions options;
public ArgHandlerServer(HostedModeOptions options) {
this.options = options;
}
@Override
public String[] getDefaultArgs() {
if (options.isNoServer()) {
return null;
} else {
return new String[] {getTag(), JettyLauncher.class.getName()};
}
}
@Override
public String getPurpose() {
return "Specify a different embedded web server to run (must implement ServletContainerLauncher)";
}
@Override
public String getTag() {
return "-server";
}
@Override
public String[] getTagArgs() {
return new String[] {"servletContainerLauncher"};
}
@Override
public boolean setString(String sclClassName) {
// Supercedes -noserver.
options.setNoServer(false);
Throwable t;
try {
Class<?> clazz = Class.forName(sclClassName, true,
Thread.currentThread().getContextClassLoader());
Class<? extends ServletContainerLauncher> sclClass = clazz.asSubclass(ServletContainerLauncher.class);
options.setServletContainerLauncher(sclClass.newInstance());
return true;
} catch (ClassCastException e) {
t = e;
} catch (ClassNotFoundException e) {
t = e;
} catch (InstantiationException e) {
t = e;
} catch (IllegalAccessException e) {
t = e;
}
System.err.println("Unable to load server class '" + sclClassName + "'");
t.printStackTrace();
return false;
}
}
/**
* Handles a startup url that can be passed on the command line.
*/
protected static class ArgHandlerStartupURLs extends ArgHandlerString {
private final OptionStartupURLs options;
public ArgHandlerStartupURLs(OptionStartupURLs options) {
this.options = options;
}
@Override
public String getPurpose() {
return "Automatically launches the specified URL";
}
@Override
public String getTag() {
return "-startupUrl";
}
@Override
public String[] getTagArgs() {
return new String[] {"url"};
}
@Override
public boolean setString(String arg) {
options.addStartupURL(arg);
return true;
}
}
static class ArgProcessor extends OophmHostedModeBase.ArgProcessor {
public ArgProcessor(HostedModeOptions options) {
super(options, false);
registerHandler(new ArgHandlerServer(options));
registerHandler(new ArgHandlerStartupURLs(options));
registerHandler(new ArgHandlerWarDir(options));
registerHandler(new ArgHandlerExtraDir(options));
registerHandler(new ArgHandlerWorkDirOptional(options));
registerHandler(new ArgHandlerLocalWorkers(options));
registerHandler(new ArgHandlerModuleName(options) {
@Override
public String getPurpose() {
return super.getPurpose() + " to host";
}
});
}
@Override
protected String getName() {
return HostedMode.class.getName();
}
}
interface HostedModeOptions extends OophmHostedModeBaseOptions,
CompilerOptions {
ServletContainerLauncher getServletContainerLauncher();
void setServletContainerLauncher(ServletContainerLauncher scl);
}
/**
* Concrete class to implement all hosted mode options.
*/
static class HostedModeOptionsImpl extends OophmHostedModeBaseOptionsImpl
implements HostedModeOptions {
private File extraDir;
private int localWorkers;
private File outDir;
private ServletContainerLauncher scl;
private File warDir;
public File getExtraDir() {
return extraDir;
}
public int getLocalWorkers() {
return localWorkers;
}
@Deprecated
public File getOutDir() {
return outDir;
}
public ServletContainerLauncher getServletContainerLauncher() {
return scl;
}
@Override
public File getShellBaseWorkDir(ModuleDef moduleDef) {
return new File(new File(getWorkDir(), moduleDef.getName()), "shell");
}
public File getShellPublicGenDir(ModuleDef moduleDef) {
return new File(getShellBaseWorkDir(moduleDef), "public");
}
public File getWarDir() {
return warDir;
}
public void setExtraDir(File extraDir) {
this.extraDir = extraDir;
}
public void setLocalWorkers(int localWorkers) {
this.localWorkers = localWorkers;
}
@Deprecated
public void setOutDir(File outDir) {
this.outDir = outDir;
}
public void setServletContainerLauncher(ServletContainerLauncher scl) {
this.scl = scl;
}
public void setWarDir(File warDir) {
this.warDir = warDir;
}
}
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.
*/
HostedMode hostedMode = new HostedMode();
if (new ArgProcessor(hostedMode.options).processArgs(args)) {
hostedMode.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 HostedModeOptionsImpl options = (HostedModeOptionsImpl) super.options;
/**
* Maps each active linker stack by module.
*/
private final Map<String, StandardLinkerContext> linkerStacks = new HashMap<String, StandardLinkerContext>();
/**
* The set of specified modules by name; the keys represent the renamed name
* of each module rather than the canonical name.
*/
private Map<String, ModuleDef> modulesByName = new HashMap<String, ModuleDef>();
/**
* The server that was started.
*/
private ServletContainer server;
/**
* Tracks whether we created a temp workdir that we need to destroy.
*/
private boolean tempWorkDir = false;
/**
* Default constructor for testing; no public API yet.
*/
HostedMode() {
}
public WebServerRestart hasWebServer() {
return options.isNoServer() ? WebServerRestart.DISABLED
: WebServerRestart.ENABLED;
}
public void restartServer(TreeLogger logger) throws UnableToCompleteException {
server.refresh();
}
@Override
protected void compile(TreeLogger logger) throws UnableToCompleteException {
CompilerOptions newOptions = new CompilerOptionsImpl(options);
newOptions.setCompilationStateRetained(true);
new Compiler(newOptions).run(logger);
}
@Deprecated
@Override
protected void compile(TreeLogger logger, ModuleDef moduleDef)
throws UnableToCompleteException {
throw new UnsupportedOperationException();
}
@Override
protected HostedModeBaseOptions createOptions() {
return new HostedModeOptionsImpl();
}
@Override
protected ArtifactAcceptor doCreateArtifactAcceptor(final ModuleDef module) {
return new ArtifactAcceptor() {
public void accept(TreeLogger logger, ArtifactSet newlyGeneratedArtifacts)
throws UnableToCompleteException {
relink(logger, module, newlyGeneratedArtifacts);
}
};
}
@Override
protected void doShutDownServer() {
if (server != null) {
try {
server.stop();
} catch (UnableToCompleteException e) {
// Already logged.
}
server = null;
}
if (tempWorkDir) {
Util.recursiveDelete(options.getWorkDir(), false);
}
}
@Override
protected boolean doStartup() {
if (!super.doStartup()) {
return false;
}
tempWorkDir = options.getWorkDir() == null;
if (tempWorkDir) {
try {
options.setWorkDir(Utility.makeTemporaryDirectory(null, "gwtc"));
} catch (IOException e) {
System.err.println("Unable to create hosted mode work directory");
e.printStackTrace();
return false;
}
}
ServletValidator servletValidator = null;
File webXml = new File(options.getWarDir(), "WEB-INF/web.xml");
if (webXml.exists()) {
servletValidator = ServletValidator.create(getTopLogger(), webXml);
}
TreeLogger branch = getTopLogger().branch(TreeLogger.INFO, "Loading modules");
for (String moduleName : options.getModuleNames()) {
TreeLogger moduleBranch = branch.branch(TreeLogger.INFO, moduleName);
try {
ModuleDef module = loadModule(moduleBranch, moduleName, false);
validateServletTags(moduleBranch, servletValidator, module, webXml);
TreeLogger loadLogger = moduleBranch.branch(TreeLogger.DEBUG,
"Bootstrap link for command-line module '" + moduleName + "'");
link(loadLogger, module);
} catch (UnableToCompleteException e) {
// Already logged.
return false;
}
}
return true;
}
@Override
protected int doStartUpServer() {
try {
TreeLogger serverLogger = webServerLog.getLogger();
serverLogger.log(TreeLogger.INFO, "Starting HTTP on port " + getPort(),
null);
server = options.getServletContainerLauncher().start(serverLogger,
getPort(), options.getWarDir());
assert (server != null);
return server.getPort();
} catch (BindException e) {
System.err.println("Port "
+ getPort()
+ " is already is use; you probably still have another session active");
} catch (Exception e) {
System.err.println("Unable to start embedded HTTP server");
e.printStackTrace();
}
return -1;
}
@Override
protected String getHost() {
if (server != null) {
return server.getHost();
}
return super.getHost();
}
@Override
protected ImageIcon getWebServerIcon() {
return loadImageIcon(options.getServletContainerLauncher().getIconPath(),
false);
}
@Override
protected String getWebServerName() {
return options.getServletContainerLauncher().getName();
}
@Override
protected boolean initModule(String moduleName) {
ModuleDef module = modulesByName.get(moduleName);
if (module == null) {
getTopLogger().log(
TreeLogger.WARN,
"Unknown module requested '"
+ moduleName
+ "'; all active GWT modules must be specified in the command line arguments");
return false;
}
try {
TreeLogger logger = getTopLogger().branch(TreeLogger.DEBUG,
"Initializing module '" + module.getName() + "' for hosted mode");
boolean shouldRefreshPage = false;
if (module.isGwtXmlFileStale()) {
shouldRefreshPage = true;
module = loadModule(logger, module.getCanonicalName(), false);
}
link(logger, module);
return shouldRefreshPage;
} catch (UnableToCompleteException e) {
// Already logged.
return false;
}
}
/*
* Overridden to keep our map up to date.
*/
@Override
protected ModuleDef loadModule(TreeLogger logger, String moduleName,
boolean refresh) throws UnableToCompleteException {
ModuleDef module = super.loadModule(logger, moduleName, refresh);
modulesByName.put(module.getName(), module);
return module;
}
/**
* Perform an initial hosted mode link, without overwriting newer or
* unmodified files in the output folder.
*
* @param logger the logger to use
* @param module the module to link
* @param includePublicFiles if <code>true</code>, include public files in
* the link, otherwise do not include them
* @throws UnableToCompleteException
*/
private void link(TreeLogger logger, ModuleDef module)
throws UnableToCompleteException {
TreeLogger linkLogger = logger.branch(TreeLogger.DEBUG, "Linking module '"
+ module.getName() + "'");
// Create a new active linker stack for the fresh link.
StandardLinkerContext linkerStack = new StandardLinkerContext(linkLogger,
module, options);
linkerStacks.put(module.getName(), linkerStack);
ArtifactSet artifacts = linkerStack.invokeLink(linkLogger);
produceOutput(linkLogger, linkerStack, artifacts, module);
}
private void produceOutput(TreeLogger logger,
StandardLinkerContext linkerStack, ArtifactSet artifacts, ModuleDef module)
throws UnableToCompleteException {
File moduleOutDir = new File(options.getWarDir(), module.getName());
linkerStack.produceOutputDirectory(logger, artifacts, moduleOutDir);
if (options.getExtraDir() != null) {
File moduleExtraDir = new File(options.getExtraDir(), module.getName());
linkerStack.produceExtraDirectory(logger, artifacts, moduleExtraDir);
}
}
/**
* Perform hosted mode relink when new artifacts are generated, without
* overwriting newer or unmodified files in the output folder.
*
* @param logger the logger to use
* @param module the module to link
* @param newlyGeneratedArtifacts the set of new artifacts
* @throws UnableToCompleteException
*/
private void relink(TreeLogger logger, ModuleDef module,
ArtifactSet newlyGeneratedArtifacts) throws UnableToCompleteException {
TreeLogger linkLogger = logger.branch(TreeLogger.DEBUG,
"Relinking module '" + module.getName() + "'");
// Find the existing linker stack.
StandardLinkerContext linkerStack = linkerStacks.get(module.getName());
assert linkerStack != null;
ArtifactSet artifacts = linkerStack.invokeRelink(linkLogger,
newlyGeneratedArtifacts);
produceOutput(linkLogger, linkerStack, artifacts, module);
}
private void validateServletTags(TreeLogger logger,
ServletValidator servletValidator, ModuleDef module, File webXml) {
TreeLogger servletLogger = logger.branch(TreeLogger.DEBUG,
"Validating <servlet> tags for module '" + module.getName() + "'",
null, new InstalledHelpInfo("servletMappings.html"));
String[] servletPaths = module.getServletPaths();
if (servletValidator == null && servletPaths.length > 0) {
servletLogger.log(
TreeLogger.WARN,
"Module declares "
+ servletPaths.length
+ " <servlet> declaration(s), but a valid 'web.xml' was not found at '"
+ webXml.getAbsolutePath() + "'");
} else {
for (String servletPath : servletPaths) {
String servletClass = module.findServletForPath(servletPath);
assert (servletClass != null);
// Prefix module name to convert module mapping to global mapping.
servletPath = "/" + module.getName() + servletPath;
servletValidator.validate(servletLogger, servletClass, servletPath);
}
}
}
}