Skip to content
neoneo edited this page Jun 9, 2012 · 34 revisions

Configuring CFlow not only allows you to describe your application flow, but it also adds some nice features.

First, you get to define tasks for different phases. There are 5 phases, in order of execution: start, before, event, after and end.

  • start: these tasks are executed exactly once during the whole event handling cycle (request).
  • before: these tasks are executed every time an event is dispatched on this target, but only if the event is not canceled in one of the start tasks.
  • event: these tasks are specific for a given event type. They are executed only for that particular event, provided the event is not canceled in one of the previous phases.
  • after: these tasks are executed every time an event is dispatched on this target, if the event is not canceled in one of the previous phases.
  • end: these tasks are always executed exactly once during the event handling cycle, regardless of event canceling.

All this you can also do programmatically, by using the methods provided by Context.

The second nifty feature you get with configuration is not available programmatically. You can include definitions from one target in another. These includes can be recursive: a target that is included may itself include yet another target, and so forth. See below at the <include> node description.

Third, aspect oriented programming becomes easy: every task is a potential join point. Read more about this here.

As a bonus, you get additional debugging information when you use configuration:

  • Redirects (defined with <redirect>) are delayed; instead the debug output is generated so you can see what happened until that point. The redirect itself is displayed as a hyperlink that you can click on to proceed as normal.
  • Exceptions in threads are notorious to debug, but not in CFlow. If threads (defined with <thread>) are joined, all information generated inside the joined threads is merged in the debug output, including any exceptions.

The XML dialect

The XML reflects how tasks are executed by CFlow. Tasks are organized in targets (a 'category') and within those in events. Additionally there are the various phases as described above.

The root node of an xml file is either <targets>, if you define multiple targets in one file, or <target>, for one target per file. Below all the node types are described, followed by an example.

<targets>

Contains one or more <target> nodes.

<target>

Attributes:

  • name: the name of the target
  • defaultcontroller: the name of the controller to use for invoke tasks, if no controller is specified there (optional)
  • abstract: a boolean; if true, this target cannot accept events directly but must be included in a concrete target (optional, default is false)

<target> can have the following child nodes:

<start>

Defines tasks for the start phase. At most 1 node can be present.

<before>

Defines tasks for the before phase. At most 1 node can be present.

<event>

Defines targets for an event on this target. For each event there will be a node. Attributes:

  • type: the event type (required)

<after>

Defines tasks for the after phase. At most 1 node can be present.

<end>

Defines tasks for the end phase. At most 1 node can be present.

<include>

Includes tasks from another target. Attributes:

  • target: the name of the target to include tasks from.
  • event: the event type to include.

Include nodes are allowed as child nodes in two places:

  • As a child node of <target>. In this case, you include a single event or all events and phases from the target. The target attribute is required, the event attribute is not. If no event attribute is present, the complete target is included, including all phases defined for it. Including tasks like this occurs following these rules:

    • If the receiving target already has a definition for an event, the event is not included.
    • Start and before tasks from the included target are prepended to the existing start and before tasks, respectively.
    • After and end tasks are appended to any existing after and end tasks.

    Of course the last two rules don't apply if you only include a specific event (i.e. if there is an event attribute).

    With multiple <include>s, start tasks from the first <include> are executed first, followed by start tasks from the one after that, and so on. The same goes for before tasks. For after and end tasks, the order of execution is reversed: tasks from the first <include> are executed last.

    If some to be included target A includes another target B, first target B is included in target A, and the result of this is then included. Circular references are not allowed.

  • As a child node of another task node. In this case, the include node is 'replaced by' the tasks of a single event. The target attribute is not required, the event attribute is. If no target is defined, the event is included from the current target.

