blob: 3c39cd02df404176b52f48d3ab0c5d4d893b97e8 [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.cfg.ModuleDef;
import com.google.gwt.dev.shell.jetty.JettyLauncher;
import com.google.gwt.dev.ui.RestartServerCallback;
import com.google.gwt.dev.ui.RestartServerEvent;
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.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;
/**
* 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 DevMode extends DevModeBase implements RestartServerCallback {
/**
* 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 DevModeBase.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 ArgHandlerModuleName(options) {
@Override
public String getPurpose() {
return super.getPurpose() + " to host";
}
});
}
@Override
protected String getName() {
return DevMode.class.getName();
}
}
interface HostedModeOptions extends HostedModeBaseOptions, CompilerOptions {
ServletContainerLauncher getServletContainerLauncher();
void setServletContainerLauncher(ServletContainerLauncher scl);
}
/**
* Concrete class to implement all hosted mode options.
*/
static class HostedModeOptionsImpl extends HostedModeBaseOptionsImpl
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 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.
*/
DevMode hostedMode = new DevMode();
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;
/**
* 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.
*/
DevMode() {
}
/**
* Called by the UI on a restart server event.
*/
public void onRestartServer(TreeLogger logger) {
try {
server.refresh();
} catch (UnableToCompleteException e) {
// ignore, problem already logged
}
}
@Override
protected HostedModeBaseOptions createOptions() {
return new HostedModeOptionsImpl();
}
@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);
Util.recursiveDelete(options.getShellBaseWorkDir(module), 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 {
ui.setCallback(RestartServerEvent.getType(), this);
// TODO(jat): find a safe way to get an icon for the servlet container
TreeLogger serverLogger = ui.getWebServerLogger(getWebServerName(), null);
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();
}
// Clear the callback if we failed to start the server
ui.setCallback(RestartServerEvent.getType(), null);
return -1;
}
@Override
protected String getHost() {
if (server != null) {
return server.getHost();
}
return super.getHost();
}
protected String getWebServerName() {
return options.getServletContainerLauncher().getName();
}
protected synchronized 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);
}
}
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);
}
}
}
}