All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gwt.dev.DevMode Maven / Gradle / Ivy

/*
 * 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.resource.impl.ResourceOracleImpl;
import com.google.gwt.dev.shell.BrowserListener;
import com.google.gwt.dev.shell.CodeServerListener;
import com.google.gwt.dev.shell.OophmSessionHandler;
import com.google.gwt.dev.shell.SuperDevListener;
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.ArgHandlerDeployDir;
import com.google.gwt.dev.util.arg.ArgHandlerDisableUpdateCheck;
import com.google.gwt.dev.util.arg.ArgHandlerExtraDir;
import com.google.gwt.dev.util.arg.ArgHandlerIncrementalCompile;
import com.google.gwt.dev.util.arg.ArgHandlerJsInteropMode;
import com.google.gwt.dev.util.arg.ArgHandlerMethodNameDisplayMode;
import com.google.gwt.dev.util.arg.ArgHandlerModuleName;
import com.google.gwt.dev.util.arg.ArgHandlerModulePathPrefix;
import com.google.gwt.dev.util.arg.ArgHandlerSourceLevel;
import com.google.gwt.dev.util.arg.ArgHandlerWarDir;
import com.google.gwt.dev.util.arg.ArgHandlerWorkDirOptional;
import com.google.gwt.dev.util.arg.OptionModulePathPrefix;
import com.google.gwt.dev.util.log.speedtracer.DevModeEventType;
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.ArgHandlerFlag;
import com.google.gwt.util.tools.ArgHandlerString;
import com.google.gwt.util.tools.Utility;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.BindException;
import java.net.URL;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * 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 no
 * public API other than {@link #main(String[])}.
 */
public class DevMode extends DevModeBase implements RestartServerCallback {

  /**
   * Handles the -superDevMode command line flag.
   */
  public interface HostedModeOptions extends HostedModeBaseOptions, CompilerOptions,
      OptionSuperDevMode, OptionModulePathPrefix {
    ServletContainerLauncher getServletContainerLauncher();

    String getServletContainerLauncherArgs();

    void setServletContainerLauncher(ServletContainerLauncher scl);

    void setServletContainerLauncherArgs(String args);
  }

  /**
   * Runs the superdev-mode code server instead of classic one.
   */
  protected static class ArgHandlerSuperDevMode extends ArgHandlerFlag {
    private final HostedModeOptions options;

    public ArgHandlerSuperDevMode(HostedModeOptions options) {
      this.options = options;
      addTagValue("-superDevMode", true);
    }

    @Override
    public boolean getDefaultValue() {
      return true;
    }

    @Override
    public String getLabel() {
      return "superDevMode";
    }

    @Override
    public String getPurposeSnippet() {
      return "Runs Super Dev Mode instead of classic Development Mode.";
    }

    @Override
    public boolean setFlag(boolean value) {
      options.setSuperDevMode(value);
      // Superdev uses incremental by default
      if (options.isSuperDevMode()) {
        options.setIncrementalCompileEnabled(true);
      }
      return true;
    }
  }

  /**
   * Handles the -server command line flag.
   */
  protected static class ArgHandlerServer extends ArgHandlerString {

    private static final String DEFAULT_SCL = JettyLauncher.class.getName();

    private HostedModeOptions options;

    public ArgHandlerServer(HostedModeOptions options) {
      this.options = options;
    }

    @Override
    public String[] getDefaultArgs() {
      if (options.isNoServer()) {
        return null;
      } else {
        return new String[] {getTag(), DEFAULT_SCL};
      }
    }

    @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[:args]"};
    }

    @Override
    public boolean setString(String arg) {
      // Supercedes -noserver.
      options.setNoServer(false);
      String sclClassName;
      String sclArgs;
      int idx = arg.indexOf(':');
      if (idx >= 0) {
        sclArgs = arg.substring(idx + 1);
        sclClassName = arg.substring(0, idx);
      } else {
        sclArgs = null;
        sclClassName = arg;
      }
      if (sclClassName.length() == 0) {
        sclClassName = DEFAULT_SCL;
      }
      Throwable t;
      try {
        Class clazz =
            Class.forName(sclClassName, true, Thread.currentThread().getContextClassLoader());
        Class sclClass =
            clazz.asSubclass(ServletContainerLauncher.class);
        options.setServletContainerLauncher(sclClass.newInstance());
        options.setServletContainerLauncherArgs(sclArgs);
        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;
    }
  }

