Interceptors in coldbox are very powerful. They are components which listen to events which are announced by the core framework or modules or custom events created by your application. But this post is not about all details of creating interceptors, you can read all about it in the coldbox documentation. I am using them all the time since they are so powerful, but sometimes they don’t behave as expected, especially when your interceptor depends on other modules.
Ahh yes, the good ol chicken and the egg problem
Luis Majano
So, what’s the problem with this chicken and her egg? Actually there are two related problems. One of them is easy to solve, the other takes some more effort.
Problem 1:
Let’s say you are using some fancy module in your application. In this case I was using the Jwtservice in the cbsecurity module, and let’s say I wanted to parse some token on the preProcess Interceptions point. Looks easy, so I created my interceptor SimpleInterceptor on the preProcess interception point. So fire up commandbox and issue a
coldbox create interceptor name=SimpleInterceptor points=preProcess
and I will have an interceptor component. I just inject my dependency in the component and register my interceptor in the coldbox config as explained in the manual. My interceptor looks like this:
component{
//DI
property name="Jwt" inject="Jwtservice@cbsecurity";
function preProcess( event, interceptData, buffer, rc, prc ){
var myToken=JwtService.parseToken();
//do something interesting with myToken..
}
}
This code is not much different from a regular component, but on reinitialization of coldbox it is failing with some error The target ‘interceptor-simpleInterceptor’ requested a missing dependency with a Name of ‘jwt’ and DSL of ‘Jwtservice@cbsecurity’.
So when this interceptor is initialized it also tries to init the jwt service. The reason for this: loading order. Interceptors for the main app are initialized very early, before any module is loaded. So when trying to init the jwt service, it will fail, because the app has no notion of the cbsecurity module yet. There is a simple solution for that, you can delay the creation of your dependency until you need it, by means of the provider keyword in your injection, e.g
//DI
property name="Jwt"
inject="provider:Jwtservice@cbsecurity";
This syntax delays the creation of your object until you really need it in your code. In the wirebox manual there’s more information on providers, but in many cases, especially for interceptors it is good to know prepending your component with “provider:” will do the trick. Easy. But now on to:
Problem 2
I was reading the cbsecurity manual and found this:
You can inject our model or you can use our handy
cbsecure()
mixin (interceptors/handlers/layouts/views) and then call the appropriate security functions:
The same is true for Jwtservice, it has a handy jwtauth() mixin. Nowadays, many of the more recent ortus modules have these mixins available, so you don’t have to call getInstance(“someService@favouriteModule”) or inject it. Behind the scenes the jwtauth() mixin is just calling getInstance(“JwtService@cbsecurity”). And best thing of all, according to the docs, it is available in all interceptors. So let’s try it:
function preProcess( event, interceptData, buffer, rc, prc ){
var myToken=jwtAuth().parseToken();
//continue
}
Too bad, this is also failing. No matching function [jwtAuth] found. So these handy mixins are not very helpful for interceptors. I asked some expert:
Hi, are you sure cbsecure() is automatically injected in interceptors as said in the docs?
-> hmm, it should. f not, that’s a bigger problem
But no. This is also the famous chicken and egg problem. When initializing an interceptor it calls loadApplicationHelpers()
which loads all globally available helpers. Unfortunately modules have not started, so they did not have a change to register their global helpers yet. After init the loadApplicationHelpers
will not run again so no matter what you try, these handy mixins from your modules will NOT be available in your interceptor.
Solutions
So, can we avoid this? As shown above, we can use the provider syntax and use the injected service. These helpers are just a shortcut, and most of the times you can find all shortcodes in myModule/helpers/mixins.cfm or something simular. It is just good to know the documented helpers do not always work in interceptors.
There is another way to prevent this. You can create a module for your interceptor and register your interceptor in the moduleconfig of MyCustomModule. If I need some cbsecurity stuff in MyCustomModule I could specify cbsecurity as a dependency in the moduleconfig in this.dependencies
. Actually that is quite some (simple) work if you could also use the provider keyword, but with this approach you also have your helpers available, because your interceptor in your module is only created after the cbsecurity model.
In my case this approach was not very useful. My preProcess interceptor had to fire BEFORE the cbsecurity preprocess interceptor. By putting it in a module you loose some control over the loading order, so you can’t be sure it will fire first.
So I just used the provider approach. For my helper shortcuts I just looked in the module helper map to find out what these mixins were doing.
This (relatively small) problem has been present for quite some time now, and as long as you know about it it is easy to circumvent, so documenting it in the manual may save people some time. Still it would be nice if the helpers are always loaded. By using some internal coldbox interception which fires when all modules have been loaded (simular to the afterAspectsLoad interception point) and a base interception class which calls loadApplicationHelpers()
on this interception point I think the mixins would be available on almost all interceptors, except the afterConfigurationLoad
interception, which always fires BEFORE modules have been loaded.
Leave a Reply