ColdBox and automatically loading interceptors for modules within your application

In the last few years I have been working on a fairly largish project in my spare time, and I was getting a little tired of registering all my interceptors in the configuration each and every time. I thought to myself that there had to be a better way than this.

After thinking about this for a long time I came up with the following solution.

The ability to drop a module in ColdBox can be as simple as just dropping it in, and re-initialise the application. However if your module then uses interceptors you need to first put this information into the configuration so it is defined, I wanted to stop doing this step and my solution is as follows.

The one thing that is required is an interceptor that is part of the main part of the application, its sole job will be the meat and bones to this, and it only needs to be setup once. After that you just re-initialise the application.

So lets begin by creating a directory called interceptors, or you can juts place this into the location that you store them now. But I try to keep everything in their own location to help segregate  logic from services, and models and domain models, with everything in their place.

The first thing we need to do is modify the configuration file for our application, so that it will then load our interceptor and listen for the required interception point.

So we then just add

<config>

{ class="system.interceptors.applicationInterceptor", properties={} }

</config>

I should point out this is using the script version and you will need to modify any of the code snippets to suit you, this doesn't matter in the order it gets loaded or at least I haven't seen any problems by just adding it to the end of the current list of interceptors.

our interceptor is now going to be called applicationInterceptor.cfc and I named it this way, because I wanted to place code in here that is going to be related to the running of my main application part. However you can call it anything you want, just don't forget to acknowledge that in the configuration file.

Modules have two interception points that we need to listen for, and will provide the functionality that we require to do this job.

Lets look at the first one, with the following snippet of code.

public void function postModuleLoad(event, interceptData) {
   var modules = controller.getSetting("modules");
}

So far we have just set it up to listen for the interception point for post loading of a module, this means that when ColdBox loads a module we are going to listen for that event.

And we will now modify it to do the work that we require it to do.

public void function postModuleLoad(event, interceptData) {
   var modules = controller.getSetting("modules");
   var modulePath = ExpandPath(replace(modules[interceptData.modulename].invocationPath & '.interceptors', '.', '/', 'all'));
   var files = DirectoryList(moduleRoot, false, "query", "*.cfc");
   for(var count=1; count <= files.recordCount; count++) {
      var interceptorName = listGetAt(files['name'][count], 1, '.');
      var interceptorClass = right(interceptorPath, len(interceptorPath)-1);
      var interceptorClass = reReplace(interceptorClass & '/' & interceptorName, '/', '.', 'ALL');
      arrayAppend(instance.interceptors, interceptorName);
      controller.getInterceptorService().registerInterceptor(interceptorClass=interceptorClass, interceptorName=interceptorName);
   }
}

Ok that might be not self explanatory so I will break it down, when the interceptor event is announced the interceptData passed in will contain certain information. We first get the module name and append the directory interceptors to it, it is then changed form the dot notation into a path structure and the we expand path to get the physical directory.

Once we have that we then get a list of components in that directory, as we are going to always assume that these are only going to be interceptors, we then get the name of the file and the class. We then append this to an internal array for later use, and then register the interceptor.

So now we can drop any form of interceptor into this directory, re-initialise the application and away we go.

The last thing we need to do to make sure we are cleaning up after ourselves, is to now make sure that the interception point of unloading a module is listened for, and to do that we now add the following code.

public void function preModuleUnload(event, interceptData) {
   for(var key in instance.interceptors) {
      controller.getInterceptorService().unRegister(interceptorName=key);
   }
}

So now that any time that the module is unloaded, we can now make sure we remove the interceptors we had registered.

Now the caveat here is that the assumption is made that the interceptors are not required in any particular order, if you require an interceptor to be loaded and acted on before any module interceptor is required, you will have to add the registration of that interceptor to the configuration file.

Now although I haven't checked this as I have taken this as a given, if I try to register an interceptor that is already registered it will be skipped in the registration process, as it has already been found to be setup.

One of my next steps to this is to modify this code, so that the interceptor can be added via annotations in the component. This means that what you can setup when registering these in the configuration file, can be used by annotations from the component being registered. I have even thought about a priority system when adding the interceptors, so that they are setup in a predefined order, which would allow for interceptors to be loaded in the order that you need them.


ColdBox and automatically loading interceptors for modules within your application - http://goo.gl/CCAbh Apr 16, 2011