  /**
   * The argument processor.
   */
  protected static class ArgProcessor extends DevModeBase.ArgProcessor {
    public ArgProcessor(HostedModeOptions options) {
      super(options, false);
      registerHandler(new ArgHandlerSuperDevMode(options));
      registerHandler(new ArgHandlerServer(options));
      registerHandler(new ArgHandlerStartupURLs(options));
      registerHandler(new ArgHandlerWarDir(options));
      registerHandler(new ArgHandlerDeployDir(options));
      registerHandler(new ArgHandlerExtraDir(options));
      registerHandler(new ArgHandlerModulePathPrefix(options));
      registerHandler(new ArgHandlerWorkDirOptional(options));
      registerHandler(new ArgHandlerDisableUpdateCheck(options));
      registerHandler(new ArgHandlerMethodNameDisplayMode(options));
      registerHandler(new ArgHandlerSourceLevel(options));
      registerHandler(new ArgHandlerJsInteropMode(options));
      registerHandler(new ArgHandlerIncrementalCompile(options));
      registerHandler(new ArgHandlerModuleName(options) {
        @Override
        public String getPurpose() {
          return super.getPurpose() + " to host";
        }
      });
    }

    @Override
    protected String getName() {
      return DevMode.class.getName();
    }
  }

  /**
   * Concrete class to implement all hosted mode options.
   */
  @SuppressWarnings("serial")
  protected static class HostedModeOptionsImpl extends HostedModeBaseOptionsImpl implements
      HostedModeOptions {
    private File deployDir;
    private File extraDir;
    private int localWorkers;
    private ServletContainerLauncher scl;
    private String sclArgs;
    private boolean sdm = true;
    private File moduleBaseDir;
    private String modulePathPrefix = "";
    private File warDir;

    @Override
    public File getDeployDir() {
      return (deployDir == null) ? new File(warDir, "WEB-INF/deploy") : deployDir;
    }

    @Override
    public File getExtraDir() {
      return extraDir;
    }

    @Override
    public int getLocalWorkers() {
      return localWorkers;
    }

    @Override
    public File getSaveSourceOutput() {
      return null;
    }

    @Override
    public ServletContainerLauncher getServletContainerLauncher() {
      return scl;
    }

    @Override
    public File getModuleBaseDir() {
      return moduleBaseDir;
    }

    @Override
    public String getServletContainerLauncherArgs() {
      return sclArgs;
    }

    @Override
    public File getWarDir() {
      return warDir;
    }

    @Override
    public boolean isSuperDevMode() {
      return sdm;
    }

    @Override
    public void setDeployDir(File deployDir) {
      this.deployDir = deployDir;
    }

    @Override
    public void setExtraDir(File extraDir) {
      this.extraDir = extraDir;
    }

    @Override
    public void setLocalWorkers(int localWorkers) {
      this.localWorkers = localWorkers;
    }

    @Override
    public void setModulePathPrefix(String prefix) {
      if (!prefix.equals(modulePathPrefix)) {
        modulePathPrefix = prefix;
        updateModuleBaseDir();
      }
    }

    @Deprecated
    public void setOutDir(File outDir) {
      setWarDir(outDir);
    }

    @Override
    public void setSaveSourceOutput(File debugDir) {
      throw new UnsupportedOperationException();
    }

    @Override
    public void setSuperDevMode(boolean sdm) {
      this.sdm = sdm;
    }

    @Override
    public void setServletContainerLauncher(ServletContainerLauncher scl) {
      this.scl = scl;
    }

    @Override
    public void setServletContainerLauncherArgs(String args) {
      sclArgs = args;
    }

    @Override
    public void setWarDir(File warDir) {
      this.warDir = warDir;
      updateModuleBaseDir();
    }

    private void updateModuleBaseDir() {
      this.moduleBaseDir = new File(warDir, modulePathPrefix);
    }
  }

  /**
   * Determines whether to start the code server or not.
   */
  protected interface OptionSuperDevMode {
    boolean isSuperDevMode();

    void setSuperDevMode(boolean sdm);
  }

  /**
   * The pattern for files usable as startup URLs.
   */
  private static final Pattern STARTUP_FILE_PATTERN = Pattern.compile(".*\\.(html|jsp)",
      Pattern.CASE_INSENSITIVE);

