/FileExtraction/Java_unseen/dlitz_resin/modules/resin/src/com/caucho/server/host/HostContainer.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.host;

import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.DispatcherType;
import javax.servlet.FilterChain;

import com.caucho.config.ConfigException;
import com.caucho.env.deploy.DeployContainer;
import com.caucho.env.deploy.DeployContainerApi;
import com.caucho.lifecycle.Lifecycle;
import com.caucho.loader.EnvironmentClassLoader;
import com.caucho.make.AlwaysModified;
import com.caucho.server.cluster.Server;
import com.caucho.server.dispatch.InvocationBuilder;
import com.caucho.server.dispatch.ErrorFilterChain;
import com.caucho.server.dispatch.Invocation;
import com.caucho.server.e_app.EarConfig;
import com.caucho.server.rewrite.RewriteDispatch;
import com.caucho.server.webapp.AccessLogFilterChain;
import com.caucho.server.webapp.WebApp;
import com.caucho.server.webapp.WebAppConfig;
import com.caucho.server.webapp.WebAppFilterChain;
import com.caucho.vfs.Path;
import com.caucho.vfs.Vfs;

/**
 * Resin's host container implementation.
 */
public class HostContainer implements InvocationBuilder {
  private static final Logger log
    = Logger.getLogger(HostContainer.class.getName());
  
  // the owning servlet container
  private final Server _server;
  
  // The environment class loader
  private EnvironmentClassLoader _classLoader;

  private Host _errorHost;

  private String _url = "";
  
  // The root directory.
  private Path _rootDir;

  // dispatch mapping
  private RewriteDispatch _rewriteDispatch;

  // List of default host configurations
  private ArrayList<HostConfig> _hostDefaultList = new ArrayList<HostConfig>();

  // The host deploy
  private DeployContainer<HostController> _hostDeploy
    = new DeployContainer<HostController>(HostController.class);
  
  // Cache of hosts
  private ConcurrentHashMap<String,HostController> _hostMap
    = new ConcurrentHashMap<String,HostController>();

  // Regexp host
  private ArrayList<HostConfig> _hostRegexpList = new ArrayList<HostConfig>();

  // List of default webApp configurations
  private ArrayList<WebAppConfig> _webAppDefaultList
    = new ArrayList<WebAppConfig>();
  
  // List of default ear configurations
  private ArrayList<EarConfig> _earDefaultList
    = new ArrayList<EarConfig>();

  // lifecycle
  private final Lifecycle _lifecycle = new Lifecycle();

  /**
   * Creates the webApp with its environment loader.
   */
  public HostContainer(Server server)
  {
    _server = server;
    _classLoader = server.getClassLoader();

    _rootDir = Vfs.lookup();

    /*
    _errorHost = createErrorHost();
    
    if (_errorHost == null)
      throw new NullPointerException();
      */
  }
  
  public String getStageTag()
  {
    return "production";
  }

  /**
   * Gets the environment class loader.
   */
  public ClassLoader getClassLoader()
  {
    return _classLoader;
  }

  /**
   * Sets the URL for the default server.
   */
  public void setURL(String url)
  {
    _url = url;
  }

  /**
   * Gets the URL for the default server.
   */
  public String getURL()
  {
    return _url;
  }

  /**
   * Gets the dispatch server.
   */
  public Server getServer()
  {
    return _server;
  }

  /**
   * Gets the root directory.
   */
  public Path getRootDirectory()
  {
    return _rootDir;
  }

  /**
   * Sets the root directory.
   */
  public void setRootDirectory(Path path)
  {
    _rootDir = path;
  }

  /**
   * Sets the root directory (obsolete).
   * @deprecated
   */
  public void setRootDir(Path path)
  {
    setRootDirectory(path);
  }

  /**
   * Adds a host default
   */
  public void addHostDefault(HostConfig init)
  {
    _hostDefaultList.add(init);
  }

  /**
   * Returns the list of host defaults
   */
  public ArrayList<HostConfig> getHostDefaultList()
  {
    return _hostDefaultList;
  }

