Last message we toured the premise, design, interface and base
implementation class for the proposed Watchdog/Reconfigurator functionality.
Ceki was the only one to make comments, all of which were good.  I have not
had a chance to incorporate them, obviously, so the code reviewed in this
message is still based on the code from the original message.

In this segment of the tour we will review some specific implementations for
watching files, http urls, and sockets all built on top of the base class.
I will also show some example code for using each one, to give a flavor of
usage.  As before, the terms "Watchdog" and "Reconfigurator" will be used
interchangeably.  The code will be cleaned up to use the new term we decide
upon ("Scout" is in the running).

First up will be FileWatchdog and HttpWatchdog.  These two classes are
related in that they are configured to watch a URL.  How they check the URL
for changes is different, but the usage of URL to describe a location to
"watch" is the same.  So, before implementing them specifically, another
abstract base class is created, URLWatchdogBase.java.  Highlights:

- Has properties for the url to watch, the interval of time to "sleep"
between checking for a changed url.  It also allows for a modification time
to be set.  This modification time will be used to test against the current
modification time of the url.
- Implements the reconfigure() method to use the WatchdogBase helper method
reconfigureByURL() since that is what is being watched.

Once the URLWatchdogBase class is in place, the specific implementations are
fairly trivial.

FileWatchdog highlights:

- Implements override setup() and cleanup() methods to create a File member.
If the file does not exist, then the watchdog does not start.  That behavior
could be changed to keep looking for a file.
- Implements checkForReconfiguration() method to check the modification date
of the file.  This borrows very heavily from the current watchdog
implementation in log4j.
- Notice that at the end of checkForReconfiguration(), the sleep() call to
sleep until the next interval time.

HttpWatchdog highlights:

- setup() method stops the watchdog if no url has been set.
- checkForReconfiguration() method checks for a changed url by requesting a
"HEAD" of the url and checking the getLastModified() value of the response
(this give the value of the "Last-Modified" http response header).
- Again, the sleep() method is called at the end of the method if no change
is detected.

Fairly simple implementations given the design and implementation of the
base class.

SocketWatchdog has a little more meat, but not much more.  SocketWatchdog is
configured with a port number.  It creates a ServerSocket on the port and
then watches it for connections.  When a connection is made, the input
stream is used as the source of the configuration data.  Highlights:

- setup() creates the ServerSocket.  It sets an SoTimeout on it for 2
seconds.  This is an "automatic" timeout so the watchdog is not always
locked up waiting for a connection and gets a chance to shutdown when told
to.
- cleanup() shuts down the ServerSocket.
- checkForReconfiguration() calls the ServerSocket.accept() method.  If a
connection is made, the accepted Socket is stored in a member variable and
will be used by the subsequent call to reconfigure().  If the ServerSocket
is interupted by the 2 second SoTimeout, then it exits the method to give
the base class a chance to check for shutdown.
- reconfigure() is implemented to use the Socket member's input stream to
reconfigure.  It uses the base class method reconfigureByInputStream() to
accomplish this.

And that is it.  One part I would like to point out is the use of
reconfigureByInputStream().  In this case I think it is obvious that trying
to reconfigure by a URL will not work.  This is my argument for adding a
reconfigure(InputStream) to the Configurator interface.  I think a
Configurator should be required to support both.  If it can support a URL,
it can certainly support an InputStream.  Of course, I don't know what
happens if you extend the source type to something else, like the new
Preferences classes.  Would we then need to support a
reconfigure(Preferences)?

OK, some quick examples of usage.

Here is an example of creating a FileWatchdog:

    // create the watchdog
    FileWatchdog watchdog = new FileWatchdog();

    // set the name
    watchdog.setName("file_watchdog");

    // set the file url to watch
    watchdog.setURL(url);

    // set the watch interval to 2 seconds
    watchdog.setInterval(2000);

    // set the configurator class
    // if not set, watchdog defaults to PropertyConfigurator
    watchdog.setConfigurator(DOMConfigurator.getClass().getName());

Here is an example of creating a HttpWatchdog:

    // create the watchdog
    HttpWatchdog watchdog = new HttpWatchdog();

    // set the name
    watchdog.setName("http_watchdog");

    // set the file url to watch
    watchdog.setURL(url);

    // set the watch interval to 2 seconds
    watchdog.setInterval(2000);

    // set the configurator class
    // if not set, watchdog defaults to PropertyConfigurator
    watchdog.setConfigurator(DOMConfigurator.getClass().getName());

Here is an example of creating a SocketWatchdog:

    // create the watchdog
    SocketWatchdog watchdog = new SocketWatchdog();

    // set the name
    watchdog.setName("socket_watchdog");

    // set the port to watch
    watchdog.setPort(3000);

    // set the configurator class
    // if not set, watchdog defaults to PropertyConfigurator
    watchdog.setConfigurator(DOMConfigurator.getClass().getName());

Kind of monotonous, isn't it?  As you can see, the watchdogs come ready for
configuration with all the property setters/getters.  It gets better.
Because of the Watchdog interface, you can run them generically, regardless
of specific class.  The following example runs them for a specified time.
Once the watchdog has been stopped, you can reuse/restart it by calling the
startWatchdog() method.

  void runWatchdog(Watchdog watchdog, long runTime) {
    // start the watchdog
    watchdog.startWatching();

    System.out.println("watchdog is now running; changing the source will
cause reconfiguration.");

    // run our local loop to kill time while the watchdog does its thing
    long startTime = System.currentTimeMillis();
    long stopTime = startTime + runTime;
    while (watchdog.isRunning() && System.currentTimeMillis() < stopTime) {
        try {
            System.out.println(((System.currentTimeMillis()-startTime)/1000)
+
              " seconds elapsed.");
            Thread.currentThread().sleep(5000);
        } catch (Exception e) {

        }
    }

    System.out.println(((System.currentTimeMillis()-startTime)/1000) +
      " seconds elapsed.");

    // stop the watchdog
    watchdog.stopWatching();

   // give the watchdog a chance to stop
   try {
     Thread.currentThread().sleep(5000);
    } catch (Exception e) {

    }
  }

This concludes the Watchdog/Reconfigurator nickel tour.  Please do not bump
your heads on the way out and watch that first step.  Comments are welcome
as always.  Once it looks like anyone that is going to comment has, I will
clean up the code, apply the suggestions, and start checking code into cvs.

We should discuss how the configuration/starting/stopping of watchdogs will
be handled.  All the watchdogs can have names, but the current set/single
watchdog will need to be stored someplace.  Where?  The repository?

thanks,
-Mark

URLWatchdogBase.java----------------------------------------

/**
  Abstract base implementation for Watchdogs that watch a url.  Adds support
  for setting a url, a modification time to test against the url, and an
  interval of time to check that url.  Implements the reconfigure() method.
  Subclasses are expected to implement the checkForReconfiguration() method.

  @author Mark Womack
*/
public abstract class URLWatchdogBase extends WatchdogBase {

  /**
    The url this watchdog is watching. */
  protected URL url;

  /**
    The modification time that will be tested against.  Defaults to 0. */
  protected long modTime = 0;

  /**
    The interval time between checks for configuration changes.  Defaults to
    60 seconds. */
  protected long interval = DEFAULT_INTERVAL;

  /**
    Get the url that will be watched. */
  public URL getURL() {
    return url;
  }

  /**
    Set the url that will be watched. */
  public void setURL(URL _url) {
    url = _url;
  }

  /**
    Get the current interval setting. */
  public long getInterval() {
    return interval;
  }

  /**
    Set the interval time between checks for configuration changes. */
  public void setInterval(long _interval) {
    interval = _interval;
  }

  /**
    Get the current modification time being tested against. */
  public long getModificationTime() {
    return modTime;
  }

  /**
    Sets the modification time that will be tested against. */
  public void setModificationTime(long _modTime) {
    modTime = _modTime;
  }

  /**
    Reconfigure log4j using the url. */
  protected
  void reconfigure() {
    reconfigureByURL(url);
  }
}


FileWatchdog.java--------------------------------------

/**
  Implementation of a Watchdog that watches a file.  When the file's
  modification time changes, the contents of the file are used to
  reconfigure log4j.

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

  @author Mark Womack
*/
public class FileWatchdog extends URLWatchdogBase {
  /**
    The file to observe for changes. */
  protected File file;

  // a flag used to indicate if warnings have been generated previously.
  private boolean warnedAlready = false;

  public
  FileWatchdog() { }

  public
  FileWatchdog(URL fileURL) {
    setURL(fileURL);
  }

  /**
    Setup method to create the File object used to compare modification
time. */
  protected
  void setup() {
    // create a File object to use
    if (url != null) {
      file = new File(url.getFile());
    }
    else {
      LogLog.error("no url defined for watchdog, deactivating");
      this.stopWatching();
    }
  }

  /**
    Cleanup method to release the reference to the file object created in
setup(). */
  protected
  void cleanup() {
    // release the reference to the file
    file = null;
  }

  /**
    Implementation that checks the modification time of the file to see
    if reconfiguration should be performed. */
  protected
  boolean checkForReconfiguration() {

    // check to see if the file exists
    boolean fileExists;
    try {
      fileExists = file.exists();
    } catch(SecurityException  e) {
      LogLog.warn(this.getName() +
        " watchdog: was not allowed to read check file existance, file:["
        + file.getPath() +"], deactivating.");
      this.stopWatching(); // there is no point in continuing
      return false;
    }

    // if the file exists, then check last modified time
    if(fileExists) {
      // this can also throw a SecurityException
      long newModTime = file.lastModified();
      warnedAlready = false;
      if (newModTime > modTime) {
        LogLog.debug(this.getName() +
          " watchdog: the file has been modified");
        modTime = newModTime;
        return true;
      }
      else {
        LogLog.debug(this.getName() +
          " watchdog: the file has not been modified");
      }
    } else {
      if(!warnedAlready) {
        LogLog.debug(this.getName() +
          " watchdog: ["+file.getPath()+"] does not exist.");
        warnedAlready = true;
      }
    }

    // no new configuration, so go ahead and wait for the interval
    try {
      LogLog.debug(getName() +
        " watchdog going to sleep for " + interval + "ms");
      Thread.currentThread().sleep(interval);
    } catch (InterruptedException e) {
      // do nothing, fall through
    }

    return false;
  }
}


HttpWatchdog.java-----------------------------------------

/**
  Implementation for a Watchdogs that watches a given url via http.
  When the Last-Modified header value changes, then the contents of
  the url is used to reconfigure log4j.

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

  @author Mark Womack
*/
public class HttpWatchdog extends URLWatchdogBase {

  // a flag used to indicate if warnings have been generated previously.
  private boolean warnedAlready = false;

  public
  HttpWatchdog() { }

  public
  HttpWatchdog(URL _url) {
    setURL(_url);
  }

  /**
    Setup which checks to see if the url is defined. Stops watchdog if
    it is not. */
  protected
  void setup() {
        // make sure we have a url
        if (url == null) {
                LogLog.warn(this.getName() +
              " watchdog no url defined for watchdog, deactivating");
                this.stopWatching();
        }
  }

  /**
    Implementation which checks the Last-Modified response header for
    the url to decide if reconfiguration should be performed. */
  protected
  boolean checkForReconfiguration() {
        try {
      HttpURLConnection connection =
(HttpURLConnection)url.openConnection();

      // request just the headers
          connection.setRequestMethod("HEAD");
          connection.connect();

          if (connection.getResponseCode() == 200) {
            // get the last modified date
            long newModTime = connection.getLastModified();
              warnedAlready = false;
        if (newModTime > modTime) {
          LogLog.debug(this.getName() +
            " watchdog: the url has been modified");
          modTime = newModTime;
          return true;
        }
        else {
          LogLog.debug(this.getName() +
            " watchdog: the url has not been modified");
        }
      }
      else {
          if (!warnedAlready) {
            LogLog.warn(this.getName() +
              " watchdog error accessing url, response code " +
              connection.getResponseCode());
            warnedAlready = true;
          }
      }
        } catch (Exception e) {
      if (!warnedAlready) {
        LogLog.warn(this.getName() + " watchdog error accessing url", e);
        warnedAlready = true;
          }
        }

    // no new configuration, so go ahead and wait for the interval
    try {
     LogLog.debug(getName() +
       " watchdog going to sleep for " + interval + "ms");
     Thread.currentThread().sleep(interval);
    } catch (InterruptedException e) {
      // do nothing, fall through
    }

        return false;
  }
}


SocketWatchdog.java-----------------------------------------

/**
  Implementation for Watchdog that sets up a server socket and then
  watches it for open sockets.  When a socket is opened, the input
  stream from the socket is used to reconfigure log4j.

  @author Mark Womack
*/
public class SocketWatchdog extends WatchdogBase {

  /**
    The port the server socket will be created on. */
  protected int port = 0;

  /**
    The server socket this watchdog will watch. */
  protected ServerSocket server;

  /**
    The socket we are using a configuration source. */
  protected Socket socket;

  public
  SocketWatchdog() { }

  public
  SocketWatchdog(int _port) {
    port = _port;
  }

  /**
    Get the port the server socket will be created on. */
  public int getPort() {
    return port;
  }

  /**
    Set the port the server socket will be created on. */
  public void setPort(int _port) {
    port = _port;
  }

  /**
    Setup the server socket on the configured port. */
  protected
  void setup() {
    // if the server has not been created yet, create it
    if (server == null) {
      try {
        server = new ServerSocket(port);

        // wait for new sockets in 2 second increments
        server.setSoTimeout(2000);
      } catch (Exception e) {
        LogLog.error(this.getName() +
          " watchdog exception creating ServerSocket, deactivating", e);
        this.stopWatching();
      }
    }
  }

  /**
    Close and release the reference to the server socket. */
  protected
  void cleanup() {
    try {
        server.close();
        server = null;
    } catch (IOException e) {
        // do nothing
    }
  }

  /**
    Wait for a socket to open on the server socket. If one opens, save it
    and return true.  Otherwise, if not an error, return false.  If an
    error, stop the watchdog and return false. */
  protected
  boolean checkForReconfiguration() {
    // close any previous sockets
    if (socket != null) {
      try {
        socket.close();
      } catch (IOException e) {
        // do nothing
      }

      socket = null;
    }

    try {
      // look for a new one
      socket = server.accept();
      LogLog.debug(this.getName() +
        " watchdog accepting socket connetion.");
      return true;
    } catch (InterruptedIOException e) {
      // do nothing, fall through
    } catch (SecurityException e) {
      LogLog.error(this.getName() +
        " watchdog security exception accepting socket connection,
deactivating", e);
      this.stopWatching();
    } catch (Exception e) {
      LogLog.error(this.getName() +
        " watchdog exception accepting socket connection, deactivating", e);
      this.stopWatching();
    }

    return false;
  }

  /**
    Reconfigure using the input stream from the socket. */
  protected
  void reconfigure() {
    if (socket != null) {
      InputStream configInput = null;
      try {
        configInput = socket.getInputStream();
        reconfigureByInputStream(configInput);
      } catch (IOException e) {
        LogLog.error(this.getName() +
          " watchdog exception handling socket input stream", e);
      }
      finally {
        if (configInput != null) {
          try {
            configInput.close();
          }
          catch (Exception e) {
            // do nothing with this exception
          }
        }
      }
    }
    else {
      LogLog.warn(this.getName() +
        " watchdog socket is null, no input stream to reconfigure with");
    }
  }
}


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

Reply via email to