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

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.logging.LogManager;

import javax.enterprise.context.spi.CreationalContext;

import com.caucho.cloud.topology.CloudCluster;
import com.caucho.cloud.topology.CloudServer;
import com.caucho.cloud.topology.CloudSystem;
import com.caucho.cloud.topology.TopologyService;
import com.caucho.config.ConfigException;
import com.caucho.config.program.ConfigProgram;
import com.caucho.config.types.RawString;
import com.caucho.lifecycle.Lifecycle;
import com.caucho.loader.Environment;
import com.caucho.network.listen.StreamSocketLink;
import com.caucho.server.cluster.Server;
import com.caucho.server.host.Host;
import com.caucho.server.host.HostConfig;
import com.caucho.server.http.HttpRequest;
import com.caucho.server.resin.Resin;
import com.caucho.server.webapp.*;
import com.caucho.util.L10N;
import com.caucho.vfs.Vfs;
import com.caucho.vfs.VfsStream;
import com.caucho.vfs.WriteStream;

/**
 * Embeddable version of the Resin server.
 *
 * <code><pre>
 * ResinEmbed resin = new ResinEmbed();
 *
 * HttpEmbed http = new HttpEmbed(8080);
 * resin.addPort(http);
 *
 * WebAppEmbed webApp = new WebAppEmbed("/foo", "/home/ferg/ws/foo");
 *
 * resin.addWebApp(webApp);
 *
 * resin.start();
 *
 * resin.join();
 * </pre></code>
 */
public class ResinEmbed
{
  private static final L10N L = new L10N(ResinEmbed.class);

  private static final String EMBED_CONF
    = "classpath:com/caucho/resin/resin-embed.xml";

  private final Resin _resin;
  private String _configFile = EMBED_CONF;

  private CloudCluster _cluster;
  
  private Host _host;
  private Server _server;

  private String _serverHeader;

  private final ArrayList<BeanEmbed> _beanList
    = new ArrayList<BeanEmbed>();

  private final ArrayList<WebAppEmbed> _webAppList
    = new ArrayList<WebAppEmbed>();
  
  private final ArrayList<PortEmbed> _portList
    = new ArrayList<PortEmbed>();

  private Lifecycle _lifecycle = new Lifecycle();
  private boolean _isConfig;
  private boolean _isDevelopmentMode;

  /**
   * Creates a new resin server.
   */
  public ResinEmbed()
  {
    _resin = Resin.create("embed");
    _resin.setEmbedded(true);
    _resin.setRootDirectory(Vfs.lookup());
  }

  /**
   * Creates a new resin server.
   */
  public ResinEmbed(String configFile)
  {
    this();
    
    setConfig(configFile);
  }

  //
  // Configuration/Injection methods
  //
  
  /**
   * Sets the root directory
   */
  public void setRootDirectory(String rootUrl)
  {
    _resin.setRootDirectory(Vfs.lookup(rootUrl));
  }

  /**
   * Sets the config file
   */
  public void setConfig(String configFile)
  {
    _configFile = configFile;
  }

  /**
   * Adds a port to the server, e.g. a HTTP port.
   *
   * @param port the embedded port to add to the server
   */
  public void addPort(PortEmbed port)
  {
    _portList.add(port);
    
    if (_server != null)
      port.bindTo(_server);
    
    /*
    // server/1e00
    if (_clusterServer == null)
      initConfig(_configFile);
    
    // XXX: port.bindTo(_clusterServer);
     */
  }

  /**
   * Sets a list of ports.
   */
  public void setPorts(PortEmbed []ports)
  {
    for (PortEmbed port : ports)
      addPort(port);
  }

  /**
   * Sets the server header
   */
  public void setServerHeader(String serverName)
  {
    _serverHeader = serverName;
  }

