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