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

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.management.MBeanServer;
import javax.management.ObjectName;

import com.caucho.admin.RemoteAdminService;
import com.caucho.cloud.network.NetworkListenSystem;
import com.caucho.cloud.topology.CloudCluster;
import com.caucho.cloud.topology.CloudPod;
import com.caucho.cloud.topology.CloudSystem;
import com.caucho.cloud.topology.TopologyService;
import com.caucho.config.Config;
import com.caucho.config.ConfigException;
import com.caucho.config.inject.BeanBuilder;
import com.caucho.config.inject.DefaultLiteral;
import com.caucho.config.inject.InjectManager;
import com.caucho.config.lib.ResinConfigLibrary;
import com.caucho.config.types.Period;
import com.caucho.env.service.ResinSystem;
import com.caucho.env.thread.ThreadPool;
import com.caucho.hemp.broker.HempBroker;
import com.caucho.jmx.Jmx;
import com.caucho.lifecycle.Lifecycle;
import com.caucho.loader.DependencyCheckInterval;
import com.caucho.loader.DynamicClassLoader;
import com.caucho.log.EnvironmentStream;
import com.caucho.log.LogHandlerConfig;
import com.caucho.log.RotateStream;
import com.caucho.network.listen.TcpSocketLinkListener;
import com.caucho.security.AdminAuthenticator;
import com.caucho.security.Authenticator;
import com.caucho.server.cluster.Server;
import com.caucho.server.http.HttpProtocol;
import com.caucho.server.resin.Resin;
import com.caucho.server.resin.ResinELContext;
import com.caucho.server.util.JniCauchoSystem;
import com.caucho.util.Alarm;
import com.caucho.util.AlarmListener;
import com.caucho.util.L10N;
import com.caucho.vfs.Path;
import com.caucho.vfs.Vfs;
import com.caucho.vfs.WriteStream;

/**
 * Process responsible for watching a backend watchdog.
 */
class WatchdogManager implements AlarmListener {
  private static L10N _L;
  private static Logger _log;

  private static WatchdogManager _watchdog;

  private Lifecycle _lifecycle = new Lifecycle();

  private WatchdogArgs _args;

  private int _watchdogPort;

  private String _adminCookie;
  private BootManagementConfig _management;
  private final ResinSystem _system;

  private Server _server;
  private TcpSocketLinkListener _httpPort;

  private HashMap<String,WatchdogChild> _watchdogMap
    = new HashMap<String,WatchdogChild>();

