Portlet Development Guide

     Previous  Next    Open TOC in new window    View as PDF - New Window  Get Adobe Reader - New Window
Content starts here

JSF Portlet Development

This appendix provides information on specific use cases, development tips, and code examples. It contains the following sections:

Note: For information about procedures and best practices for developing and configuring JSF portlets, see Working With JSF Portlets.

 


Code Examples

This section includes the following topics:

The JSFPortletHelper Class

This section provides sample code for a helper class that could be used in JSF portlets. It provides helpful methods for developing managed beans that need to have access to WLP context.

Listing B-1 A Comprehensive Helper Class for JSF Portlets
package oracle.samples.wlp.jsf;
import java.io.Serializable;
import java.security.Principal;
import java.util.ResourceBundle;
import javax.faces.context.FacesContext;
import javax.portlet.PortletPreferences;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import weblogic.servlet.security.ServletAuthentication;
import com.bea.netuix.servlets.controls.portlet.PortletPresentationContext;
import com.bea.netuix.servlets.controls.portlet.backing.PortletBackingContext;
import com.bea.netuix.servlets.manager.AppContext;
/**
* A helper class with many useful methods for JSF portlets. These
* methods are expected to be called from JSF managed beans primarily,
* but some may also be useful in the portlet backing files of JSF portlets.
*/
public class JSFPortletHelper {
// STANDARD CONTEXT
/**
	* Gets the HttpServletRequest from the FacesContext.
	* Must only be called from a JSF managed bean.
	*
	* @return a HttpServletRequest implementation, which is actually a FacesRequest object
	*/
	static public HttpServletRequest getRequest() {
		FacesContext fc = FacesContext.getCurrentInstance();
		return (HttpServletRequest)fc.getExternalContext().getRequest();
	}
	/**
	* Gets the HttpSession from the FacesContext.
	* Must only be called from a JSF managed bean.
	*
	* @return a HttpSession implementation
	*/
	static public HttpSession getSession() {
		HttpServletRequest request = getRequest();
		return request.getSession();
	}
	/**
	* Gets the HttpServletResponse from the FacesContext.
	* Must only be called from a JSF managed bean.
	*
	* @return a HttpServletResponse implementation, which is actually a FacesResponse object
	*/
	static public HttpServletResponse getResponse() {
		FacesContext fc = FacesContext.getCurrentInstance();
		return (HttpServletResponse)fc.getExternalContext().getResponse();
	}
	/**
	* Gets the localized resource bundle using the passed bundle name
	* Must only be called from a JSF managed bean.
	*
	* @param bundleName the String name of the bundle
	* @return the ResourceBundle containing the localized messages for the view
	*/
	static public ResourceBundle getBundle(String bundleName) {
		FacesContext context = FacesContext.getCurrentInstance();
		ResourceBundle bundle = ResourceBundle.getBundle(bundleName,
			context.getViewRoot().getLocale());
		return bundle;
	}
	/**
	* Gets the localized message using the passed bundle name and message key.
	* Must only be called from a JSF managed bean.
	*
	* @param bundleName the String name of the bundle
	* @param messageKey the String key to be found in the bundle properties file
	* @return the String containing the localized message
	*/
	static public String getBundleMessage(String bundleName, String messageKey) {
		String message = "";
		ResourceBundle bundle = getBundle(bundleName);
		if (bundle != null) {
			message = bundle.getString(messageKey);
		}
		return message;
	}
	// PORTAL ENVIRONMENT
	/**
	* Gets the PortletBackingContext object. This method will return null
	* if called during the RENDER_RESPONSE JSF lifecycle.
	* Must only be called from a JSF managed bean.
	*
	* @return the active PortletBackingContext, or null
	*/
	static public PortletBackingContext getPortletBackingContext() {
		FacesContext fc = FacesContext.getCurrentInstance();
		HttpServletRequest request = (HttpServletRequest)fc.getExternalContext().getRequest();
		return PortletBackingContext.getPortletBackingContext(request);
	}
	/**
	* Gets the PortletPresentationContext object. This method will return null
	* if NOT called during the RENDER_RESPONSE JSF lifecycle.
	* Must only be called from a JSF managed bean.
	*
	* @return the active PortletPresentationContext, or null
	*/
	static public PortletPresentationContext getPortletPresentationContext() {
		FacesContext fc = FacesContext.getCurrentInstance();
		HttpServletRequest request = (HttpServletRequest)fc.getExternalContext().getRequest();
		return PortletPresentationContext.getPortletPresentationContext(request);
	}
	/**
	* Returns true if the user can make customizations
	* (preferences, add/move/remove portlets, add pages) to the portal.
	* This is based on factors such as: is the user authenticated,
	* is it a streaming portal (not a .portal file),
	* and customization is enabled in netuix-config.xml.
	* Can be called from any web application class.
	*
	* @return a boolean, true if it is possible for the user to make customizations
	*/
	static public boolean isCustomizable() {
		return AppContext.isCustomizationAllowed(getRequest());
	}
	// AUTHENTICATION
	/**
	* Is the current user authenticated?
	* Must only be called from a JSF managed bean.
	*
	* @return true if the user is authenticated, false if not
	*/
	static public boolean isAuthenticated() {
		Principal principal = FacesContext.getCurrentInstance().getExternalContext().getUserPrincipal();
		return principal != null;
	}
	/**
	* Get the current user's username from the container.
	* Must only be called from a JSF managed bean.
	*
	* @return the user name, null if the user is not authenticated
	*/
	static public String getUsername() {
		String username = null;
		Principal principal = FacesContext.getCurrentInstance().getExternalContext().getUserPrincipal();
		if (principal != null) {
			username = principal.getName();
		}
		return username;
	}
	/**
	* Get the current user's username for display. If the user is not authenticated, it
	* will return the name passed as the anonymousUsername parameter. DO NOT use this method
	* for anything other than display (e.g. access control, auditing, business logic),
	* as the passed anonymous name may conflict with an actual username in the system.
	* Must only be called from a JSF managed bean.
	*
	* @param anonymousUsername a String localized name to use for an anonymous user, like "Guest"
	* @return the user name
	*/
	static public String getUsernameForDisplay(String anonymousUsername) {
		String username = anonymousUsername;
		Principal principal = FacesContext.getCurrentInstance().getExternalContext().getUserPrincipal();
		if (principal != null) {
			username = principal.getName();
		}
		return username;
	}
	// USER AUTHENTICATION ROUTINES
	/**
	* Authenticate the user with WebLogic Server
	* Can be called from any web application class.
	*
	* @param username the String username
	* @param password the String password as provided by the user
	* @return true if the login was successful, false if not
	*/
	static public boolean authenticate(String username, String password) {
		HttpServletRequest request = getRequest();
		HttpServletResponse response = getResponse();
		int result = ServletAuthentication.weak(username, password, request, response);
		return result != ServletAuthentication.FAILED_AUTHENTICATION;
	}
	// NAMESPACES AND LABELS
	/**
	* Gets the current portlet's instance label.
	* Must only be called from a JSF managed bean.
	*
	* @return the String instance label
	*/
	static public String getInstanceLabel() {
		return getInstanceLabel(getRequest());
	}
	/**
	* Gets the current portlet's instance label.
	* Can be called from any web application class.
	*
	* @param the HttpServletRequest object
	* @return the String instance label
	*/
	static public String getInstanceLabel(HttpServletRequest request) {
		String label = "_global";
		PortletBackingContext pbc = PortletBackingContext.getPortletBackingContext(request);
		if (pbc != null) {
			label = pbc.getInstanceLabel();
		}
		else {
			PortletPresentationContext ppc = PortletPresentationContext.getPortletPresentationContext(request);
			if (ppc != null) {
				label = ppc.getInstanceLabel();
			}
		}
		return label;
	}
	/**
	* Gets the current portlet's definition label.
	* Must only be called from a JSF managed bean.
	*
	* @return the String definition label
	*/
	static public String getDefinitionLabel() {
		return getDefinitionLabel(getRequest());
	}
	/**
	* Gets the current portlet's definition label.
	* Can be called from any web application class.
	*
	* @param the HttpServletRequest object
	* @return the String definition label
	*/
	static public String getDefinitionLabel(HttpServletRequest request) {
		String label = "_global";
		PortletBackingContext pbc = PortletBackingContext.getPortletBackingContext(request);
		if (pbc != null) {
			label = pbc.getDefinitionLabel();
		}
		else {
			PortletPresentationContext ppc = PortletPresentationContext.getPortletPresentationContext(request);
			if (ppc != null) {
				label = ppc.getDefinitionLabel();
			}
		}
		return label;
	}
	/**
	* Finds a namespace embedded in a portlet instance
	* or definition label. This namespace is intended to
	* be used as a prefix for attributes set into the
	* HttpSession as a way to share state between portlet
	* instances. See the State Sharing Patterns section.
	* <p>
	* This method expects to find the namespace at the end
	* of the label, following the passed delimiter.
	* <p>
	* For example: <ul>
	* <li>label = "myportlet_group1"
	* <li>delimiter = "_"
	* <li>return = "group1"
	* </ul>
	* @param label the String instance or definition label
	* @param delimiter the
	* @return the String namespace
	*/
	static public String splitNamespaceFromLabel(String label,
			String delimiter) {
		String namespace = label;
		int lastIndex = label.lastIndexOf(delimiter);
		if (lastIndex > -1) {
			// namespaced
			namespace = label.substring(lastIndex);
		}
		else {
			// not namespaced, noop
		}
		return namespace;
	}
	// PORTAL EVENTS
	/**
	* Fires a custom portal event with a payload.
	* Must only be called from a JSF managed bean.
	*
	* @param eventName the String name of the event
	* @param payload the Serializable payload
	* @return true if the event was fired, false if it could not be
	*/
	static public boolean fireCustomEvent(String eventName, Serializable payload) {
		boolean fired = false;
		PortletBackingContext pbc = getPortletBackingContext();
		if (pbc != null) {
			pbc.fireCustomEvent(eventName, payload);
			fired = true;
		}
		return fired;
	}
	// WLP PORTLET PREFERENCE OPERATIONS
	/**
	* Gets an instantiated preferences object for the portlet;
	* it must be obtained once per request.
	* Must only be called from a JSF managed bean.
	*
	* @return the PortletPreferences object for the request
	*/
	static public PortletPreferences getPreferencesObject() {
		PortletPreferences prefs = null;
		PortletBackingContext pbc = getPortletBackingContext();
		HttpServletRequest request = getRequest();
		if (pbc != null) {
			prefs = pbc.getPortletPreferences(request);
		} else {
			PortletPresentationContext ppc = PortletPresentationContext.getPortletPresentationContext(request);
			if (ppc != null) {
				prefs = ppc.getPortletPreferences(request);
			}
		}
		return prefs;
	}
	/**
	* Gets the single value preference.
	*
	* @param name the String name of the preference
	* @param value the String default value to use if the preference isn't set
	* @return the String value
	*/
	static public String getPreference(PortletPreferences prefs, String name, String value) {
		if (prefs != null) {
			value = prefs.getValue(name, value);
		}
		return value;
	}
	/**
	* Sets a single value preference into the preferences object.
	* storePreferences() must be called subsequently to persist the change.
	*
	* @param name the String name of the preference
	* @param value the String value of the preference
	*/
	static public void setPreference(PortletPreferences prefs, String name, String value) {
		if (prefs != null) {
			try {
				prefs.setValue(name, value);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
	/**
	* After setting updated values into the preferences object, call this
	* method so they can be stored in the persistent store in a single atomic
	* operation.
	*
	* @param prefs the PortletPreferences to be persisted
	* @return a boolean, true if the store succeeded
	*/
	static public boolean storePreferences(PortletPreferences prefs) {
		if (!isCustomizable()) {
			return false;
		}
		try {
			prefs.store();
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
		return true;
	}
}

Login Portlet Example

This section provides one example of how a Login portlet may be implemented with JSF in order to demonstrate a few key concepts already discussed. Each situation may be different, but this example provides an illustration to help explain details of what can be a complex topic.

This section includes the following topics:

Login Portlet Motivation

WebLogic Portal does not provide a Login portlet out of the box. It has assumed that each developer has custom login requirements, and therefore needs to implement a custom login portlet. While this is true, often the first portlet that a developer attempts to build is a Login portlet. Unfortunately, a Login portlet is not the easiest task to start from, and many improperly designed Login portlets have been created for WLP.

This section, in recognition of this history, provides a working Login portlet implemented with JSF that is both a useful resource to deploy, but also demonstrates a few key concepts already discussed. It also presents solutions to several unique challenges that are encountered when implementing a login/logout portlet.

Login Portlet Design

The login portlet is implemented in JSF. It uses a single view that toggles the visibility of the login/logout controls based on the authenticated state of the user. In summary, the login portlet offers the following features:

It appears to be a straightforward form driven JSF portlet. However, as stated in the introduction, a login portlet has more complexities than a typical portlet. Upon logging into or out of a portal, the portal framework must recompute the view that the user has of the portal to take into account that user's authorization and customizations. When login is implemented as a standalone page within a WLP web application, this is not an issue. But when the login/logout facility is itself a portlet, there are several problems.

This section includes the following topics:

Redirects

By the time a user becomes authenticated, the portal framework is already in the middle of processing the rendered page. It is too late for the framework to recompute the page. Therefore, the solution is to force a redirect after the user is authenticated or logged out with the login portlet. As covered in Native Bridge Architecture, there are some constraints that must be observed to accomplish a redirect:

This leads to the design requirement that the portlet has both a JSF managed backing bean and a portlet backing file. The backing file is responsible for the redirect, and the managed bean handles the form processing and authentication. However, these aren't executed in the right order. Ideally, the redirect would only occur on a successful authentication or log out. The solution offered in this login portlet is to always redirect whenever a user interacts with the portlet. This causes the browser to redirect even for failed login attempts, but that is a minor concession.

Invalidating the Session

A second issue must also be addressed. The WLP JSF portlet native bridge has a limitation in that a user's HttpSession cannot be invalidated in the middle of processing the JSF lifecycles. This best location for the logout logic is in the backing file.

Login Portlet Implementation

This section includes the following topics:

JSF Login View

The login.jsp page contains two forms – one for login and one with a button to logout. Only one form is visible at a time, and the visibility is controlled by a flag that indicates whether the user is authenticated. Otherwise, this JSF view is straightforward.

Listing B-2 The JSF JSP for the Login Portlet
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
	pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html"%>
<%@ taglib uri='http://bea.com/faces/adapter/tags-naming' prefix='jsf-naming' %>
<f:loadBundle basename="oracle.samples.wlp.jsf.portlets.login.loginportlet"
var="i18n" />
<f:view>
<jsf-naming:namingContainer id="login_portlet">
<h:form id="loginBeanForm"
	rendered="#{!JSFLoginPortletRequestBean.authenticated}">
<h:panelGrid id="outerLayout" columns="1" width="100%"
		style="background-color: azure;">
<h:panelGroup id="titleLine">
	<h:outputText value="#{i18n.login_intro}:"
		style="color: cornflowerblue; font-size: medium"/>
</h:panelGroup>
<h:panelGroup id="errorMessage">
	<h:messages layout="table" style="color: red; font-weight: bold"/>
</h:panelGroup>
<h:panelGroup id="formFields">
	<h:panelGrid columns="2" width="60%"
		style="background-color: azure">
	<h:panelGroup style="text-align: right">
	<h:outputText value="#{i18n.login_username}:"/>
	</h:panelGroup>
	<h:panelGroup style="text-align: left">
		<h:inputText id="username" required="true"
		value="#{JSFLoginPortletRequestBean.username}" />
	</h:panelGroup>
	<h:panelGroup style="text-align: right">
		<h:outputText value="#{i18n.login_password}:"/>
	</h:panelGroup>
	<h:panelGroup style="text-align: left">
		<h:inputSecret id="password" required="true"
		value="#{JSFLoginPortletRequestBean.password}" />
	</h:panelGroup>
	<h:panelGroup/>
	<h:panelGroup style="text-align: left">
		<h:commandButton id="loginButton" immediate="false"
		action="#{JSFLoginPortletRequestBean.authenticate}"
		value="#{i18n.login_button}"/>
	</h:panelGroup>
	</h:panelGrid>
</h:panelGroup>
</h:panelGrid>
</h:form>
<h:form id="logoutForm" rendered="#{JSFLoginPortletRequestBean.authenticated}">
	<h:commandButton action="#{JSFLoginPortletRequestBean.userLogout}"
		id="logoutButton" value="#{i18n.logout_button}"/>
</h:form>
</jsf-naming:namingContainer>
</f:view>
JSF Managed Backing Bean

The JSF managed backing bean contains the core logic for logging the user in. There is nothing tricky about this code. It does contain the WebLogic specific code for authenticating a user, but is otherwise standard code. For information about the referenced JSFPortletHelper class, see The JSFPortletHelper Class.

Listing B-3 The JSF Managed Bean for the Login Portlet
package oracle.samples.wlp.jsf.portlets.login;
import java.io.Serializable;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import oracle.samples.wlp.jsf.JSFPortletHelper;
public class JSFLoginPortletRequestBean implements Serializable {
	private static final long serialVersionUID = 1L;
	private static final String BUNDLE_NAME = "oracle.samples.wlp.jsf.portlets.login.loginportlet";
	private String username;
	private String password;
	// ACTION METHODS
	/**
	* Action method called during logout
	*/
	public String userLogout() {
		// Due to a limitation in the bridge, logout must NOT
		// be done in the middle of the JSF lifecycles.
		// Therefore logout is actually done prior to the JSF
		// lifecycles in the request, in the handlePostbackData()
		// method of the JSFLoginPortletBacking backing file.
		return null;
	}
	/**
	* Action method for the login CommandButton
	*
	* @return
	*/
	public String authenticate() {
		// perform the actual authentication call
		boolean success = JSFPortletHelper.authenticate(
			username, password);
		// Handle the result
		if (!success) {
			// Login failed
			// Add an error message to indicate login failure
			String errorText = JSFPortletHelper.getBundleMessage(
				BUNDLE_NAME, "login_error");
			// Add the message to the context
			FacesContext fc = FacesContext.getCurrentInstance();
			FacesMessage msg = new FacesMessage(
				FacesMessage.SEVERITY_ERROR, errorText, errorText);
				fc.addMessage(null, msg);
		}
		else {
			// Login succeeded
			// Wipe out the password, just in case someone
			// is tempted to make this bean Session scoped (should be
			// Request) By keeping this password around in the bean,
			// it is open to temptation for abuse
			password = "invalidated";
		}
		return null;
	}
	// GETTERS AND SETTERS
	/**
	* @return true if the user is authenticated
	*/
	public boolean isAuthenticated() {
		return JSFPortletHelper.isAuthenticated();
	}
	/**
	* @return the user name
	*/
	public String getUsername() {
		return JSFPortletHelper.getUsernameForDisplay("");
	}
	/**
	* @param username the user name to be authenticated
	*/
	public void setUsername(String username) {
		this.username = username;
	}
	/**
	* Retrieves a placeholder for the password. We never
	* want to display back the actual password to the user,
	* so just return an empty string
	* @return an empty String
	*/
	public String getPassword() {
		return "";
	}
	/**
	* @param password the password to be used for authentication
	*/
	public void setPassword(String password) {
		this.password = password;
	}
}
faces-config.xml

The managed bean needs to be wired into the application. The following XML element must be added to faces-config.xml.

Listing B-4 Registering the Login Managed Bean in faces-config.xml
<managed-bean>
	<description>Handles authentication for the Login portlet.</description>
	<managed-bean-name>JSFLoginRequestBean</managed-bean-name>
	<managed-bean-class>
		oracle.samples.wlp.jsf.JSFLoginPortletRequestBean
	</managed-bean-class>
	<managed-bean-scope>request</managed-bean-scope>
</managed-bean>
Backing File

The backing file is where the redirect happens. In this example, the redirect is directed at the page on which the login portlet resides. This is important because the login credentials passed by the user may be incorrect, so redirecting to the same page allows the user to see if the login failed.

In addition, the WLP JSF portlet native bridge does not work properly if the user's HttpSession is invalidated during the middle of the JSF lifecycles. Therefore, logout cannot happen in the JSF managed bean. It must happen before the JSF lifecycles are invoked, which should be done in a backing file.

Listing B-5 Implementation of the Backing File that Performs the Redirect for the Login Portlet
package oracle.samples.wlp.jsf.portlets.login;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import oracle.samples.wlp.jsf.JSFPortletHelper;
import com.bea.netuix.servlets.controls.content.backing.AbstractJspBacking;
import com.bea.netuix.servlets.controls.portlet.backing.PortletBackingContext;
import com.bea.portlet.PageURL;
public class JSFLoginPortletBacking extends AbstractJspBacking {
	private static final long serialVersionUID = 1L;
	@Override
	public boolean handlePostbackData(HttpServletRequest request,
			HttpServletResponse response) {
		// As per the design, the login portlet will ALWAYS redirect if the
		// user interacts with the portlet.
		if (isRequestTargeted(request)) {
			// If the user is authenticated, a form POST
			// to this portlet signals a logout.
			// Logout must be done before the JSF lifecycles start
			// (bridge limitation).
			if (("POST".equals(request.getMethod())) &&
				isAuthenticated(request)) {
				HttpSession session = request.getSession(false);
				session.invalidate();
			}
			// redirect back to the same portal page
			PageURL url = PageURL.createPageURL(request, response);
			// make sure the URL uses the proper ampersands... url.setForcedAmpForm(false);
			// ...and is encoded with a session token if necessary
			String redirectUrl = response.encodeRedirectURL(url.toString());
			PortletBackingContext pbc = PortletBackingContext.getPortletBackingContext(request);
			pbc.sendRedirect(redirectUrl);
		}
		return super.handlePostbackData(request, response);
	}
	/**
	* Is the current user authenticated?
	*
	* @return true if the user is authenticated, false if not
	*/
	protected boolean isAuthenticated(HttpServletRequest request) {
		return JSFPortletHelper.isAuthenticated(request);
	}
}
Resource Bundle

As per best practices, create a resource bundle for the login portlet. This one should be located in a file oracle/samples/wlp/jsf/portlets/login/loginportlet.properties in the Java Resources/src folder.

Listing B-6 The Localizable Resource Bundle for the Login Portlet
# login.jsp
login_title=Login Page
login_imageAlt=Login
login_intro=Please enter your username and password
login_username=Username
login_password=Password
login_button=Login
logout_button=Logout
login_error=The username or password are invalid.
Portlet Definition File

Finally, the .portlet file ties everything together. Most important is the backing file reference. You will not normally create this file as XML – use the portlet editor.

Listing B-7 The Portlet Definition File for the Login Portlet
<?xml version="1.0" encoding="UTF-8"?>
<portal:root
xmlns:netuix="http://www.bea.com/servers/netuix/xsd/controls/netuix/1.0.0"
	xmlns:portal="http://www.bea.com/servers/netuix/xsd/portal/support/1.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.bea.com/servers/netuix/xsd/portal/support/1.0.0 portal-support-1_0_0.xsd">
	<netuix:portlet backingFile="oracle.samples.wlp.jsf.portlets.login.JSFLoginPortletBacking"
	definitionLabel="login" title="Login">
	<netuix:titlebar>
		<netuix:minimize/>
	</netuix:titlebar>
	<netuix:content>
		<netuix:facesContent contentUri="/portlets/login/login.faces"/>
	</netuix:content>
	</netuix:portlet>
</portal:root>

 


Using Facelets

This section contains the following topics:

Introduction to Facelets

Intermixing JSF components with JSP tags can cause problems. While JSP is the default technology in which to implement JSF views, there are issues in how the two technologies work together. There are many references to these problems available in articles and blog posts on the internet.

An alternate view technology called Facelets has been created by the JSF community which avoids the problems seen with JSPs. It offers an XML grammar for declaratively wiring up a JSF view. It more cleanly separates the view definition from the programming logic. As an assertion of the strength of this approach, JSF 2.0 has adopted Facelets as the preferred view technology. This section explains how to configure a Portal Web Project with Facelets.

Note: Before committing to Facelets, be sure to understand the IDE impact. The Workshop IDE only supports Facelets configuration with JSF 1.1. For more details, see Working With JSF Portlets.

Configuring Facelets Support

Follow these steps to configure Facelets within a Portal Web Project:

  1. Start with a Web Project that is properly configured to support JSF JSPs, including a prefix or suffix mapping if you wish to support access to the views outside of portlets.
  2. Download a Facelet implementation library (see https://facelets.dev.java.net/).
  3. Copy the library into the Web Project's WEB-INF/lib directory.
  4. Add the Facelets view handler to faces-config.xml. (see Listing B-8).
  5. Change the default suffix for Faces to be .xhtml. (see Listing B-9).
  6. Create a new Facelet file with suffix .xhtml. (Source code is provided in Listing B-10).
  7. Test the configuration by targeting the Facelet directly. The URL will depend on how you have configured prefix or suffix mapping for Faces. http://localhost/jsfweb/mytest.jsf
  8. Create a new portlet, selecting JSF as the type, and the .xhtml file as the content. Note: You will have to enter the path by hand as the wizard does not allow you to select an .xhtml file.
  9. Add the portlet to a portal, and republish the web application.
Note: The current JSF portlet support (native bridge) cannot support both JSP and Facelets JSF portlets in the same web application. You must choose one or the other. The standard workaround for combining both JSPs and Facelets in the same web application, the VIEW_MAPPINGS technique, is not possible with the WLP native bridge. WSRP could be used as a workaround for this issue.
Listing B-8 The view-handler Element in faces-config.xml
<application>
	<!-- tell JSF to use Facelets ->
	<view-handler>com.sun.facelets.FaceletViewHandler</view-handler>
</application>
Listing B-9 Change the Default Suffix for Faces Requests in web.xml
<context-param>
	<param-name>javax.faces.DEFAULT_SUFFIX</param-name>
	<param-value>.xhtml</param-value>
</context-param>
Listing B-10 Create a Basic facelet .xhtml File in the WebContent Folder
<html xmlns="http://www.w3.org/1999/xhtml"
	xmlns:h="http://java.sun.com/jsf/html"
	xmlns:f="http://java.sun.com/jsf/core">
<head>
<meta http-equiv="Content-Type"
	content="text/html; charset=iso-8859-1" />
<title>My First Facelet</title>
</head>
<body>
	<!-- body content goes here ->
	Hello #{param.name}!
</body>
</html>

 


Using Tomahawk

This section includes the following topics:

What is Apache MyFaces Tomahawk?

Apache MyFaces Tomahawk is an open source component library that provides an enhanced set of JSF components that go beyond the basic set provided by the core JSF specification. Tomahawk enhances the standard components, and provides more than 40 additional components not included in the standard set. This section covers how to use Tomahawk components within WLP JSF portlets.

Official documentation and project information can be found at these locations:

http://myfaces.apache.org/tomahawk/index.html

http://wiki.apache.org/myfaces/Tomahawk

The enhancements to the standard set include:

The additional components offered include a calendar, a data grid, a tree, a tabbed pane, and many more.

Support for Tomahawk in WLP

The JSR-286 and JSR-329 standards bodies are ensuring that JSF applications work properly in a portal environment. In addition, the component libraries such as Tomahawk continue to change to support the new portal requirements. In general, the Tomahawk components work properly. But WLP does not certify Tomahawk or officially support it, so issues may be found in certain use cases.

Use Tomahawk 1.1.7 and Later

Note that some of the work that the Tomahawk team implemented for JSR-301 and JSR-329 will help even when using WebLogic Portal versions that do not support JSR-329. For example, Tomahawk version 1.1.7 obviated the need for the MyFaces Extensions Filter, which is problematic in a portal environment, to help support JSR-329. This change fortunately also benefits WLP 10.3.

Tomahawk 1.1.8 was the version used in these examples. Because of ongoing work to support JSR-329, it is beneficial to look at the latest available version of Tomahawk. It is not recommended to adopt a version prior to 1.1.7.

Portlet Scoping

The Namespacing section covered the WLP specific NamingContainer component. This aids in preventing conflicts when the same portlet is placed on a portal page multiple times. One case in which this is necessary is when a component uses JavaScript. Since Tomahawk components are typically JavaScript-enabled, remember to add a NamingContainer to any portlet that uses a Tomahawk component.

However, not all Tomahawk components work properly when they appear in multiple portlets on the same page, even when the NamingContainer is used. Oracle recommends explicitly testing all Tomahawk components to determine if they suffer from this issue. Check with the Tomahawk documentation for more information on this topic. Later versions may be less problematic.

Ajax Enablement

Tomahawk components are not Ajax enabled. However, the WLP asynchronous desktop and portlet features described in the Ajax Enablement section do work properly with the Tomahawk components. This provides the user with the Ajax responsiveness usually only found in Ajax enabled component libraries such as Apache MyFaces Trinidad.

Installing and Configuring Tomahawk

Tomahawk is not included in a WebLogic Portal installation. It is necessary to download Tomahawk and install and configure it into the WLP web application.

Follow these steps:

  1. Download the Tomahawk JAR file, like tomahawk-1.1.8.jar or later.
  2. Copy the JAR file into WEB-INF/lib.
  3. Add the following entries in web.xml in the appropriate places.
  4. Listing B-11 web.xml Settings for Tomahawk
    <!-- Make two entries that explicitly enable the portal-friendly changes
    introduced with v1.1.7 ->
    	<context-param>
    	<param-name>org.apache.myfaces.CHECK_EXTENSIONS_FILTER</param-name>
    	<param-value>false</param-value>
    	</context-param>
    	<context-param>
    	<param-name>
    		org.apache.myfaces.DISABLE_TOMAHAWK_FACES_CONTEXT_WRAPPER
    	</param-name>
    	<param-value>false</param-value>
    	</context-param>
    	<!-- Configure the mechanism for bringing in resources (.js, .css). ->
    	<context-param>
    	<param-name>org.apache.myfaces.ADD_RESOURCE_CLASS</param-name>
    	<param-value>
    		org.apache.myfaces.renderkit.html.util.NonBufferingAddResource
    	</param-value>
    	</context-param>
    	<!-- Map the resource loading capability of Tomahawk ->
    	<servlet-mapping>
    		<servlet-name>faces</servlet-name>
    		<url-pattern>/faces/myFacesExtensionResource/*</url-pattern>
    	</servlet-mapping>

Resolving the Duplicate ID Issue

Some Tomahawk components, when used in a portlet environment will emit a Duplicate Id Exception. To handle this case, the CleanupPhaseListener Class is a recommended workaround.

CleanupPhaseListener Class

This class is a solution to a Duplicate ID issue found with the use of some Tomahawk components. The JSF specification allows custom components to be marked transient, which means they must be discarded at the end of the HttpRequest. Some component libraries, such as Tomahawk, rely on this behavior.

This PhaseListener is a solution to the problem. Listing B-12 shows the code for the PhaseListener.

Listing B-12 The CleanupPhaseListener Source Code
package oracle.samples.wlp.jsf;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.faces.component.UIComponent;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;
/**
* This PhaseListener must be employed when using the WLP JSF portlet native
* bridge with certain component libraries like Tomahawk. The native bridge
* by default does not correctly handle UIComponent's that are marked as 
* transient via the UIComponent interface.
* <p>
* This PhaseListener implementation corrects that by clearing out all
* transient UIComponents at the end the request lifecycle.
* <p>
* @version Affects all WLP releases 10.x (10.0, 10.2, 10gR3)
*/
public class CleanupPhaseListener implements PhaseListener {
	/**
	* This phases listener is only executed after all JSF phases
	* are run. Therefore this method returns PhaseId.RENDER_RESPONSE
	*/
	@Override
	public PhaseId getPhaseId() {
		return PhaseId.RENDER_RESPONSE;
	}
	/**
	* After RENDER_RESPONSE, purges any transient UIComponent from the view.
	*/
	@Override
	public void afterPhase(PhaseEvent event) {
		FacesContext context = event.getFacesContext();
		UIViewRoot root = context.getViewRoot();
		purgeTransientFromTree(context, root, "");
	}
	/**
	* Walks the component tree, clearing out any transient component.
	*/
	protected void purgeTransientFromTree(FacesContext context, UIComponent component, String parentIndent) {
		UIComponent child;
		List<UIComponent> children = component.getChildren();
		Iterator<UIComponent> childrenIterator = children.iterator();
		List<UIComponent> transients = new ArrayList<UIComponent>();
		while (childrenIterator.hasNext()) {
			child = childrenIterator.next();
			if (child.isTransient()) {
				transients.add(child);
			} else {
				purgeTransientFromTree(context, child,
					parentIndent+" ");
			}
		}
		// now remove the children we found to be transient
		childrenIterator = transients.iterator();
		while (childrenIterator.hasNext()) {
			child = (UIComponent) childrenIterator.next();
			children.remove(child);
		}
	}
	@Override
	public void beforePhase(PhaseEvent event) {}
	private static final long serialVersionUID = 1L;
}

It must be registered in faces-config.xml, as shown in Listing B-13.

Listing B-13 Registering the CleanupPhaseListener in faces-config.xml
<lifecycle>
	<!-- The CleanupPhaseListener corrects an issue in the native bridge
		implementation only seen with custom components. This phase
		listener can always be used, but is needed specifically when
		Tomahawk components are used.
	->
	<phase-listener>
	 oracle.samples.wlp.jsf.CleanupPhaseListener
	</phase-listener>
</lifecycle>

Referring to Resources

Most web applications require JavaScript and CSS files. JSF portlets will often have such a requirement, and the Using Custom JavaScript in JSF Portlets section explained how to accomplish this with WLP for the general JSF case. Most Tomahawk components require resources to be loaded to function properly. This section explains the options when Tomahawk components are present.

Tomahawk has a pluggable mechanism for inserting references to JavaScript and CSS files into a page containing Tomahawk components. There are three implementations that are included in the library, each with benefits and drawbacks. WLP offers an additional option, Render Dependencies, that should also be considered. This section will explain several options.

The pluggable resource handling interface within Tomahawk is AddResource, and the three implementations are:

The chosen implementation is configured in web.xml, shown in .

This section includes the following topics:

Using DefaultAddResource (Not recommended)

The DefaultAddResource buffers the entire response, and inserts any needed resources into <HEAD> before returning the response to the client. The primary benefit is that the resources are included in the proper location in the HTML document (HEAD). The downside is that the entire response must be buffered, which can consume significant server memory.

When using this option with JSF portlets within WLP, there is an additional drawback. The DefaultAddResource implementation works by locating the HEAD element in the markup rendered by the JSF view. In a portal, each JSF portlet contains a JSF view and thus its own DefaultAddResource instance. Because of this fact, each portlet must contain a HEAD element into which its DefaultAddResource instance will write the resource references. However, it is a best practice to not render HTML document elements (HTML, HEAD, BODY) in portlets as it creates invalid HTML.

Another drawback in a portal environment is that this approach cannot eliminate duplicate resource references. This is due to each portlet having its own DefaultAddResource instance. Each portlet will have references to the resources it needs, even if another portlet has already included it.

When the DefaultAddResource is operating outside of a portal environment, its chief advantage is that is renders valid HTML. Within a portal, it does not have this advantage. There are several additional drawbacks with this approach:

Listing B-14 shows an example portlet JSP that will work properly with DefaultAddResource. Notice the required HEAD element, which will be populated at runtime with the necessary resource references.

Listing B-14 A JSP Using Tomahawk with DefaultAddResource
<%@ page language="java" contentType="text/html;charset=UTF-8"%>
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
<%@ taglib uri="http://myfaces.apache.org/tomahawk" prefix="t" %>
<%@ taglib uri='http://bea.com/faces/adapter/tags-naming' prefix='jsf-naming' %>
<head><!-- required for Tomahawk, do not remove -></head>
<f:view>
<jsf-naming:namingContainer id="swapImage">
	<t:panelGroup id="swapImagePanel">
	<t:swapImage id="image" value="/tomahawk/images/MyFaces_logo.jpg"
		swapImageUrl="/tomahawk/images/MyFaces_logo_inverse2.jpg"/>
	</t:panelGroup>
</jsf-naming:namingContainer>
</f:view>

Using NonBufferingAddResource (Simplest)

The NonBufferingAddResource implementation does not buffer the response. Instead, it writes resource references directly into the Tomahawk component's markup within the BODY element of the HTML document. This is technically invalid HTML, as the resource references should be in the HEAD element. However modern browsers can handle this markup properly.

The advantage of this approach is it is simpler and does not buffer the response. Also, the portlet developer does not need to remember to add an empty HEAD element. There are several drawbacks with this approach:

Using a Static WLP Render Dependencies File (Most correct, but tedious)

WLP provides a general facility for solving the resource inclusion problem that works across all portlet types and the various WLP asynchronous modes. It generates valid HTML, and eliminates duplicate resource references. It is a best practice to use this facility. The Render Dependencies facility is covered in Using Custom JavaScript in JSF Portlets.

As an example, this is a .dependencies file for a JSF portlet that contains a Tomahawk Tree2 component. Notice how it explicitly references the Tomahawk scripts that it needs.

Listing B-16 Using the WLP Render Dependencies Mechanism to Include Tomahawk Resources
<?xml version="1.0" encoding="UTF-8"?>
<window
	xmlns="http://www.bea.com/servers/portal/framework/laf/1.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.bea.com/servers/portal/framework/laf/1.0.0 lafwindow-1_0_0.xsd">
	<render-dependencies>
		<html>
			<scripts>
				<script type="text/javascript" src="/wlpJSF/faces/myFacesExtensionResource/org.apache.myfaces.renderkit.html.util.MyFacesResourceLoader/12306184/tree2.HtmlTreeRenderer/javascript/tree.js" />
				<script type="text/javascript" src="/wlpJSF/faces/myFacesExtensionResource/org.apache.myfaces.renderkit.html.util.MyFacesResourceLoader/12306184/tree2.HtmlTreeRenderer/javascript/cookielib.js" />
			</scripts>
		</html>
	</render-dependencies>
</window>

However, there are several issues with this approach.

First, the developer must assemble the list of scripts that are needed by all of the Tomahawk components within all views of the portlet. This is a manual process, and must be kept up to date as the portlet implementation changes. One way to assemble the list is to use one of the other approaches covered above during development, and look into the HTML document to determine what scripts were referenced.

Second, when using WLP Render Dependencies mechanism to write the resource references, it is important to make sure that a Tomahawk AddResource implementation doesn't also write resource references into the response. But Tomahawk does not provide an AddResource implementation that never writes resources to the response. One way to achieve this with the standard Tomahawk distribution is to use the DefaultAddResource implementation and ensure that all portlets do not include a HEAD element. Another option would be to implement a custom AddResource implementation that does not write resource references.

Using Dynamic WLP Render Dependencies (Not possible, for reference only)

This section documents an approach that will not work. This is provided to illuminate boundaries as to what is possible.

The previous section described how a static Render Dependencies file could be used to include the resource references. There are drawbacks with that approach in that the list of references must be known at development time, and must be maintained as the portlet implementation changes. Another approach would be to attempt to write a new AddResource implementation that would invoke the WLP Render Dependency mechanism to dynamically add the references. This would be ideal, as it would eliminate the drawbacks discussed with the previous approach.

The documentation for the dynamic render dependencies facility is available here:

Unfortunately, the Tomahawk AddResource mechanism operates during the JSF Render lifecycle. As seen in the Understanding WLP and JSF Rendering Life Cycles section, the JSF Render lifecycle is invoked within the WLP Render lifecycle. This is a problem because the dynamic Render Dependencies must be established before the WLP Render lifecycle. Therefore, this approach will not work.

Here is an example of how an AddResource method might be implemented, but unfortunately does not work because of the timing of the lifecycles.

Listing B-17 shows a non-working example of how WLP can be dynamically notified of render dependencies using the Tomahawk AddResource mechanism.

Listing B-17 Using the Tomahawk AddResource Mechanism to Dynamically Notify WLP of Render Dependencies
@Override
public void addJavaScriptHerePlain(FacesContext context, String uri)
throws IOException {
HttpServletRequest request =
(HttpServletRequest)context.getExternalContext().getRequest();
PortalLookAndFeel laf = PortalLookAndFeel.getInstance(request);
DynamicHtmlRenderDependencies injector =
laf.getDynamicHtmlRenderDependencies();
injector.addScript("text/javascript", null, null, uri, null);
}

forceId Attribute

Tomahawk provides an attribute named forceId for many Tomahawk components. This attribute indicates that the component's client id should be exactly what is specified in the component's id attribute. This feature short circuits any namespacing provided by the enclosing JSF naming containers (f:view, f:subview, WLP NamingContainer). This feature helps in cases in which JavaScript cannot cope with the scoped client ids.

However, as noted in the Namespacing section and in JSF texts, namespacing client ids is sometimes critical. In the case of WebLogic Portal, when the same portlet is placed on a portal page multiple times, the client id scoping is needed to prevent certain collisions. For better or for worse, the Tomahawk forceId attribute does exactly what it promises, even in a WLP environment. It eliminates any scoping added for components in portlet instances. Be careful using this attribute, especially when the same portlet may be added to a page more than once.

Listing B-18 shows the use of forceId. When this JSP is used as a portlet, the HTML id for the <div> that is written for the panelGroup component is exactly "swapImagePanel". It is not namespaced with the portlet instance label.

Listing B-18 An Example Showing the Use of the Tomahawk forceId Attribute
<%@ page language="java" contentType="text/html;charset=UTF-8"%>
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
<%@ taglib uri="http://myfaces.apache.org/tomahawk" prefix="t" %>
<%@ taglib uri='http://bea.com/faces/adapter/tags-naming' prefix='jsf-naming' %>
<f:view>
	<jsf-naming:namingContainer id="swapImage">
	<t:panelGroup id="swapImagePanel" forceId="true">
		<t:swapImage id="image" value="/tomahawk/images/MyFaces_logo.jpg"
			swapImageUrl="/tomahawk/images/MyFaces_logo_inverse.jpg"/>
	</t:panelGroup>
	/jsf-naming:namingContainer>
</f:view>

File Upload

Tomahawk offers a File Upload component that enables a developer to easily create a web page that allows a user to upload a document. Unfortunately, this component does not function properly within a WLP portal. The only workaround currently is to implement File Upload in a non-JSF portlet.

 


Integrating Apache Beehive Pageflow Controller

This section includes the following topics:

Apache Beehive Page Flow

The Apache Beehive project supports a full featured controller technology called Page Flows. Page Flows are annotated Java class files that provide navigation control, state management, form validation and more. Page Flows are built on top of the Struts controller.

The use of the Page Flow controller is only recommended for customers that have existing assets written in Apache Beehive.

JSF and Page Flows

Beehive Page Flows use regular JSPs as the view technology. However, the Beehive project built an integration with JSF such that JSF JSPs can be used. This integration combines the full JSF rendering framework (with lifecycles and managed beans) with the power of the Beehive Page Flow controller. This integration was built independently of WebLogic Portal and it works equally well both inside and outside of WLP JSF portlets.

The integration provides the following features:

Actions

Backing Beans

JSF Expression Language Binding Contexts

The integration works by plugging a custom ViewHandler and NavigationHandler into the JSF implementation. There are several places to go for more information about the JSF-Beehive integration:

http://www.oracle.com/technology/pub/articles/dev2arch/2005/12/integrating-jsfbeehive.html

http://beehive.apache.org/docs/1.0/netui/sample_jpf_jsf_integration.html

http://edocs.bea.com/wlw/docs103/guide/webapplications/jsf/jsf-integrationtutorial/tutJSFIntro.html

Configuring the JSF Integration with Page Flows

The JSF integration is built directly into the Apache Beehive library. Since all Portal Web Projects currently require the Apache Beehive facet, this integration is provisioned with every WLP web application. There aren't any configuration changes necessary for the integration to work within a WLP portlet.

PageFlowApplicationFactory

After creating a Portal Web Project with the JSF facet enabled, the default faces-config.xml will contain this configuration artifact:

Listing B-19 The Beehive PageFlowApplicationFactory Configuration in faces-config.xml
<application-factory>
	org.apache.beehive.netui.pageflow.faces.PageFlowApplicationFactory
</application-factory>

This configuration is not coming from a WebLogic Portal facet – it is added by the Apache Beehive Netui facet. It enables the Apache Beehive Page Flow integration with JSF. If the integration is not needed for the web project, it can be removed.

Note that the Page Flow integration is currently only certified with JSF 1.1.

 


Building Unsupported JSF Implementations

It is not officially supported by Oracle to use a JSF implementation outside of the set provided in the box. However, there are times when it is helpful to experiment with different versions of an implementation when troubleshooting an issue. The exact steps may vary depending on what implementation and version is in use.

  1. Extract the closest existing JSF library module from BEA_HOME/WL_HOME/common/deployable-libraries into an empty folder.
  2. Update META-INF/manifest.mf to the new implementation version.
  3. Copy in the new implementation JARs into the lib folder.
  4. Create a new WAR file with the updated contents, updating the filename with the new version number.
  5. Copy the WAR file into BEA_HOME/WL_HOME/common/deployable-libraries.
  6. In Workshop, navigate to Project > Properties > Java Build Path > Libraries > Add> Manage WebLogic Shared Libraries > Add... and locate the new library module.
  7. Navigate to weblogic.xml in your web application, and set the JSF libraryImplementation Version to exactly the version you just added.
  8. Republish the application.
  9. Some JSPs may throw an exception when rendered after the above changes. In these cases, go to Workshop and drag and drop any JSF tag to the page and then remove it. This will clear out the old compiled view.

  Back to Top       Previous  Next