UI-Component Sets
Project Documentation
Flow Documentation

Installation

To install, just put the orchestra flow jar in your classpath. The necessary custom ViewHandler and NavigationHandler objects will be installed automatically.

Design Philosophy

The process of "calling" a flow from some page has been made as much like a java method call as possible. Actually, the approach is like looking up an abstract service interface, then invoking that interface, rather like this bit of java code:

  MyAbstractService service = (MyAbstractService) serviceLocator.find(serviceName);
  Results results = service.run(arg1, arg2, arg3);

The analogy between this code and what Orchestra Flows provides isn't 100% exact, but it's a good starting point for understanding how things work.

As with the above, an abstract service name maps to the actual implementation. In a flow, the serviceName is a navigation outcome, and the concrete implementation is whatever viewId the JSF navigation rules map that outcome to.

The discovered Java service object is expected to implement a particular interface, and it is a runtime error (ClassCastException) if it does not. Similarly, the caller of a flow declares what "service" it expects the flow to provide. If the nagivation rules map to a viewId that is not a callable flow, or has a "service" value that does not match then that is also a runtime error (OrchestraException). Note that unlike a java service interface, a flow can provides only one "method" to call.

A java service interface declares a fixed set of parameters that can be passed to it. Similarly, a callable flow "declares" what parameters it accepts. It is a runtime exception if a call is made to a flow but an incorrect number of parameters are passed. Actually, for flows parameters are matched by name rather than just index (as some programming languages support, but not java). So order of parameters is not important, but for each param declared by the called flow the caller must provide a value. NB: it is theoretically possible for mismatched param errors to be reported at startup rather than runtime (ie make it a "compile time" error like java), but this is not yet implemented.

Note, however, that the way in which the values to pass as input parameters is rather unlike java. Each parameter is specified as an EL expression that simply pulls a value from the current environment at the time the call is made.

A flow also returns values. Unlike java, a flow can return multiple values. And also unlike java, return values are specified as EL expressions that state where each return value should be stored.

Defining a Callable Flow

All pages related to a flow need to be placed in their own directory (ie no pages that are not part of the flow should be in the same directory). Subdirectories are permitted, so that large and complicated flows can still be properly structured.

One page of the flow must be designated as the "entry page". Create an xml file whose name is the same as the entry page, but replace the page suffix with "-flow.xml". For example, if the entry page is "searchCustomer.jsp" then create a file named "searchCustomer-flow.xml". Or for entry page "selectDestination.xhtml", create file "selectDestination-flow.xml".

The flow.xml file must contain a flowAccept section that defines:

  • What logical service this flow provides
  • What parameters the caller is required to pass to this flow, and where those parameters get stored when the flow starts (ie which properties of which backing beans they should be assigned to).
  • What parameters the flow will pass back to the caller when the flow is committed (ends successfully), and where that data can be found.
  • What navigation outcome will trigger a return back to the calling page.

Here is an example of a flow.xml file containing a flowAccept section:

<flowConfig>
  <!--
    - This service prompts the user to define either a postal or pobox address,
    - and returns an Address object.
    -->
  <flowAccept service="com.acme.services.CreateAddress">
    <param name="type" dst="#{addressCreator.addressType}"/>
    <return name="address" src="#{addressCreator.address}"/>

    <commitWhen outcome="commit"/>
    <cancelWhen outcome="cancel"/>
  </flowAccept>
</flowConfig>

As for any java service, good documentation is important; the flowConfig clause is a good place to document what the flow does and what the parameters mean.

The "service" property is similar to a java interface name, and states what logical operation the flow performs. This is a free-format string, but it is recommended that it contain something that resembles a fully-qualified java class name. Its main purpose is to ensure that the caller has correctly found the right kind of called flow, and to report a clear error immediately when things go wrong, rather than getting some weird behaviour later. It is also excellent documenation.

One or more input parameters are required. The name attribute must match the name specified for a parameter passed by the caller. The dst attribute is an EL expression that states where the input parameter passed by the caller should be stored when the flow starts.

One or more return parameters are required. The name attribute must match the name specified for a return parameter of the caller. The src attribute is an EL expression that states where the return value should be fetched from when the flow returns with a "commit" operation.

The commitWhen element specifies what navigation outcome should cause the flow to perform a "commit". When "committed", a flow copies all its return parameters back to the caller, discards all flow-related objects then navigates back to the calling page. The outcome value here applies only while the flow is active. This element is optional; the string "commit" is the default value so this element is needed only if a value other than the default is desired.

The cancelWhen element specifies what navigation outcome should cause the flow to perform a "cancel". When "cancelled", a flow discards all flow-related objects then navigate back to the calling page. No return values are pushed to the caller. This element is optional; the string "commit" is the default value so this element is needed only if a value other than the default is desired.

Note that for both commitWhen and cancelWhen, there does not need to be any JSF navigation case defined for the outcome; the "to-view-id" is always the viewId of the calling flow and any navigation case defined will be ignored.

Defining a Flow Caller

Where a page should trigger a call to a flow, create an xml file whose name is the same as the page file, but replace the page suffix with "-flow.xml". For example, if page "manageUser.jsp" should call the "createAddress" flow, then create a file named "manageUser-flow.xml".

The flow.xml file must contain a flowCall section that defines:

  • what navigation outcome triggers the call
  • What logical service the called flow is expected to provide
  • What parameters the caller is passing to the flow, and where that data can be found.
  • What return values are expected from the flow, and where that data should be stored after the flow returns it.

Here is an example of a flow.xml file containing a flowCall section:

<flowConfig>
  <flowCall outcome="newAddress" service="com.acme.services.CreateAddress">
    <param name="type" src="#{userManager.addressType}"/>
    <return name="address" dst="#{user.address}"/>
    <onCommit action="#{userManager.newAddress}"/>
  </flowCall>
</flowConfig>

The "outcome" property defines what navigation outcome from the associated page triggers a call to the flow. The matching JSF navigation rule must of course point to a viewId which has a flow.xml file with a corresponding flowAccept clause. Defining a navigation rule that goes to the entry point of a flow without having a corresponding flowCall clause causes an error to be reported; the target flow cannot sensibly run without any input parameters or anywhere to put its return values!

The "service" property defines what is expected of the called flow. If the called flow does not declare exactly the same service type, then an error is reported.

One or more input parameters are required. The name attribute must match the name specified for a parameter expected by the called flow. The src attribute is an EL expression that states where the value to pass can be found.

One or more return parameters are required. The name attribute must match the name specified for a return parameter of the called flow. The dst attribute is an EL expression that states where the named value returned by the flow should be stored.

An optional onCommit section can be defined. If present, then the specified method is executed after the called flow has successfully finished and all return parameters have been updated. This allows the calling page to process the returned parameters after a flow has "committed". The expression normally points to a method that returns void. However if it points to a method that returns a String, and a non-null value is returned from that method then it is used as a navigation outcome. The onCommit method can therefore cause the page rendered after flow return to be something other than the caller; and as the return values are available when this function runs, it can be a page dynamically chosen based on the outputs of the flow.

Note that a page can trigger calls to as many different flows as it wants; multiple flowCall entries can exist in a single flow.xml file. This is unlike flowAccept, where a maximum of one entry is valid.

Making Flow Calls From Within Flows

Pages within flows can call other flows if they wish. The calls nest just as would be expected. Note that when the entry page for a flow itself can call other flows, then the flow.xml file will have both flowAccept and flowCall clauses in it.

Occasionally, a flow will want to call itself. One example might be "display user details", where a user might have links to other users. This is simply done by adding a flowCall element to the flow.xml file just like a normal call to a flow. No special handling is required.

Cancelling Flows

When a page that is part of a flow returns a navigation outcome that matches the cancelWhen setting for the called flow ("cancel" by default) then the flow discards all data and navigates back to the calling page.

However it is also possible for pages to contain links that lead outside the current flow (eg navigation menus at the top of a page) or for a user to simply enter a new url directly into their browser. Orchestra Flow automatically detect when this happens, and simply discards the current flow. This avoids any memory leaks for data associated with a flow.

Points of Interest

Passing Mutable Parameters to a Flow

The flow system is designed to isolate the called flow from the caller except through the specified input and return parameters. In particular, if a flow is cancelled, then the caller will not be affected in any way.

However this is not true if the input parameters to a flow are mutable objects. In that case, any modifications made by the called flow are still visible in the passed object when the cancelled flow returns to the caller. This is of course just like java, where a mutable object is passed to a method that then throws an exception; changes to the parameter objecgt cannot be "undone".

Therefore it is recommended that only immutable objects (or safe copies of mutable ones) are passed as input parameters to flows. This isn't absolutely necessary if you know that the called flow does not modify the parameter, but that cannot always be assumed.

Selecting Return Params

The process of passing parameters from the caller to the called flow is not quite like Java, but is reasonably intuitive. The way that data gets selected from the called flow environment on return is somewhat odd, and requires some further explanation. The flow entry point defines what outcome triggers a "commit" of the flow. Then when *any* page in the flow performs a navigation using that outcome, then that is detected and the EL expressions that the flow entry point defined are executed to select the data to return. The EL expressions therefore do need to be written with care, so that they are valid at the point that the commit outcome is used. However there is no way to force a more java-style return to happen; requiring a flow to return to the caller only from the entry page of the flow would be a closer analogy to Java but is too limiting from a UI design viewpoint.

In practice, flows generally only have one or two points at which they can "successfully" exit, ie at which a flow commit can happen.

The process of "cancelling" a flow is of course much simpler, as no return values are returned to the caller.

Persistence Contexts

When the Persistence features of Orchestra Core are enabled, then they work in Flows exactly as they do in "plain" Orchestra; each conversation has its own persistence context.