Skip to content

Introduction to Plugins

thofrey edited this page Apr 1, 2014 · 9 revisions

Table of Contents

  1. Typical Uses for a Plugin
  2. Registering a Plugin
  3. Configure-Time Parameters
  4. Anatomy of the Plugin CFC
  5. Login Plugin
  6. Halting Event Processing from the PreEvent Plugin Point
  7. 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.

Typical Uses for a Plugin

  • 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 or preEvent 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.

Registering a Plugin

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

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

Anatomy of the Plugin CFC

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>&nbsp;SimplePlugin.preProcess()<br /></cfoutput>
        </cffunction>

        <cffunction name="preEvent" access="public" returntype="void" output="true">
            <cfargument name="eventContext" type="MachII.framework.EventContext" required="true" />
            <cfoutput>&nbsp;SimplePlugin.preEvent()<br /></cfoutput>
        </cffunction>

        <cffunction name="postEvent" access="public" returntype="void" output="true">
            <cfargument name="eventContext" type="MachII.framework.EventContext" required="true" />
            <cfoutput>&nbsp;SimplePlugin.postEvent()<br /></cfoutput>
        </cffunction>

        <cffunction name="preView" access="public" returntype="void" output="true">
            <cfargument name="eventContext" type="MachII.framework.EventContext" required="true" />
            <cfoutput>&nbsp;SimplePlugin.preView()<br /></cfoutput>
        </cffunction>

        <cffunction name="postView" access="public" returntype="void" output="true">
            <cfargument name="eventContext" type="MachII.framework.EventContext" required="true" />
            <cfoutput>&nbsp;SimplePlugin.postView()<br /></cfoutput>
        </cffunction>

        <cffunction name="postProcess" access="public" returntype="void" output="true">
            <cfargument name="eventContext" type="MachII.framework.EventContext" required="true" />
            <cfoutput>&nbsp;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>&nbsp;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.

Login Plugin

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.

Example Login Plugin Code

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>

Registration XML

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>

Halting Event Processing from the PreEvent Plugin Point

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>

Additional Notes and Considerations

  • 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 the preEvent plugin point, then remove the remaining points.
  • How do I get an Event object in the preProcess() method of my plugins?
Clone this wiki locally