  /**
   * Creates a host deploy
   */
  public HostExpandDeployGenerator createHostDeploy()
  {
    String id = getStageTag() + "/host";
    
    return new HostExpandDeployGenerator(id, _hostDeploy, this);
  }

  public DeployContainerApi<HostController> getHostDeployContainer()
  {
    return _hostDeploy;
  }
  
  /**
   * Adds a host deploy
   */
  public void addHostDeploy(HostExpandDeployGenerator hostDeploy)
  {
    _hostDeploy.add(hostDeploy);
  }

  /**
   * Adds a host.
   */
  public void addHost(HostConfig hostConfig)
  {
    if (hostConfig.getRegexp() != null) {
      _hostDeploy.add(new HostRegexpDeployGenerator(_hostDeploy, this, hostConfig));
      return;
    }

    HostSingleDeployGenerator deploy;
    deploy = new HostSingleDeployGenerator(_hostDeploy, this, hostConfig);
    
    _hostDeploy.add(deploy);
  }

  /**
   * Adds a web-app default
   */
  public void addWebAppDefault(WebAppConfig init)
  {
    _webAppDefaultList.add(init);
  }

  /**
   * Returns the list of web-app defaults
   */
  public ArrayList<WebAppConfig> getWebAppDefaultList()
  {
    return _webAppDefaultList;
  }

  /**
   * Adds an ear default
   */
  public void addEarDefault(EarConfig init)
  {
    _earDefaultList.add(init);
  }

  /**
   * Returns the list of ear defaults
   */
  public ArrayList<EarConfig> getEarDefaultList()
  {
    return _earDefaultList;
  }

  /**
   * Adds rewrite-dispatch.
   */
  public RewriteDispatch createRewriteDispatch()
  {
    if (_rewriteDispatch == null) {
      _rewriteDispatch = new RewriteDispatch(getErrorWebApp());
    }

    return _rewriteDispatch;
  }

  /**
   * Clears the cache.
   */
  public void clearCache()
  {
    _hostMap.clear();
    
    getServer().clearCache();
  }

  /**
   * Creates the invocation.
   */
  @Override
  public Invocation buildInvocation(Invocation invocation)
  {
    String rawHost = invocation.getHost();
    int rawPort = invocation.getPort();

    String hostName;

    if (rawHost == null)
      hostName = "";
    else
      hostName = DomainName.fromAscii(rawHost);

    invocation.setHostName(hostName);

    boolean isAlwaysModified;

    Host host = getHost(hostName, rawPort);

    if (host != null) {
      invocation = host.buildInvocation(invocation);
      isAlwaysModified = false;
    }
    else {
      FilterChain chain = new ErrorFilterChain(404);
      invocation.setFilterChain(chain);
      invocation.setWebApp(getErrorWebApp());
      isAlwaysModified = true;
    }

    if (_rewriteDispatch != null) {
      String url;

      if (invocation.isSecure())
        url = "https://" + hostName + invocation.getURI();
      else
        url = "http://" + hostName + invocation.getURI();

      String queryString = invocation.getQueryString();

      FilterChain chain = invocation.getFilterChain();
      FilterChain rewriteChain = _rewriteDispatch.map(DispatcherType.REQUEST,
                                                      url,
                                                      queryString,
                                                      chain);

      if (rewriteChain != chain) {
        Server server = getServer();
        WebApp webApp = server.getDefaultWebApp();
        invocation.setWebApp(webApp);

        if (webApp != null) {
          rewriteChain = new WebAppFilterChain(rewriteChain, webApp);

          if (webApp.getAccessLog() != null)
            rewriteChain = new AccessLogFilterChain(rewriteChain, webApp);
        }

        invocation.setFilterChain(rewriteChain);
        isAlwaysModified = false;
      }
    }

    if (isAlwaysModified)
      invocation.setDependency(AlwaysModified.create());

    return invocation;
  }

  public HostController []getHostList()
  {
    return _hostDeploy.getControllers();
  }

