Mach II Development Guide - Designing Event Filters

Release 1.2.1 (4/14/2005)

« Designing Event Handlers | Contents | Designing Plugins »

Printable Version

Designing Event Filters For Mach II

This section looks at designing and using event-filters. The first issue to consider is whether you want an event filter or a plugin. Having decided that you want an event filter, this section looks at some practical reasons for using them to improve the structure and flow of your application and then examines larger issues such as form handling, validation and security.

Event Filter or Plugin?

When you first start using Mach II, it can be difficult to tell whether you need an event filter or a plugin. You need to ask yourself a few questions about what you are trying to achieve so you can determine which will work better for the problem you are trying to solve:

If you answered "yes" to any of those questions, you probably need a plugin.

If you answered "yes" to any of those questions, you probably need an event filter.

If you answered "no" to all of the above but still think you need an event filter or a plugin, then read through the rest of this section and the next section, on plugins, and decide for yourself.

Anatomy Of An Event Filter

An event filter is a component that provides a filterEvent() method which is called with three arguments:

  1. the current event
  2. the current eventContext
  3. (optional) a struct of name/value pairs from the <parameter> tags in the filter invocation

filterEvent can either return true, in which case processing of the event handler continues, or it can return false, in which case processing of the current event handler is terminated (and processing continues with the next event in the queue - unless you clear the event queue by calling arguments.eventContext.clearEventQueue() in the filter). Usually, when a filter returns false, it will also announce a new event before doing so (if it clears the event queue, it must announce a new event after doing so!).

An event filter can define a configure() method if it needs to perform initialization - this is called automatically by the framework. Event filters - like all other parts of the Mach II framework - are stored in application scope so their instance variables are effectively application scope variables.

A minimal event filter CFC looks like this:

<cfcomponent extends="MachII.framework.EventFilter">
	<cffunction name="configure" returntype="void" access="public" output="false">
		<!--- perform any initialization --->
	</cffunction>
	<cffunction name="filterEvent" returntype="boolean" access="public" output="false">
		<cfargument name="event" type="MachII.framework.Event" required="yes" />
		<cfargument name="eventContext" type="MachII.framework.EventContext" required="yes" />
		<cfargument name="paramArgs" type="struct" required="yes" />
		<!--- return false if you need to abort the current event --->
		<cfreturn true />	<!--- indicate success --->
	</cffunction>
</cfcomponent>

The filter is declared inside the <event-filters> .. </event-filters> tags in mach-ii.xml as follows:

<event-filter name="filterName" type="Path.To.YourFilter" />

where filterName is the name you want to give the filter within the mach-ii.xml file and Path.To.YourFilter is the fully-qualified component name for YourFilter.cfc. You may optionally specify parameters for the filter declaration:

<event-filter name="filterName" type="Path.To.YourFilter">
	<parameters>
		<parameter name="param1" value="value1" />
		<parameter name="param2" value="value2" />
	</parameters>
</event-filter>

This provides default parameter values for param1 and param2 (of value1 and value2 respectively). These can be accessed within the filter using the getParameter() method - see below.

The filter is used in an event handler as follows:

<filter name="filterName" />

This causes the filterEvent() method to be called with the current event, the current event context and an empty paramArgs struct.

You may optionally specify parameters for the filter usage:

<filter name="filterName">
	<parameter name="param1" value="newValue1" />
	<parameter name="param3" value="value3" />
</filter>

This causes the filterEvent() method to be called with the current event, the current event context and a paramArgs struct containing two keys: param1 with a value of newValue1 and param3 with a value of value3. The intent is that parameter values passed in this way would override any default parameter value specified in the declaration of the filter. You would typically manage this with code that looked like this:

<cffunction name="filterEvent" returntype="boolean" access="public" output="false">
	<cfargument name="event" type="MachII.framework.Event" required="yes" />
	<cfargument name="eventContext" type="MachII.framework.EventContext" required="yes" />
	<cfargument name="paramArgs" type="struct" required="yes" />
	<cfset var param1 = getParameter("param1") />
	<cfset var param2 = getParameter("param2") />
	<cfset var param3 = getParameter("param3") />
	<!--- other var declarations --->
	<cfif structKeyExists(paramArgs,"param1")>
		<cfset param1 = paramArgs.param1 />
	</cfif>
	<cfif structKeyExists(paramArgs,"param2")>
		<cfset param2 = paramArgs.param2 />
	</cfif>
	<cfif structKeyExists(paramArgs,"param3")>
		<cfset param3 = paramArgs.param3 />
	</cfif>
	<!--- perform filter processing --->
	<!--- return false if you need to abort the current event --->
	<cfreturn true />	<!--- indicate success --->
