/*
 * JBoss, the OpenSource WebOS
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */
package org.jboss.web.loadbalancer.monitor;

import java.io.IOException;
import java.util.ArrayList;
import javax.management.ObjectName;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.methods.GetMethod;
import org.jboss.logging.Logger;
import org.jboss.mx.util.MBeanProxyExt;
import org.jboss.system.ServiceMBeanSupport;
import org.jboss.web.loadbalancer.scheduler.AbstractSchedulerMBean;
import org.jboss.web.loadbalancer.scheduler.Host;
import org.jboss.web.loadbalancer.util.Constants;

/**
 * A base class for loadbalancer-monitors.
 *
 * @jmx:mbean name="jboss.web.loadbalancer: service=Monitor"
 *            extends="org.jboss.system.ServiceMBean"
 * @author Thomas Peuss <jboss@peuss.de>
 * @version $Revision: 1.3 $
 */
public abstract class AbstractMonitor
    extends ServiceMBeanSupport
    implements AbstractMonitorMBean, Runnable
{
  protected boolean keepRunning = true;
  protected long interval = 15000;
  protected AbstractSchedulerMBean scheduler;
  protected Logger log = Logger.getLogger(this.getClass());
  protected ObjectName schedulerObjectName;
  protected int timeout;
  protected String path;
  protected Thread monitorThread;

  protected void startService() throws java.lang.Exception
  {
    // create a proxy object to the scheduler service
    scheduler = (AbstractSchedulerMBean)
        MBeanProxyExt.create(AbstractSchedulerMBean.class,
                             schedulerObjectName);

    // start monitoring thread
    this.setKeepRunning(true);
    monitorThread = new Thread(this, "LoadbalancerMonitor");
    monitorThread.setDaemon(true);
    monitorThread.start();
  }

  protected void stopService() throws java.lang.Exception
  {
    this.setKeepRunning(false);
    monitorThread.interrupt();
    monitorThread.join();
  }

  protected void destroyService() throws java.lang.Exception
  {
    monitorThread = null;
  }

  /**
   * Override this method to create new monitors.
   * @param method
   * @return
   */
  protected abstract boolean checkHostStatus(HttpMethod method);

  protected void monitorHosts()
  {
    // go through down-list
    ArrayList list = (ArrayList) scheduler.getHostsDown().clone();

    for (int i = 0; i < list.size(); ++i)
    {
      Host checkHost = (Host) list.get(i);

      if (checkHost.getState()==Constants.STATE_NODE_FORCED_DOWN)
      {
        log.debug("Ignoring Host "+checkHost+" because it is forced down");
        continue;
      }

      if (checkHost(checkHost))
      {
        log.info("Host " + checkHost + " is up again - adding to up list");
        checkHost.markNodeUp();
      }
    }

    // go through up-list
    list = (ArrayList) scheduler.getHostsUp().clone();

    for (int i = 0; i < list.size(); ++i)
    {
      Host checkHost = (Host) list.get(i);

      if (checkHost.getState()==Constants.STATE_NODE_FORCED_DOWN)
      {
        log.debug("Ignoring Host "+checkHost+" because it is forced down");
        continue;
      }

      if (!checkHost(checkHost))
      {
        log.error("Host " + checkHost + " is DOWN - adding to down list");
        checkHost.markNodeDown();
      }
    }
  }

  /**
   * Check the given host.
   * @param url
   * @return
   */
  protected boolean checkHost(Host host)
  {
    log.debug("Checking host " + host.getUrl() + path);

    // set up HttpClient
    HttpClient httpClient = new HttpClient();
    httpClient.setConnectionTimeout(this.getTimeout());
    httpClient.setTimeout(this.getTimeout());

    // set up request method
    GetMethod method = new GetMethod(host.getUrl().toExternalForm() + path);
    method.setFollowRedirects(false);
    method.setDoAuthentication(false);

    try
    {
      // initiate request
      httpClient.executeMethod(method);

      // a status code >= 400 is BAD
      if (method.getStatusCode() >= HttpServletResponse.SC_BAD_REQUEST)
      {
        log.error("Server is up but sends error: " + method.getStatusLine());
        return false;
      }
      return checkHostStatus(method);
    }
    catch (IOException ex)
    {
      log.error("Check for host " + host.getUrl() + " failed", ex);
      return false;
    }
    finally
    {
      method.recycle();
    }
  }

  // Runnable interface implementation
  public void run()
  {
    while (keepRunning)
    {
      try
      {
        Thread.sleep(interval);
        monitorHosts();
      }
      catch (InterruptedException ex)
      {
      }
    }
  }

  public boolean isKeepRunning()
  {
    return keepRunning;
  }

  public void setKeepRunning(boolean keepRunning)
  {
    this.keepRunning = keepRunning;
  }

  /**
   * @jmx:managed-attribute
   */
  public void setPath(String path)
  {
    if (path.startsWith("/"))
    {
      this.path = path.substring(1);
    }
    else
    {
      this.path = path;
    }
  }

  /**
   * @jmx:managed-attribute
   */
  public String getPath()
  {
    return "/" + path;
  }

  /**
   * @jmx:managed-attribute
   */
  public void setInterval(long interval)
  {
    this.interval = interval;
  }

  /**
   * @jmx:managed-attribute
   */
  public long getInterval()
  {
    return interval;
  }

  /**
   * @jmx:managed-attribute
   */
  public int getTimeout()
  {
    return timeout;
  }

  /**
   * @jmx:managed-attribute
   */
  public void setTimeout(int timeout)
  {
    this.timeout = timeout;
  }

  /**
   * @jmx:managed-attribute
   */
  public ObjectName getScheduler()
  {
    return schedulerObjectName;
  }

  /**
   * @jmx:managed-attribute
   */
  public void setScheduler(ObjectName schedulerObjectName)
  {
    this.schedulerObjectName = schedulerObjectName;
  }
}