 | |  |
| Sticking to the MVC pattern |
 | |
This extension relies on the assumption that your whole application strictly sticks
to the Model View Controller pattern. Unfortunately the Struts framework does not
really coerce you to do that. You are not forced to serve each JSP file (view) by
going through a Struts action (controller). Thus, Struts allows you to leave the
MVC pattern in this respect. So it is up to the developer to stick to the MVC pattern,
which means that each JSP file must be delivered by an action. This action should be
responsible to check whether the user is correctly logged on. Direcly accessing a
JSP file should never be allowed.
On the Struts mailing list there have been quite a few discussions on this topic
and the general advice is, to put all the JSP files that should not be directly
accessible under the WEB-INF directory.
Each view that does not require any special tasks in its action can simply use
the action mappings's forward parameter like so:
<action path="/displayLoginAction" forward="/WEB-INF/web/login.jsp" />
This definition does nothing but forward to the specified JSP page
|  |  |
| Integration with your applications |
 | |
To use this extension you must put the library struts-workflow-x-y-z.jar into your
application's lib directory.
Then you must change your configuration files, so they look like this:
<struts-config>
<form-beans>
...
</form-beans>
<global-forwards>
...
</global-forwards>
<action-mappings type="com.livinglogic.struts.workflow.WorkflowMapping">
...
</action-mappings>
<controller processorClass="com.livinglogic.struts.workflow.WorkflowRequestProcessor" />
</struts-config>
As you can see, you must specify the attribute
type="com.livinglogic.workflow.WorkflowMapping"
for all action mappings and the controller tag's attribute
processorClass must be set to
com.livinglogic.struts.workflow.WorkflowRequestProcessor.
From Version 1.0.2 on, the Struts Workflow Extension can also be used together
with the Tiles plugin. Then you need to specify the class
com.livinglogic.struts.workflow.TilesWorkflowRequestProcessor
as the processor class, instead.
That should be all you need to do. Most important, you do not need to modify any of the
original struts code or your application code.
|  |  |
 | |
The Struts Framework itself only supports role based authentication checks. But as
many applications implement their own session based authentication mechanism, this
extension provides a simple means to control access to each action in a more flexible
way. With authentication checks the test is meant, that verifies that the current user is
correctly logged into the web application before executing an action that only logged
in users are allowed to execute.
All you have to do is add a single property to each action definition you want to
access protect:
<action path="/displayInHomeAction" forward="/WEB-INF/web/inHome.jsp">
<set-property property="authClass" value="com.livinglogic.struts.workflow.test.TestAuthentication" />
</action>
The class that is specified must implement the interface
Authentication that defines the
method check which is called upon each authentication check.
For example:
public class TestAuthentication implements Authentication
{
public boolean check(HttpServletRequest request)
{
return request.getSession().getAttribute("username") != null;
}
}
If check returns true, everything
is fine, otherwise an automatic forward is done to the forward definition
authenticationException which should be globally defined
but can certainly be locally redefined for certain actions:
<global-forwards>
<forward name="authenticationException" path="/WEB-INF/web/authenticationException.jsp" />
...
</global-forwards>
|  |  |
 | |
