Monday, March 12, 2012

JSF Portlet File Download using WebSphere JSF 1.2 Portlet Bridge

This note summarise my experience of JSF portlet file download using WebSphere JSF 1.2 Portlet Bridge.

One approach to implement file download from portlets is to use the JSR 286 resource serving mechanism. In order to make it working in JSF portlets, one needs to mix portlet and JSF programming as there is no direct support of the mechanism from JSF 1.2 Portlet Bridge.

The implementation consists of three components: a portlet class that extends FacesPortlet and overrides the serveResource method; a JSF page that provides use the download link; and a JSF managed bean that provides the file content.

The portlet class

package demo;

import java.io.IOException;
import java.io.OutputStream;

import javax.el.ExpressionFactory;
import javax.el.ValueExpression;
import javax.faces.context.FacesContext;
import javax.faces.lifecycle.Lifecycle;
import javax.portlet.PortletException;
import javax.portlet.PortletSession;
import javax.portlet.ResourceRequest;
import javax.portlet.ResourceResponse;

import com.ibm.faces.portlet.FacesPortlet;
import com.ibm.faces.portlet.httpbridge.PortletRequestWrapper;
import com.ibm.faces.portlet.httpbridge.PortletResponseWrapper;
import com.ibm.faces.portlet.httpbridge.ResourceRequestWrapper;
import com.ibm.faces.portlet.httpbridge.ResourceResponseWrapper;

public class JSFPortlet extends FacesPortlet {

 @Override
 public void serveResource(ResourceRequest request, ResourceResponse response)
   throws PortletException, IOException {

  super.serveResource(request, response);

  TestBean testBean = (TestBean) getJSFManagedBean(
    request, response, TestBean.BEAN_NAME, TestBean.class);

  final OutputStream out = response.getPortletOutputStream();
  byte[] bytes = testBean.getCsv();
  response.setContentType("text/csv");
  response.setProperty("Content-Disposition",
    "attachment; filename=download.csv");
  response.setProperty("Content-length", String.valueOf(bytes.length));

  out.write(bytes);
  out.flush();
  out.close();
 }

 public Object getJSFManagedBean(ResourceRequest request,
   ResourceResponse response, String beanName, Class beanClass)
   throws PortletException {

  PortletSession portletSession = request.getPortletSession();

  Object jsfBean = (TestBean) portletSession.getAttribute(beanName);

  if (jsfBean == null) {
   PortletRequestWrapper requestWrapper = new ResourceRequestWrapper(
     request);
   requestWrapper.setPortletContext(getPortletContext());
   PortletResponseWrapper responseWrapper = new ResourceResponseWrapper(
     response);
   Lifecycle lifecycle = getLifecycle(requestWrapper);
   FacesContext context = getFacesContext(requestWrapper,
     responseWrapper, lifecycle);

   final ExpressionFactory expressionFactory = context
     .getApplication().getExpressionFactory();

   final ValueExpression valueExpression = expressionFactory
     .createValueExpression(context.getELContext(), 
       "#{" + beanName + "}", beanClass);
   jsfBean = (TestBean) valueExpression.getValue(context
     .getELContext());
  }

  return jsfBean;
 }
}

The JSF page

<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<%@ taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet"%>               
<%@taglib uri="http://www.ibm.com/xmlns/prod/websphere/portal/v6.1/portlet-client-model"   
       prefix="portlet-client-model" %>              
<%@ page language="java" contentType="text/html" pageEncoding="ISO-8859-1" session="false"%>
<portlet:defineObjects />
<portlet-client-model:init>
      <portlet-client-model:require module="ibm.portal.xml.*"/>
      <portlet-client-model:require module="ibm.portal.portlet.*"/>   
</portlet-client-model:init>  
 
       
<f:view>
  
  <portlet:resourceURL var="resourceUrl" >
  <portlet:param name="action" value="csv" />
  </portlet:resourceURL>

  <a href="<%=resourceUrl%>">Download CSV</a>
  
</f:view>

The JSF managed bean

package demo;
import java.util.Date;

public class TestBean {
 public static final String BEAN_NAME = "testBean";
 
 public byte[] getCsv() {
     return new Date().toString().getBytes();
   }
 
 public String getText() {
  return "Hello";
 }
}

Some notes are:

- The IBM JSF 1.2 Portlet Bridge doesn't provide a Facelet taglib xml for the <portlet:resourceURL> tag, so the tag cannot be used in Facelet based JSF pages. This issue has been addressed in IBM JSF 2 Portlet Bridge.

- The <portlet:resourceURL> tag provides a var attribute that can be used to export the resource URL. Based on the JSR 286 spec, the exported variable is created in JSP page scope. As JSF code has no access to JSP page scope, the exported variable can not be used in JSF code.

- In traditional Servlet programming, the response.setHeader method is used to set HTTP response heads like Content-Disposition and Content-length, but in portlet, the ResourceResponse.setProperty method is used.

- There are two ways to access JSF managed beans from the portlet code: if the managed bean is a session scope bean and it's already exists in the session, the portletSession.getAttribute(BEAN_NAME) call can be used; otherwise, JSF value expression can be used to get and/or create the bean from FacesContext, as the IBM JSF 1.2 Portlet Bridge doesn't provide any method that the portlet implementation class can directly access FacesContext, some internal code must be used to achieve this.

2 comments: