-
Notifications
You must be signed in to change notification settings - Fork 3
Configuration
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.
- 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, 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.
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.
Contains one or more <target> nodes.
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:
Defines tasks for the start phase. At most 1 node can be present.
Defines tasks for the before phase. At most 1 node can be present.
Defines targets for an event on this target. For each event there will be a node. Attributes:
- type: the event type (required)
Defines tasks for the after phase. At most 1 node can be present.
Defines tasks for the end phase. At most 1 node can be present.
Includes tasks from another target. Attributes:
- target: the name of the target to include tasks from (required)
- event: the event type to include (optional). If no event attribute is present, the complete target is included, including all phases defined for it.
Including another target 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.
Most nodes have optional attributes, that are evaluated as follows:
-
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 haveabstract="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 within the above nodes (phases) is done with the nodes <invoke>, <dispatch>, <render> and <redirect>. 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.
Invokes a 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. - method: the name of the method to invoke (required).
This node can contain child task nodes. These are only executed if the event is canceled.
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
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.
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.
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.
- parameters: a comma-separated list of parameter names available in the event to append on the url. You can optionally rename these parameters, by using the form "[newname]=[name]", where [name] is the variable name available on the event.
When using the target and event attributes, the RequestStrategy is used to construct the url.
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 to your controller that you don't really want, or dispatching a lot of events to get some more elaborate logic working. These constructs are:
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.).
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.
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. By default the attribute value is taken as a string literal. If you prepend the value with a percentage sign, the value is evaluated as a ColdFusion expression. Any variables used in the expression are searched on the event object. For instance<set fullname="%bean.getFirstName() & ' ' & bean.getLastName()">requires abeanparameter to be available on the event object, and will set a new parameterfullnameon it. Use %% to escape evaluation. - 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.
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 method="authenticate">
<dispatch event="failed" />
</invoke>
</start>
<event type="failed">
<invoke controller="global.Logger" method="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" method="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.
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:
- An invoke task that will invoke the method
exit()on controllersession. 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. - 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
Contextobject), and the event type is the name of the template.
So you can mix configuration with convention, although it's probably not good practice.
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.