Published on dev2dev (http://dev2dev.bea.com/)
http://dev2dev.bea.com/pub/a/2005/12/integrating-jsf-beehive.html
See this if you're having trouble printing code examples
by Rich Feit
01/10/2006
JavaServer Faces (JSF) is a technology for building Web application user interfaces. It goes beyond JavaServer Pages (JSP) by offering true server-side event handling within a page, and component-based pages that can live across multiple server requests. Apache Beehive is the evolution of the BEA WebLogic Workshop 8.1 runtime, which is now an open-source project of the Apache Software Foundation. Page Flow is Beehive's annotation-based Web controller technology, built on Apache Struts.
JSF is great for building pages by wiring up components and events, but, like all view technologies, it needs a controller to separate out the navigation decisions between pages, and to provide a link to the business tier. It comes with a very basic navigation handler that is meant to be swapped out for a full-featured one. Page Flow provides the base for creating reusable, encapsulated flows of pages, and it works alongside a view layer. It is a full-featured navigation handler that treats JSF pages as first-class citizens. This article looks at how to integrate these two technologies to leverage the strengths of both.
To set up a Beehive/JSF application, first you'll enable Page Flow, and then add support for JSF. The place to start is with a basic NetUI-enabled project. (NetUI is the piece of Beehive that contains Page Flow.) Set up a basic NetUI-enabled Web application according to these instructions. For this article, assume that it's called "jsf-beehive," and can be reached at http://localhost:8080/jsf-beehive.
Next, install and configure JSF. Page Flow should work with any JavaServer Faces 1.1-compliant implementation, and it is tested against two popular ones: Apache MyFaces and the JSF Reference Implementation. Install JSF into your new Web application according to the instructions for one of the following: MyFaces v1.0.9 or later, the JSF Reference Implementation v1.1_01, or your preferred implementation. After that, you can enable Page Flow integration with one simple entry in WEB-INF/faces-config.xml, below the <application> tag and above the <navigation-rule> tag(s):
<factory> <application-factory> org.apache.beehive.netui.pageflow.faces.PageFlowApplicationFactory </application-factory> </factory>
Adding this gives Page Flow a chance to provide its own version of JSF framework objects in order to customize its behavior. In general, JSF behavior is modified only when you are using Page Flow features; the basic behavior of JSF is not changed.
The most basic use of Page Flow in JSF is the raising (invoking) of actions from a JSF page. While your JSF page can handle intra-page events, a Page Flow action is the way to navigate from page to page. First, create a directory called "example" in your Web application, and in it create a Page Flow controller class:
package example;
import org.apache.beehive.netui.pageflow.Forward;
import org.apache.beehive.netui.pageflow.PageFlowController;
import org.apache.beehive.netui.pageflow.annotations.Jpf;
@Jpf.Controller(
simpleActions={
@Jpf.SimpleAction(name="begin", path="page1.faces")
}
)
public class ExampleController extends PageFlowController
{
@Jpf.Action(
forwards={
@Jpf.Forward(name="success", path="page2.faces")
}
)
public Forward goPage2()
{
Forward fwd = new Forward("success");
return fwd;
}
}
There are two actions in this Page Flow: a begin action that forwards to page1.faces, and a goPage2 action that forwards to page2.faces. The reason for making goPage2 a method action (rather than a simple action) is because you'll be expanding on it later.
When you construct your pages, you should create page1 and page2 with the ".jsp" extension; the JSF servlet processes each ".faces" request and ultimately forwards to the associated JSP. So forwarding to "page1.faces" will end up showing your page1.jsp, which looks something like this:
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>
<html>
<body>
<f:view>
<h:form>
<h:panelGrid>
<h:outputText value="Page 1 of page flow #{pageFlow.URI}"/>
<h:commandLink action="goPage2" value="Go to page 2"/>
</h:panelGrid>
</h:form>
</f:view>
</body>
</html>
Raising an action from a JSF page is simple; just use the action name in the action attribute of a command component. In the example above, the commandLink points to action goPage2. With Page Flow integration, this means that the goPage2 action will be run in example.ExampleController.
That's it. To try this out, build your application and hit http://localhost:8080/jsf-beehive/example/ExampleController.jpf, which forwards you through the begin action to page1.faces. Click the "Go to page 2" link, which will raise the goPage2 action and will take you to page2.faces.
The Page Flow framework can manage a "backing bean" that is associated with your JSF page. This class is a convenient place for event handlers and state that are related to the page. Think of it as a single place to put all the code that runs when you interact with the page. When you hit a JSF page, Page Flow will determine if there is a class with the same name and package, for example, class example.page1 for page /example/page1.faces. If such a class exists, and if it is annotated with @Jpf.FacesBacking and extends FacesBackingBean, it will create an instance of it. When you leave the JSF page by going to an action or any other page, the backing bean will be destroyed. The backing bean lives and dies with your JSF page.
Here is a very simple backing bean for page1.faces, with a property "someProperty." The filename is page1.java:
package example;
import org.apache.beehive.netui.pageflow.FacesBackingBean;
import org.apache.beehive.netui.pageflow.annotations.Jpf;
@Jpf.FacesBacking
public class page1 extends FacesBackingBean
{
private String _someProperty = "This is a property value from"
+ getClass().getName() + ".";
public String getSomeProperty()
{
return _someProperty;
}
public void setSomeProperty(String someProperty)
{
_someProperty = someProperty;
}
}
In your JSF page (page1.jsp), you can bind to this property using the backing binding context:
<h:outputText value="#{backing.someProperty}"/>
The example above displays the value of someProperty (ultimately calling getSomeProperty() on the backing bean). Setting the value is similar:
<h:inputText value="#{backing.someProperty}"/>
Note that in this example, no event handlers or component references are present in the backing bean. This simply shortens the code; a backing bean is a great place to put all your page's event handlers and component references!
|
In the Basic Integration section above, you raised a Page Flow action directly from a JSF component. Often, this is simply all you need; when you click a button or a link, an action runs and takes you to another page. If you would like to run some page-related code before calling out to the controller, or if you would like the page to choose dynamically between several actions, then you can raise an action from within a command handler—a Java method that is run by the JSF page. Here is an sample command handler, which you can put in the backing bean page2.java (or in any other publicly-accessible bean):
public String
chooseNextPage()
{
return "goPage3";
}
This is a very simple command handler, which chooses the goPage3 action. You bind to this command handler from a JSF command component in the standard JSF way:
<h:commandButton action="#{backing.chooseNextPage}"
value="Submit"/>
When you click the link, your chooseNextPage command handler is run, and it chooses to raise action goPage3. You can optionally use a special Page Flow annotation—@Jpf.CommandHandler—on the command handler method:
@Jpf.CommandHandler(
raiseActions={
@Jpf.RaiseAction(action="goPage3")
}
)
public String chooseNextPage()
{
return "goPage3";
}
This annotation allows Beehive-aware tools to understand which actions are raised by a command handler in a backing bean, and it also allows you to extend the capabilities of JSF action processing (see the Sending data from a JSF page to a Page Flow section below).
In some cases, you may want to access the current Page Flow, or an active shared flow, directly from a backing bean. To do this, simply create a field of the correct type, and annotate it with @Jpf.PageFlowField or @Jpf.SharedFlowField as appropriate:
@Jpf.PageFlowField
private ExampleController myController;
@Jpf.SharedFlowField(name="sharedFlow2") // "sharedFlow2" is a
// name defined in the
// page flow controller
private ExampleSharedFlow mySharedFlow;
These fields will be initialized when the backing bean is created. You never need to initialize them manually. Here is an example of using the auto-initialized ExampleController field. In this case, an event handler for a "show hints" radio button sets a general preference in the Page Flow.
public void showHintsValueChanged(ValueChangeEvent event)
{
if (event.getNewValue().equals("show"))
{
myController.turnOnHints();
}
else
{
myController.turnOffHints();
}
}
In many cases, your page will never need to interact directly with a Page Flow or a shared flow; instead, it's sufficient to use other means to pass data from a Page Flow to a JSF page, or vice versa. I'll show examples of these below.
You can't access the backing bean from a Page Flow controller! At least, it's not easy, and intentionally so. The backing bean is associated tightly with your JSF page, and it is destroyed when you leave the page. The Page Flow controller should be no more aware of the backing bean than it should be aware of the specifics of a page. Of course, you can pass data from the backing bean to the controller (described below), and you can even pass the backing bean instance itself, but in most cases the content of the backing bean is not something that should leak out to the controller.
Often, you want to run code when something happens to your backing bean such as when it is created or destroyed. The Page Flow framework will call several methods on the backing bean as it progresses through its lifecycle:
onCreate(): When the bean is created.onDestroy(): When the bean is destroyed (removed from the user session).onRestore(): This one bears a bit of explaining. I said that the backing bean is destroyed when you leave the page. In most cases this is true, but if your Page Flow uses the navigateTo feature, which allows you to revisit a previously shown page, the framework hangs onto your backing bean for a short time after you leave the page, in case it needs to be restored. When a JSF page is restored by going through a @Jpf.Forward or @Jpf.SimpleAction with navigateTo=Jpf.NavigateTo.currentPage or navigateTo=Jpf.NavigateTo.previousPage, both the page's component tree and its backing bean are restored by the Page Flow framework. When this happens, onRestore() is called.To run code at any of these stages, you simply override the appropriate method:
protected void onCreate()
{
/*some create-time logic */
}
When you override one of these methods, you do not need to call the super version, which is empty.
Now you're ready to look at how to pass data between a JSF page and a Page Flow.
Often you will want to initialize a page with data from the page flow. To do this, add "action outputs" to the Forward for page2.faces:
@Jpf.Action(
forwards={
@Jpf.Forward(
name="success", path="page2.faces",
actionOutputs={
@Jpf.ActionOutput(name="message", type=String.class,
required=true)
}
)
}
)
public Forward goPage2()
{
Forward fwd = new
Forward("success");
fwd.addActionOutput("message", "Got the message.");
return fwd;
}
Once you have done this, you can access this value as a page input, either directly in your JSF page or in your backing bean. (If you don't like typing verbose annotations, you can leave out the ones in italics. They mainly serve to double-check that you added an object of the right type, and that it isn't missing.)
In your page you can bind to the value using the Page Flow pageInput binding context in the JSF expression language:
<h:outputText value="#{pageInput.message}"/>
Note that you can also bind to properties of the page flow controller itself, or of any available shared flow, using the pageFlow and sharedFlow binding contexts:
<h:outputText value="#{pageFlow.someProperty}"/>
<h:outputText value="#{sharedFlow.mySharedFlow.someProperty}"/>
Finally, to access a page input from a backing bean, just call getPageInput from code anywhere in the bean class:
String message = (String) getPageInput("message");
|
You can also send data along with an action that is raised on the Page Flow. Many actions will require a form bean as input; in general, a form bean is used to get data from a page to the controller. First, let's set up an action that accepts a form bean and then forwards to a page:
@Jpf.Action(
forwards={
@Jpf.Forward(name="success", path="page3.faces")
}
)
public Forward goPage3(NameBean nameBean)
{
_userName = nameBean.getFirstName() + ' ' +
nameBean.getLastName();
return new Forward("success");
}
This action takes a NameBean, which is a form bean class that has getters/setters for firstName and lastName properties. It sets a member variable to the full name, and then forwards to page3.faces. As you know, you can raise an action either directly from a JSF page or from its backing bean. In both cases, you can send a form bean to the action. Let's look at each case in turn.
To send a form bean from a command handler in the backing bean, you use a special annotation. Here is what it looks like in page2.java:
private ExampleController.NameBean _nameBean;
protected void onCreate()
{
_nameBean = new ExampleController.NameBean();
}
public ExampleController.NameBean getName()
{
return _nameBean;
}
@Jpf.CommandHandler(
raiseActions={
@Jpf.RaiseAction(action="goPage3",
outputFormBean="_nameBean")
}
)
public String chooseNextPage()
{
return "goPage3";
}
In this example, the JSF page can fill in the value for _nameBean in any way it chooses (for example, by binding h:inputText values to #{backing.name.firstName} and #{backing.name.lastName}). Then, it uses the outputFormBean attribute on @Jpf.RaiseAction to signal that _nameBean should be passed to action goPage3.
Sending a form bean directly from the JSF page is simple, as long as you can get the bean value through a data binding expression. It is done by adding a special h:attribute component named submitFormBean inside the commandButton component:
<h:commandButton action="#{backing.chooseNextPage}"
value="Submit directly from page">
<f:attribute name="submitFormBean" value="backing.name" />
</h:commandButton>
Here, the button binds to the backing bean's "name" property (getName) for the form bean to send to action goPage3.
This article has shown how to pair the richness of JSF for building pages with the power of Beehive Page Flow for controlling navigation between pages. While integrating the two is easy, it has a profound impact on your application: It separates your JSF pages from application-level logic, and brings the pages into the world of features that Page Flow provides. A JSF page gains a clear mission: participate in the flow of an application as a single (if highly capable) view element. What I haven't shown here is a fully fleshed out application, with event handling in JSF pages and complicated navigational logic in the controller. But the more complex an application gets, the more it demands the separation of responsibilities and advanced flow features that Page Flow adds to JSF. Spend a few minutes trying it out—it doesn't take long to start realizing the benefits.
Rich Feit is an Apache Beehive committer. He has spent the last four years writing and contributing to web application frameworks.
Return to dev2dev.