As promised, here is a review of the current Watchdog/Reconfigurator
premise, design, and implementation.  PLEASE feel free to make comments and
suggestions.  Also note that the version of the code at the bottom of this
message that will be placed in cvs will have the correct package/class name
etc.  I will clean it up.

The premise of the "Watchdog" or reconfigurator came from suggestions from
the user list.  It was suggested that the basic watchdog functionality that
is implemented in DOMConfigurator and PropertyConfigurator
configureAndWatch() methods be broken out and configurable on its own.
Wouldn't it be nice if one could define a watchdog in the configuration file
and then not need to use the configureAndWatch() method in custom code at
all?  More discussion led to the suggestion that the source of the new
configuration data should be expanded to cover more than just files.
Through trial and error the following guidelines in the reconfigurator
design have been synthesized:

1) The purpose of the reconfigurator is to primarily "watch" or "monitor" a
"source" for some indication that said "source" has been changed or updated
with new/different configuration data.

2) Once the change in state has been detected, the reconfigurator is
implemented and configured with enough information to reconfigure log4j
using the new/updated configuration data.

3) The reconfigurator implementation does not deal with the specific format
of the configuration data or even the specific reconfiguration of log4j.  It
delegates this responsibility to the Configurator class, where it belongs.
This means that the reconfigurator design should support any Configurator
implementation.  The most the reconfigurator knows about the source of the
new configuration data is how to locate it and hand it off to the
Configurator.

4) The reconfigurator design should provide for better control over when a
reconfigurator is active and allows for reconfigurators to be stopped and
even restarted.

While configurators deal with the source format (like xml or properties),
reconfigurators deal more with the source type.  The reconfigurator is
implemented to monitor a specific type of thing, like a file or url.  So,
while we have a DOMConfigurator, we can have a FileReconfigurator,
URLReconfigurator, or SocketReconfigurator, all of which can use the
DOMConfigurator to process their new/updated configuration data.

Like Configurators, Reconfigurators must implement a basic interface
required for all Reconfigurator implementations.  This is the Watchdog.java
class listed below (remember, I will rename everything before check in).
Some highlights:

- every configurator can have a name (though where that name is used is
still an open issue).
- The configurator class to use for configuration can be specified via a
property setting.
- Reconfigurators can be started and stopped.

So, to implement a basic reconfigurator is fairly simple from the interface
requirements.  To save developers the trouble of reimplementing the wheel,
there is a base, abstract implementation provided, WatchdogBase.java, below.
Highlights:

- Obviously it implements the required interface.  It uses the
Runnable/Thread classes to implement a process that can run independent of
the parent/main process.
- It requires a couple of methods to be implemented by subclasses,
reconfigure() and checkForReconfiguration().  It is expected that these
methods will need to be specific to the type of source the reconfigurator
has been implemented to watch.
- It allows subclasses to override setup() and cleanup() methods to their
own needs.
- It provides useful helper methods to create a configurator instance, and
to handle configuration using either a URL or an InputStream.  It is
expected that most reconfigurators will use one or the other as a source for
new configuration data, but other source types are not precluded.

And that is enough to digest for now.  Please review the enclosed code
below.  Tomorrow I will post a message that goes into details about some
specific implementations to watch files, urls, and sockets.  They are
actually quite trivial given the base class implementation

When reviewing this design and code, it might be useful to keep the
following items in mind:

1) How easy would it be for a developer to pick up the interface and/or base
class and use it to develop their own reconfigurator?  Does the base class
help or hinder in this regard?
2) Knowing that allowing reconfigurators to be created/started within
settings in a configuration file, does this design lend itself to this goal?
Should the current set of active reconfigurators "live" in a known location?
Where would this location be?  How can this be strictly enforced?
3) Is Reconfigurator the right name for this functionality?

All questions, comments, critiques appreciated.

-Mark


Watchdog.java----------------------------------------------