  /**
   * Adds a web-app to the server.
   */
  public void addWebApp(WebAppEmbed webApplication)
  {
    if (webApplication == null) {
      throw new NullPointerException();
    }

    _webAppList.add(webApplication);

    // If the server has already been started, the web application will need to
    // be explicitly deployed.
    if (_lifecycle.isActive()) {
      deployWebApplication(webApplication);
    }
  }
  
  public void removeWebApp(WebAppEmbed webApplication)
  {
    if (webApplication == null) {
      throw new NullPointerException();
    }

    _webAppList.remove(webApplication);

    // If the server has already been started, the web application will need
    // to be undeployed.
    if (_lifecycle.isActive()) {
      undeployWebApplication(webApplication);
    }
  }

  /**
   * Sets a list of webapps
   */
  public void setWebApps(WebAppEmbed []webApps)
  {
    for (WebAppEmbed webApp : webApps)
      addWebApp(webApp);
  }

  /**
   * Adds a web bean.
   */
  public void addBean(BeanEmbed bean)
  {
    _beanList.add(bean);

    if (_lifecycle.isActive()) {
      Thread thread = Thread.currentThread();
      ClassLoader oldLoader = thread.getContextClassLoader();

      try {
        thread.setContextClassLoader(_server.getClassLoader());

        bean.configure();
      } catch (RuntimeException e) {
        throw e;
      } catch (Throwable e) {
        throw ConfigException.create(e);
      } finally {
        thread.setContextClassLoader(oldLoader);
      }
    }
  }
  
  public void setDevelopmentMode(boolean isDevelopment)
  {
    _isDevelopmentMode = isDevelopment;
  }

  /**
   * Initialize the Resin environment
   */
  public void initializeEnvironment()
  {
    Environment.initializeEnvironment();
  }

  /**
   * Set log handler
   */
  public void resetLogManager()
  {
    LogManager.getLogManager().reset();
  }

  public void addScanRoot() {
    _resin.getClassLoader().addScanRoot();
  }

  //
  // Lifecycle
  //

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

    Thread thread = Thread.currentThread();
    ClassLoader oldLoader = thread.getContextClassLoader();

