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