/FileExtraction/Java_unseen/dlitz_resin/modules/resin/src/com/caucho/server/webapp/WebAppController.java
/*
 * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Resin Open Source is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.server.webapp;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Map;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.ServletContext;

import com.caucho.config.Config;
import com.caucho.config.inject.BeanBuilder;
import com.caucho.config.inject.InjectManager;
import com.caucho.env.deploy.DeployConfig;
import com.caucho.env.deploy.DeployControllerAdmin;
import com.caucho.env.deploy.EnvironmentDeployController;
import com.caucho.inject.Module;
import com.caucho.server.cluster.Server;
import com.caucho.server.host.Host;
import com.caucho.server.util.CauchoSystem;
import com.caucho.util.Alarm;
import com.caucho.vfs.Path;

/**
 * Manages the lifecycle of a web-app. The same WebAppController is used for
 * each web-app instantiation, for example on restarts. It's only created or
 * destroyed if the web-app-deploy indicates it should be created/destroyed.
 * 
 * Each WebAppController corresponds to a DeployNetworkService tag with the
 * name "WebApp/[host]/[context-path]"
 */
@Module
public class WebAppController
  extends EnvironmentDeployController<WebApp,WebAppConfig>
{
  private static final Logger log
    = Logger.getLogger(WebAppController.class.getName());

  private final WebAppContainer _container;

  private WebAppController _parent;

  private String _idTail;
  
  // The context path is the URL prefix for the web-app
  private String _contextPath;
  private String _version = "";

  // Any old version web-app
  private WebAppController _oldWebAppController;
  private long _oldWebAppExpireTime;

  private String _warName;

  // regexp values
  private ArrayList<String> _regexpValues;

  private boolean _isInheritSession;
  private boolean _isDynamicDeploy;

  private ArrayList<Path> _dependPathList = new ArrayList<Path>();

  private String _sourceType = "unknown";

  private final Object _statisticsLock = new Object();

  private volatile long _lifetimeConnectionCount;
  private volatile long _lifetimeConnectionTime;
  private volatile long _lifetimeReadBytes;
  private volatile long _lifetimeWriteBytes;
  private volatile long _lifetimeClientDisconnectCount;

  private WebAppAdmin _admin;

  public WebAppController(String id, 
                          Path rootDirectory, 
                          WebAppContainer container)
  {
    this(id, rootDirectory, container, "/");
  }
  
  public WebAppController(Path rootDirectory,
                          WebAppContainer container,
                          String contextPath)
  {
    this(calculateId(container, contextPath), 
         rootDirectory,
         container,
         contextPath);
  }

  public WebAppController(String id,
                          Path rootDirectory,
                          WebAppContainer container,
                          String contextPath)
  {
    super(id, rootDirectory);

    _container = container;
    
    if (container == null)
      throw new NullPointerException();
    
    if (container.getHost() == null)
      throw new NullPointerException();

    _contextPath = contextPath;
    
    _idTail = calculateIdTail(getId());
  }
  
  private static String calculateIdTail(String id)
  {
    int p1 = id.indexOf('/');
    //String stage = id.substring(0, p1);
    int p2 = id.indexOf('/', p1 + 1);
    //String type = id.substring(p1 + 1, p2);
    int p3 = id.indexOf('/', p2 + 1);
    //String host = id.substring(p2 + 1, p3);
    
    return id.substring(p3);
  }
  private static String calculateId(WebAppContainer container,
                                    String contextPath)
  {
    if (contextPath.equals("") || contextPath.equals("/"))
      contextPath = "/ROOT";
    
    String stage = container.getServer().getStage();
    String hostId = container.getHost().getIdTail();
    
    return stage + "/webapp/" + hostId + contextPath;
  }
  
  public String getName()
  {
    // return getContextPath();
    return _idTail;
  }

  /**
   * Returns the webApp's canonical context path, e.g. /foo-1.0
   */
  public String getContextPath()
  {
    return _contextPath;
  }

  void setContextPath(String contextPath)
  {
    // server/1h10

    _contextPath = contextPath;
  }
  
  protected boolean isVersioning()
  {
    return false;
  }

  /**
   * Returns the webApp's context path
   */
  public String getContextPath(String uri)
  {
    if (getConfig() == null || getConfig().getURLRegexp() == null) {
      return getContextPath();
    }

    Pattern regexp = getConfig().getURLRegexp();
    Matcher matcher = regexp.matcher(uri);

    int tail = 0;
    while (tail >= 0 && tail <= uri.length()) {
      String prefix = uri.substring(0, tail);

      matcher.reset(prefix);

      if (matcher.find() && matcher.start() == 0)
        return matcher.group();

      if (tail < uri.length()) {
        tail = uri.indexOf('/', tail + 1);
        if (tail < 0)
          tail = uri.length();
      }
      else
        break;
    }

    return _contextPath;
  }

  /**
   * Sets the war name prefix.
   */
  public void setWarName(String warName)
  {
    _warName = warName;
  }

  /**
   * Gets the war name prefix.
   */
  public String getWarName()
  {
    return _warName;
  }

  /**
   * Gets the URL
   */
  public String getURL()
  {
    return getHost().getURL() + _contextPath;
  }

  /**
   * Returns the parent controller.
   */
  public WebAppController getParent()
  {
    return _parent;
  }

  /**
   * Returns the web-app container.
   */
  public WebAppContainer getContainer()
  {
    return _container;
  }
  
  public Server getWebManager()
  {
    return _container.getServer();
  }

  /**
   * Sets the parent controller.
   */
  public void setParentWebApp(WebAppController parent)
  {
    _parent = parent;
  }

  /**
   * Returns the containing host.
   */
  public Host getHost()
  {
    return _container.getHost();
  }

  /**
   * Returns the source (for backwards compatibility)
   */
  public String getSourceType()
  {
    return _sourceType;
  }

  /**
   * Sets the source (for backwards compatibility)
   */
  public void setSourceType(String type)
  {
    _sourceType = type;
  }

  /**
   * Sets the regexp values.
   */
  public void setRegexpValues(ArrayList<String> values)
  {
    _regexpValues = values;
  }

  /**
   * True for inherit-session webApps.
   */
  public boolean isInheritSession()
  {
    return _isInheritSession;
  }

  /**
   * True for inherit-session
   */
  public void setInheritSession(boolean inheritSession)
  {
    _isInheritSession = inheritSession;
  }

  /**
   * Returns the webApp object.
   */
  public WebApp getWebApp()
  {
    return getDeployInstance();
  }

  /**
   * Set true for a dynamically deployed webApp.
   */
  public void setDynamicDeploy(boolean isDynamicDeploy)
  {
    _isDynamicDeploy = isDynamicDeploy;
  }

  /**
   * Returns true for a dynamically deployed webApp.
   */
  public boolean isDynamicDeploy()
  {
    return _isDynamicDeploy;
  }

  @Override
  protected String getMBeanTypeName()
  {
    return "WebApp";
  }

  @Override
  protected String getMBeanId()
  {
    String name = getName();
    
    if (name.equals("/ROOT"))
      return "/";
    else
      return name;
  }

  /**
   * Sets the version id.
   */
  protected void setVersion(String version)
  {
    _version = version;
  }

  /**
   * Gets the version id.
   */
  public String getVersion()
  {
    return _version;
  }

  /**
   * versionAlias is true if a versioned web-app is currently acting
   * as the primary web-app.
   */
  public void setVersionAlias(boolean isVersionAlias)
  {
  }

  /**
   * versionAlias is true if a versioned web-app is currently acting
   * as the primary web-app.
   */
  public boolean isVersionAlias()
  {
    return false;
  }

  /**
   * Sets the old version web-app.
   */
  public void setOldWebApp(WebAppController oldWebApp, long expireTime)
  {
    _oldWebAppController = oldWebApp;
    _oldWebAppExpireTime = expireTime;

    WebApp webApp = getDeployInstance();

    if (webApp != null)
      webApp.setOldWebApp(oldWebApp.request(), expireTime);
  }

  /**
   * Adds a version to the controller list.
   */
  /*
  protected WebAppController addVersion(WebAppController controller)
  {
    WebAppVersioningController versioningController
      = new WebAppVersioningController(getContextPath());

    versioningController.addVersion(this);
    versioningController.addVersion(controller);

    return versioningController;
  }
  */

  /**
   * Returns the deploy admin.
   */
  @Override
  protected DeployControllerAdmin<?> getDeployAdmin()
  {
    return _admin;
  }

  @Override
  protected void initEnd()
  {
    if (! getId().startsWith("error/") && ! isVersioning()) {
      _admin = new WebAppAdmin(this);
    }
    
    super.initEnd();
  }

  /**
   * Returns the admin.
   */
  public WebAppAdmin getAdmin()
  {
    return _admin;
  }

  /**
   * Returns true if the controller matches.
   */
  @Override
  public boolean isNameMatch(String url)
  {
    if (CauchoSystem.isCaseInsensitive()) {
      if (url.equalsIgnoreCase(_contextPath))
        return true;
    }
    else {
      if (url.equals(_contextPath))
        return true;
    }

    if (getConfig() != null)
      return getConfig().isUrlMatch(url);
    else
      return false;
  }

  /**
   * Returns the var.
   */
  public Var getVar()
  {
    return new Var();
  }

  /**
   * Any extra steps needed to deploy the webApp.
   */
  protected void protectedWebApp()
    throws Exception
  {
    Path root = getRootDirectory();
    // XXX: need to re-add to control.
    root.lookup("WEB-INF").chmod(0750);
    root.lookup("META-INF").chmod(0750);
  }

  /**
   * Adding any dependencies.
   */
  @Override
  protected void addDependencies()
  {
    super.addDependencies();
  }

  /**
   * Adds a dependent file.
   */
  public void addDepend(Path path)
  {
    _dependPathList.add(path);
  }

  /**
   * Initialize the controller.
   */
  @Override
  protected void initBegin()
  {
    super.initBegin();
  }

  @Override
  protected void fillInitList(ArrayList<DeployConfig> initList)
  {
    if (_container != null) {
      for (WebAppConfig config : _container.getWebAppDefaultList()) {
        if (config.getPrologue() != null)
          initList.add(config.getPrologue());
      }

      for (WebAppConfig config : _container.getWebAppDefaultList())
        initList.add(config);
    }

    super.fillInitList(initList);
  }

  /**
   * Instantiate the webApp.
   */
  @Override
  protected WebApp instantiateDeployInstance()
  {
    return new WebApp(this);
  }

  /**
   * Creates the webApp.
   */
  @Override
  protected void configureInstanceVariables(WebApp webApp)
  {
    InjectManager beanManager = InjectManager.create();
    BeanBuilder<WebApp> factory = beanManager.createBeanFactory(WebApp.class);
    factory.type(WebApp.class);
    factory.type(ServletContext.class);
    // factory.stereotype(CauchoDeploymentLiteral.create());

    beanManager.addBean(factory.singleton(webApp));

    Config.setProperty("webApp", getVar());
    Config.setProperty("app", getVar());

    webApp.setRegexp(_regexpValues);
    webApp.setDynamicDeploy(isDynamicDeploy());

    if (_oldWebAppController != null
        && Alarm.getCurrentTime() < _oldWebAppExpireTime) {
      webApp.setOldWebApp(_oldWebAppController.request(),
                          _oldWebAppExpireTime);
    }

    super.configureInstanceVariables(webApp);
  }

  @Override
  protected void onStartComplete()
  {
    super.onStartComplete();
    
    clearCache();
  }

  @Override
  protected void onStop()
  {
    super.onStop();
    
    clearCache();
  }

  /**
   * Clears the 
   */
  public void clearCache()
  {
    _container.clearCache();
  }

  /**
   * Destroy the controller
   */
  @Override
  public boolean destroy()
  {
    if (! super.destroy())
      return false;

    if (_container != null)
      _container.removeWebApp(this);

    return true;
  }

  @Override
  protected void extendJMXContext(Map<String,String> context)
  {
    context.put("WebApp", getMBeanId());
  }

  /**
   * Override to prevent removing of special files.
   */
  @Override
  protected void removeExpandFile(Path path, String relPath)
    throws IOException
  {
    if (relPath.equals("./WEB-INF/resin-web.xml"))
      return;

    super.removeExpandFile(path, relPath);
  }

  public long getLifetimeConnectionCount()
  {
    return _lifetimeConnectionCount;
  }

  public long getLifetimeConnectionTime()
  {
    return _lifetimeConnectionTime;
  }

  public long getLifetimeReadBytes()
  {
    return _lifetimeReadBytes;
  }

  public long getLifetimeWriteBytes()
  {
    return _lifetimeWriteBytes;
  }

  public long getLifetimeClientDisconnectCount()
  {
    return _lifetimeClientDisconnectCount;
  }

  /**
   * Update statistics with the results of one request.
   *
   * @param milliseconds the number of millesconds for the request
   * @param readBytes the number of bytes read
   * @param writeBytes the number of bytes written
   * @param isClientDisconnect true if the request ended with a client DisconnectException
   */
  public void updateStatistics(long milliseconds,
                               int readBytes,
                               int writeBytes,
                               boolean isClientDisconnect)
  {
    synchronized (_statisticsLock) {
      _lifetimeConnectionCount++;
      _lifetimeConnectionTime += milliseconds;
      _lifetimeReadBytes += readBytes;
      _lifetimeWriteBytes += writeBytes;
      if (isClientDisconnect)
        _lifetimeClientDisconnectCount++;
    }
  }

  @Override
  protected Logger getLog()
  {
    return log;
  }

  /**
   * Returns a printable view.
   */
  @Override
  public String toString()
  {
    if (com.caucho.util.Alarm.isTest())
      return getClass().getSimpleName() + "[" + getId() + "]";
    else
      return getClass().getSimpleName() + "$" + System.identityHashCode(this) + "[" + getId() + "]";
  }

  /**
   * EL variables for the app.
   */
  public class Var {
    public String getUrl()
    {
      return WebAppController.this.getURL();
    }

    public String getId()
    {
      String id = WebAppController.this.getId();

      if (id != null)
        return id;
      else
        return WebAppController.this.getContextPath();
    }

    public String getName()
    {
      String name;

      if (getWarName() != null)
        name = getWarName();
      else
        name = getContextPath();

      if (name.startsWith("/"))
        return name;
      else
        return "/" + name;
    }

    public Path getAppDir()
    {
      return WebAppController.this.getRootDirectory();
    }

    public Path getDocDir()
    {
      return WebAppController.this.getRootDirectory();
    }

    public Path getRoot()
    {
      return WebAppController.this.getRootDirectory();
    }

    public Path getRootDir()
    {
      return WebAppController.this.getRootDirectory();
    }

    public String getContextPath()
    {
      return WebAppController.this.getContextPath();
    }

    public ArrayList<String> getRegexp()
    {
      return _regexpValues;
    }

    public String getVersion()
    {
      return _version;
    }

    @Override
    public String toString()
    {
      return "WebApp[" + getId() + "]";
    }
  }
}