  WatchdogManager(String []argv)
    throws Exception
  {
    _watchdog = this;

    _args = new WatchdogArgs(argv);
    
    _system = new ResinSystem("watchdog");

    Vfs.setPwd(_args.getRootDirectory());
    
    boolean isLogDirectoryExists = getLogDirectory().exists();

    Path logPath = getLogDirectory().lookup("watchdog-manager.log");
    
    try {
      getLogDirectory().mkdirs();
    } catch (Exception e) {
      log().log(Level.ALL, e.toString(), e);
    }
    
    // #4333 - check watchdog-manager.log can be written
    WriteStream testOut = logPath.openAppend();
    testOut.close();
    
    if (! logPath.canWrite()) {
      throw new ConfigException("Cannot open " + logPath.getNativePath()
                                + " required for Resin start. Please check permissions");
    }

    RotateStream logStream = RotateStream.create(logPath);
    logStream.setRolloverSize(64L * 1024 * 1024);
    logStream.init();
    WriteStream out = logStream.getStream();
    out.setDisableClose(true);

    EnvironmentStream.setStdout(out);
    EnvironmentStream.setStderr(out);

    LogHandlerConfig log = new LogHandlerConfig();
    log.setName("");
    log.setPath(logPath);
    log.init();

    Thread thread = Thread.currentThread();
    thread.setContextClassLoader(_system.getClassLoader());

    ThreadPool.getThreadPool().setIdleMin(4);
    ThreadPool.getThreadPool().setPriorityIdleMin(4);

    ResinELContext elContext = _args.getELContext();
    
    Resin resin = Resin.createWatchdog(_system);
    
    if (_args.getDataDirectory() != null)
      resin.setDataDirectory(_args.getDataDirectory());
    
    resin.preConfigureInit();
    
    // XXX: needs to be config

    InjectManager cdiManager = InjectManager.create();

    Config.setProperty("resinHome", elContext.getResinHome());
    Config.setProperty("resin", elContext.getResinVar());
    Config.setProperty("server", elContext.getServerVar());
    Config.setProperty("java", elContext.getJavaVar());
    Config.setProperty("system", System.getProperties());
    Config.setProperty("getenv", System.getenv());

    ResinConfigLibrary.configure(cdiManager);

    _watchdogPort = _args.getWatchdogPort();
    
    readConfig(_args);
    
    WatchdogChild server = null;
    
    String serverId = _args.getServerId();

    if (_args.isDynamicServer()) {
      serverId = _args.getDynamicServerId();
    }
      
    server = _watchdogMap.get(_args.getServerId());

    if (server == null)
      throw new IllegalStateException(L().l("'{0}' is an unknown server",
                                            serverId));
    
    JniBoot boot = new JniBoot();
    Path logDirectory = getLogDirectory();

    if (boot.isValid()) {
      if (! isLogDirectoryExists) {
        logDirectory.mkdirs();

        boot.chown(logDirectory, server.getUserName(), server.getGroupName());
      }
    }

    server.getConfig().logInit(logStream);

    resin.preConfigureInit();
    resin.setConfigFile(_args.getResinConf().getNativePath());

    thread = Thread.currentThread();
    thread.setContextClassLoader(resin.getClassLoader());

    CloudSystem cloudSystem = TopologyService.getCurrent().getSystem();
    
    CloudCluster cluster = cloudSystem.createCluster("watchdog");
    CloudPod pod = cluster.createPod();
    pod.createStaticServer("default", "localhost", -1, false);

    _server = resin.createServer();
    
    thread.setContextClassLoader(_server.getClassLoader());
    
    
    NetworkListenSystem listenService 
      = _system.getService(NetworkListenSystem.class);
    
    _httpPort = new TcpSocketLinkListener();
    _httpPort.setProtocol(new HttpProtocol());

    if (_watchdogPort > 0)
      _httpPort.setPort(_watchdogPort);
    else
      _httpPort.setPort(server.getWatchdogPort());

    _httpPort.setAddress(server.getWatchdogAddress());

    _httpPort.setAcceptThreadMin(2);
    _httpPort.setAcceptThreadMax(3);

    _httpPort.init();

    listenService.addListener(_httpPort);
    
    ClassLoader oldLoader = thread.getContextClassLoader();

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

      cdiManager = InjectManager.create();
      AdminAuthenticator auth = null;

      if (_management != null)
        auth = _management.getAdminAuthenticator();

      if (auth != null) {
        BeanBuilder<Authenticator> factory = cdiManager.createBeanFactory(Authenticator.class);

        factory.type(Authenticator.class);
        factory.type(AdminAuthenticator.class);
        factory.qualifier(DefaultLiteral.DEFAULT);

        cdiManager.addBean(factory.singleton(auth));
      }

      DependencyCheckInterval depend = new DependencyCheckInterval();
      depend.setValue(new Period(-1));
      depend.init();

      RemoteAdminService adminService = new RemoteAdminService();
      adminService.setAuthenticationRequired(false);
      adminService.init();

      HempBroker broker = HempBroker.getCurrent();
      
      WatchdogService service
        = new WatchdogService(this, "watchdog@admin.resin.caucho", broker);


      /*
      broker.setAdmin(true);
      broker.setAllowNullAdminAuthenticator(true);
      */

      broker.createAgent(service.getActor());

      ResinSystem.getCurrent().start();

      _lifecycle.toActive();
      
      // valid checker
      new Alarm(this).queue(60000);
    } finally {
      thread.setContextClassLoader(oldLoader);
    }
  }
  
  ClassLoader getClassLoader()
  {
    return _system.getClassLoader();
  }

  static WatchdogManager getWatchdog()
  {
    return _watchdog;
  }

  public void setAdminCookie(String cookie)
  {
    if (_adminCookie == null)
      _adminCookie = cookie;
  }

  public String getAdminCookie()
  {
    if (_adminCookie != null)
      return _adminCookie;
    else if (_management != null)
      return _management.getAdminCookie();
    else
      return null;
  }

  boolean isActive()
  {
    return _server.isActive() && _httpPort.isActive();
  }

  Path getRootDirectory()
  {
    return _args.getRootDirectory();
  }

  Path getLogDirectory()
  {
    Path logDirectory = _args.getLogDirectory();

    if (logDirectory != null)
      return logDirectory;
    else
      return getRootDirectory().lookup("log");
  }

  boolean authenticate(String password)
  {
    String cookie = getAdminCookie();

    if (password == null && cookie == null)
      return true;
    else if  (password != null && password.equals(cookie))
      return true;
    else
      return false;
  }

  WatchdogChild findServer(String id)
  {
    return _watchdogMap.get(id);
  }

  /**
   * Called from the Hessian API to report the status of the watchdog
   *
   * @return a human-readable description of the current status
   */
  String status()
  {
    StringBuilder sb = new StringBuilder();

    synchronized (_watchdogMap) {
      ArrayList<String> keys = new ArrayList<String>(_watchdogMap.keySet());
      Collections.sort(keys);
      
      sb.append("\nwatchdog:\n");
      sb.append("  watchdog-pid: " + getWatchdogPid());

      for (String key : keys) {
        WatchdogChild child = _watchdogMap.get(key);

        sb.append("\n\n");
        sb.append("server '" + key + "' : " + child.getState() + "\n");

        if (getAdminCookie() == null)
          sb.append("  password: missing\n");
        else
          sb.append("  password: ok\n");
        
        sb.append("  watchdog-user: " + System.getProperty("user.name") + "\n");

        if (child.getUserName() != null)
          sb.append("  user: " + child.getUserName());
        else
          sb.append("  user: " + System.getProperty("user.name"));

        if (child.getGroupName() != null)
          sb.append("(" + child.getGroupName() + ")");

        sb.append("\n");

        sb.append("  root: " + child.getResinRoot() + "\n");
        sb.append("  conf: " + child.getResinConf() + "\n");

        if (child.getPid() > 0)
          sb.append("  pid: " + child.getPid() + "\n");
        
        sb.append("  uptime: " + child.getUptimeString() + "\n");
      }
    }

    return sb.toString();
  }

  /**
   * Called from the Hessian API to start a server.
   *
   * @param argv the command-line arguments to start the server
   */
  void startServer(String []argv)
    throws ConfigException
  {
    synchronized (_watchdogMap) {
      WatchdogArgs args = new WatchdogArgs(argv, false);

      Vfs.setPwd(_args.getRootDirectory());

      try {
        readConfig(args);
      } catch (Exception e) {
        throw ConfigException.create(e);
      }

      String serverId = args.getServerId();

      if (args.isDynamicServer())
        serverId = args.getDynamicServerId();

      WatchdogChild watchdog = _watchdogMap.get(serverId);

      if (watchdog == null)
        throw new ConfigException(L().l("No matching <server> found for -server '{0}' in '{1}'",
                                        serverId, _args.getResinConf()));

      watchdog.start();
    }
  }

  /**
   * Called from the hessian API to gracefully stop a Resin instance
   *
   * @param serverId the Resin instance to stop
   */
  void stopServer(String serverId)
  {
    synchronized (_watchdogMap) {
      WatchdogChild watchdog = getWatchdog(serverId); 
      if (watchdog == null)
        throw new ConfigException(L().l("No matching <server> found for -server '{0}' in {1}",
                                        serverId, _args.getResinConf()));

      watchdog.stop();
    }
  }
  
  private WatchdogChild getWatchdog(String serverId)
  {
    WatchdogChild watchdog = _watchdogMap.get(serverId);
  
    if (watchdog == null && "".equals(serverId) && _watchdogMap.size() == 1) {
      watchdog = _watchdogMap.values().iterator().next();
    }
    
    return watchdog;
  }

  /**
   * Called from the hessian API to forcibly kill a Resin instance
   *
   * @param serverId the server id to kill
   */
  void killServer(String serverId)
  {
    // no synchronization because kill must avoid blocking

    WatchdogChild watchdog = getWatchdog(serverId);

    if (watchdog == null)
      throw new ConfigException(L().l("No matching <server> found for -server '{0}' in {1}",
                                      serverId, _args.getResinConf()));

    watchdog.kill();
  }
  
  /**
   * Checks to see if any watchdog is active.
   */
  boolean isEmpty()
  {
    synchronized (_watchdogMap) {
      for (WatchdogChild child : _watchdogMap.values()) {
        if (child.isActive())
          return false;
      }
    }
    
    return true;
  }

  /**
   * Called from the HMTP API to restart a Resin instance.
   *
   * @param serverId the server identifier to restart
   * @param argv the command-line arguments to apply to the start
   */
  void restartServer(String serverId, String []argv)
  {
    synchronized (_watchdogMap) {
      WatchdogChild server = _watchdogMap.get(serverId);

      if (server != null)
        server.restart();
      else
        startServer(argv);
    }
  }

  boolean isValid()
  {
    return _server != null && _server.isActive();
  }

  private WatchdogChild readConfig(WatchdogArgs args)
    throws Exception
  {
    Config config = new Config();
    // ignore since we don't want to start databases
    config.setIgnoreEnvironment(true);

    Vfs.setPwd(args.getRootDirectory());
    BootResinConfig resin = new BootResinConfig(_system, args);

    config.configure(resin,
                     args.getResinConf(),
                     "com/caucho/server/resin/resin.rnc");

    if (_management == null)
      _management = resin.getManagement();

    /*
    // The configuration file has already been validated by ResinBoot, so
    // it doesn't need a second validation
    config.configure(resin,
                     args.getResinConf());
    */

    String serverId = args.getServerId();
    WatchdogConfig serverConfig = null;

    if (args.isDynamicServer()) {
      String clusterId = args.getDynamicCluster();
      String address = args.getDynamicAddress();
      int port = args.getDynamicPort();

      BootClusterConfig cluster = resin.findCluster(clusterId);

      if (cluster == null) {
        throw new ConfigException(L().l("'{0}' is an unknown cluster",
                                      clusterId));
      }

      serverConfig = cluster.createServer();
      serverId = args.getDynamicServerId();
      serverConfig.setId(serverId);
      serverConfig.setAddress(address);
      serverConfig.setPort(port);
      cluster.addServer(serverConfig);
    }
    else {
      WatchdogClient client = resin.findClient(serverId);

      if (client != null)
        serverConfig = client.getConfig();
      else
        serverConfig = resin.findServer(serverId);
    }

    WatchdogChild watchdog = _watchdogMap.get(serverId);

    if (watchdog != null) {
      if (watchdog.isActive()) {
        throw new ConfigException(L().l("server '{0}' cannot be started because a running instance already exists.  stop or restart the old server first.",
                                        serverId));
      }

      watchdog = _watchdogMap.remove(serverId);

      if (watchdog != null)
        watchdog.close();
    }
    
    if (serverConfig == null) {
      throw new ConfigException(L().l("server '{0}' cannot be started because no configuration has been found.",
                                      serverId));
    }

    watchdog = new WatchdogChild(_system, serverConfig);

    _watchdogMap.put(serverId, watchdog);

    return watchdog;
  }
  
  private int getWatchdogPid()
  {
    try {
      MBeanServer server = Jmx.getGlobalMBeanServer();
      ObjectName objName = new ObjectName("java.lang:type=Runtime");
      
      String runtimeName = (String) server.getAttribute(objName, "Name");
      
      if (runtimeName == null) {
        return 0;
      }
      
      int p = runtimeName.indexOf('@');
      
      if (p > 0) {
        int pid = Integer.parseInt(runtimeName.substring(0, p));

        return pid;
      }
      
      return 0;
    } catch (Exception e) {
      log().log(Level.FINE, e.toString(), e);
      
      return 0;
    }

  }

  public void waitForExit()
  {
    while (_lifecycle.isActive()) {
      try {
        synchronized (this) {
          wait();
        }
      } catch (Exception e) {
      }
    }
  }

  public void handleAlarm(Alarm alarm)
  {
    try {
      if (! _args.getResinConf().canRead()) {
        log().severe(L().l("{0} exiting because '{1}' is no longer valid",
                           this, _args.getResinConf()));

        System.exit(1);
      }
    } finally {
      alarm.queue(60000);
    }
  }

  /**
   * The launching program for the watchdog manager, generally called
   * from ResinBoot.
   */
  public static void main(String []argv)
    throws Exception
  {
    boolean isValid = false;

    try {
      DynamicClassLoader.setJarCacheEnabled(false);

      JniCauchoSystem.create().initJniBackground();

      WatchdogManager manager = new WatchdogManager(argv);
      manager.startServer(argv);

      isValid = manager.isActive() && manager.isValid();

      if (isValid) {
        manager.waitForExit();
      }
    } catch (Exception e) {
      log().log(Level.WARNING, e.toString(), e);
      System.out.println(e);
      e.printStackTrace();
    } finally {
      System.exit(1);
    }
  }

  private static L10N L()
  {
    if (_L == null)
      _L = new L10N(ResinBoot.class);

    return _L;
  }

  private static Logger log()
  {
    if (_log == null)
      _log = Logger.getLogger(ResinBoot.class.getName());

    return _log;
  }
}
