Expresso Workflow and Forms

The following new features were added to Expresso to both facilitate and encourage a certain approach to developing screen flow logic. This approach expects controller objects to only perform ‘controller’ functions as described in the MVC design pattern.

Version:

Expresso 5.0

Author:

Aime Bazin

Expresso Components Listing


Caching Configuration Values Controller Objects
Database Objects DB Connection Pooling Email Connectivity
Event Notification and Error Handling Health Check Job Control
Logging Registration & Login Security
Taglibs Unit Testing Workflow
XML


Introduction

The following new features were added to Expresso to both facilitate and encourage a certain approach to developing screen flow logic. This approach expects controller objects to only perform ‘controller’ functions as described in the MVC design pattern. This should limit the controller to ‘adapt’ and ‘mediate’ type of actions. Thanks to the Controller class’ support for the HTTP protocol, there should be little need for web applications to provide their own ‘adapting’ code. This is because Expresso provides abstractions such as ControllerRequest and PersistentSession objects that have already done the adapting work between the HTTP and Java worlds.

Topics

Introduction
Theory Introduction
Controller & State Forms
Workflow
Controller Definition
Screen Build
State Run
Conclusion

Theory Introduction

Most of the code required in Controller descendant classes should be solely for the purpose of providing ‘mediation’ logic. This is the logic that ties components of the application together. The new features that have been added to the Controller class encourage developers to keep this mediation logic inside controller objects by providing support for mediation between state objects. This should help to avoid mediation code finding its way into state classes.

Keeping the mediation code away from state classes allows these objects to focus on ‘model’ functions as described in MVC. In particular the state classes are ‘commands’ as defined in the Command design pattern. The state classes accept any required input via a StateForm at the time they are invoked, perform their ‘business’ logic and return any results via the state’s form and/or the ControllerResponse object. If the business logic that needs to be performed is fairly complex then the state should act as a type of adapter. This is adapting between a user interaction and the classes used to support that user request (note: this is NOT like the adapting role of the Controller from HTTP to Java). The state would be responsible to take the single user request and adapt it into many calls onto supporting custom business ‘model’ objects. The state class (and any supporting model objects) should rarely include any mediation or workflow logic otherwise this reduces the potential for reuse in different application configurations. Therefore a properly coded state will be ignorant about who called it as well as who it should call once complete.

The new features were designed with the following intent: A controller should relate to a ‘use-case’. This is a single business function such as ‘Register to a website’ or ‘Add a new widget to the inventory’. These use cases could comprise either a single user interaction or many user interactions/screens. A state relates to a single user interaction such as ‘Enter address information’ where the address information fits on a single screen (Note: since we kept mediation logic out of the state, this same state could be used both within a wizard-style registration and as part of a stand-alone screen to revise address information).

When a controller is coded, states are added to the controller in one of 3 ways:

  • As a ‘prompt’ state
  • As a ‘handle’ state
  • As a ‘final’ state

Prompt states are those which build the contents of a screen using Input, Output, Block and Transition objects. The Controller will add its own transition objects based on the ‘position’ of the prompt state within the controller these are the Next and Previous buttons common in wizards. When errors are generated in a prompt state’s associated handle state, the prompt state will be invoked by default assuming the handle state has not overridden this at runtime.

Handle states accept user input from a state form and usually perform validation on that data. These states are normally directly invoked as a result of a user action however they will also be invoked as a result of a ‘final’ state execution. Restricting these states to validation-type logic allows them to be reused more easily (eg Within a wizard or stand-alone). Even in a single-screen controller it is a good practice to separate the validation from the commit processing using a final state.
Note: Keeping the handle state ignorant of its associated prompt state allows it to be reused more readily. This can be useful for example, when automating a number of user tasks in a batch or proxy mode.

Final states are the last state to be invoked in a controller. They usually contain the guts of the processing involved in a use-case. This could include database updates or any other processing that needs to occur once all validation has succeeded. Before a final state is executed, all handle states within that controller are rerun and any validation errors cause the appropriate prompt state to be redisplayed. This is useful for wizard-style screen flow.

Structuring your states in this fashion allows a use-case to be fairly easily reconfigured so that it can be used via different user interfaces. In a WAP interface for example, a new controller would be created with states defined at a lower level of granularity. The final state could then be reused to complete the use-case operation.

Note: Use-cases with no user input form should be coded as separate controllers containing only a final state.

Controller & State Forms

The new features provide for the concept of both a state and controller form. The controller form holds all the data required by the final state to complete the use-case. The state form holds only the data required by the individual states to perform their duties. Controller forms should inherit from com.jcorporate.expresso.core.controller.ControllerForm and state forms should implement the com.jcorporate.expresso.core.controller.StateForm interface.

  • Controller form: The ActionForm defined in struts-config.xml for the given controller
  • State form: A bean that implements the StateForm interface

The state form defines the contract between the controller and the state. A state’s form is specified using the addStatePairing() method as described below. It is populated from the controller form just prior to invoking the state. A new method has been defined within the State class called ‘perform()’. This method replaces the existing run() method and should therefore be overridden to define state logic. This method has an additional parameter of type StateForm. Once the perform() has completed, the updated state form data is put back into the controller form. In a wizard setup, the attributes of the state form would be made up of a subset of the attributes on the controller form - this helps isolate the state from the other states in the controller. In a single screen controller, the state form would be the actual controller form.

