http://www.developer.com/

Back to article

Three Workflow Approaches with WebLogic Portal


August 4, 2008

Although the disclaimers usually start later, this article merits one up front: These are not the only solutions to creating workflows in WLP. For example, you're not even going to touch on JSF, or consider the possibility of special considerations in a federated portal architecture. So, don't let yourself be limited by the scope of this article or author's experiences and prejudices. What you will examine is some solutions that are known to work and should give you enough of the basics to implement any set of workflow requirements on WLP.

Simple Page Flows

Page flows provide a very straightforward approach to creating a workflow. Using the built-in wizard will quickly generate your page flow controller with the default begin action. This default action is a simple action that doesn't do much for flow; all it does is forward to the generated index.jsp.



Click here for a larger image.

Figure 1: Default Generated Page Flow

This is quickly enhanced by right-clicking begin action under the Actions folder in the page flow perspective and selecting Convert to a Method.

@Jpf.Action(forwards =
   { @Jpf.Forward(name = "default", path = "index.jsp") })

public Forward begin() 
{
   return new Forward("default");
}

Now, you can begin adding workflow logic to your page flow. This approach is good for a simple process where the user will enter data in multiple forms and each submit does some level of processing on new data entered. You can even provide branching logic, forwarding to an action based on inputs. In either case, a single form bean in the page flow controller serves well to maintain the values, placing "frozen" values into hidden fields to maintain them from page to page and action to action.

The following is a series of action stubs that follow a simple workflow to create a web site user account:

/**
 * Check if existing first, last, and email
 * @param form userDataFormBean
 * @return success if new user, error if existing user
 */
@Jpf.Action(forwards =
   { @Jpf.Forward(name = "success", path = "creatUserName.jsp"),
     @Jpf.Forward(name="error", path="index.jsp")
})
public Forward processUserNameAndEmail(userDataFormBean form)
{
   Forward forward = new Forward("success");
   return forward;
}

/**
 * create user name and request address information
 * @param form userDataFormBean
 */
@Jpf.Action(forwards = { @Jpf.Forward(name = "success",
   path = "getAddress.jsp")
})
public Forward createUserName(userDataFormBean form)
{
   Forward forward = new Forward("success");
   return forward;
}

/**
 * Save the snail mail address and offer to subscribe
 * @param form userDataFormBean
 */
@Jpf.Action(forwards = { @Jpf.Forward(name = "success",
   path = "subscribeNewsletter.jsp")
})
public Forward storeAddressInfo(userDataFormBean form)
{
   Forward forward = new Forward("success");
   return forward;
}

/**
 * Save the subsription choice and send to summary page
 * @param form userDataFormBean
 */
@Jpf.Action(forwards = { @Jpf.Forward(name = "success",
   path = "summaryPage.jsp")
})
public Forward offerSubscription(userDataFormBean form)
{
   Forward forward = new Forward("success");
   return forward;
}

What makes this simple is that each JSP uses the same form bean, with the action set to the next action. In a more robust implementation, each action would also have an error page to forward to; this easily could be the JSP that submitted the information (such as processUserNameAndEmail does) with error messages. This example could be expanded with some simple branching; such as, if the user already exists in the database, the page flow action could forward to a password reminder page instead of simply going back to the index page.

Nested Page Flows

Nested page flows take planning a coordination between the originating and nested controllers. This makes them very useful when the work flow is predetermined and not expected to change much or often. In other words, the nested page flow approach is best suited to Waterfall projects where most (if not all) requirements are known prior to development.

Nested page flows allow passing control off to another controller while maintaining the state of the originating controller. This can be useful for branching logic or if you are creating multiple workflows that have the same set of steps as part of the flow. You can develop a page flow control that does the common steps, and then call it from the controllers that deal with the parts of the workflow that vary. For instance, in the earlier simple page flow, you could add a survey in the work flow before the subscription page to determine what types of subscriptions to offer. This survey workflow also could be presented to existing users at log in if their responses were out of date or when there was a new survey. In both the account creation scenario and the login scenario, the survey comes in at the middle of the process, so you want to be able to reuse the survey code without losing the state of either the enrollment or login workflow, so you call the survey flow as a nested flow.

If you know you are going to be calling a page flow as a nested flow at the beginning, you can get the necessary annotations and action methods generated by checking the "Make this a nested page flow" option at the start of the Page Flow Creation wizard. The two key ingredients to making a pageflow nested are in the controller annotation at the class declaration:

