/******************************************************************************
* JBoss, a division of Red Hat *
* Copyright 2006, Red Hat Middleware, LLC, and individual *
* contributors as indicated by the @authors tag. See the *
* copyright.txt in the distribution for a full listing of *
* individual contributors. *
* *
* This is free software; you can redistribute it and/or modify it *
* under the terms of the GNU Lesser General Public License as *
* published by the Free Software Foundation; either version 2.1 of *
* the License, or (at your option) any later version. *
* *
* This software is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this software; if not, write to the Free *
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA *
* 02110-1301 USA, or see the FSF site: http://www.fsf.org. *
******************************************************************************/
package javax.portlet.faces;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.GenericPortlet;
import javax.portlet.PortletConfig;
import javax.portlet.PortletContext;
import javax.portlet.PortletException;
import javax.portlet.PortletMode;
import javax.portlet.PortletRequest;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import javax.portlet.WindowState;
/**
* JSR 301 generic faces pottlet implementation.
*
* @author asmirnov
*
*/
public class GenericFacesPortlet extends GenericPortlet {
private static final int EXTENDED_ATTR_PREFIX_LENGTH = Bridge.EXTENDED_PORTLET_ATTR_PREFIX
.length();
private static final String BRIDGE_SERVICE_CLASSPATH = "META-INF/services/javax.portlet.faces.Bridge";
public static final String BRIDGE_CLASS = "javax.portlet.faces.BridgeImplClass";
public static final String DEFAULT_CONTENT_TYPE = Bridge.BRIDGE_PACKAGE_PREFIX
+ "defaultContentType";
public static final String DEFAULT_CHARACTERSET_ENCODING = Bridge.BRIDGE_PACKAGE_PREFIX
+ "defaultCharacterSetEncoding";
private volatile String bridgeClassName = null;
private volatile Class<? extends Bridge> facesBridgeClass;
private volatile Bridge facesPortletBridge = null;
private Map<String, String> viewIdMap;
// Following are names of request attributes a portletbridge must set before
// calling the Bridge to process a request
private static final String DEFAULT_VIEWID = Bridge.BRIDGE_PACKAGE_PREFIX
+ "defaultViewId";
private static final int DEFAULT_VIEW_ID_LENGTH = DEFAULT_VIEWID.length() + 1;
public void init(PortletConfig config) throws PortletException {
super.init(config);
PortletContext portletContext = this.getPortletContext();
String bridgeClassName = getBridgeClassName();
try {
facesBridgeClass = getClassLoader().loadClass(bridgeClassName)
.asSubclass(Bridge.class);
} catch (ClassNotFoundException e) {
throw new PortletException(
"Faces portlet Bridge implementation class not found", e);
}
String portletName = getPortletName();
Boolean isPreserveActionParams = getPreserveActionParameters();
portletContext.setAttribute(Bridge.BRIDGE_PACKAGE_PREFIX + portletName
+ "." + Bridge.PRESERVE_ACTION_PARAMS, isPreserveActionParams);
List<String> attrsList = getExcludedRequestAttributes();
if (null != attrsList) {
portletContext.setAttribute(Bridge.BRIDGE_PACKAGE_PREFIX
+ portletName + "." + Bridge.EXCLUDED_REQUEST_ATTRIBUTES,
attrsList);
}
// Get extension config attributes.
Enumeration<?> configNames = config.getInitParameterNames();
while (configNames.hasMoreElements()) {
String name = (String) configNames.nextElement();
if (name.startsWith(Bridge.EXTENDED_PORTLET_ATTR_PREFIX)) {
int i = name.lastIndexOf('.');
if (i > EXTENDED_ATTR_PREFIX_LENGTH + 2) {
String attribute = name.substring(i);
String preffix = name.substring(
EXTENDED_ATTR_PREFIX_LENGTH, i + 1);
String extensionAttributeName = Bridge.EXTENDED_PORTLET_ATTR_PREFIX
+ preffix + portletName + attribute;
portletContext.setAttribute(extensionAttributeName, config
.getInitParameter(name));
}
}
}
Map<String, String> viewIdMap = getDefaultViewIdMap();
getPortletContext().setAttribute(
Bridge.BRIDGE_PACKAGE_PREFIX + portletName + "."
+ Bridge.DEFAULT_VIEWID_MAP, viewIdMap);
}
private String calculateBridgeClassName(PortletContext portletContext)
throws PortletException {
String bridgeClassName = portletContext.getInitParameter(BRIDGE_CLASS);
if (bridgeClassName == null) {
ClassLoader loader = getClassLoader();
InputStream stream = loader
.getResourceAsStream(BRIDGE_SERVICE_CLASSPATH);
if (null != stream) {
try {
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(
stream, "UTF-8"));
} catch (UnsupportedEncodingException e) {
reader = new BufferedReader(new InputStreamReader(
stream));
}
bridgeClassName = reader.readLine();
if (null != bridgeClassName) {
bridgeClassName = bridgeClassName.trim();
}
} catch (IOException e) {
// Ignore
} catch (SecurityException e) {
// Ignore
} finally {
try {
stream.close();
} catch (IOException e) {
throw new PortletException(
"Error to close input stream for a resource "
+ BRIDGE_SERVICE_CLASSPATH);
}
}
}
}
if (null == bridgeClassName) {
throw new PortletException(
"Can't detect bridge implementation class name");
}
return bridgeClassName;
}
/**
* 4.2.7 getExcludedRequestAttributes() As a portlet lifecycle allows
* multiple (re)renders to occur following an action, the bridge manages an
* extended notion of a request scope to ensure that such rerenders produces
* identical results. Specifically, portlet scoped request attributes are
* saved/restored by the bridge across such rerenders [5.1.2]. However,
* sometimes a portlet request scoped attribute truly must be removed when
* the request scope ends. The bridge uses multiple mechanisms for
* determining which attributes are marked for exclusion from its managed
* scope. The portlet can directly instruct the bridge to exclude attributes
* on a per portlet basis by setting a PorletContext attribute [3.2]. This
* attribute's value is a List containing the excluded attribute names.
*
* The GenericFacesPortlet sets this attribute based on the result of
* calling getExcludedRequestAttributes() in its init() method. The default
* (GenericFacesPortlet) implementation for getExcludedRequestAttributes()
* returns a List constructed by parsing the comma delimited String value
* from the corresponding portlet initialization parameter,
* javax.portlet.faces.excludedRequestAttributes. If this initialization
* parameter isn't present null is returned which causes the
* GenericFacesPortlet to not set the corresponding PortletContext
* attribute.
*
* @return
*/
public List<String> getExcludedRequestAttributes() {
List<String> attrsList = null;
String excludedAttrs = getPortletConfig().getInitParameter(
Bridge.BRIDGE_PACKAGE_PREFIX
+ Bridge.EXCLUDED_REQUEST_ATTRIBUTES);
if (null != excludedAttrs) {
String[] atrs = excludedAttrs.split(",");
attrsList = new ArrayList<String>(atrs.length);
for (String string : atrs) {
attrsList.add(string);
}
}
return attrsList;
}
/**
* 4.2.9 getResponseContentType() In Portlet 1.0 a portlet must set the
* response content type prior to dispatching to a servlet or jsp as setting
* a content type in a dispatch (include) is ignored. When the Faces view is
* represented in a jsp, the bridge uses dispatch to render the view. In
* this situation any content type set by the jsp/render is ignored. To
* ensure a content type is set, the bridge sets the content type to the one
* which the portlet container indicates is preferred for this request if
* one hasn't been set by the portlet. To provide more flexibility the
* GenericFacesPortlet always sets the response content type by calling
* getResponseContentType() on itself. If not overridden, the
* GenericFacesPortlet returns the value of the portlet initialization
* parameter javax.portlet.faces.defaultContentType, or if this parameter
* doesn't exists, the portlet container's indication of the preferred
* content type for this request.
*
* @param request
* @return
*/
public String getResponseContentType(PortletRequest request) {
String contentType = getPortletConfig().getPortletContext()
.getInitParameter(DEFAULT_CONTENT_TYPE);
if (contentType == null) {
contentType = request.getResponseContentType();
}
return contentType;
}
/**
* 4.2.10 getResponseCharacterSetEncoding() Though a portlet is prevented
* from setting the character set encoding of the actual response as this is
* under the control of the portlet container, its common for portlet
* containers to interpret such settings as an indication of the encoding
* that is being written and hence should be translated by the container to
* its response encoding. In such circumstances character set encoding
* information is passed as extra information in the string used to set the
* response content type. As part of the content type it too must be
* expressed prior to any dispatching. In the situation that the bridge sets
* a response content type because none has been set yet, it doesn't set a
* corresponding character set encoding. I.e. it that the Faces view renders
* in the encoding chosen by the container. To provide more flexibility the
* GenericFacesPortlet extends its behavior which sets a response content
* type. As part of the process of determining the content type to set, the
* GenericFacesPortlet calls getResponseCharacterSetEncoding on itself. If a
* non-null value is returned, the value is appended (; delimited) to the
* content type prior to setting the response content type such that its
* form is the same as one returned in the http response CONTENT-TYPE
* header. The GenericFacesPortlet implements
* getResponseCharacterSetEncoding by returning the value of the portlet
* initialization parameter javax.portlet.faces.defaultCharacterSetEncoding
* or null if this parameter doesn't exist.
*
* @param request
* @return
*/
public String getResponseCharacterSetEncoding(PortletRequest request) {
return getPortletConfig().getPortletContext().getInitParameter(
DEFAULT_CHARACTERSET_ENCODING);
}
/**
* 4.2.8 getPreserveActionParameters() By default the bridge doesn't
* preserve action parameters into subsequent renders. This can be
* overridden on a per portlet basis by passing a value of true in the
* appropriate PortletContext attribute [3.2]. To determine the setting for
* this attributes for this particular portlet, the GenericFacesPortlet
* calls getPreserveActionParameters() in its init() method. The default
* (GenericFacesPortlet) implementation returns the Boolean value
* corresponding to the String value represented in the portlet
* initialization parameter, javax.portlet.faces.preserveActionParams. If
* this initialization parameter doesn't exist, Boolean.FALSE is returned.
*
* @return preserve or not action attributes.
*/
public Boolean getPreserveActionParameters() {
String preserveActionParams = getPortletConfig().getInitParameter(
Bridge.BRIDGE_PACKAGE_PREFIX + Bridge.PRESERVE_ACTION_PARAMS);
Boolean isPreserveActionParams = Boolean.valueOf(preserveActionParams);
return isPreserveActionParams;
}
protected void doDispatch(RenderRequest request, RenderResponse response)
throws PortletException, IOException {
PortletMode mode = request.getPortletMode();
if (mode == PortletMode.VIEW || mode == PortletMode.EDIT
|| mode == PortletMode.HELP) {
super.doDispatch(request, response);
} else {
doFacesDispatch(request, response);
}
}
protected void doEdit(RenderRequest request, RenderResponse response)
throws PortletException, IOException {
doFacesDispatch(request, response);
}
protected void doHelp(RenderRequest request, RenderResponse response)
throws PortletException, IOException {
doFacesDispatch(request, response);
}
protected void doView(RenderRequest request, RenderResponse response)
throws PortletException, IOException {
doFacesDispatch(request, response);
}
public void processAction(ActionRequest request, ActionResponse response)
throws PortletException, IOException {
// String defaultViewId = getDefaultViewIdMap().get(request
// .getPortletMode());
Bridge bridge = getFacesPortletBridge();
// request.setAttribute(GenericFacesPortlet.DEFAULT_VIEWID,
// defaultViewId);
try {
bridge.doFacesRequest(request, response);
} catch (BridgeException e) {
throw new PortletException("Error process faces request", e);
}
}
void doFacesDispatch(RenderRequest request, RenderResponse response)
throws PortletException, IOException {
String defaultViewId = getDefaultViewIdMap().get(
request.getPortletMode().toString());
if (null != defaultViewId
&& !request.getWindowState().equals(WindowState.MINIMIZED)) {
Bridge bridge = getFacesPortletBridge();
// request.setAttribute(GenericFacesPortlet.DEFAULT_VIEWID,
// defaultViewId);
String responseContentType = getResponseContentType(request);
if (null != responseContentType) {
StringBuilder contentType = new StringBuilder(
responseContentType);
String characterSetEncoding = getResponseCharacterSetEncoding(request);
if (null != characterSetEncoding) {
contentType.append(';').append(characterSetEncoding);
}
response.setContentType(contentType.toString());
}
try {
bridge.doFacesRequest(request, response);
} catch (BridgeException e) {
throw new PortletException("Error process faces request", e);
}
}
}
/**
* JSR 301 API method
*
* @return
*/
public String getBridgeClassName() throws PortletException {
if (null == bridgeClassName) {
bridgeClassName = calculateBridgeClassName(getPortletContext());
}
return bridgeClassName;
}
private ClassLoader getClassLoader() {
ClassLoader classLoader = Thread.currentThread()
.getContextClassLoader();
if (null == classLoader) {
classLoader = this.getClass().getClassLoader();
}
return classLoader;
}
public Map<String, String> getDefaultViewIdMap() {
if (null == viewIdMap) {
viewIdMap = calculateDefaultViewIdMap();
}
return viewIdMap;
}
private Map<String, String> calculateDefaultViewIdMap() {
Map<String, String> viewIdMap = new HashMap<String, String>();
PortletConfig portletConfig = getPortletConfig();
Enumeration<?> configNames = portletConfig.getInitParameterNames();
while (configNames.hasMoreElements()) {
String name = (String) configNames.nextElement();
if (name.startsWith(DEFAULT_VIEWID)) {
// Put viewId with mode name as key.
viewIdMap.put(name.substring(DEFAULT_VIEW_ID_LENGTH),
portletConfig.getInitParameter(name));
}
}
return viewIdMap;
}
public void destroy() {
bridgeClassName = null;
// If bridge was initialized, destroy it.
if (null != facesPortletBridge) {
facesPortletBridge.destroy();
facesPortletBridge = null;
}
super.destroy();
}
/**
* @return the facesPortletBridge
* @throws PortletException
*/
@SuppressWarnings("unchecked")
Bridge getFacesPortletBridge() throws PortletException {
if (null == facesPortletBridge) {
synchronized (this) {
if (null == facesPortletBridge) {
try {
// Do not assign uninitialized instance to field
Bridge bridge = (Bridge) facesBridgeClass
.newInstance();
bridge.init(getPortletConfig());
this.facesPortletBridge = bridge;
} catch (InstantiationException e) {
throw new PortletException(
"Error on create instance of a JSF Portlet Bridge",
e);
} catch (IllegalAccessException e) {
throw new PortletException(
"IllegalAccess on create instance of a JSF Portlet Bridge",
e);
} catch (BridgeException e) {
throw new PortletException(
"Bridge initialization error", e);
}
}
}
}
return facesPortletBridge;
}
}