Now let's turn to the main feature this framework provides: Workflow control.
For each action you can specify one or more labeled "workflows" this action
belongs to. In the notion of this package, workflows are logical groupings of
actions following each other in a rather simple, mostly linear or circular way.
More complicated sequences of actions in an application should be described by
branching off new workflows at a specific point in a workflow or replacing
one workflow by another. This means that several workflows are allowed to
persist or progress in parallel.
One of the workflows specified for an action may be defined as "primary", the
others must be defined as "secondary". An action depends on its secondary
workflows and can influence their progression, but is itself not part of them.
In the Struts configuration file you can specify for each action...
-
a labeled state (
newState) this action leads to in the
workflow.
-
multiple labeled states one of which must precede this action in the
workflow (
prevState).
-
multiple labeled states one of which must follow the state this action leads to in a
workflow (
nextState). Specifying one or more nextState
definitions lets the application developer very selectively specify, which actions
are allowed to be executed directly after an action. Please note, that nextState
definitions are allowed for primary workflows only. Specifying a nextState condition
for a secondary workflow results in an exception, when the struts configuration file
is read.
-
that the workflow ends (
endWorkflow) after this
action, which means that all stored state information is dismissed.
It is very important to know in which order the conditions are checked and at which point
the specified action code is executed:
-
If the previously executed action belongs to a primary workflow and has at least a
nextState condition defined, the current action must change the state of this workflow
to one of the allowed states (or leave the state unmodified if the workflow's state
is already contained in the allowed nextState definitions). If this condition is not
met, a workflow violation of the previously executed action's primary workflow is
detected. If this happens, further progression is stopped and the workflow violation
is handled
-
If no workflow violation was encountered already, the prevState conditions for the current
action are checked. These checks are performed in the order the workflows are specified in
the configuration, i. e. whether a workflow for the current action is a primary or a
secondary one does not matter concerning the order of prevState checks. If the workflow
that is currently checked defines at least a prevState, a workflow with the same label
must already be active and the state this workflow is in, must be one of the prevState
values that are specified. Otherwise, a workflow violation for this workflow is encountered
and further processing is stopped.
-
If no workflow violation is encountered, all workflows' state information is updated, i. e.
the states of all defined workflows are set to the specified new values and all the workflows
that end with the execution of this action are cleaned up and removed.
-
After having updated all workflows, the action code is executed. Please note that all workflows
have already been updated before. Remember in particular, that every workflow that is specified
to end with this action has already been removed at the time of action code execution.
If a workflow is violated, it is cleaned up and removed from the current session and the action
code is not executed. Instead, control is forwarded to a global forward definition
workflowViolation_workflowLabel with workflowLabel specifying the
name of the workflow that was violated.
Here is an example action definition that illustrates the above:
<action path="/beginWf3Transition"
type="com.livinglogic.struts.workflow.test.Wf3St1Action">
<set-property property="authClass" value="com.livinglogic.struts.workflow.test.TestAuthentication" />
<set-property property="secondaryWorkflow" value="wf2" />
<set-property property="prevState" value="suspended" />
<set-property property="prevState" value="2" />
<set-property property="newState" value="suspended" />
<set-property property="primaryWorkflow" value="wf3" />
<set-property property="newState" value="1" />
<set-property property="nextState" value="2" />
<forward name="success" path="/WEB-INF/web/inHome.jsp" />
</action>
This action can only be executed, if a workflow with the label wf2
is active and currently in the state suspended or 2.
If someone tries to execute this action, even though this is not true, the action will not be
executed. Instead control is forwarded to the global forward definition
workflowViolation_wf2. The action that is associated with this forward can
again violate a workflow, if its prevState conditions are not met.
If the above action's prevState condition is met, the action brings this workflow into the state
suspended and the workflow wf3 (which is the primary
workflow, i. e. the workflow this action belongs to) into the state 1. It
also defines a nextState condition for this workflow, which means that the action that is requested
after this action must bring this workflow into the state 2. If the user
tries to execute an action that does not do that, the workflow wf3 is violated,
i. e. control is forwarded to workflowViolation_wf3.
The nextState condition is a very powerful condition that allows the developer to define modal
dialogs, because it denies the user from executing another action than one of the actions that
meet the defined nextState condition. However in every web application, there are actions that
must be executable no matter in which workflow state the application currently is. For instance,
you can think of an application that always wants to provide a "Logout"-Button. What if the user
is in the middle of a modal dialog workflow, i. e. the action for displaying the dialog page has
been executed already and the nextState-condition expects the next action to bring this workflow
into a certain state? There is a simple means for cases like this one. An action can
be excluded from all workflow checks by setting the property noWorkflowChecks
to true in the action mapping like so:
<action path="/logoutAction"
type="com.livinglogic.struts.workflow.test.LogoutAction">
<set-property property="noWorkflowChecks" value="true" />
<forward name="success" path="/displayLoginAction.do" />
</action>
If this property is set, no primary or secondary workflow definitions are allowed for the same action
mapping. When the action is executed, no nextState conditions of the previous primary workflow are checked
and all the active workflows are left untouched, i. e. after the action has been executed, all the
workflows are in exactly the same states like if the action had not been executed at all. In our example
this would normally mean that after the logout action has been executed, the next action would need to
bring the workflow of the modal dialog into the state that was defined by the nextState condition
in order to be a valid action. I said normally, because in our special case, the action invalidates the
user's session that stores all workflow state information. Thus, after the action has been executed, no
workflows are active any longer and the application behaves like if the user newly visits the application.
|  |  |
 | |
In every "normal" Java-based web application, there are at least three different scopes available:
application, session and request scope. Each of these scopes allows the web application developer to
store attributes. In order to keep user sessions lean and clean, it is desirable to use the request scope
whenever possible over session scope. However, as soon as a data object that cannot be passed around with
hidden fields needs to be available across multiple requests, this object must to be put in session scope.
This can turn into a problem in some cases, as in a normal web application, the user can enter a sequence
of actions, leave this sequence and turn to some other part of the application. The session scope attributes
that have been stored in the sequence of actions, the user accessed before simply stay there until the
session times out or the user enters the same sequence of actions again and finishes it, so the session
is cleaned up with the last action of this sequence. Therefore, what we would like to have is a more granular
scope that persists longer than the request scope but not for the whole session. This is what the workflow
scope does. This scope can be used to store attributes just like the session scope, but has one significant
difference: There is a separate scope for each workflow, which is cleaned up when a workflow ends.
The following code snippet demonstrates how the workflow scope can be accessed from within an action:
Workflow workflow = WorkflowContainer.get(session).getPrimary();
if (null != workflow)
{
workflow.setAttribute("anAttribute", "Example Attribute");
}
Only the primary workflow's scope can be accessed from an action. If the action does not belong to a
primary workflow, the method getPrimary returns null.
Please note, thate the workflow state information is always updated before the action code is executed.
Thus, in an action that ends its primary workflow, the method getPrimary returns
null, because at the time it is called, the workflow has already been cleaned
up and removed.
In many cases, the workflow scope is not sufficient for attribute storage. Often enough it is necessary
to store attributes in the session scope, e.g. because multiple different workflows need to have access
to it. These session scope attributes are not automatically cleaned up during workflow cleanup.
For cases like this, the Struts Workflow Extension offers the application developer the chance to add
cleanup objects to a workflow. A cleanup object is a class that must implement the interface
WorkflowCleanup which defines the method cleanup(HttpServletRequest).
Here is an example class that cleans the user's session from the session variable
wizardForm, when the cleanup method is executed.
public static class WizardFormCleanup implements
WorkflowCleanup
{
public void cleanup(HttpServletRequest request)
{
request.getSession().removeAttribute("wizardForm");
}
}
This is how this cleanup object can be added to the primary workflow of an action:
Workflow workflow =
WorkflowContainer.get(session).getPrimary();
if (null != workflow)
{
workflow.addCleanupObject("wizardFormCleanup",
new WizardFormCleanup());
}
When a workflow ends, because it was finished normally or it was violated, all cleanup objects' cleanup
methods are executed in the reverse order as they were added, i. e. the cleanup object added last, will be
executed first. Again, the cleanup tasks are performed before the corresponding action code
For further documentation on all these concepts refer to the example
application that is provided with this package.
|  |  |
|