The controller form holds all the data required to perform the use-case. For wizard controllers, the form would normally be kept in session scope as defined in the Struts config file. In these controllers, the controller form will accumulate more data as the user progresses through the screens. In single screen situations, the controller form is used as the state form.

Struts calls an ActionForm’s reset() method every time it is populated from a user request. This would cause our data to be wiped out so the reset() method should not be overridden. Instead the resetController() method should be overridden. This method is only called when the initial state of a controller is invoked. The initial state is automatically defined when the controller’s states are added in the controller constructor..

Workflow

The new features to support workflow capabilities revolve around the ability to direct execution between states and controllers. A new method, enableReturnToSender(), has been added to the Transition class and special Transition parameters can also be added to direct traffic.

Calling the enableReturnToSender() method will indicate that the destination controller/state as specified in the transition object should return control back to this source state once the destination controller/state has completed without errors. If the target state is in a different controller and is also a wizard controller then the flow does not return to the source state until the successful completion of all the wizard screens. If the target state is in the current controller then flow returns to the source state after the target state has completed without errors. In either case, when the source state is eventually re-invoked, it will have its parameter context reestablished.

An alternative to return-to-sender allows a transition to be defined with one of the following special workflow parameter pairs (as defined in the Controller class):

CTL_SUCC_CTL and CTL_SUCC_STATE
STATE_SUCC_CTL and STATE_SUCC_STATE
STATE_ERR_CTL and STATE_ERR_STATE

CTL_SUCC_XXXX - indicate to the target controller where it should transition to once the final state has completed successfully. This should always be used for transitions between different controllers.

STATE_SUCC_XXXX - indicate to the target state where it should transition to if it completes successfully. This should always be used for transitions within a controller.

STATE_ERR_XXXX - indicate to the target state where it should transition to if it completes with errors. This should always be used for transitions within a controller.

The flow of execution can be defined at any or all of the following times:

  • Controller Definition time
  • Screen Build time
  • State Run time

The preferred approach is to define the flow at Controller Definition time. This keeps the mediation logic in the controller wherever possible. Placing it in the Screen Build time allows for more flexibility while keeping the flow logic outside of the state handlers. The last option allows a state to determine where to route to based on run time considerations that are only known by the state itself (use one of the first two options whenever possible). If flow is defined in more than one of these locations then the following priority is followed: 1) State Run 2) Screen Build 3) Controller Definition.

Controller Definition

State default flow

When using the new features, a controller’s constructor should no longer call the addState() method. The new addStatePairing(State, State, String) and addFinalState(State) methods should be called instead.

The addStatePairing() pairs up a handle state with its associated prompt state. This will automatically cause a prompt state to be invoked if any errors were generated in the associated handle state.

The addFinalState() will be used by the Controller to identify when it needs to rerun all its handle states. This will occur automatically just prior to running the final state. This is needed in wizard screens to ensure that all data is still valid. If any errors are generated by a handle state then the associated prompt state will be invoked in order to display the error message on the appropriate screen.

The sequencing of calls to addStatePairing() is significant. This defines the order of the screens in the controller. The Controller will add 'Previous' and 'Next' transition object(s) to all prompt states’ responses. These transitions refer to either the previous prompt state (for 'Previous' buttons) or to a prompt state’s associated handle state (for 'Next' or 'Done' buttons). If no errors are generated by the handle state then the next prompt state in the sequence is then invoked.

Controller Security & Chaining flow

The following two new attributes have been added to the Controller class: controllerChainingTransition and controllerSecurityTransition. Controller Chaining allows an application to specify the controller to be invoked once the current one completes. A controller completes once the final state has run without errors. Controller Security allows an application to specify the controller to be invoked if a user encounters controller authorization failure on a state in the current controller. Both of these new attributes are of type Transition, and if needed would normally be setup in the controller’s constructor.

Screen Build

A state normally adds transition objects to its response in order to be displayed as links or buttons on an HTML page. These transition objects can have either the special parameters set or can have the enableReturnToSender() method called before doing this. Enabling the return-to-sender would cause the target controller/state to re-invoke this source state so that it can redisplay the screen to the user. Alternatively the special parameters can be used to route to some other destination.

State Run

The State class now has two new attributes of type Transition:

  • successTransition
  • errorTransition

These attributes can be set in the state’s perform() method. Doing so would override any routing that was defined at the Controller Definition or Screen Build times. When a state completes its execution, these two attributes will be used to determine which transition to execute (if any). If the ErrorCollection is not empty then the errorTransition will be used otherwise the successTransition will be executed. This approach is similar to returning an ActionForward in Struts and so state logic should now create transition objects and set the required attributes but not call the transition() method. Rather, the newly created transition objects should be assigned to one of the two new attributes of the State class..

Conclusion

For technical information about how to use workflow, please refer to the Expresso Developers Guide (EDG) or the Javadocs
.


Home | Products | Services | Partners | Customers | About Us | Login | Forums | Contact Us

Copyright © 2002 Jcorporate Ltd. All rights reserved. Copyright Privacy

Last Modified: 07-Oct-02 6:49:58 PM