/**
  Defines the basic interface that all Watchdogs must support.

  Watchdogs will "watch" a log4j configuration source, and when new,
  changed configuration data is available, the watchdog will
  initiate a reconfiguration of the log4j settings using the new
  configuration data.

  Several different watchdog classes are implemented, such as
  FileWatchdog (watches a configuration file), HttpWatchdog (watches
  a configuration file at a url location), and SocketWatchdog (watches
  a socket for incoming configuration data).  More types of watchdogs
  can be easily written subclassing  either the WatchdogBase or
  URLWatchdogBase classes.  Please see those classes for more
  information.  Developers can also create their own versions
  implementing the Watchdog interface.

  All watchdogs can have a name and configurator class.  The configurator
  class will be used when reconfiguring using the new data.  All
  watchdogs can be started and stopped.

  @author Mark Womack
*/
public interface Watchdog {

  /**
    For watchdogs that need an interval setting, this is the
    default, 60 seconds. */

  public static final long DEFAULT_INTERVAL = 60000;

  /**
    Returns true if this watchdog is currently running. */
  public
  boolean isRunning();

  /**
     Set the name of this watchdog. The name is used by other
     components to identify this watchdog. */
  public
  void setName(String name);

  /**
     Get the name of this watchdog. The name uniquely identifies the
     watchdog.  */
  public
  String getName();

  /**
    Sets the configurator class used for reconfiguration. */
  public
  void setConfigurator(String configuratorClassName);

  /**
    Gets the configurator class used for reconfiguration. */
  public
  String getConfigurator();

  /**
    Starts this watchdog watching. After calling this method the
    watchdog will be active. */
  public
  void startWatching();

  /**
    Stops this watchdog. After calling this method the
    watchdog will become inactive, but it is not guaranteed
    to be immediately inactive. If threads are involved in the
    implementation, it may take time for them to be interupted and
    exited. */
  public
  void stopWatching();
}


WatchdogBase.java----------------------------------------------

/**
  Abstract base implementation for Watchdogs.  Subclasses
  are expected to implement the reconfigure() and
  checkForReconfiguration() methods.  Subclasses can provide their
  own implementations of the setup() and cleanup() methods as needed.

  Subclasses can use the helper methods implemented by this class:
  getConfiguratorInstance(), reconfigureByURL(), and
  reconfigureByInputStream().

  The behavior of the watchdog is undefined when setting property
  values while the Watchdog is running.  Property values should be
  set up before starting the watchdog and should remain constant while
  running.

  @author Mark Womack
*/
public abstract class WatchdogBase implements Watchdog, Runnable {

  /**
    The thread running this watchdog. */
  protected Thread watchdogThread;

  /**
    True if this watchdog is running. */
  private boolean running = false;

  /**
    The name of this watchdog. */
  protected String name;

  /**
    The class name of the configurator to use when reconfigurating. */
  protected String configuratorClassName;

  /**
    Returns true if this watchdog is currently running. */
  public
  synchronized
  boolean isRunning() {
    return running;
  }

  /**
     Set the name of this watchdog. The name is used by other
     components to identify this watchdog. */
  public
  void setName(String _name) {
    name = _name;
  }

  /**
     Get the name of this watchdog. The name uniquely identifies the
     watchdog.  */
  public
  String getName() {
    return name;
  }

  /**
    Sets the configurator class used for reconfiguration. */
  public
  void setConfigurator(String _configuratorClassName) {
    configuratorClassName = _configuratorClassName;
  }

  /**
    Gets the configurator class used for reconfiguration. */
  public
  String getConfigurator() {
    return configuratorClassName;
  }

  /**
    Starts this watchdog. After calling this method the
    watchdog will be active. */
  public
  synchronized
  void startWatching() {
    // if not already running
    if (!running) {
      // create a thread to run this
      watchdogThread = new Thread(this);

      // setup the thread
      watchdogThread.setName(this.name);
      watchdogThread.setDaemon(true);

      LogLog.debug(this.getName() + " watchdog starting");

      // start the thread
      running = true;
      watchdogThread.start();
    }
  }

  /**
    Stops this watchdog. After calling this method the
    watchdog will become inactive, but it is not guaranteed
    to be immediately inactive. It may take time for the
    thread to be interupted and exited. */
  public
  synchronized
  void stopWatching() {
    // if already running
    if (running) {
      LogLog.debug(this.getName() + " watchdog stopping");

      // indicate we are no longer supposed to run
      running = false;

      // interrupt the thread if it is sleeping
      watchdogThread.interrupt();

      // lose the reference to this thread for gc
      watchdogThread = null;
    }
  }