</cffunction>

An event filter has access to both the current event and the current event context so it is able to affect the logical flow of an application by manipulating the event context. For example, when an event filter decides to abort the current event (by returning false), it generally needs to announce another event for the framework to execute and it may also decide to clear any events that are queued up for execution. By convention, event filters that do that usually have a parameter that specifies the event to announce as well as a parameter that specifies whether or not to clear the event queue:

<cffunction name="filterEvent" returntype="boolean" access="public" output="false">
	<cfargument name="event" type="MachII.framework.Event" required="yes" />
	<cfargument name="eventContext" type="MachII.framework.EventContext" required="yes" />
	<cfargument name="paramArgs" type="struct" required="yes" />
	<cfset var invalidEvent = getParameter("invalidEvent") />
	<cfset var clearEventQueue = getParameter("clearEventQueue") />
	<!--- other var declarations --->
	<cfif structKeyExists(paramArgs,"invalidEvent")>
		<cfset invalidEvent = paramArgs.invalidEvent />
	</cfif>
	<cfif structKeyExists(paramArgs,"clearEventQueue")>
		<cfset clearEventQueue = paramArgs.clearEventQueue />
	</cfif>
	<cfif someCondition>
		<!--- note: clearEventQueue parameter is really a string --->
		<cfif clearEventQueue is "true">
			<cfset arguments.eventContext.clearEventQueue() />
		</cfif>
		<!--- pass current event's arguments into the new event: --->
		<cfset arguments.eventContext.announceEvent(invalidEvent,arguments.event.getArgs()) />
		<cfreturn false />
	</cfif>
	<cfreturn true />
</cffunction>

Data Validation

When user data enters your application, either as URL scope or form scope variables, you generally need to perform some sort of data validation. This may be as simple as checking that certain variables have been provided or it may be something substantially more complex.

The Mach II framework provides the RequiredFieldsFilter event filter to allow you to check that a specified list of URL or form scope variables are all present, announcing a specified event if one or more is missing and returning false to abort the current event. This filter takes two parameters:

If any fields are missing, the event that is announced has the same arguments as the current event plus the following additional arguments:

For more complex web form handling, you should create a bean to encapsulate the web form data (and use the <event-bean> command to create the bean object from the event arguments) and provide a validate() method on that data object that returns a boolean. Then you can use the ValidateFormObject filter (from corfield.org) to invoke that method and handle any failure. This filter takes two or three parameters:

If validate() returns false, the filter also returns false after optionally clearing the event queue and then announcing the specified event with the same arguments as the current event plus the following additional argument:

It is up to the specified event handler how to deal with reporting the validation failure, which is likely to involve additional calls to the data object and, therefore, additional event filter invocations to manage that interaction.

You can specify default parameters for the ValidateFormObject event filter:

<event-filter name="barValidator" type="MachII.filters.ValidateFormObject">
	<parameters>
		<parameter name="formObjectName" value="bar" />
		<parameter name="invalidEvent" value="formHasInvalidBar" />
	</parameters>
</event-filter>
<event-filter name="fooValidator" type="MachII.filters.ValidateFormObject">
	<parameters>
		<parameter name="formObjectName" value="foo" />
		<parameter name="invalidEvent" value="formHasInvalidFoo" />
		<parameter name="clearEventQueue" value="true" />
	</parameters>
</event-filter>

You can now use these event filters without needing to specify the parameters each time or you can override the defaults:

<event-bean name="bar" type="my.model.bar" />
<filter name="barValidator" />
...
<event-bean name="foo" type="my.model.foo" />
<filter name="fooValidator">
	<parameter name="invalidEvent" value="warnAboutBadFoo" />
	<parameter name="clearEventQueue" value="false" />
</filter>

Security

When user authorization needs to be performed on certain events, an event filter is probably the simplest way to achieve this. The Mach II framework provides the PermissionsFilter event filter to support simple permission-based authorization checking. This filter checks that the current user has all of the necessary permissions (specified as a comma-separated list) and if they do not, it announces the event specified (after optionally clearing the event queue). See the comments in the source code for more detail.

You may be able to simply extend the PermissionsFilter component to implement your own security, overriding the getUserPermissions() method to return a comma-separated list of user permissions. If you don't want the default inclusive permission checking behavior, or your user permissions are more complex than a comma-separated list, you can also override validatePermissions() and implement your own checking.

If your security system is more complex than simple permissions, you will need to write your own security event filter.

« Designing Event Handlers | Contents | Designing Plugins »

 

Send me an e-mail when comments are added to this page | Comment Report

Current page: http://livedocs.adobe.com/wtg/public/machiidevguide/eventfilters.html