ColdBox Interceptor's Guide
- ColdBox Interceptor's Guide
- Introduction
- How do they work?
- Order of Creation
- Reserved Words & Methods
- Core Interception Points
- Core Interception Intercept Data
- Interceptor Output Buffer
- Custom Interception Points
- Announcing Interceptions
- Unregistering Interceptors
- Appending Custom Interception Points
- Register Interceptors at Runtime
- Reporting Methods
- Conclusion
Introduction
The main purpose of creating ColdBox interceptors is to increase functionality for applications and framework alike, without touching the core functionality, and thus encapsulating logic into separate objects. This pattern wraps itself around a request in specific execution points in which it can process, pre-process, post-process and redirect requests. These interceptors can also be stacked to form interceptor chains that can be executed implicitly. The chaining is all about positioning in the configuration file or order of registration. The order of declaration is very important. These stacked interceptor chains form a chain of separate, declarative-deployable services to an existing web application or framework without incurring any changes to the main application or framework source code. This is a powerful feature that can help developers and framework contributors share and interact with their work. Another important aspect to note is that Interceptors have full access to a request lifecycle and the framework. Thus, they can get application settings, redirect control, execute events, use the cache manager, get plugins, transform views, adapt views for certain protocols and much more.(Read more on Intercepting Filters)
However, they are much more than just interceptions at specific execution points. ColdBox interceptors also provides you with the ability to create your own broadcasters or listeners for your applications. You can register any amount of custom interception points (listeners) and register ANY object to listen to those interception points you registered either in the configuration file or programmatically at runtime. If you are familiar with design patterns, custom interceptors can give you an implementation of observer/observable objects, much like any event-driven system can provide you. If you are not familiar with event-driven systems or what an observer is, please read the following resources. In a nutshell, an observer is an object that is registered to listen for certain types of events, let's say as an example onError is a custom interception point and we create a cfc that has this onError method. Whenever in your application you announce or broadcast that an event of type onError occurred, this cfc will be called by the ColdBox interceptor service. Please keep on reading and you will understand more.
Resources
- http://en.wikipedia.org/wiki/Observer_pattern
- http://sourcemaking.com/design_patterns/observer
- http://java.sun.com/blueprints/corej2eepatterns/Patterns/InterceptingFilter.html
Interceptor Class Diagram
Interceptor Sequence Diagram
For what can I use them
Below are just a few applications of ColdBox Interceptors:
- Security
- Event based Security
- Method Tracing
- AOP Interceptions
- Publisher/Consumer operations
- Implicit chain of events
- Content Appending or Pre-Pending
- View Manipulations
- Custom SES support
- Cache advices on insert and remove
- Much much more...
How do they work?
The basics of an interceptor are on its base class, coldbox.system.interceptor. You will have to make your custom interceptors extend this base class if you would like to register them in your configuration file. As of ColdBox 2.6.0 you can register ANY object to be a custom interceptor by using the API provided in the interceptor service, please look at the full coldbox API for more information. Below you can see a class diagram of this class and how you would extend it.
The interceptor has one important method that you can use for configuration, called configure. This method will be called right after the interceptor gets created and injected with your properties. I will go over properties received later in the guide, but just understand that when you declare an interceptor in your configuration file or register them programmatically, you will also pass in properties into the interceptor (Simple or Complex). This properties are local to the interceptor only, and thus you have the utility functions to retrieve them, verify them and even dynamically set them. As you can see on the diagram, the interceptor class is part of the frameworkSuperType, and thus inheriting the functionality of the framework. (See API).
Common Property Methods
- getProperty(name) : Get a single property
- setProperty(name, value) : Set a property
- propertyExists() : Verify that a property exists
- getProperties() : Get the entire properties struct
- setProperties() : Set the entire properties struct
Conventions
What about the interception points that you keep mentioning. Well, below we will cover all of the interception points that ColdBox provides for you. However, the main convention on interceptors is that you will have to create a method with the same name as the interception point you would like to listen to. If you want it to listen to the preProcess point, you will create a preProcess method. What else? Well, this method has a return type of boolean and two arguments. So let's explore their rules.
- The interceptor method must have the same name as the interception point it is trying to listen to
- This method must accept two arguments
- event of type coldbox.system.beans.requestContext, which is the request context
- interceptData of type structure. This is a structure of data that the method receives.
- This method must return a boolean value or none at all.
The return boolean variable is very important because it tells the interceptor service whether to continue executing interceptors in the chain or not. Ok, what chain, well you can have as many interceptor objects listening to the same interception point. So if interceptors are declared like so:
- interceptor X - preProcess
- interceptor Y - preProcess
- interceptor Z - preProcess
Let's say that interceptor Y returns a true from the preProcess method, then interceptor Z will never get executed, the chain is broken.
Return Values
- False or Nothing - Continue executing Chain
- True - Stop executing Chain
Below you can see a sample interception method:
<cffunction name="afterConfigurationLoad" output="false" access="public" returntype="boolean" hint="ENVIRONMENT control the settings"> <!--- *********************************************************************** ---> <cfargument name="event" required="true" type="coldbox.system.beans.requestContext" hint="The event object."> <cfargument name="interceptData" required="true" type="struct" hint="A structure containing intercepted information. NONE BY DEFAULT HERE"> <!--- *********************************************************************** ---> <cfscript> if( getProperty('interceptorCompleted') eq false){ parseAndSet(); setProperty('interceptorCompleted',true); } return false; </cfscript> </cffunction>
Interceptor Properties
An interceptor can have 0,1 or more properties that can be defined in the coldbox.xml or at runtime. The important note about interceptor properties are that they can be simple or complex by using JSON notation. This follows the ColdBox complex variables JSON notation we have seen before.
//Simple <Property name="DSN" value="MyDSN" /> //Complex <Property name="myArray" value="[1,2,3,4]" /> <Property name="arrayOfStructs" value="[{'name':'luis','alias':'pio'},{'name':'Sana Ullah','alias':'Code Machine'}]" />
Sample Interceptor Declaration
Below we can see how we declared an interceptor in our configuration file. For the full guide on how to declare interceptors, please read the Configuration Guide The point to grind here is the order of declaration. The interceptors will be executed in order of declaration, if they match on the interception points they implement. So please be aware that order matters. To declare an interceptor, all you need to do is declare an Interceptor element and provide a class attribute to it. If the interceptor takes in any properties, then just nest as many Property elements as you need. The property element has one attribute for the name and the contents for its value. The important notes, is that you can use the ColdBox syntax for declaring simple or complex properties here, which is JSON syntax.
The other element is the CustomInterceptionPoints element. Here you will declare a comma-delimited list of custom execution points you would like to implement in your application. ColdBox will then register them as valid interception points.
<Interceptors throwOnInvalidStates="true"> <CustomInterceptions>onError,onDataIn</CustomInterceptions> <Interceptor class="coldbox.system.interceptors.environmentControl"> <Property name="configFile">/applications/coldbox/testing/tests/resources/environments.xml.cfm</Property> <Property name="fireOnInit">true</Property> </Interceptor> <Interceptor class="coldbox.interceptors.executionTracer"> <Property name="Simple">Luis</Property> <Property name="myArray">[1,2,3,4,5]</Property> <Property name="myStruct">[db:"luis", username:"test", password:"luis" ]</Property> </Interceptor> <Interceptor class="coldbox.interceptors.requestTrim"> <Property name="trimall">true</Property> </Interceptor> <Interceptor class="coldbox.interceptors.security"> <Property name="defaultRole">user</Property> <Property name="method">database</Property> </Interceptor> </Interceptors>
- @throwOnInvalidStates:boolean (If an interception is announced and no interceptors have been registered for it, throw an exception or not)
- <CustomInterceptions>:string (The list of custom interception points)
- <Interceptor> : The interceptor to register, there can be 0..* declarations.
- @class : The instantiation path of the interceptor
- <Property> : The property to declare, there can be 0..* declarations.
- @name : The name of the property
- value : The value of the property element.
Sample Interceptor Component
<cfcomponent name="myInterceptor" hint="This is a simple interceptor" output="false" extends="coldbox.system.interceptor"> <cffunction name="Configure" access="public" returntype="void" hint="This is the configuration method for your interceptors" output="false" > <!--- I set up myself ---> <cfset setProperty('ConfigurationTime', now() )> </cffunction> <cffunction name="afterConfigurationLoad" output="false" access="public" returntype="void" hint="ENVIRONMENT control the settings"> <!--- *********************************************************************** ---> <cfargument name="event" required="true" type="coldbox.system.beans.requestContext" hint="The event object."> <cfargument name="interceptData" required="true" type="struct" hint="A structure containing intercepted information. NONE BY DEFAULT HERE"> <!--- *********************************************************************** ---> <cfscript> if( getProperty('interceptorCompleted') eq false){ parseAndSet(); setProperty('interceptorCompleted',true); } </cfscript> </cffunction> </cfcomponent>
Order of Creation
This is an important but small section that discusses the order of creation of the interceptors. Interceptors are created and configured at application start up, right after the framework reads your configuration file and parses it. This means, that the application is brand new, not even aspects have been setup, so you have to be careful of the code that you place in your configure methods because you might be calling something that doesn't exist yet. But what if I want a service object injected into my interceptor? Well, you use the interception method afterAspectsLoad to get what you need from the ioc plugin, but if you are not using an IoC container, then just create the object on the configure method and save it in the coldbox cache. Another approach to injecting dependencies on interceptors is to actually use another interceptor. Ok, this is more confusing. Not at all, we have built an interceptor called autowire that actually wires up your objects with dependencies from the cache or from IoC frameworks like ColdSpring or LightWire. I highly suggest reading the Autowire Guide.
Self Injection
<cfcomponent name="myInterceptor" hint="This is a simple interceptor" output="false" extends="coldbox.system.interceptor"> <cffunction name="afterAspectsLoad" output="false" returntype="void"> <!--- ************************************************************* ---> <cfargument name="event" required="true" type="coldbox.system.beans.requestContext" hint="The event object."> <cfargument name="interceptData" required="true" type="struct" hint="interceptData of intercepted info."> <!--- ************************************************************* ---> <cfscript> //Inject this interceptor with a service. setSecurityService( getPlugin("ioc").getBean("SecurityService") ); setUserService( getPlugin("ioc").getBean("UserService") ); //Inject this interceptor with model objects. setSecurityService( getModel("SecurityService") ); setUserService( getModel("UserService") ); </cfscript> </cffunction> </cfcomponent>
Using Autowire
As you will see below, by using the autowire plugin, I just eliminated creating another interception point in my interceptor. My code is clean and compact.
<cfcomponent name="myInterceptor" hint="This is a simple interceptor" output="false" extends="coldbox.system.interceptor" autowire="true"> <!--- Dependencies ---> <cfproperty name="SecurityService" type="ioc" scope="variables" /> <cfproperty name="UserService" type="ioc" scope="variables" /> <!--- Or Model Integration ---> <cfproperty name="SecurityService" type="model" scope="variables" /> <cfproperty name="UserService" type="model" scope="variables" /> </cfcomponent>
Please note that autowiring has an extensive array of types that you can use for injection. Please read the Autowire Guide for an in-depth overview of how to do autowiring of dependencies.
Order Outline
Below is a simple order of execution list:
- Framework loads application configuration
- Framework parses configuration
- Framework loops over the interceptors declarations and
- Creates the interceptor
- Registers the interceptor with the appropriate interception points
- Calls its configure method.
- Frameworks calls the afterConfigurationLoad interception point
- Framework loads and prepares the application's aspects (ioc, logging, etc)
- Framework calls the afterAspectsLoad interception point
- Framework calls application start handler.
And so forth...
Reserved Words & Methods
For a full list of reserved words and methods for interceptors, please visit our Reserved Guide.
Core Interception Points
As you can see from the diagram, there are several core interception points that you can tap into. Below is a simple chart for you to understand these interception points.
| Interception Point | Description |
| afterConfigurationLoad | This occurs after the framework loads and your applications' configuration file is read and loaded. An important note here is that your application aspects have not been configured yet: bug reports, ioc plugin, logging, and internationalization. This is a great interception point to load custom configuration settings per tier. Tier control plugins should tap into this point. |
| afterAspectsLoad | This occurs after the configuration loads and the aspects have been configured. This is a great way to intercept on application start. You can also use the application start handler setting and point to an event. However, if you want to have separation and even chaining, this is the way to do it. |
| preProcess | This occurs after a request is received and made ready for processing. This simulates an on request start interception point. Please note that this interception point occurs before the request start handler. |
| preEvent | This occurs before ANY event execution, whether it is the current event or called via the run event method. It is important to note that this interception point occurs before the preHandler convention. (See Event Handler Guide) |
| postEvent | This occurs after ANY event execution, whether it is the current event or called via the run event method. It is important to note that this interception point occurs after the postHandler convention (See Event Handler Guide) |
| preRender | This occurs before the main set view/layout combination is rendered. A great way to transform output or inject output. |
| postRender | This occurs after the main set view/layout combination is rendered. A great way to finalize output transformations or inject your own output. |
| postProcess | This occurs after rendering, usually the last step in an execution. This simulates an on request end interception point. |
| sessionStart | This occurs only when using an Application.cfc application, because it models the onSessionStart method |
| sessionEnd | This occurs only when using an Application.cfc application, because it models the onSessionEnd method |
| afterCacheElementInsert | This occurs when a new element is inserted into the coldbox cache. |
| afterCacheElementRemoved | This occurs when an element is removed or expunged from the coldbox cache. |
| afterCacheElementExpired | This occurs when an element is expired/reaped from the cache. It happens after the element has been removed |
| afterPluginCreation | This occurs right after a plugin gets created by coldbox |
| afterHandlerCreation | This occurs right after a handler gets created by coldbox |
| onException | This occurs whenever the framework detects an exception |
Core Interception Intercept Data
The interceptData argument that interceptors receive is a structure that was prepared at the time of execution with convenience data according to interception point or your own custom interception point. Below is a chart of the keys created for you on the core execution points. Also, when you are using your own interception points, you can use this structure to advice your interceptors with intercepted data. How cool is that!!
| Interception Point | InterceptedData Keys |
| afterConfigurationLoad | NOT USED |
| afterAspectsLoad | NOT USED |
| preProcess | NOT USED |
| preEvent | processedEvent : the event fired |
| postEvent | processedEvent : the event fired |
| preRender | renderedContent : the content to render |
| postRender | NOT USED |
| postProcess | NOT USED |
| sessionStart | NOT USED |
| sessionEnd | NOT USED |
| afterCacheElementInsert | cacheObjectKey : the key of the object cached, cacheObjectTimeout : the timeout used, cacheObjectLastAccessTimeout : the object last access timeout used. |
| afterCacheElementRemoved | cacheObjectKey : the key of the object removed |
| afterCacheElementExpired | cacheObjectKey : the key of the object expired |
| onException | exception : The exception object that ColdFusion creates |
| afterHandlerCreation | handlerPath : The handler instantiation path, oHandler: The handler object |
| afterPluginCreation | pluginPath : The plugin instantiation path, custom : Boolean flag that denotes if the plugin is core or custom, oPlugin : The plugin object |
Interceptor Output Buffer
This feature gets introduced in version 2.6.1 as an output buffer for interceptions. What this means is that an interceptor has the following methods that enable you to add content to an output buffer:
- clearBuffer():void
- appendToBuffer(string):void
- getBufferString():string
- getBufferObject():coldbox.system.util.RequestBuffer
The buffer is unique per interception point but available to the entire chain of execution within an interception point. Once the interception point is executed, the interceptor service will check to see if the output buffer has content, if it does it will advice to write the output to the output stream. This way, you can produce output very cleanly from your interception points, without adding any messy-encapsulation breaking output=true tags to your interceptors. (BAD PRACTICE). This is an elegant solution that can work for both core and custom interception points.
<!--- Pre Render Execution ---> <cffunction name="preRender" access="public" returntype="boolean" hint="Executes before the framework starts the rendering cycle." output="false" > <cfargument name="event" required="true" type="any" hint="The event object : coldbox.system.beans.requestContext"> <cfargument name="interceptData" required="true" type="struct" hint="A structure containing intercepted information. NONE BY DEFAULT HERE"> <cfscript> //clear all of it first clearBuffer(); //Append to buffer appendToBuffer('This software is copyright by Luis Majano'); </cfscript> </cffunction>
Custom Interception Points
Your custom interception points are declared in the ColdBox configuration file as you will see later in the guide. Below is a snippet of the config file:
<Interceptors> <CustomInterceptionPoints>onLog,onRecordInserted</CustomInterceptionPoints> </Interceptors>
As you can see, I have declared the CustomInterceptionPoints element with a comma-delimited list of my own custom execution points. These keywords are your own conventions when creating your methods. So in the case above, I have an onLog and onRecordInserted custom interception points. So my code in my interceptors would be the following:
<cffunction name="onLog" access="public" returntype="void" hint="My custom convention interceptor" output="false" > <!--- ************************************************************* ---> <cfargument name="event" required="true" type="coldbox.system.beans.requestContext" hint="The event object."> <cfargument name="interceptData" required="true" type="struct" hint="interceptData of intercepted info."> <!--- ************************************************************* ---> <!--- Do whatever I want here ---> </cffunction> <cffunction name="onRecordInserted" access="public" returntype="void" hint="My custom convention interceptor" output="false" > <!--- ************************************************************* ---> <cfargument name="event" required="true" type="coldbox.system.beans.requestContext" hint="The event object."> <cfargument name="interceptData" required="true" type="struct" hint="interceptData of intercepted info."> <!--- ************************************************************* ---> <!--- Do whatever I want here ---> </cffunction>
That is awesome, I declared them and then I code them, but how do they get executed? Well, there is a special method called announceInterception() that all your handlers,plugins and even the interceptors themselves receive. So all you need to do to broadcast an interception is to actually call this method with the interception state keyword and the intercepted data structure (if used). Please note that you can also explicitly announce the core interception points using the API. The sample below is called from an event handler:
<!--- prepare interception structure ---> <cfset var data = structnew()> <cfset data.timeIntercepted = now()> <cfset data.user = event.getValue("oUser")> <cfset data.event = event.getCurrentEvent()> <!--- Broadcast Interception ---> <cfset announceInterception('onLog', data)> <!--- Alternate Broadcast Interception Syntax ---> <cfset getController().getInterceptorService().processState(state,interceptData)>
I just created an interception data structure and broadcasted the message to the interceptor service.
Announcing Interceptions
There are two ways to announce interceptions:
- announceInterception(state, interceptData) inherited by plugins, handlers and interceptors.
- getController().getInterceptorService().processState(state,interceptData)' via the controller.
So what is best practice? I would go with the facade method of announceInterception, or if you have injected the coldbox controller in your model, then via the interceptor service.
Unregistering Interceptors
The interceptor class has a utility method called unregister(state) which can be used to unregister an interceptor from a specific execution point.
<cfset unregister('preProcess')>
By using the code above in my interceptor, I would have unregistered it from the preProcess execution point or any point I pass.
Appending Custom Interception Points
You can append custom interception points programmatically by using the following method located in the interceptor service:
- appendInterceptionPoints(comma delimited list)
This method is used to register at runtime any custom interception points. If they are already registered, they will not be added again.
//Add new interception points
getController().getInterceptorService().appendInterceptionPoints('onLog,onError,flexcalls');
Register Interceptors at Runtime
Since version 2.6.0 you can now register ANY object to be an interceptor at runtime by using the utility methods in the interceptor service:
- registerInterceptor([interceptorClass],[intercetorObject],[interceptorProperties],[customPoints])
All the arguments are not optional, so let's explore them:
| argument | type | required | default | description |
| interceptorclass | string | false | --- | The interceptor class path to register. The service will instantiate it, configure it and cache it. |
| interceptorObject | object | false | --- | The interceptor object to register. MUTEX with the interceptor class |
| interceptorProperties | struct | false | structNew() | A structure of name-value pairs to use as properties to configure the interceptor with |
| customPoints | string | false | --- | A comma-delimited list of custom interception points to append to the custom interception list. This is most likely used to register an interceptor an its custom interception points in one single call |
As you can see from the arguments above, you can register an interceptor by just using a class path or by passing an already instantiated object. The interesting aspect of programmatic registration, is that the object just needs to implement the points it listens too. It can be ANY object of ANY type. Below are some samples:
//Add the current handler as an observer for the interception point of "flexcalls" getController().getInterceptorService().registerInterceptor(interceptorObject=this,customPoints='flexcalls'); //Add a non-instantiated interceptor as an observer for the interception point of 'wikiTranslate': getController().getInterceptorService().registerInterceptor(interceptorClass='model.wikiparser.feedparser',interceptorProperties=myProps,customPoints='wikiTranslate');
Reporting Methods
There are several reporting and utility methods in the interceptor service that I recommend you explore. Below are some sample methods:
//get the entire state container for preProcess For metadata or reporting
getController().getInterceptorService().getStateContainer('preProcess');
//Get all the interception state containers for metadata or reporting
getController().getInterceptorService().getInterceptionStates();
//Get all the interception points registered in the application
getController().getInterceptorService().getInterceptionPoints();
Conclusion
As you can see from this guide, constructing ColdBox Interceptors are very easy and powerful. You have a pre-defined structure to use and a very big list of core interception points, but not only that, you can also create your own execution points. This is true power to the developer. The framework does not limit you, it actually creates a very rock solid observer/observable framework for you. So now that you understand what an interceptor is, what are the core interception points, what kind of data they receive, how they are called, how they are stored, and how to use them, you are ready to start coding your own. Godspeed my friend, Happy Intercepting!!
