本文讨论在基于WebSphere JSF 1.2 Portlet Bridge的JSF Portlet中实现文件下载。
JSR 286规范提供了一个基于resource serving的文件下载机制,但由于JSF 1.2 Portlet Bridge并没有提供相应的支持,所以必须在混合使用portlet和JSF来实现这个机制。
具体的实现包含三部分: 一个继承FacesPortlet并实现serveResource方法的portlet类, 一个显示下载链接的JSF页面, 以及一个提供文件内容的JSF managed bean。
portlet类
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;
}
}
JSF页面
<%@ 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>
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";
}
}
开发中的一些发现:
- IBM JSF 1.2 Portlet Bridge没有提供portlet tag的对应Facelet taglib xml, 因此不能在基于Facelet的JSF页面中使用<portlet:resourceURL>。 这个问题已经在IBM JSF 2 Portlet Bridge中得到解决。
- <portlet:resourceURL> tag提供一个var属性, 可以用来定义一个变量来保存resource URL供页面上得其他代码使用。 JSR 286规范将该变量定义于JSP page scope中。 由于JSF无法访问JSP page scope, 因此页面上的JSF组件也无法使用该变量。
- 在传统的Servlet编程中,我们使用response.setHeader方法来设置HTTP response的头信息,包括Content-Disposition和Content-length, 而在portlet中,我们使用ResourceResponse.setProperty方法。
-在portlet中可以通过两种方法来访问JSF managed beans, 如果要访问的JSF managed bean是一个session scope bean 且已存在于session只用, 那么可以使用portletSession.getAttribute(BEAN_NAME)来获取该bean, 否则的话, 就必须从FacesContext中使用JSF value expression表达式来创建和获取该bean。 由于IBM JSF 1.2 Portlet Bridge并没有提供一个直接的方法来获取FacesContext, 必须使用一些内部代码来实现该功能。