Package com.google.gwt.dev.shell

Source Code of com.google.gwt.dev.shell.GWTShellServlet

/*
* 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.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.util.tools.Utility;

import org.apache.commons.collections.map.AbstractReferenceMap;
import org.apache.commons.collections.map.ReferenceIdentityMap;
import org.apache.commons.collections.map.ReferenceMap;

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.
   */
  @SuppressWarnings("unchecked")
  private final Map<String, ModuleDef> loadedModulesByName = new ReferenceMap(
      AbstractReferenceMap.HARD, AbstractReferenceMap.WEAK);

  /**
   * 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.
   */
  @SuppressWarnings("unchecked")
  private final Map<ModuleDef, Map<String, HttpServlet>> loadedServletsByModuleAndClassName = new ReferenceIdentityMap(
      AbstractReferenceMap.WEAK, AbstractReferenceMap.HARD, true);

  private final Map<String, String> mimeTypes = new HashMap<String, String>();

  /**
   * Only for backwards compatibility. Shouldn't we remove this now?
   */
  @SuppressWarnings("unchecked")
  private final Map<String, ModuleDef> modulesByServletPath = new ReferenceMap(
      AbstractReferenceMap.HARD, AbstractReferenceMap.WEAK);

  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.
    String msg = "The development shell servlet received a request for '"
        + partialPath + "' in module '" + moduleName + ".gwt.xml' ";
    logger = logger.branch(TreeLogger.TRACE, msg, 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) {
        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);
      msg = "Guessed MIME type '" + mimeType + "'";
      logger.log(TreeLogger.TRACE, msg, 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 {
    logger.log(TreeLogger.TRACE,
        "Generating a script selection script for module " + moduleName);

    StandardLinkerContext context = new StandardLinkerContext(logger,
        getModuleDef(logger, moduleName), new JJSOptionsImpl());
    HostedModeLinker linker = new HostedModeLinker();
    return linker.generateSelectionScript(logger, context,
        context.getArtifacts());
  }

  /**
   * 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
        // ServeletContext.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;
    }
  }
}
TOP

Related Classes of com.google.gwt.dev.shell.GWTShellServlet

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.