About as Sexy as Java Configuration Gets

Tagged:

The Jakarta Commons project recently released version 1.6 of their open-source Java configuration library. Code to handle project configuration isn't, admittedly, the sexiest of technologies — but the Jakarta project does it exactly right. Its "composite configuration" features are particularly useful to manage configuration settings for different runtime environments.

As an example of how composite configuration works, let's say we're working an eCommerce project creatively named "estore." We need a library that sends out email notifications to all existing customers of new products, say. In production, these emails should obviously be sent to the customers. But in a QA environment, they most certainly should not be sent to the customers — imagine their confusion if they received notification of a new product named "Test Product Two" and costing $9999.99! In development, only emails to the currently working dev should be sent. And so on.

At Ideograph we've addressed this category of problem by checking each outgoing email against a regular expression filter right before sending. If the outgoing address doesn't pass through the filter, then it just isn't sent. Preventing emails from "escaping" keep us safe from potentially embarrassing error. The code looks something like this:

  1. public class OutgoingEmail extends Email {
  2.  
  3. public OutgoingEmail() {
  4. filter = Pattern.compile(config.getEmailFilter());
  5. }
  6.  
  7. // ...blah blah blah...
  8.  
  9. // Add the specified address to the list of recipients
  10. public void addTo(String address) {
  11. Matcher m = filter.matcher(address);
  12. if(m.matches()) {
  13. super.addTo(address);
  14. } else {
  15. log.warn("Not sending to " + address + " because does not pass filter.");
  16. }
  17.  
  18. // ...blah blah blah...
  19.  
  20. private static Config config = new Config();
  21. private Pattern filter;
  22. }

This code is pretty easy to understand: if the recipient address passes through our filter, then the email is sent; if it doesn't, then we drop it on the floor and log a warning. The right filter to use is selected at runtime by checking with our Config object, which is just a thin wrapper around the commons-configuration library. Here are the two relevant methods:

  1. public class Config {
  2.  
  3. // Load up the configuration from estore.xml
  4. public Config() {
  5. ConfigurationFactory factory = new ConfigurationFactory();
  6. URL configURL = Configuration.class.getResource("/estore-conf.xml");
  7. factory.setConfigurationURL(configURL);
  8. config = factory.getConfiguration();
  9. }
  10.  
  11. // ...blah blah blah...
  12.  
  13. // Return the email filter
  14. public String getEmailFilter() {
  15. return config.getString("email.filter", "");
  16. }
  17.  
  18. // ...blah blah blah...
  19.  
  20. private org.apache.commons.configuration.Configuration config;
  21. }

In the constructor of this object, we read up all the settings from the configuration file estore-main.xml, which is found in the project's classpath. (commons-configuration can read from a variety of different file formats, but we've chosen to use XML here.) The getEmailFilter method reads from the email/filter element, defaulting to the empty string if there's no other setting. (Note that we've chosen a very safe default here — unless there's a specific filter pattern set, no email address will match the default and thus no emails will get out.)

We keep our default configuration settings in the file estore-default.xml, which looks in part like this:

  1. <?xml version="1.0" encoding="ISO-8859-1"?>
  2. <estore>
  3. <!-- ...blah blah blah... -->
  4.  
  5. <mail>
  6.  
  7. <!-- SMTP settings -->
  8. <host>smtp.ideograph.com</host>
  9. <username>jon</username>
  10. <password>v3rys3cret!</password>
  11.  
  12. <!-- Email filter: only send to these addresses -->
  13. <filter>.*ideograph.com</filter>
  14.  
  15. </mail>
  16.  
  17. <!-- ...blah blah blah... -->
  18. </estore>

Nothing too fancy so far: by default, the filter effectively permits emails to be sent to ideograph.com addresses only.

But note that this file, estore-defaults.xml is not the one read by our Config object. The Config object reads estore-conf.xml, which looks like this:

  1. <?xml version="1.0" encoding="ISO-8859-1"?>
  2. <configuration>
  3. <xml fileName="estore-node.xml" optional="true"/>
  4. <xml fileName="estore-default.xml"/>
  5. </configuration>

This is where the composite configuration comes into play. This block tells commons-configuration to read the settings from two files, in the specified order of priority. If there is a estore-node.xml file, and if that file contains a value for the setting we're reading, then that value is returned. Otherwise, drop back and pull the value from estore-default.xml.

estore-node.xml contains the overrides for a particular node type — in this example, either production, QA or dev. The settings specified in that file are "composited" on top of the values that are in default.

We keep QA settings in a file named estore-qa.xml. These settings allow emails to be sent to the estore QA team but no one else:

  1. <?xml version="1.0" encoding="ISO-8859-1"?>
  2. <estore>
  3. <mail>
  4. <filter>qa@estore.com</filter>
  5. </mail>
  6. </estore>

Jon's dev settings are in a file named estore-local.xml:

  1. <?xml version="1.0" encoding="ISO-8859-1"?>
  2. <estore>
  3. <mail>
  4. <filter>jon@ideograph.com</filter>
  5. </mail>
  6. </estore>

And finally, for production, the settings in estore-production.xml allow emails to be sent to the actual customers:

  1. <?xml version="1.0" encoding="ISO-8859-1"?>
  2. <estore>
  3. <mail>
  4. <filter>.*</filter>
  5. </mail>
  6. </estore>

We keep three files in the source code control system: the full set of system configuration settings, in estore-default.xml; settings for the QA node in estore-qa.xml; and of course the production settings in estore-production.xml. Each dev has his or her own copy of estore-local.xml. Our Ant script knows which file to copy over into estore-node.xml before package and release.

True, configuration is not particularly sexy. But it's very important to get right. The Jakarta project's commons-configuration makes it easy for us to get right.

Comments

How to override specific properties

Is there a way to control the properties that can be overridden. I want some settings to always be in the default configuration and others which can be overridden. Here is what I am looking at.

<?xml version="1.0" encoding="UTF-8"?>
<myconfig>
<sysprops>
<testkey>98759875789</testkey>
</sysprops>
<configprops>
<testkey1>bmnbbn</testkey1>
</configprops>
</myconfig>

The properties under sysprops cannot be overridden but everything under configpros can. Is there a way to do this using the conf XML approach? I have already tried using override combiner and specifying the list-nodes but it loads all settings from the override file.

I had this working in the past but its been a long time and I am having difficulty getting this to work. Any help would be appreciated.

No way that I'm aware of

There's no way that I'm aware of that lets you specify specific values that cannot be overridden.

I suppose one approach would be to split the configuration into two separate areas -- have one Configuration object for the sysprops, which loads only sysprops.xml, and another for all the normal overrideable ones. Then pick the Configuration object to read from based on the path.

I'd love to see what you end up with.

Client Login