  /**
   * Startup development mode.
   *
   * @param args command line arguments
   */
  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);
  }

  protected CodeServerListener listener;

  /**
   * Hiding super field because it's actually the same object, just with a stronger type.
   */
  @SuppressWarnings("hiding")
  protected final HostedModeOptions options = (HostedModeOptionsImpl) super.options;

  /**
   * The server that was started.
   */
  private ServletContainer server;

  private final Map startupModules = new LinkedHashMap();

  /**
   * Tracks whether we created a temp workdir that we need to destroy.
   */
  private boolean tempWorkDir = false;

  /**
   * Default constructor for testing; no public API yet.
   */
  protected DevMode() {
  }

  /**
   * Called by the UI on a restart server event.
   */
  @Override
  public void onRestartServer(TreeLogger logger) {
    try {
      server.refresh();
    } catch (UnableToCompleteException e) {
      // ignore, problem already logged
    }
  }

  @Override
  protected HostedModeOptions createOptions() {
    HostedModeOptionsImpl hostedModeOptions = new HostedModeOptionsImpl();
    hostedModeOptions.setIncrementalCompileEnabled(true);
    compilerContext = compilerContextBuilder.options(hostedModeOptions).build();
    return hostedModeOptions;
  }

  @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 doSlowStartup() {
    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;
      }
    }

    TreeLogger branch = getTopLogger().branch(TreeLogger.TRACE, "Linking modules");
    Event slowStartupEvent = SpeedTracerLogger.start(DevModeEventType.SLOW_STARTUP);
    try {
      for (ModuleDef module : startupModules.values()) {
        TreeLogger loadLogger =
            branch.branch(TreeLogger.DEBUG, "Bootstrap link for command-line module '"
                + module.getCanonicalName() + "'");
        link(loadLogger, module);
      }
    } catch (UnableToCompleteException e) {
      // Already logged.
      return false;
    } finally {
      slowStartupEvent.end();
    }
    return true;
  }

  @Override
  protected boolean doStartup() {
    // Background scan the classpath to warm the cache.
    Thread scanThread = new Thread(new Runnable() {
      @Override
      public void run() {
        ResourceOracleImpl.preload(TreeLogger.NULL);
      }
    });
    scanThread.setDaemon(true);
    scanThread.setPriority((Thread.MIN_PRIORITY + Thread.NORM_PRIORITY) / 2);
    scanThread.start();

    File persistentCacheDir = null;
    if (options.getWarDir() != null && !options.getWarDir().getName().endsWith(".jar")) {
      persistentCacheDir = new File(options.getWarDir(), "../");
    }

    if (!super.doStartup(persistentCacheDir)) {
      return false;
    }

    ServletValidator servletValidator = null;
    ServletWriter servletWriter = null;
    File webXml = new File(options.getWarDir(), "WEB-INF/web.xml");
    if (!options.isNoServer()) {
      if (webXml.exists()) {
        servletValidator = ServletValidator.create(getTopLogger(), webXml);
      } else {
        servletWriter = new ServletWriter();
      }
    }

    TreeLogger branch = getTopLogger().branch(TreeLogger.TRACE, "Loading modules");
    try {
      for (String moduleName : options.getModuleNames()) {
        TreeLogger moduleBranch = branch.branch(TreeLogger.TRACE, moduleName);
        ModuleDef module = loadModule(moduleBranch, moduleName, false);
        // Create a hard reference to the module to avoid gc-ing it until we
        // actually load the module from the browser.
        startupModules.put(module.getName(), module);

        if (!options.isNoServer()) {
          validateServletTags(moduleBranch, servletValidator, servletWriter, module);
        }
      }
      if (servletWriter != null) {
        servletWriter.realize(webXml);
      }
    } catch (IOException e) {
      getTopLogger().log(TreeLogger.WARN, "Unable to generate '" + webXml.getAbsolutePath() + "'");
    } catch (UnableToCompleteException e) {
      // Already logged.
      return false;
    }
    return true;
  }

  @Override
  protected int doStartUpServer() {
    // Create the war directory if it doesn't exist
    File warDir = options.getWarDir();
    if (!warDir.exists() && !warDir.mkdirs()) {
      getTopLogger().log(TreeLogger.ERROR, "Unable to create war directory " + warDir);
      return -1;
    }

    Event jettyStartupEvent = SpeedTracerLogger.start(DevModeEventType.JETTY_STARTUP);
    boolean clearCallback = true;
    try {
      ui.setCallback(RestartServerEvent.getType(), this);

      ServletContainerLauncher scl = options.getServletContainerLauncher();

      TreeLogger serverLogger = ui.getWebServerLogger(getWebServerName(), scl.getIconBytes());

      String sclArgs = options.getServletContainerLauncherArgs();
      if (sclArgs != null) {
        if (!scl.processArguments(serverLogger, sclArgs)) {
          return -1;
        }
      }

      isHttps = scl.isSecure();

      // Tell the UI if the web server is secure
      if (isHttps) {
        ui.setWebServerSecure(serverLogger);
      }

      /*
       * TODO: This is a hack to pass the base log level to the SCL. We'll have
       * to figure out a better way to do this for SCLs in general.
       */
      if (scl instanceof JettyLauncher) {
        JettyLauncher jetty = (JettyLauncher) scl;
        jetty.setBaseRequestLogLevel(getBaseLogLevelForUI());
      }
      scl.setBindAddress(options.getBindAddress());

      if (serverLogger.isLoggable(TreeLogger.TRACE)) {
        serverLogger.log(TreeLogger.TRACE, "Starting HTTP on port " + getPort(), null);
      }
      server = scl.start(serverLogger, getPort(), options.getWarDir());
      assert (server != null);
      clearCallback = false;
      return server.getPort();
    } catch (BindException e) {
      System.err.println("Port " + options.getBindAddress() + ':' + 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();
    } finally {
      jettyStartupEvent.end();
      if (clearCallback) {
        // Clear the callback if we failed to start the server
        ui.setCallback(RestartServerEvent.getType(), null);
      }
    }
    return -1;
  }

  @Override
  protected void ensureCodeServerListener() {
    if (listener == null) {
      if (options.isSuperDevMode()) {
        listener = new SuperDevListener(getTopLogger(), options);
      } else {
        listener =
            new BrowserListener(getTopLogger(), options, new OophmSessionHandler(getTopLogger(),
                browserHost));
      }
      listener.start();
    }
  }

  protected String getWebServerName() {
    return options.getServletContainerLauncher().getName();
  }

  @Override
  protected void inferStartupUrls() {
    // Look for launchable files directly under war
    File warDir = options.getWarDir();
    if (!warDir.exists()) {
      // if the war directory doesn't exist, there are no startup files there
      return;
    }
    for (File htmlFile : warDir.listFiles(new FilenameFilter() {
      @Override
      public boolean accept(File dir, String name) {
        return STARTUP_FILE_PATTERN.matcher(name).matches();
      }
    })) {
      options.addStartupURL(htmlFile.getName());
    }
  }

  @Override
  protected ModuleDef loadModule(TreeLogger logger, String moduleName, boolean refresh)
      throws UnableToCompleteException {
    if (startupModules.containsKey(moduleName)) {
      // First load of a startup module; remove from list, no need to refresh.
      return startupModules.remove(moduleName);
    }
    return super.loadModule(logger, moduleName, refresh);
  }

  @Override
  protected URL makeStartupUrl(String url) throws UnableToCompleteException {
    return listener.makeStartupUrl(url);
  }

  @Override
  protected synchronized void produceOutput(TreeLogger logger, StandardLinkerContext linkerStack,
      ArtifactSet artifacts, ModuleDef module, boolean isRelink) throws UnableToCompleteException {
    listener.writeCompilerOutput(linkerStack, artifacts, module, isRelink);
  }

  @Override
  protected void warnAboutNoStartupUrls() {
    getTopLogger().log(TreeLogger.WARN,
        "No startup URLs supplied and no plausible ones found -- use " + "-startupUrl");
  }

  private void validateServletTags(TreeLogger logger, ServletValidator servletValidator,
      ServletWriter servletWriter, ModuleDef module) {
    String[] servletPaths = module.getServletPaths();
    if (servletPaths.length == 0) {
      return;
    }

    TreeLogger servletLogger =
        logger.branch(TreeLogger.DEBUG, "Validating  tags for module '" + module.getName()
            + "'", null, new InstalledHelpInfo("servletMappings.html"));
    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;
      if (servletValidator == null) {
        servletWriter.addMapping(servletClass, servletPath);
      } else {
        servletValidator.validate(servletLogger, servletClass, servletPath);
      }
    }
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy