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]>