~~ Licensed to the Apache Software Foundation (ASF) under one
~~ or more contributor license agreements. See the NOTICE file
~~ distributed with this work for additional information
~~ regarding copyright ownership. The ASF licenses this file
~~ to you under the Apache License, Version 2.0 (the
~~ "License"); you may not use this file except in compliance
~~ with the License. You may obtain a copy of the License at
~~
~~ http://www.apache.org/licenses/LICENSE-2.0
~~
~~ Unless required by applicable law or agreed to in writing,
~~ software distributed under the License is distributed on an
~~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
~~ KIND, either express or implied. See the License for the
~~ specific language governing permissions and limitations
~~ under the License.
------
Apache MyFaces Orchestra Flow - Usage
------
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:
--------------
--------------
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:
--------------
--------------
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.