/*
 * Copyright (C) The Apache Software Foundation. All rights reserved.
 *
 * This software is published under the terms of the Apache Software License
 * version 1.1, a copy of which has been included  with this distribution in
 * the LICENSE file.
 */
 
// Contributors:  Mark Womack

package org.apache.log4j.helpers;

import java.util.Vector;
import java.io.InputStream;
import java.io.IOException;
import java.lang.reflect.Method;

import org.apache.log4j.spi.Configurator;
import org.apache.log4j.spi.LoggerRepository;
import org.apache.log4j.PropertyConfigurator;
import org.apache.log4j.LogManager;

/**
  Check periodically to see if an "entity" has been changed, and if so,
  reconfigure based on the entities new configuration values.  This class
  is an abstract parent class for all watchdog types. It makes no
  assumptions about the type of entity that is being watched.
  It maintains a static list of active Watchdog objects. All watchdogs
  can be started and stopped. A configurator class can be set via
  properties (otherwise it defaults to PropertyConfigurator), but the
  configurator class must implement a method with the signature of
  "doConfigure(InputStream, LoggingRepository)" in order for the watchdog
  to function correctly.  All subclasses must provide methods to
  retrieve the modification time of the watched entity and provide an
  InputStream to the configuration element of the watched entity.
  */
public abstract class Watchdog implements Runnable {
	
  /**
     The default interval between configuration checks, set to 60
     seconds.  */
  public static final long DEFAULT_INTERVAL = 60000; 
	
  // list of all active watchdogs
  private static Vector watchList = new Vector();
  
  /**
  	Stops all of the active watchdogs. */
  public
  static
  synchronized
  void stopAllWatchdogs() {
  	while (watchList.size() != 0) {
  	  Watchdog watchDog = (Watchdog) watchList.elementAt(0);
  	  watchDog.stopWatching();
  	}
  }
  
  /**
  	Adds a new Watchdog to current list of watchdogs. */
  protected
  static
  synchronized
  void addWatchdog(Watchdog watchdog) {
  	watchList.add(watchdog);
  }
  
  /**
  	Removes a Watchdog from the current list of watchdogs. */
  protected
  static
  synchronized
  void removeWatchdog(Watchdog watchdog) {
  	watchList.remove(watchdog);
  }
  
  /**
  	flag to indicate this watchdog is active */
  private boolean keepWatching;
  
  /** 
  	thread the watchdog is running in */
  private Thread watchThread;
  
  /**
  	The time from when the watched element changed */
  protected long lastModificationTime;
  
  /**
  	Interval, in milliseconds, between checks for configuration changes */
  protected long interval = DEFAULT_INTERVAL;
  
  /**
  	The configurator class to use during reconfiguration */
  protected String configuratorClass;
  
  /**
  	Set the interval time between checks for configuration changes */
  public
  void setWatchInterval(long watchInterval) {
  	interval = watchInterval;
  }
  
  /**
  	Get the interval time between checks for configuration changes */
  public
  long getWatchInterval() {
  	return interval;
  }
  
  /**
  	Set last modifcation time to test against when checking for
  	configuration changes */
  public
  void setLastModifiedTime(long _lastModificationTime) {
  	lastModificationTime = _lastModificationTime;
  }
  
  /**
  	Get last modifcation time used to test against when checking for
  	configuration changes */
  public
  long getLastModifiedTime() {
  	return lastModificationTime;
  }

  /**
    Set the configurator class used when reconfiguring. */
  public
  void setConfiguratorClass(String _configuratorClass) {
  	configuratorClass = _configuratorClass;
  }
  
  /**
    Get the configurator class used when reconfiguring. */
  public
  String getConfiguratorClass() {
  	return configuratorClass;
  }

  /**
  	Starts a watchdog watching. */
  public
  synchronized
  void startWatching() {
  	// if not already watching
  	if (!keepWatching) {
  	  // attach this runnable to a daemon thread
	  watchThread = new Thread(this);
	  watchThread.setDaemon(true);
	  
  	  LogLog.debug("starting watchdog ["+watchThread.getName()+"].");

	  // start the watchdog
  	  keepWatching = true;
  	  watchThread.start();
  	  
  	  // add it to the watch list
  	  addWatchdog(this);  	  
	}
  }
  
  /**
  	Stops this watchdog from watching. */
  public
  synchronized
  void stopWatching() {
  	if (keepWatching) {
  	  LogLog.debug("stopping watchdog ["+watchThread.getName()+"].");

  	  // remove from the watch list
  	  removeWatchdog(this);
  	
  	  // stop watchdog
  	  keepWatching = false;
  	  watchThread.interrupt();
  	
  	  // free the reference to the thread
  	  watchThread = null;
    }
  }
  
  /**
  	Reconfigures configuration using configuration stream, */
  protected void reconfigure() {
    InputStream configStream = getConfigurationStream();
    if (configStream != null) {
      String clazz = configuratorClass;
      Configurator configurator = null;
      if (configuratorClass != null) {
      	configurator = (Configurator) OptionConverter.instantiateByClassName(
      		configuratorClass, Configurator.class, null);
      }
      else {
      	configurator = new PropertyConfigurator();
      }
      
      if (configurator != null) {
      	try {
      	  // use reflection to find a doConfigure() method that supports an InputStream
	      Class[] classArray = new Class[2];
	      classArray[0] = InputStream.class;
	      classArray[1] = LoggerRepository.class;
     	  Method configMethod = configurator.getClass().getMethod("doConfigure",
     	  							classArray);
      	
      	  LogLog.debug("watchdog created configurator, starting configuration processing");
      	  
      	  // call the method
	      Object[] objArray = new Object[2];
	      objArray[0] = configStream;
	      objArray[1] = LogManager.getLoggerRepository();
	      configMethod.invoke(configurator, objArray);
	    } catch (Exception e) {
	  	  LogLog.error("watchdog error working with configurator", e);
	  	  LogLog.error("watchdog ignoring new configuration settings");
	    }
	  }
	  else {
	  	LogLog.error("watchdog could not create configurator");
	  	LogLog.error("watchdog ignoring new configuration settings");
	  }
	  
	  // close the configuration stream
	  try {
	  	configStream.close();
	  } catch (IOException e) {
	  	// do nothing
	  }
    }
    else {
      LogLog.error("watchdog could not get input stream to configuration settings");
      LogLog.error("watchdog ignoring new configuration settings");
    }
  }
  
  /**
  	Implemented by subclass to return modified time of watched element. */
  protected
  abstract
  long getModificationTime();
  
  /**
  	Implemented by subclass to provide the input stream to configuration. */
  protected
  abstract
  InputStream getConfigurationStream();
  
  /**
    Public run method that drives the watchdog activity. */
  public
  void run() {
  	while (keepWatching) {
  	
  	  // check for a configuration change
  	  long newModificationTime = getModificationTime();
  	  if (newModificationTime > lastModificationTime) {
  	  	// remember this modification time
  	  	lastModificationTime = newModificationTime;
  	  	
  	  	// reconfigure
  	  	reconfigure();
  	  }
  	  else {
  	  	LogLog.debug("watched configuration element not modified");
  	  }
  	  
  	  if (keepWatching) {
  	  	try {
   	      // sleep for the specified interval
 	  	  Thread.currentThread().sleep(interval);
  	    } catch (Exception e) {
  	  	  // do nothing
  	    }
      }
  	}
  	
  	LogLog.debug("watchdog terminated ["+Thread.currentThread().getName()+"].");
  }
}
