|
The Java Specialists' Newsletter
Issue 095 2004-09-30
Category:
Language
Java version: Sun JDK 1.5.0-rc Self-reloading XML Property Filesby Dr. Heinz M. Kabutz
Welcome to the 95th edition of
The Java(tm) Specialists' Newsletter.
A special thank you goes to Ng Keng Yap from Malaysia for
motivating me to publish this newsletter.
Upcoming Java Specialist Master Courses:
- please click here to sign up.
As from May 2010, we are also offering this course on the island of Crete. We
only accept 6 students per class in Crete, due to the size of our conference
room. Please book early to avoid disappointment!
San Jose CA, Mar 16-19 2010, $3500 Ottawa, Canada, Mar 22-25 2010, $3500 Oslo, Norway, Apr 13-16 2010, Kr 24500 Montreal, Canada, Apr 20-23 2010, $3500 Toronto, Canada, May 17-20 2010, $3500 Chania, Crete, May 25-28, Jun 29-Jul 2 or Aug 24-27 2010, €2500
In-house courses if these dates or locations do not suit you - click here for more information. Self-reloading XML Property Files
I was chatting to a friend of mine tonight, who mentioned
that after installing automatic Windows updates, the glorious
operating system was now prompting him every five minutes
whether he wanted to restart his computer. Irritating, is it
not?
Whenever possible, I try to write software that you can
configure "on the fly" without requiring a restart. This
can get difficult when the only way of configuration is a
bunch of XML files. Usually, the only way for these changes
to apply to our system is to restart the program once the
XML files have been edited.
To start, I use an interface that I call Configuration. It
allows me to get one or all properties, and allows me to
add listeners for property changes. I try to use generics
whenever I can, since they make the code clearer, in my
opinion (not shared by many ;-) If you look at
getAllProperties(), we can clearly see what it returns - a
set of Map.Entry implementations, each of which consists of
a String/Object pair.
import java.beans.PropertyChangeListener;
import java.util.*;
public interface Configuration {
Object getProperty(String propertyName);
Set<Map.Entry<String,Object>> getAllProperties();
void addPropertyChangeListener(PropertyChangeListener listener);
boolean removePropertyChangeListener(PropertyChangeListener listener);
}
I have implemented a basic AbstractConfiguration that could
be extended with many different types of config loaders.
Whenever a property is set, it checks whether it is the same
value as before. If it is not, it sends a
PropertyChangeEvent to all the listeners.
There are two types of lists that I use: ArrayList and
my CircularArrayList.
I have not found a single use for
LinkedList yet, have you? A new list that I will use here
is the CopyOnWriteArrayList from the new java.util.concurrent
package. This list is handy when the contents of your list
change seldomly. Whenever the list is changed, it makes a
copy of the old list and changes the copy. You can therefore
have active Iterators on the old list and you will not get a
ConcurrentModificationException anymore.
import java.beans.*;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* @author Heinz Kabutz
*/
public abstract class AbstractConfiguration implements Configuration {
/** * A map of all the properties for the configuration */
private final Map<String, Object> properties =
new HashMap<String, Object>();
private final Collection<PropertyChangeListener> listeners =
new CopyOnWriteArrayList<PropertyChangeListener>();
/** Make a daemon timer to check for changes. */
private final Timer timer = new Timer(true);
/**
* This class has a timer to check periodically if the
* configuration has changed. If it has, it reloads the
* properties. This may cause the property change events to
* fire.
*
* @param period number of milliseconds between checking for
* property changes.
*/
protected AbstractConfiguration(int period) {
timer.schedule(new TimerTask() {
public void run() {
checkForPropertyChanges();
}
}, period, period);
}
/**
* This method should be overridden to check whether the
* properties could maybe have changed, and if yes, to reload
* them.
*/
protected abstract void checkForPropertyChanges();
public final Object getProperty(String propertyName) {
synchronized (properties) {
return properties.get(propertyName);
}
}
public Set<Map.Entry<String, Object>> getAllProperties() {
synchronized (properties) {
return properties.entrySet();
}
}
/**
* Each time we set a property, we check whether it has changed
* and if it has, we let the listeners know.
*/
protected final void setProperty(String propertyName, Object value) {
synchronized (properties) {
Object old = properties.get(propertyName);
if ((value != null && !value.equals(old))
|| value == null && old != null) {
properties.put(propertyName, value);
PropertyChangeEvent event = new PropertyChangeEvent(this,
propertyName, old, value);
for (PropertyChangeListener listener : listeners) {
listener.propertyChange(event);
}
}
}
}
public void addPropertyChangeListener(PropertyChangeListener l) {
listeners.add(l);
}
public boolean removePropertyChangeListener(PropertyChangeListener l) {
return listeners.remove(l);
}
}
In my code, I usually have a central place to send Exceptions
so that they can be logged, or the user alerted. I usually
call this class Exceptions:
/**
* In your code, you can make Exceptions more robust. For
* example, show a dialog to the user, send an email to the QA
* team, etc.
*
* @author Heinz Kabutz
*/
public class Exceptions {
public static void throwException(Throwable e) {
e.printStackTrace();
}
}
JDK 1.5 java.util.Properties has the ability to load and
store property files in XML format directly, thanks to Ng
Keng Yap for pointing this out to me. Our
XMLFileConfiguration class reads the file date time and if
it has changed, reads the values and sets them. Those
properties that have changed will now cause events.
import java.io.*;
import java.util.*;
public class XMLFileConfiguration extends AbstractConfiguration {
private final File file;
private long lastModified = 0;
public XMLFileConfiguration(Properties defaults, String filename, int period)
throws IOException {
super(period);
setAllProperties(defaults);
file = new File(filename);
if (!file.exists()) {
storeProperties();
}
loadProperties();
}
private void loadProperties() throws IOException {
Properties properties = new Properties();
properties.loadFromXML(new FileInputStream(file));
setAllProperties(properties);
}
private void setAllProperties(Properties properties) {
for (Map.Entry<Object, Object> entry : properties.entrySet()) {
setProperty((String) entry.getKey(), entry.getValue());
}
}
public void storeProperties() {
Properties properties = new Properties();
for (Map.Entry<String, Object> entry : getAllProperties()) {
properties.put(entry.getKey(), entry.getValue());
}
try {
properties.storeToXML(new FileOutputStream(file),
"Generated by XMLFileConfiguration");
} catch (IOException e) {
Exceptions.throwException(e);
}
}
protected void checkForPropertyChanges() {
if (lastModified != file.lastModified()) {
try {
lastModified = file.lastModified();
loadProperties();
} catch (IOException e) {
Exceptions.throwException(e);
}
}
}
}
This code is by no means robust. I wrote it very quickly,
and there are many caveats that are not dealt with. For
example, what if we need several properties in order to
effectively change our system, such as a user name and
password.
To show how it works, here is a short test class:
import java.beans.*;
import java.util.Properties;
import java.io.FileOutputStream;
public class Test {
public static void main(String[] args) throws Exception {
String filename = "test.xml";
Properties defaults = new Properties();
defaults.setProperty("name", "Heinz");
defaults.setProperty("yahooid", "heinzkabutz");
defaults.setProperty("age", "32");
Configuration cfg = new XMLFileConfiguration(defaults, filename, 300);
cfg.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
System.out.println("Property " + evt.getPropertyName() +
" has now changed from <" + evt.getOldValue() +
"> to <" + evt.getNewValue() + ">");
}
});
Thread.sleep(1000);
defaults.setProperty("age", "33");
FileOutputStream fos = new FileOutputStream(filename);
defaults.storeToXML(fos, "");
fos.close();
Thread.sleep(1000);
defaults.setProperty("age", "32");
fos = new FileOutputStream(filename);
defaults.storeToXML(fos, "");
fos.close();
Thread.sleep(1000);
defaults.setProperty("age", "2 ^ 4");
fos = new FileOutputStream(filename);
defaults.storeToXML(fos, "");
fos.close();
Thread.sleep(1000);
}
}
Tomorrow (oh tomorrow is already today ;-) I am driving with
a good friend all over the countryside upgrading a system
that we built. We leave at 06:00, so now (00:35) I need to
stop writing. Please let me know if you find obvious errors
in the code :-)
Kind regards
Heinz
Language Articles
Related Java Course
|