-
Notifications
You must be signed in to change notification settings - Fork 24
Introduction to Plugins
- Typical Uses for a Plugin
- Registering a Plugin
- Configure-Time Parameters
- Anatomy of the Plugin CFC
- Login Plugin
- Halting Event Processing from the
PreEvent
Plugin Point - Additional Notes and Considerations
Along with Filters, Listeners, and Properties, Plugins make up one of the four developer defined controller layer objects used in Mach-II applications. What makes plugins so powerful is their unique "cross-cutting" ability, which enables developers to define specific functionality that will run on any every request in the application. Plugins offer very precise control through nine plugin points that allow developers to specify exactly when in the request a certain piece of functionality will run. On each request, each of the nine plugin points is called automatically by Mach-II.
- Ensuring an user is logged before letting the event processing continue (using the
preProcess
plugin point) - Putting data from session or other variables in the Event object so it is available in the Event (using the
preProcess
orpreEvent
plugin points) - Redirecting requests from HTTP to HTTPS for events that require SSL for secure communication (using the
preProcess
plugin point) - Putting internationalization information in the Event for views (using the
preView
plugin point) - Catching invalid announced events and mapping to generic handlers (using the
handleException
plugin point) - Any logic that may or may not change the flow of your application
Feel free to add other typical examples.
First, let's take a quick look at how to register a plugin within the mach-ii.xml configuration file. The two required attributes of the <plugin>
node are name, which specifies the handle to use to reference the plugin throughout the configuration file, and type, which should be a dot delimited path to the plugin CFC.
Configuration file fragment:
<plugins>
<plugin name="myPlugin" type="path.to.myPlugin" />
</plugins>
If needed, configure-time parameters can be defined when registering a plugin:
<plugins>
<plugin name="myPlugin" type="path.to.myPlugin">
<parameters>
<parameter name="myParamName" value="myParamValue" />
</parameters>
</plugin>
</plugins>
Configure-time parameters can be accessed within the plugin using a variety of methods made available to the plugin by its superclasses. Parameters defined in the registration XML section of your Mach-II configuration file (as demonstrated above) sets the data that the below methods access when you configure your plugin.
Method Name | Syntax | Description |
---|---|---|
getParameter | getParameter('nameOfParameter', 'defaultValue') | Gets a configuration parameter value, or a default value if not defined. The defaultValue is optional. |
getParameters | getParameters() | Gets the full set of configuration parameters for the component. |
isParameterDefined | isParameterDefined('nameOfParameter') | Checks to see whether or not a configuration parameter is defined. |
Parameters are used so you do not have to hard-code data within your plugin and allows you to pass data to your plugin at configuration time (runtime). This architectural building block enables you to build reusable plugins that can be used as a common set of developer extended components to the framework across all of your applications. Parameters let your plugin act in the manner that is appropriate in respects to the application that defines the plugin without having to hard-code data within the plugin source code. This type of flexibility is important to consider when developing plugins - favor passing in parameters instead of hard-coding values such as event names within the plugin directly. While re-usability is great by-product, this approach was used to reduce coupling between logic within the plugin relying on particular data being defined within the XML.
Remember, your plugin will extend from MachII.framework.Plugin
, so by nature, it also inherits the methods from the BaseComponent
.
Your Plugin -> MachII.framework.Plugin -> MachII.framework.BaseComponent
As mentioned above, the Mach-II framework calls your registered plugins at each of nine available plugin points (if they are defined). The nine plugin points that are available for implementation within the plugin are:
Plugin Point Name | Called... |
---|---|
preProcess | at the beginning of the request before Event processing begins |
preEvent | before each Event is processed |
postEvent | after each Event is processed |
preView | before each View is processed |
postView | after each View is processed |
postProcess | at the end of the request after all Event processing has completed |
handleException | when an exception occurs (before the exception event is announced and handled) |
Plugin points added in Mach-II 1.6 and requires use of Application.cfc and the MachII.mach-ii bootstrapper:
Plugin Point Name | Called... |
---|---|
onSessionStart | when a session starts |
onSessionEnd | when a session ends |
While you would typically not render any output from a plugin, the example below (SamplePlugin
borrowed from the Mach-II Application Skeleton) does a great job illustrating how plugins are called by the Mach-II framework by outputting the name of the plugin point to the browser as it is called.
<cfcomponent extends="MachII.framework.Plugin" output="false">
<cffunction name="configure" access="public" output="false" returntype="void"
hint="Configures the plugin">
<!--- Add logic here for custom configuration --->
</cffunction>
<cffunction name="preProcess" access="public" returntype="void" output="true">
<cfargument name="eventContext" type="MachII.framework.EventContext" required="true" />
<cfoutput> SimplePlugin.preProcess()<br /></cfoutput>
</cffunction>
<cffunction name="preEvent" access="public" returntype="void" output="true">
<cfargument name="eventContext" type="MachII.framework.EventContext" required="true" />
<cfoutput> SimplePlugin.preEvent()<br /></cfoutput>
</cffunction>
<cffunction name="postEvent" access="public" returntype="void" output="true">
<cfargument name="eventContext" type="MachII.framework.EventContext" required="true" />
<cfoutput> SimplePlugin.postEvent()<br /></cfoutput>
</cffunction>
<cffunction name="preView" access="public" returntype="void" output="true">
<cfargument name="eventContext" type="MachII.framework.EventContext" required="true" />
<cfoutput> SimplePlugin.preView()<br /></cfoutput>
</cffunction>
<cffunction name="postView" access="public" returntype="void" output="true">
<cfargument name="eventContext" type="MachII.framework.EventContext" required="true" />
<cfoutput> SimplePlugin.postView()<br /></cfoutput>
</cffunction>
<cffunction name="postProcess" access="public" returntype="void" output="true">
<cfargument name="eventContext" type="MachII.framework.EventContext" required="true" />
<cfoutput> SimplePlugin.postProcess()<br /></cfoutput>
</cffunction>
<cffunction name="onSessionStart" access="public" returntype="void" output="true">
<!--- There is no access to the eventContext since sessions start asynchronously
from the Mach-II request life cycle --->
</cffunction>
<cffunction name="onSessionEnd" access="public" returntype="void" output="true">
<cfargument name="sessionScope" type="struct" required="true"
hint="The session scope is passed in since direct access to it is not available." />
<!--- There is no access to the eventContext since sessions end asynchronously
from the Mach-II request life cycle --->
</cffunction>
<cffunction name="handleException" access="public" returntype="void" output="true">
<cfargument name="eventContext" type="MachII.framework.EventContext" required="true" />
<cfargument name="exception" type="MachII.util.Exception" required="true" />
<cfoutput> SimplePlugin.handleException()<br /></cfoutput>
</cffunction>
</cfcomponent>
The first thing to notice is that the all developer defined plugin components must extend MachII.framework.Plugin
. Next, note that the plugin does not define nor implement an init() method as doing so would override the init() defined in its superclass which is used by the framework. Instead, the plugin performs any necessary configuration in its configure() method, which is called automatically on application start up by Mach-II. Initializing the plugin in this way, allows Mach-II to load the plugin into application memory for use on each request. See this FAQ for a description on init() versus configure().
Next, notice that, apart from configure()
, each plugin method accepts an argument called eventContext
, which is automatically passed in by Mach-II, and is an object of type MachII.framework.EventContext
. This allows you as the plugin developer to access all of the functionality of the EventContext object within the implementation of the methods in your plugin.
Finally, notice that plugins offer a dedicated method for you to add logic at each of our nine plugin points.
One of the more common uses of a plugin is to check to see if an user is logged in. A login plugin is only useful when the majority of events required authenticate and a fewer number of events are not protected by the login plugin. If you have a fewer number of events to authenticate, using a login filter is a better choice.
The following in an example. The session facade is autowired in by ColdSpring. The plugin takes one parameter named ignoreEvents
that is a struct of events to not protect by the login plugin. The notLoggedInEvent
parameter takes the name of an event to announce if the user is not logged in. This event will probably just perform a redirect (e.g. <cflocation>
) to the login event (so the URL in the browser is updated).
<cfcomponent
displayname="loginPlugin"
extends="MachII.framework.Plugin"
depends="sessionFacade"
output="false"
hint="Checks if a user is logged in.">
<!---
PROPERTIES
--->
<cfset variables.instance = StructNew() />
<!---
INITIALIZATION / CONFIGURATION
--->
<cffunction name="configure" access="public" returntype="void" output="false"
hint="Configures the plugin.">
<cfif isParameterDefined("ignoreEvents") AND IsStruct(getParameter("ignoreEvents"))>
<cfset setIgnoreEvents(getParameter("ignoreEvents")) />
<cfelse>
<cfthrow type="LoginPlugin.invalidParameter"
message="The parameter named 'ingoreEvents' is either not
defined in the plugin registration XML or not a struct." />
</cfif>
<cfif isParameterDefined("notLoggedInEvent") AND Len(getParameter("notLoggedInEvent"))>
<cfset setNotLoggedInEvent(getParameter("notLoggedInEvent")) />
<cfelse>
<cfthrow type="LoginPlugin.invalidParameter"
message="The parameter named 'notLoggedInEvent' is either not
defined in the plugin registration XML or does not have a value." />
</cfif>
</cffunction>
<!---
PUBLIC FUNCTIONS
--->
<cffunction name="preProcess" access="public" returntype="void" output="false"
hint="Checks to see if the user is logged in during the preProcess plugin point.">
<cfargument name="eventContext" type="MachII.framework.EventContext" required="true" />
<cfset var event = arguments.eventContext.getNextEvent() />
<cfset var eventName = event.getRequestName() />
<cfif NOT StructKeyExists(getIgnoreEvents(), eventName)
AND NOT getSessionFacade().isLoggedIn()>
<cfset arguments.eventContext.clearEventQueue() />
<cfset announceEvent(getNotLoggedInEvent(), event.getArgs()) />
</cfif>
</cffunction>
<!---
PROTECTED FUNCTIONS
--->
<cffunction name="setIgnoreEvents" access="private" returntype="void" output="false">
<cfargument name="ignoreEvents" type="struct" required="true" />
<cfset variables.instance.ignoreEvents = arguments.ignoreEvents />
</cffunction>
<cffunction name="getIgnoreEvents" access="private" returntype="struct" output="false">
<cfreturn variables.instance.ignoreEvents />
</cffunction>
<cffunction name="setNotLoggedInEvent" access="private" returntype="void" output="false">
<cfargument name="notLoggedInEvent" type="string" required="true" />
<cfset variables.instance.notLoggedInEvent = arguments.notLoggedInEvent />
</cffunction>
<cffunction name="getNotLoggedInEvent" access="private" returntype="string" output="false">
<cfreturn variables.instance.notLoggedInEvent />
</cffunction>
</cfcomponent>
The XML below is an example of how the above plugin would be registered in your Mach-II configuration file. The keys in the ignoreEvent
need to correspond to names of actual event-handlers in your configuration file. In the example below, we are indicating that login
, login_redirect
, login_process
and logout
events should not trigger the plugin to redirect an user to the login event. If you were to use this code in your application, you will need to change / add / remove the events you would want the plugin to ignore as the example below is for illustrative purposes only.
The notLoggedInEvent
indicates which event-handler you would like the plugin to announce if the user is not logged in. This value needs to correspond to the name of an actual event-handler in your configuration file.
<plugin name="login" type="dot.path.to.plugins.LoginPlugin">
<parameters>
<parameter name="ignoreEvents">
<struct>
<key name="login" value=""/>
<key name="login_redirect" value=""/>
<key name="login_process" value=""/>
<key name="logout" value=""/>
</struct>
</parameter>
<parameter name="notLoggedInEvent" value="login_redirect"/>
</parameters>
</plugin>
To halt the processing of the current event from within the preEvent
plugin point, use the abortEvent()
method of the superclass. Because Mach-II has already already removed the current event from the event queue by the time the preEvent
plugin point is called, calling the clearEventQueue()
method on the eventContext
argument will prevent all following events from being processed, but will not affect the current event. Depending on the business need, you may have to call both the clearEventQueue()
and abortEvent()
methods in succession.
Building on the login plugin above, the following example shows how the login check could instead be performed within the preEvent
plugin point:
<cffunction name="preEvent" access="public" returntype="void" output="false">
hint="Checks to see if the user is logged in during the preEvent plugin point.">
<cfargument name="eventContext" type="MachII.framework.EventContext" required="true" />
<cfset var event = arguments.eventContext.getCurrentEvent() />
<cfset var eventName = event.getName() />
<cfif NOT StructKeyExists(getIgnoreEvents(), eventName)
AND NOT getSessionFacade().isLoggedIn()>
<!--- Clear all but the current event being processed, which is no longer in the queue --->
<cfset arguments.eventContext.clearEventQueue() />
<!--- Announce the new event --->
<cfset announceEvent(getNotLoggedInEvent(), event.getArgs()) />
<!--- Stop processing the current event --->
<cfset abortEvent() />
</cfif>
</cffunction>
- Mach-II calls the plugins in the order they are defined in the configuration file.
- Mach-II only calls plugin points that are utilized. Remove any un-implemented plugin point methods (i.e.
preProcess
, etc.) to improve application performance as fewer plugin points will be called on each request. For example if your plugin only implements thepreEvent
plugin point, then remove the remaining points. - How do I get an Event object in the
preProcess()
method of my plugins?