With both types of includes, the following rules apply to optional attributes of included tasks:

  • Included <invoke> nodes that have no controller attribute will have this attribute set to the default controller of the receiving target after the include cascade has been processed completely. If the receiving target has no default controller specified, it should be included in another target (and so on), and have abstract="true" set. If the include cascade produces invoke nodes without controllers, an exception is thrown.

  • Included <dispatch> and <redirect> nodes that have no target attribute will have this attribute set to the receiving target after the include cascade is processed.

  • For included <render> nodes, the location of the view template depends on the phase that the node is defined in:

    • for nodes defined in the start, before, after and end phases, the view template should be located in the folder of the target that originally defines the node.
    • for nodes defined in an event, the view template should be located in the folder of the receiving target, i.e. after the include cascade is processed.

    This mechanism allows you to let targets inherit a common view template, and override other view templates.

Defining tasks

Defining tasks within the above nodes (phases) is done with the nodes <invoke>, <dispatch>, <render> <redirect> and <thread>. Additionally, the nodes <if>, <else> and <set> allow you to use some elementary programming constructs. The order of these nodes specifies the order of execution.

All tasks can become a join point (aspect oriented programming) when you specify the advice attribute. Read more about this here.

<invoke>

Invokes a handler method on a controller. Attributes:

  • controller: the name of the controller (optional). If this attribute is not present, the default controller specified in the <target> node is used instead.
  • handler: the name of the handler to invoke (required).

This node can contain child task nodes. These are only executed if the event is canceled.

<dispatch>

Dispatches an event. Attributes:

  • target: the target to dispatch the event on (optional). If not present, the target is the one this task is defined in.
  • event: the event type

These attributes can be dynamic.

If the dispatched event is canceled, the current event is also canceled.

This node can contain child task nodes. These are only executed if the dispatched event, and therefore the current event, is canceled.

<render>

Renders a view by including the corresponding template. Attributes:

  • view: the template to render. Leave out the .cfm extension, it is appended by the task. The task looks for the template using the view mapping (if set), in a directory with the name of the target.

<redirect>

Performs a redirect (<cflocation>). Attributes:

  • permanent: when true, uses status code 301. Otherwise, a 302 is issued (default).
  • url: the url to redirect to. When url is present, the following attributes are ignored.
  • target: the target to redirect to. When specified, the event attribute is required.
  • event: the event to redirect to. If no target attribute is specified, the target is assumed to be the target this task is defined in.

All remaining attributes become parameters on the url. Note that the formal attributes cannot be used as url parameters this way.

All attributes except permanent can be dynamic.

When using the target and event attributes, the RequestStrategy is used to construct the url.

<thread>

Similar to <cfthread>. The attributes are the same:

  • action: run/ terminate/ join/ sleep.
  • name: the name of the thread, or in the case of join, a list of names.
  • priority: normal/ high/ low, applicable to run.
  • timeout: time in milliseconds to wait for threads being joined to finish.
  • duration: time in milliseconds to sleep.

This task can contain child task nodes, but only in the case of action="run" (similar to <cfthread>). When the thread runs, the child tasks receive a deep copy of the event object for thread-safety. Properties that are put on that event object are only available to the tasks in the thread, and don't become available to the page thread unless the thread is joined later. Only properties of event objects in threads that have completed at the time of joining are merged onto the page event. Properties are not overwritten.

Next to the tasks, you can use the following elementary programming constructs in your configuration. You probably should keep the use of these nodes to a minimum, but on the other hand, it allows you to keep the controller a little bit cleaner. Without these, you may sometimes find yourself including methods in your controller that you don't really want, or dispatching a lot of events to get some more elaborate logic working. These constructs are:

<if>

This does what you expect. If the condition is true, child tasks are executed. Attributes:

  • condition: the condition to be evaluated (required). This is a regular ColdFusion expression that should return a boolean. Any variables that you use in the expression are looked up on the event object. It is advisable to use the CFML style operators (eq, neq, gt, lt, etc.).

<else>

Used in <if>. This node has no child nodes. This works the same as <cfif> and <cfelse>, except that the closing tag is required since we're working in xml. Attributes:

  • condition: same as with <if>, except it's not required.

<set>