@Jpf.Controller(nested = true)
public class NestedPageFlowAController
   extends PageFlowController{

And, the necessity to have an action with a forward that includes a return action:

@Jpf.Action(forwards = { @Jpf.Forward(name = "done",
   returnAction = "portlets_nestedPageFlowADone")})
protected Forward done() {return new Forward("done");}

The return action must be an action method that exists in the controller that called the nested controller. Calling the nested controller is simply a matter of having an action with a forward that resolves to the nested controller (or a method within the controller), like this:

@Jpf.Action(forwards = { @Jpf.Forward(name = "success",
   path = "subscribeNewsletter.jsp")})
   public
      Forward portlets_nestedPageFlowADone(userDataFormBean form)
   {return new Forward("success");}

As noted, this takes a good deal of planning up front. For a more Agile approach, look at a new approach.

Event Flows

As far as the author knows, this is the first description of using events for this particular purpose. This is probably because the author doesn't have as much time to read articles as write them, because it is a fairly intuitive leap to go from inter-portlet communication (a common use of portal events), to passing control back and forth between specialized controllers as well as loading hidden pages used only for special purposes in a workflow.

Events are a handy way of decoupling your controllers and actions. They allow you to move from one controller to another and back again with the only explicit relationship being to the event rather than the action. If you come up with a better way of handling an event or your workflow rules change, you can simply change how the event is handled rather changing all parts of the workflow.

Say you are looking at a login workflow. When the user logs in, the first step would always be to check their credentials. From that point, there are many tasks you may want the user to do. It may be time for them to change their password, or there may be a message you want to show them based on some demographic information. None of these activities are mutually exclusive and could occur in any combination. You could use simple page flows or nested page flows to achieve this, but that would require tight coupling between the actions and/or controllers.

Instead, you can fire an event based on an evaluation and send the user off to take a survey (for example). When they have completed the survey, you may want them to see a bulletin. So, rather than having the logic in the survey action as to where to send them to next, you can send them back to the initial action which then will evaluate whether they should just go to the landing page or somewhere else (such as your bulletin) first. The bulletin could either send them back to the same action after the user acknowledges receipt or forward them on to the landing page itself.

Accomplishing is fairly straightforward. For each page where you want to handle an event, create a .portlet file. Even though the portlet configuration must have some class defined where it would presumably start, once you add event handling to the configuration you have ultimate control over how to respond to that event. Look at a simple example of how this works.

Yor logic can go in any action, but for simplicity you will put it in the begin action:

public Forward begin(userFromBean form)
{
   PortletBackingContext pbc =
   PortletBackingContext.getPortletBackingContext(getRequest());;
   int status = getStatus(form.getUserId());

   switch(status)
      {
      case 0:
         pbc.fireCustomEvent("callDisplayBulletin", form);
         break;
      case 1:
         pbc.fireCustomEvent("callChangePassword", form);
         break;
      case 2:
         pbc.fireCustomEvent("callPresentSurvey", form);
        break;
      }
   return new Forward("default");
}

Because this action method always evaluates the users' status, you can continue to send them back here and determine where to go next. If value of the status doesn't have a case, you simply send them to the forward destination.

Each of the events has a portlet event handler registered to listen for it. The handlers can be in as many different portlet definitions as you want, allowing for reusing the methods in the same controller on different pages or be able to have several different controllers interact with each other through the event framework. Keeping your example simple, you will have the methods in one controller in a single portlet:

<netuix:portlet definitionLabel="eventBasedPageFlow"
                title="Event Based Page Flow">
   <netuix:handleCustomEvent event="callDisplayBulletin"
                             eventLabel="callDisplayBulletin"
                             fromSelfInstanceOnly="false"
                             onlyIfDisplayed="false"
                             sourceDefinitionWildcard="any">
      <netuix:activatePage/>
      <netuix:invokePageFlowAction action="callDisplayBulletin"/>
   </netuix:handleCustomEvent>
   <netuix:handleCustomEvent event="callChangePassword"
                             eventLabel="callChangePassword"
                             fromSelfInstanceOnly="false"
                             onlyIfDisplayed="false"
                             sourceDefinitionWildcard="any">
      <netuix:invokePageFlowAction action="changePassword"/>
   </netuix:handleCustomEvent>
   <netuix:handleCustomEvent event="callPresentSurvey"
                             eventLabel="callPresentSurvey"
                             fromSelfInstanceOnly="false"
                             onlyIfDisplayed="true"
                             sourceDefinitionWildcard="any">
      <netuix:activatePage/>
      <netuix:invokePageFlowAction action="presentSurvey"/>
   </netuix:handleCustomEvent>
   <netuix:titlebar/>
   <netuix:content>
      <netuix:pageflowContent contentUri="/portlets/
       eventBasePageFlow/EventBasePageFlowController.jpf"/>
   </netuix:content>
</netuix:portlet>

The above example is for the sake or brevity. It is far more likely that these events would be handled by multiple portlets either due to presentation considerations (such as going from a page full of portlets to a page with a single portlet) or logical separation of functionality (such as a survey controller, bulletin controller, and so forth).

In addition to custom events, page flow actions are events that also can be listened for, allowing for the possibility of completely changing the functionality of action by listening for it and adding additional or new behaviors. The combinations are endless and can often be changed with only a minor configuration update or a single line of code. This simplicity is key to agile methodologies and provides developers with a rapid way to add functionality on an as-needed basis.

Conclusion

Workflows are a common requirement for portals. Although the examples in this article revolved around a simple registration and login process, they were chosen for their commonality. Employment applications, freight logistics, legal document creation, supply requisitioning, and financial transactions are other common workflows that are often required within a portal. Those that are straightforward with little or no deviation are implemented easily with a simple page flow. Nested page flows provide a solution to complex workflows that need to interact and provide an opportunity for the reuse of common sub-flows when a project has well-defined requirements. For a more agile approach, listening for and calling events provides a flexible, loosely-coupled way to call portlet methods within and between controllers without having to know all of the specifics what future requirements may be.

About the Author

Scott Nelson is a Senior Principal Consultant with well over 10 years of experience designing, developing, and maintaining web-based applications for manufacturing, pharmaceutical, financial services, non-profit organizations, and real estate agencies for use by employees, customers, vendors, franchisees, executive management, and others who use a browser. He also blogs all of the funny emails forwarded to him at Frequently Unasked Questions.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date