But Where Does It Come From?
Have you ever wondered where the configuration for CruiseControl.Net comes from? How about what happens to convert from the configuration to the projects list? And then, the question that I’m interested in, how can we extend it?
As these questions are all tied together, I’m going to run a series on posts on answering them. Why? I’m looking at adding security to CruiseControl.Net, and any security system needs to store its configuration information somewhere. So rather than write my own configuration system for the security, I figured I’d just extend the existing configuration system. Along the way I might take a couple of detours to address other issues, but it’ll all be focused around the configuration.
A Starting Point
My first question addressed the issue of where the configuration is stored, and it has an easy answer – ccnet.config. This is a file that is stored in the file system (the location can be configured). It’s an XML file in a similar vein to the configuration files for a .Net application. I can think of some reasons why it was done this way:
- Ease of set-up: no need to install or configure a database server or other storage systems.
- Portability: can easily just copy it from one machine to other.
- System-independence: every operating system has a file system, therefore no need to worry about converting for Linux, etc.
- Easily extended: can add extensions without needing to add additional configuration processing
Having said that, the way the configuration loading works we could write a configuration loader that would handle an alternate storage type (e.g. a database). The configuration is implemented using a series of interfaces – of which one or more can be replied (you’d have to add code to wire them up though). These classes are:
- IConfigurationService: service interface for exposing the configuration system
- IConfigurationFileLoader: interface for loading the configuration settings
- IConfigurationFileSaver: interface for saving the configuration settings
- IConfiguration: interface to expose the actual configuration settings
IConfigurationService only uses IConfiguration, it know nothing about the other two interfaces. There are three implementations, of which only one does know about these interfaces. The implementations are:
- FileConfigurationService: an implementation that uses IConfigurationFileLoader and IConfigurationFileSaver to load and save from the file system.
- FileWatcherConfigurationService: an implementation that monitors any file changes and if detected tells any listeners to reload. This uses an IConfigurationService instance internally to actualy load the settings.
- CachingConfigurationService: the settings are cached so the system doesn’t need to go to the file system everytime the settings are queried. This also uses an IConfigurationService instance internally to actualy load the settings.
In the current code these three services are chained. That is CachingConfigurationService uses an instance of FileWatcherConfigurationService, which in turn uses an instance of FileConfigurationService (the FileWatcherConfigurationService instance can be skipped). If we wanted to store our settings in a database we would write a database service and then change the start-up process to use this instead of one of the three existing implementations (choose which other functionality is required).
Loading from the File
The actual loading from the file is handled by an instance of IConfigurationFileLoader (assuming we’re using FileConfigurationService). The default implementation, DefaultConfigurationFileLoader, uses the Exortech.NetReflector library to handle the XML deserialisation. If we wanted to use another file-system format (say CSV), we could write our own implementation of IConfigurationFileLoader to handle this and we wouldn’t need to change anything else! Pretty neat in my opinion
Back to DefaultConfigurationFileLoader. It uses another class called NetReflectorConfigurationReader, to call into the Exortech.NetReflector library.This is mainly a wrapper class to handle loading each project and adding it to the projects list in an IConfiguration instance.
The Exortech.NetReflector library library appears to do some cool stuff around dynamically loading types from some attributes. If we take a look through some of the various classes that are loaded from configuration we see instances of ReflectorType and ReflectorProperty attributes. These obviously tell Exortech.NetReflector how to handle the XML data and which classes to generate. I don’t know too much about them, so I’m not going to go into detail here.
Extending the Configuration
So far we covered my first two questions, that merely leaves how to extend the configuration. If we want to add a new instance of an already existing interface (e.g. an ITrigger, ITask, IProject, etc.) this is easy. We just add the new class, make sure it implements the correct interface and then decorate it with ReflectorType and ReflectorProperty attributes.
We use the ReflectorType attribute to tell the loader which class instance to instantiate. Then, each property that we want to load we decorate with a ReflectorProperty attribute. And that’s all there is to it. When we add our new configuration into CCNet.config the loader will handle it for us automatically. Sure makes extending the existing interfaces simple.
Coming Up
I’ve covered very quickly how to add new implementations of existing interfaces. However for the security sub-system I’m thinking of adding an entirely different type of interface – completely unrelated to what’s already there. Not only will it be new, it will need to hook into a couple of different places. Therefore I’ll need to do more than just decorating my classes with ReflectorType and ReflectorProperty attributes. In my next post in this series I’m planning on looking at just what we need to do for this scenario.
RSS - Posts