  /**
   * Returns the matching host.
   */
  public Host getHost(String hostName, int port)
  {
    try {
      HostController controller = findHost(hostName, port);

      if (controller != null)
        return controller.request();
      else
        return null;
    } catch (Throwable e) {
      log.log(Level.WARNING, e.toString(), e);
      
      return null;
    }
  }

  /**
   * Returns the matching host.
   */
  public HostController getHostController(String hostName, int port)
  {
    try {
      return findHost(hostName, port);
    } catch (Throwable e) {
      log.log(Level.WARNING, e.toString(), e);
      
      return null;
    }
  }

  /**
   * Finds the best matching host entry.
   */
  private HostController findHost(String rawHost, int rawPort)
    throws Exception
  {
    if (rawHost == null)
      rawHost = "";

    int p = rawHost.lastIndexOf(':');
    int q = rawHost.lastIndexOf(']');

    String shortHost = rawHost;

    if (p > 0 && q < p)
      shortHost = rawHost.substring(0, p);
    
    if (shortHost.startsWith("["))
      shortHost = Host.calculateCanonicalIPv6(shortHost);
    
    String fullHost = shortHost + ':' + rawPort;

    HostController hostController = null;
    
    hostController = _hostMap.get(fullHost);

    if (hostController != null && ! hostController.getState().isDestroyed())
      return hostController;

    if (hostController == null || hostController.getState().isDestroyed())
      hostController = _hostMap.get(shortHost);

    if (hostController == null || hostController.getState().isDestroyed())
      hostController = findHostController(fullHost);

    if (hostController == null || hostController.getState().isDestroyed())
      hostController = findHostController(shortHost);

    if (hostController == null || hostController.getState().isDestroyed())
      hostController = findHostController(":" + rawPort);

    if (hostController == null || hostController.getState().isDestroyed())
      hostController = findHostController("");

    if (hostController == null) {
    }
    else if (! hostController.getState().isDestroyed())
      _hostMap.put(fullHost, hostController);
    else {
      hostController = null;
      _hostMap.remove(fullHost);
    }

    return hostController;
  }

  /**
   * Returns the HostController based on a host name.  The canonical name
   * and the host aliases are tested for the match.
   *
   * @param hostName name to match on
   * @return the host entry or null if none are found.
   */
  private HostController findHostController(String hostName)
    throws Exception
  {
    HostController controller = _hostDeploy.findController(hostName);
    
    return controller;
  }

  /**
   * Returns the error webApp during startup.
   */
  public WebApp getErrorWebApp()
  {
    Host errorHost = getErrorHost();
    
    if (errorHost != null)
      return errorHost.getWebAppContainer().getErrorWebApp();
    else
      return null;
  }
  
  private Host createErrorHost()
  {
    Thread thread = Thread.currentThread();
    ClassLoader loader = thread.getContextClassLoader();
    try {
      thread.setContextClassLoader(_classLoader);

      Path rootDirectory = Vfs.lookup("memory:/error");
      HostController controller
        = new HostController("error/host/error", rootDirectory, "error", 
                             null, this, null);
      controller.init();
      controller.startOnInit();
      controller.start();
      
      Host host = controller.request();

      return host;
    } catch (Exception e) {
      throw ConfigException.create(e);
    } finally {
      thread.setContextClassLoader(loader);
    }
  }
  
  private Host getErrorHost()
  {
    if (! _lifecycle.isActive())
      return null;
    
    Host defaultHost = getHost("", 0);
    
    if (defaultHost != null)
      return defaultHost;
    else {
      if (_errorHost == null)
        _errorHost = createErrorHost();

      return _errorHost;
    }
  }

  /**
   * Starts the container.
   */
  public void start()
  {
    if (! _lifecycle.toActive())
      return;

    _lifecycle.toActive();

    _hostDeploy.start();
  }

  /**
   * Stops the container.
   */
  public void stop()
  {
    if (! _lifecycle.toStop())
      return;

    _hostDeploy.stop();
    
    // _classLoader.stop();
  }

  /**
   * Closes the container.
   */
  public void destroy()
  {
    stop();

    if (! _lifecycle.toDestroy())
      return;

    _hostDeploy.destroy();

    _classLoader.destroy();
  }
}
