[[Dashboard | << Back to Dashboard ]] {|align="right" | __TOC__ |} = ColdBox Interceptor's Guide = {{{Messagebox message="Covers up to version 3.5.0" type="info"}}} == Introduction == The main purpose of creating ColdBox interceptors is to increase functionality for applications and framework alike, without touching the core functionality or events, and thus encapsulating logic into separate decoupled 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 for you. 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. (Read more on [http://www.corej2eepatterns.com/Patterns2ndEd/InterceptingFilter.htm Intercepting Filters])
=== ForgeBox Interceptors === Please remember that in our ColdBox developer community site, [http://coldbox.org/forgebox ForgeBox], you can find a big collection of [http://coldbox.org/forgebox/type/interceptors Interceptors] and so much more. Anybody can contribute and automatically all contributions will be accessible via our ColdBox application templates and via ColdFusion Builder. === Event-Driven Programming === However, we went a step further with ColdBox interceptors and created the hooks necessary in order to implement an event-driven programming 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 or register at runtime, create your interceptors with the execution point you declared (Conventions baby!!) and then just announce interceptions in your code via the interception API.
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. === Resources === * http://en.wikipedia.org/wiki/Observer_pattern * http://sourcemaking.com/design_patterns/observer * http://java.sun.com/blueprints/corej2eepatterns/Patterns/InterceptingFilter.html
=== 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? == Interceptors are CFC's that can either extend or not our ColdBox base class: '''coldbox.system.Interceptor''', implement a configuration method called '''configure''' and then create as many methods as events it will listen to. All interceptors are treated as '''singletons''' in your application, so make sure they are thread safe and var scoped.
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 configuration file properties. {{{Messagebox type="info" message="These properties are local to the interceptor only!"}}} As you can see on the diagram, the interceptor class is part of the ColdBox framework super type family, and thus inheriting the functionality of the framework. (See [http://apidocs.coldbox.org API]). /** * My Interceptor */ component{ function configure(){} function preProcess(event,interceptData){} } === 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(event,interceptData)''' 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''' which is the request context object ** '''interceptData''' which is a structure of data that the broadcaster sends * This method must return a boolean value or none at all. ** '''True''' means break the chain of execution ** '''False''' or '''void''' continue execution 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 now. component extends="coldbox.system.Interceptor"{ function configure(){} boolean function afterConfigurationLoad(event,interceptData){ if( getProperty('interceptorCompleted') eq false){ parseAndSet(); setProperty('interceptorCompleted',true); } return false; } } Also remember that all interceptors are created by [[WireBox]], so you can use dependency injection, configuration binder's, and even [[WireBox-AOP|AOP]] on interceptor objects. Here is a more complex sample: '''HTTP Security Example''': /** * Intercepts with HTTP Basic Authentication */ component { // Security Service property name="securityService" inject="id:SecurityService"; void function configure(){ if( !propertyExists("enabled") ){ setProperty("enabled", true ); } } void function preProcess(event,struct interceptData){ // verify turned on if( !getProperty("enabled") ){ return; } // Verify Incoming Headers to see if we are authorizing already or we are already Authorized if( !securityService.isLoggedIn() OR len( event.getHTTPHeader("Authorization","") ) ){ // Verify incoming authorization var credentials = event.getHTTPBasicCredentials(); if( securityService.authorize(credentials.username, credentials.password) ){ // we are secured woot woot! return; }; // Not secure! event.setHTTPHeader(name="WWW-Authenticate",value="basic realm=""Please enter your username and password for our Cool App!"""); // secured content data and skip event execution event.renderData(data="

Unathorized Access

Content Requires Authentication

",statusCode="401",statusText="Unauthorized") .noExecution(); } } }
=== eventpattern annotation === We have one annotation you can use on any interceptor event function called '''eventPattern'''. This annotation will be placed in the function and the value is a regular expression that the interceptor service uses to match against the incoming event. If the regex matches, the interception function executes, else it skips it. This is a great way for you to create interceptors that only fire not only for the specific interception point you want, but also on the specific incoming event. // only execute for the admin package void function preProcess(event,struct interceptData) eventPattern="^admin\."{} // only execute for the blog module void function preProcess(event,struct interceptData) eventPattern="^blog:"{} // only execute for the blog module and the home handler void function preProcess(event,struct interceptData) eventPattern="^blog:home\."{} // only execute for the blog, forums, and shop modules void function preProcess(event,struct interceptData) eventPattern="^(blog|forum|shop):"{} == Interceptor Declaration == Interceptors can be declared in the [[ConfigurationCFC]] or programmatically at runtime. If you declare them in the configuration file, then you have control in the order in which they fire. If you register interceptors programmtically, you won't have control of order of execution. Interceptors are declared as an array of structures in an element called '''interceptors'''. The elements of the structure are: * '''class''' - The instantiation path of the CFC. * '''name''' - An optional unique name for the interceptor, if not passed then the name of the CFC will be used. We highly encourage the use of a name to avoid collisions. * '''properties''' - A structure of configuration properties for the interceptor {{{Messagebox type="info" message="Remember that order is important!"}}} === Configuration File === interceptors = [ {class="coldbox.system.interceptors.SES", properties={}}, {class="interceptors.RequestTrim", properties={trimAll=true}}, {class="coldbox.system.interceptors.Security", name="AppSecurity", properties={ rulesSource = "model", rulesModel = "securityRuleService@cb", rulesModelMethod = "getSecurityRules", validatorModel = "securityService@cb"} }} ]; === Programmatically === You can also register CFCs as interceptors programmatically by talking to the application's '''Interceptor Service''': // via controller controller.getInterceptorService() // via injection property name="eventManager" inject="coldbox:InterceptorService"; Once you have a handle on the interceptor service you can use the following methods to register interceptors: * '''registerInterceptor()''' - Register an instance or CFC path and discover all events it listens to by conventions * '''registerInterceptionPoint()''' - Register an instance in a specific event only Here are the method signatures: public any registerInterceptor([any interceptorClass], [any interceptorObject], [any interceptorProperties='[runtime expression]'], [any customPoints=''], [any interceptorName]) public any registerInterceptionPoint(any interceptorKey, any state, any oInterceptor) Here are a few samples of objects that can register themselves: // register yourself to listen to all events declared controller.getInterceptorService() .registerInterceptor(interceptorObject=this); // register yourself to listen to all events declared and register new events: onError, onLogin controller.getInterceptorService() .registerInterceptor(interceptorObject=this, customPoints="onError,onLogin"); // Register yourself to listen to ONLY the afterInstanceAutowire event controller.getInterceptorService() .registerInterceptionPoint(interceptorKey="MyService",state="afterInstanceAutowire",oInterceptor=this); == Custom Events == As we discussed before, you can also define your own events for your application. You can do this via the [[ConfigurationCFC]] or programmatically as well. === Instructional Video ===