Sets a parameter on the event object. This can be a literal value or a ColdFusion expression. Attributes:

  • [any name]: similar to <cfset>: the attribute name is the name of the parameter, and the attribute value is its value. The attribute can be a string literal, or an expression. See dynamic attributes.
  • overwrite: a boolean (not required, default is true); if false, does not overwrite the parameter if it already exists.

Note that, unfortunately, the overwrite attribute rules out the possibility of using 'overwrite' as a variable name using this node.

<task>

Adds a custom task to the flow. Attributes:

  • component: mapping to a task component. This component must implement cflow.request.Task (or extend an existing task).
  • all other attributes are passed to the constructor of the component as named arguments.

Example

Let's define some tasks for a hello world application that needs authentication. This allows us to use most of the features above.

<?xml version="1.0"?>
<targets>
    <target name="session" defaultcontroller="Session">
        <start>
             <invoke handler="authenticate">
                 <dispatch event="failed" />
             </invoke>
        </start>
        <event type="failed">
             <invoke controller="global.Logger" handler="write" />
             <redirect event="login" />
        </event>
            <event type="login">
                 <render view="login" />
            </event>
    </target>

    <target name="world" defaultcontroller="World">
        <include target="template" />
        <include target="session" />
            <start>
                <set dispatched="false" />
            </start>
        <before>
            <invoke controller="global.Logger" handler="write" />
        </before>
        <event type="hello">
            <render view="hello" />
            <set dispatched="true" />
            <dispatch event="goodbye" />
        </event>
        <event type="goodbye">
            <dispatch target="session" event="exit" />
                <if condition="dispatched">
                    <render view="goodbye" />
                <else />
                    <render view="bogus" />
                </if>
        </event>
    </target>

    <target name="template">
        <start>
            <render view="header" />
        </start>
        <end>
            <render view="footer" />
        </end>
    </target>
</targets>

As you can see this is pretty readable.
By including the session target in the world target, we've put everything that's happening in this target behind authentication. Following the rules above, in the start phase, first template/header.cfm is rendered (defined in the template target), then the Session controller's authenticate() method is invoked. If authentication fails, it cancels the event and the task's child tasks are executed, in this case dispatching an event. This event invokes the write() method on the global.Logger controller, followed by rendering session/failed.cfm. After this, the end tasks (rendering template/footer) are (always) executed.
For the case when access is allowed, it's not very difficult to follow the flow. To see the flow visualized, check out the debugging page.

Conventions

In the goodbye event in the world target, an event 'exit' is dispatched on the session target. This event is not defined there, so what happens now depends on whether setImplicitTasks(true) was called on the Context. If so, then tasks are created and executed, based on the framework convention. In this case, these are:

  1. An invoke task that will invoke the method exit() on controller session. So the target name is used for the controller name, and the event type is used for the method name. This task is only created if the controller component is found.
  2. A render task that will render the template session/exit.cfm. The target name is used for the directory (within the viewmapping set on the Context object), and the event type is the name of the template.

So you can mix configuration with convention, although it's probably not good practice.

Reading the XML configuration

For this, you use the XmlReader object. You just create one like so:

var reader = new cflow.request.XmlReader(application.context);

You then point it to the directory where your xml files are located. So you don't point to individual files. The reader uses ExpandPath() so any path that can be resolved like this, is ok. The reader now reads all xml files in this directory and its subdirectories.

reader.read("/directory/on/filesystem");

Xml files that do not have a <targets> or <target> root node are ignored. In the future, XMLSchema will be used for this.

You can call read() multiple times to read files in different directories, if needed.
When all your configuration files are read, you call:

reader.register();

At this point, first all includes are processed. Then the reader creates all Task instances and registers them at the given context. In the process, all Controller instances are created as well, so all 'static' objects are now available and ready for use.

Since the whole configuration is now compiled in object instances that execute code, CFlow contains very little boilerplate code when processing a request. Since most objects are (treated as) static, object creation is kept to a minimum, which, at least in the case of ColdFusion, is a good thing performance wise.

Clone this wiki locally