Interceptors

BoxLang is an event-driven language and you can not only listen, but register and announce events.

BoxLang is an event-driven language and it emits events throughout many different life-cycles. The entire framework for events is also extensible and can be used not only by Module developers but by anybody using the language in either BoxLang or Java. There are several core events that are emitted and you can register your own events we call interception points.

If you are familiar with the intercepting filter pattern, or observer/observable pattern, then that's what BoxLang follows.

Event Driven Programming

The way that interceptors are used is usually referred to as event-driven programming, which can be very familiar if you are already doing any Nodejs or observer/observable coding. You can listen and execute intercepting points anywhere you like in your application, you can even produce content whenever you announce these events.

Events have unique names like: onRequestStart, onHTTPRequest, onParse

Event Emitters aka Interceptor Pools

BoxLang has 3 interceptor pools that will emit events:

  • Global Runtime - The entire BoxLang runtime

  • Application.bx Request Listener - The request listener for an application

  • CacheProviders - The BoxCache providers

When you register interceptors, they will be registered within any of these pools.

Interceptors

In BoxLang, listeners are referred to as Interceptors. These can be built in Java or BoxLang as classes or closures/lambdas. If they are classes they can listen to multiple interception points and have a configure method.

class{
    
    function configure(){
        // Any code to setup the interceptor
    }
    
    // Any methods that are the same name as the events
    
    function onHTTPRequest( struct event ){
    }
    
    function preFunctionInvoke( struct event ){
    
    }
    
}

If they are closures, they will have the following format:

( event ) => {
  // Closure listener
}

( event ) -> {
  // Lambda listener
}

Registration

Interceptors can be registered via the following approaches into the different pools

  • A module

    • Registers into the global runtime

    • ModuleConfig.bx interceptors array

    • A Java class via Service Loader

  • BIFs

    • boxRegisterInterceptor() - Register into the global runtime

    • boxRegisterRequestInterceptor() - Register into the request listener

  • Java InterceptorService

    • The interceptor service has several registration method according to different types. The InterceptorService is the global runtime.

    • register( IInterceptor )

    • register( IInterceptor, properties )

    • register( IClassRunnable )

    • register( DynamicObject, states... )

    • register( IInterceptorLambda, states... )

  • CacheProvider specific poool

    • Each cache provider (BoxCache) has a getInterceptorPool() method which you can invoke and then you can use all of the pool methods for registration above.

ModuleConfig.bx

You can create an interceptors array for registration inside the ModuleConfig.bx

// Interceptors registration
interceptors = [
    {
        class : "#moduleRecord.invocationPath#.interceptors.Listener"
        properties : {
            // configuration
        }
    }
];

BIFs

  • boxRegisterInterceptor( interceptor, [states=[]] )

  • boxRegisterRequestInterceptor( interceptor, [states=[]] )

The states are the interception points to SPECICALLY register an interceptor to. If you omit it, then we will scan the interceptor for interception point states.

// Register an interceptor class in all discovered states
boxRegisterInterceptor( new MyInterceptor() )

// Register an interceptor class in all the passed states
// If those states have not been registered, register them

Configure()

The configure() method in the interceptor can be used to bootstrap anything you might need once the interceptor is loaded and registered by the runtime. The Java counterparts have a BaseInterceptor class and the configurations can have a structure of setup properties.

function configure(){

}

Properties

Interceptors when defined in a module can be defined using a structure of properties which are passed to (Java) and injected (BoxLang).

interceptors = [
{ 
	class : "#moduleRecord.invocationPath#.interceptors.Listener", 
	properties : {
	  createdOn : now(),
	  by : "Luis Majano"
	} 
}
];

Injections

If you are building BoxLang interceptors then you get a set of injections into your class' variables scope.

Name
Description

name

The name of the interceptor, if any

properties

The structure of properties

log

A BoxLang logger that maps to the runtime log file

interceptorService

A reference to the Interceptor Service

boxRuntime

A refernce to the Box Runtime class.

moduleRecord

If this interceptor belongs to a module, a reference to the modules record class will be injected as well.

Resources

For what can I use them

Below are just a few applications of BoxLang 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...

Last updated

Was this helpful?