ColdBox Interceptor's Guide
- ColdBox Interceptor's Guide
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. The order of declaration is very important. These stacked interceptor chains form a chain of separate, declaratively-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.
Interceptors are a great compliment to ColdBox plugins, in which now they can be used alongside of them and implicitly add functionality to a ColdBox application. 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 much more.(Read more on Intercepting Filters)
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...
Much more than Interceptors
However, we went a step further with interception points and created the hooks necessary in order to implement an observer/observable pattern into the entire interceptor service. Ok ok, what does this mean? It means, that you are not restricted to the pre-defined interception points that ColdBox provides, you can create your own! WOW! Really? Yes, you can very easily declare execution points via the configuration file, create your interceptors with the execution point you declared (Conventions baby!!) and then just announce interceptions in your code via the interception API. The power of conventions. However, not only can you intercept at an execution point, but you can actually send a structure of intercepted data right into the interceptor. We will cover custom interception points further down the guide.
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. 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, 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).
Sample Interceptor Declaration
<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> </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.
<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") ); </cfscript> </cffunction>
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 application start handler.
And so forth...
Reserved Words & Methods
So lets see what variables and methods you inherit:
Reserved Words
| controller | The ColdBox Controller |
| instance | Internal private scope |
| instance.__hash | An internal hash id for the event handler. |
Reserved Facade/Concrete Methods
| configure | Configure the interceptor |
| getProperties | Get the properties structure |
| setProperties | Set the properties structure |
| getProperty | Get a single property |
| setProperty | Set a single property |
| propertyExists | Check if a property exists |
| abort | cfabort facade |
| announceInterception | A way to announce custom interceptions |
| dump | cfdump facade |
| getColdBoxOCM | Get a reference to the ColdBox Object Cache Manager |
| getcontroller | Get a reference to the ColdBox controller |
| getDatasource | Get a named datasource bean |
| getDebugMode | Get the current debug mode for the user |
| getfwlocale | If using i18N, then retrieves the current locale |
| getHash | Get the hash |
| getMailSettings | Get a mail settings bean |
| getMyPlugin | Get a custom plugin |
| getPlugin | Get a core or custom plugin |
| getResource | If using i18N, then retrieves a resource from a resource bundle |
| getSetting | Get a setting |
| getSettingsBean | Geta a config settings bean |
| getSettingStructure | Get the ColdBox or Application Settings Structure |
| include | cfinclude facade |
| includeUDF | A mixin injection method, so you can inject your handler with your own methods at runtime. |
| purgeView | The ability to purge a named view from the cache |
| relocate | Facade to cflocation |
| renderExternalView | Render an external view |
| renderView | Render a view |
| runEvent | Execute a ColdBox event |
| setDebugMode | Set the debug mode for the user |
| setNextEvent | Relocate to another event |
| setNextRoute | Relocate to another route |
| setSetting | Set a setting |
| settingExists | Check if a setting exists |
| throw | cfthrow facade |
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. |
| afterPluginCreation | This occurs right after a plugin gets created by coldbox |
| afterHandlerCreation | This occurs right after a handler gets created by coldbox |
Sample Interceptor Method
WOW, there are several interceptions points for your usage, now we are talking. Let's look at some actual code now:
<!--- After Configuration Load ---> <cffunction name="afterConfigurationLoad" access="public" returntype="void" hint="Executes after the framework and application configuration loads, but before the aspects get configured. " 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."> <!--- ************************************************************* ---> <!--- Set the tracing setting ---> <cfset setSetting("ExecutionTracerStartTime",now())> </cffunction>
As you can see, the method receives the event context object and a structure of intercepted data, which can be ColdBox specific or custom. You can then do all your logic and use these arguments or any other framework specific method. Very very simple. Also note the name of the method, you must follow the conventions.
How do I inject dependencies into my interceptors?
If you would like to inject dependencies into your interceptors you have two choices:
- Create the objects in the interceptor and cache them
- Get the objects from the IoC plugin.
The first choice needs no further explanation because you can basically create the dependencies and set them. As for the second one, well, the IoC plugin does not get configured but after interceptor creation and configuration. So how do you deal with this? Well, interception of course. There is a core interception point called afterAspectsLoad that can be used to actually inject your dependencies into your interceptors. This core interception point assures you that the aspects have been loaded, your interceptor has been created and configured. So you can do something like:
<cffunction name="afterAspectsLoad" access="public" returntype="void" 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."> <!--- ************************************************************* ---> <cfscript> //set dependencies for this interceptor setSecurityService( getPlugin("ioc").getBean("securityService") ); setUserService( getPlugin("ioc").getBean("userService") ); </cfscript> </cffunction>
As you can see from the sample below. You use the interception point so you can actually inject what you need before the application process starts.
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, don't worry if you don't fully understand it yet.
<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.
Announcement Method
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.
Interception Arguments
As you can see from the code samples above, the interception method receives two arguments: event & interceptData. The event object is the current request context and interceptData is a structure that was prepared at the time of execution with convenience data according to 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 | NOT USED |
| postRender | NOT USED |
| postProcess | NOT USED |
| sessionStart | NOT USED |
| sessionEnd | NOT USED |
| afterCacheElementInsert | cacheObjectKey : the key of the object cached, cacheObjectTimeout : the timeout used. |
| afterCacheElementRemoved | cacheObjectKey : the key of the object removed |
How to declare them in your application
The last step in this guide is to show you how to actually declare them in your ColdBox configuration file. Look at the sample snippet below:
<Interceptors> <CustomInterceptionPoints>onLog</CustomInterceptionPoints> <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>
You need to create an Interceptors element that will hold all the interceptor declarations. The point to grind here is the order of delcaration. 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.
The other element is the CustomInterceptionPoints element. Here you will declare a comma-delimmited list of custom execution points you would like to implement in your application. ColdBox will then register them as valid interception points.
That's it, nothing more to it. Plain as day.
Conclusion
As you can see from this guide, constructing ColdBox Interceptors are very very easy. 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!!