    try {
      Environment.initializeEnvironment();

      _resin.preConfigureInit();
      
      thread.setContextClassLoader(_resin.getClassLoader());

      initConfig(_configFile);

      _server = _resin.createServer();

      thread.setContextClassLoader(_server.getClassLoader());

      if (_serverHeader != null)
        _server.setServerHeader(_serverHeader);
      
      _server.setDevelopmentModeErrorPage(_isDevelopmentMode);

      for (BeanEmbed beanEmbed : _beanList) {
        beanEmbed.configure();
      }
      
      for (PortEmbed port : _portList) {
        port.bindTo(_server);
      }

      _resin.start();

      _host = _server.getHost("", 0);
      if (_host == null) {
        HostConfig hostConfig = new HostConfig();
        _server.addHost(hostConfig);
        _host = _server.getHost("", 0);
      }

      if (_host == null) {
        throw new ConfigException(L.l("ResinEmbed requires a <host> to be configured in the resin.xml, because the webapps must belong to a host."));
      }

      for (WebAppEmbed webApplication : _webAppList) {
        deployWebApplication(webApplication);
      }
    } catch (Exception e) {
      throw ConfigException.create(e);
    } finally {
      thread.setContextClassLoader(oldLoader);
    }
  }

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

    try {
      _resin.stop();
    } catch (RuntimeException e) {
      throw e;
    } catch (Throwable e) {
      throw ConfigException.create(e);
    }
  }

  /**
   * Waits for the Resin process to exit.
   */
  public void join()
  {
    while (! _resin.isClosed()) {
      try {
        Thread.sleep(1000);
      } catch (Exception e) {
      }
    }
  }

  /**
   * Destroys the embedded server
   */
  public void destroy()
  {
    if (! _lifecycle.toDestroy())
      return;

    try {
      _resin.close();
    } catch (RuntimeException e) {
      throw e;
    } catch (Throwable e) {
      throw ConfigException.create(e);
    }
  }

  //
  // Testing API
  //

  /**
   * Sends a HTTP request to the embedded server for testing.
   *
   * @param is input stream containing the HTTP request
   * @param os output stream to receive the request
   */
  public void request(InputStream is, OutputStream os)
    throws IOException
  {
    start();

    TestConnection conn = createConnection();

    conn.request(is, os);
  }

  /**
   * Sends a HTTP request to the embedded server for testing.
   *
   * @param httpRequest HTTP request string, e.g. "GET /test.jsp"
   * @param os output stream to receive the request
   */
  public void request(String httpRequest, OutputStream os)
    throws IOException
  {
    start();

    TestConnection conn = createConnection();

    conn.request(httpRequest, os);
  }

  /**
   * Sends a HTTP request to the embedded server for testing.
   *
   * @param httpRequest HTTP request string, e.g. "GET /test.jsp"
   *
   * @return the HTTP result string
   */
  public String request(String httpRequest)
    throws IOException
  {
    start();

    TestConnection conn = createConnection();

    return conn.request(httpRequest);
  }

  /**
   * Creates a test connection to the server
   */
  private TestConnection createConnection()
  {
    TestConnection conn = new TestConnection();

    return conn;
  }

  //
  // utilities
  //

  private void initConfig(String configFile)
  {
    try {
      if (_isConfig)
        return;
      _isConfig = true;

      _resin.configureFile(Vfs.lookup(configFile));
    } catch (Exception e) {
      throw ConfigException.create(e);
    }
    
    CloudSystem cloudSystem = TopologyService.getCurrent().getSystem();

    if (cloudSystem.getClusterList().length == 0)
      throw new ConfigException(L.l("Resin needs at least one defined <cluster>"));

    String clusterId = cloudSystem.getClusterList()[0].getId();

    _cluster = cloudSystem.findCluster(clusterId);

    if (_cluster.getPodList().length == 0)
      throw new ConfigException(L.l("Resin needs at least one defined <server> or <cluster-pod>"));

    if (_cluster.getPodList()[0].getServerList().length == 0)
      throw new ConfigException(L.l("Resin needs at least one defined <server>"));

    CloudServer cloudServer = _cluster.getPodList()[0].getServerList()[0]; 
    // _clusterServer = cloudServer.getData(ClusterServer.class);
    
    if (cloudServer != null)
      _resin.setServerId(cloudServer.getId());
  }

  private void deployWebApplication(WebAppEmbed webApplication)
  {
    Thread thread = Thread.currentThread();
    ClassLoader oldLoader = thread.getContextClassLoader();

    try {
      // Web applications need to be loaded under the host class-loader.
      thread.setContextClassLoader(_host.getClassLoader());

      WebAppConfig configuration = new WebAppConfig();
      configuration.setContextPath(webApplication.getContextPath());
      configuration.setRootDirectory(new RawString(webApplication
          .getRootDirectory()));

      if (webApplication.getArchivePath() != null) {
        configuration.setArchivePath(new RawString(webApplication
            .getArchivePath()));
      }

      configuration.addBuilderProgram(new WebAppProgram(webApplication));

      //_host.getWebAppContainer().addWebApp(configuration);
      
      WebAppContainer container = _host.getWebAppContainer();
      
      WebAppSingleDeployGenerator deployGenerator = container.createDeployGenerator(configuration);
      webApplication.setDeployGenerator(deployGenerator);
      
      container.addWebApp(deployGenerator);
      
    } catch (Exception e) {
      throw ConfigException.create(e);
    } finally {
      thread.setContextClassLoader(oldLoader);
    }
  }
  
  private void undeployWebApplication(WebAppEmbed webApplication)
  {
    //webApplication.getWebApp().getController().destroy();
    //webApplication.getWebApp().destroy();
    _host.getWebAppContainer().removeWebApp(webApplication.getDeployGenerator());
  }

  protected void finalize()
    throws Throwable
  {
    super.finalize();

    destroy();
  }

  /**
   * Basic embedding server.
   */
  public static void main(String []args)
    throws Exception
  {
    ResinEmbed resin = new ResinEmbed();

    for (int i = 0; i < args.length; i++) {
      if (args[i].startsWith("--port=")) {
        int port = Integer.parseInt(args[i].substring("--port=".length()));

        HttpEmbed http = new HttpEmbed(port);
        resin.addPort(http);
      }
      else if (args[i].startsWith("--config=")) {
        String config = args[i].substring("--config=".length());

        resin.setConfig(config);
      }
      else if (args[i].startsWith("--deploy:")) {
        String valueString = args[i].substring("--deploy:".length());

        String []values = valueString.split("[=,]");

        String role = null;

        for (int j = 0; j < values.length; j += 2) {
          if (values[j].equals("role"))
            role = values[j + 1];
        }

        WebAppLocalDeployEmbed webApp = new WebAppLocalDeployEmbed();
        if (role != null)
          webApp.setRole(role);

        resin.addWebApp(webApp);
      }
    }

    resin.resetLogManager();

    resin.start();

    resin.join();
  }

  /**
   * Test HTTP connection
   */
  private class TestConnection {
    StreamSocketLink _conn;
    HttpRequest _request;
    VfsStream _vfsStream;
    InetAddress _localAddress;
    InetAddress _remoteAddress;
    int _port = 6666;
    char []_chars = new char[1024];
    byte []_bytes = new byte[1024];

    TestConnection()
    {
      _conn = new StreamSocketLink();
      // _conn.setVirtualHost(_virtualHost);

      _request = new HttpRequest(_resin.getServer(), _conn);
      _request.init();

      _vfsStream = new VfsStream(null, null);

      // _conn.setSecure(_isSecure);

      try {
        _localAddress = InetAddress.getByName("127.0.0.1");
        _remoteAddress = InetAddress.getByName("127.0.0.1");
      } catch (IOException e) {
      }
    }

    public String request(String input) throws IOException
    {
      OutputStream os = new ByteArrayOutputStream();

      request(input, os);

      return os.toString();
    }

    public void request(String input, OutputStream os)
      throws IOException
    {
      ByteArrayInputStream is;

      int len = input.length();
      if (_chars.length < len) {
        _chars = new char[len];
        _bytes = new byte[len];
      }

      input.getChars(0, len, _chars, 0);
      for (int i = 0; i < len; i++)
        _bytes[i] = (byte) _chars[i];

      is = new ByteArrayInputStream(_bytes, 0, len);

      request(is, os);
    }

    public void request(InputStream is, OutputStream os)
      throws IOException
    {
      Thread.yield();

      WriteStream out = Vfs.openWrite(os);
      out.setDisableClose(true);

      ClassLoader oldLoader = Thread.currentThread().getContextClassLoader();
      try {
        _vfsStream.init(is, os);
        _conn.setStream(is, os);
        _conn.setLocalAddress(_localAddress);
        _conn.setLocalPort(_port);
        _conn.setRemoteAddress(_remoteAddress);
        _conn.setRemotePort(9666);
        // _conn.setSecure(_isSecure);

        try {
          Thread.sleep(10);
        } catch (Exception e) {
        }

        while (_request.handleRequest()) {
          out.flush();
        }
      } catch (EOFException e) {
      } finally {
        out.flush();

        Thread.currentThread().setContextClassLoader(oldLoader);
      }
    }
  }

  static class WebAppProgram extends ConfigProgram {
    private final WebAppEmbed _config;

    WebAppProgram(WebAppEmbed webAppConfig)
    {
      _config = webAppConfig;
    }

    /**
     * Configures the object.
     */
    @Override
    public <T> void inject(T bean, CreationalContext<T> env)
      throws ConfigException
    {
      _config.configure((WebApp) bean);
    }
  }
}