  /**
    Implementation required by the Runnable interface.  Calls the
    setup() method, then enters a loop that will run until the
    boolean member running becomes false.  The loop calls the
    checkForReconfiguration() method, and if this method returns
    true, the reconfigure() method is called. When this loop is
    exited, the cleanup() method is called before this method
    is exited. */
  public
  void run()

    try {
      // setup before starting loop
      setup();

      LogLog.debug(this.getName() + " watchdog now active");

      // while we are supposed to be running
      while (isRunning()) {

        // check for new reconfiguration data
        if (checkForReconfiguration() && isRunning()) {

          LogLog.debug(this.getName() + " watchdog reconfiguring");

          reconfigure();
        }
      }
    }
    finally

      // cleanup after finishing loop
      cleanup();
    }

    LogLog.debug(this.name + " watchdog now inactive");
  }

  /**
    Called before the watchdog starts the running loop.  Subclasses can
    override to have specific behavior when activating. */
  protected
  void setup() {
    // subclasses can override to setup before starting main run loop
  }

  /**
    Called after the watchdog stops the running loop.  Subclasses can
    override to have specific behavior when deactivating. */
  protected
  void cleanup() {
    // subclasses can override to cleanup after ending main run loop
  }

  /**
    Called when the watchdog should reconfigure log4j. Subclasses must
    implement, reconfiguring in a way specific to the data source it is
    implemented to watch. */
  abstract
  protected
  void reconfigure();

   /**
    Returns true if watchdog should reconfigure.  Subclasses must implement,
    checking for new, changed configuration data in a way specific to the
    data source it is implemented to watch. */
  abstract
  protected
  boolean checkForReconfiguration();

  /**
    Helper method to get an instance of the configurator class. */
  protected
  Configurator getConfiguratorInstance() {
    // create an instance of the configurator class
    Configurator configurator = null;

    // if we were configured with a configurator class name, use it
    if (configuratorClassName != null) {
      configurator = (Configurator) OptionConverter.instantiateByClassName(
        configuratorClassName, Configurator.class, null);
    }
    // otherwise, default to PropertyConfigurator
    else {
      configurator = new PropertyConfigurator();
    }

    return configurator;
  }

  /**
    Helper method to reconfigure using a URL.
    The input parameter, configurationURL, should be a URL pointing to
    the configuration data in a format expected by the configurator. */
  protected
  void reconfigureByURL(URL configURL) {
    LogLog.debug(this.getName() + " watchdog reconfiguring using url " +
      configURL);

    // create an instance of the configurator class
    Configurator configurator = getConfiguratorInstance();

    // if able to create configurator, then reconfigure using input stream
    if (configurator != null) {
      configurator.doConfigure(configURL, LogManager.getLoggerRepository());
    }
          else {
            LogLog.error(this.getName() + " watchdog could not create
configurator");
            LogLog.error(this.getName() + " watchdog ignoring new configuration
settings");
          }

  }

  /**
    Helper method to reconfigure using an InputStream.
    The input parameter, configurationStream, should be a stream
    of configuration data in a format expected by the configurator.
    For this method to work it required that the configurator class
    support a version of the doConfigure() method that takes an
    InputStream. */
  protected
  void reconfigureByInputStream(InputStream configStream) {
    LogLog.debug(this.getName() + " watchdog reconfiguring using
InputStream.");

    // create an instance of the configurator class
    Configurator configurator = getConfiguratorInstance();

    // if able to create configurator, then reconfigure using input stream
    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);

        // call the method
              Object[] objArray = new Object[2];
              objArray[0] = configStream;
              objArray[1] = LogManager.getLoggerRepository();
              configMethod.invoke(configurator, objArray);
            } catch (Exception e) {
                  LogLog.error(this.getName() +
                " watchdog error working with configurator", e);
                  LogLog.error(this.getName() +
                " watchdog ignoring new configuration settings");
            }
          }
          else {
            LogLog.error(this.getName() +
            " watchdog could not create configurator");
            LogLog.error(this.getName() +
            " watchdog ignoring new configuration settings");
          }
  }
}


--
To unsubscribe, e-mail:   <mailto:[EMAIL PROTECTED]>
For additional commands, e-mail: <mailto:[EMAIL PROTECTED]>

Reply via email to