/******************************************************************************
* 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 org.jboss.portletbridge;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIViewRoot;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.render.ResponseStateManager;
import javax.portlet.faces.Bridge;
import javax.portlet.faces.annotation.ExcludeFromManagedRequestScope;
import org.jboss.portletbridge.context.AbstractExternalContext;
import org.jboss.portletbridge.context.PortletBridgeContext;
/**
* That class keep all request attributes that requires to store between portlet requests.
* These parameters described by the chapter 5.1.2 "Managing Lifecycle State"
* TODO - synchronize access to state for concurrent AJAX requests.
* @author asmirnov
*
*/
public class BridgeRequestScope implements Serializable {
public static final String AJAX_PARAMETER_NAME = "AJAXREQUEST";
/**
*
*/
private static final long serialVersionUID = 5630637804542426710L;
/**
* Array of classes that are not stored in the request scope. See PP5.1.2
*/
private static final Class<?>[] excludedClasses = {
javax.portlet.PortletConfig.class,
javax.portlet.PortletContext.class,
javax.portlet.PortletRequest.class,
javax.portlet.PortletResponse.class,
javax.portlet.PortletSession.class,
javax.portlet.PortletPreferences.class,
javax.portlet.PortalContext.class,
javax.faces.context.FacesContext.class,
javax.faces.context.ExternalContext.class,
javax.servlet.ServletConfig.class,
javax.servlet.ServletContext.class,
javax.servlet.ServletRequest.class,
javax.servlet.ServletResponse.class,
javax.servlet.http.HttpSession.class };
/**
* Names of request attributes that are not stored in the request scope.
*/
private static final String[] excludedRequestAttributes = { "ajaxContext",
"javax.servlet.", "javax.portlet.", "javax.faces.",
ResponseStateManager.VIEW_STATE_PARAM,
AbstractExternalContext.INITIAL_REQUEST_ATTRIBUTES_NAMES };
/**
* Saved {@link FacesMessage}
*/
private Map<String, List<FacesMessage>> messages = new HashMap<String, List<FacesMessage>>();
/**
* Request scope beans. That map saved by the {@code writeObject} method to
* avoid serialization exceptions.
*/
private transient Map<String, Object> beans = new HashMap<String, Object>();
/**
* JSF View Tree from the last ActionRerquest
*/
private transient UIViewRoot viewRoot;
/**
* Latest viewId
*/
private String viewId;
/**
* Request paremeters saved for consequentual render requests.
*/
private Map<String, String[]> requestParameters = new HashMap<String, String[]>(4);
/**
* Namespace from the last render request or default portlet namespace.
*/
private String namespace;
/**
* Captured value of the {@link ResponseStateManager#VIEW_STATE_PARAM} to
* proper restore view state during consequentual render requests, as
* required bu PP 5.1.2
*/
private String viewStateParameter;
private String conversationIdParameter = "conversationId";
private String conversationId;
public BridgeRequestScope() {
}
/**
* @return the viewId
*/
public String getViewId() {
return viewId;
}
/**
* @param viewId
* the viewId to set
*/
public void setViewId(String viewId) {
this.viewId = viewId;
}
/**
* @return the _messages
*/
public Map<String, List<FacesMessage>> getMessages() {
return messages;
}
/**
* @param _messages
* the _messages to set
*/
public void setMessages(Map<String, List<FacesMessage>> _messages) {
this.messages = _messages;
}
/**
* @return the beans
*/
public Map<String, Object> getBeans() {
return beans;
}
/**
* @param beans the beans to set
*/
public void setBeans(Map<String, Object> beans) {
this.beans = beans;
}
/**
* @return the _viewRoot
*/
public UIViewRoot getViewRoot() {
return viewRoot;
}
/**
* @param root
* the _viewRoot to set
*/
public void setViewRoot(UIViewRoot root) {
viewRoot = root;
}
/**
* @return the viewStateParameter
*/
public String getViewStateParameter() {
return viewStateParameter;
}
/**
* @param viewStateParameter
* the viewStateParameter to set
*/
public void setViewStateParameter(String viewStateParameter) {
this.viewStateParameter = viewStateParameter;
}
/**
* @return the namespace
*/
public String getNamespace() {
return namespace;
}
/**
* @param namespace
* the namespace to set
*/
public void setNamespace(String namespace) {
this.namespace = namespace;
}
/**
* @return the conversationIdParameter
*/
public String getConversationIdParameter() {
return conversationIdParameter;
}
/**
* @param conversationIdParameter
* the conversationIdParameter to set
*/
public void setConversationIdParameter(String conversationIdParameter) {
this.conversationIdParameter = conversationIdParameter;
}
/**
* @return the conversationId
*/
public String getConversationId() {
return conversationId;
}
/**
* @param conversationId
* the conversationId to set
*/
public void setConversationId(String conversationId) {
this.conversationId = conversationId;
}
public Map<String, String[]> getRequestParameters() {
return requestParameters;
}
public void setRequestParameters(Map<String, String[]> requestParameters) {
this.requestParameters = requestParameters;
}
/**
* @param facesContext
* @param render
*/
public void restoreRequest(FacesContext facesContext, boolean render) {
if (render) {
facesContext.setViewRoot(getViewRoot());
setViewRoot(null);
}
restoreMessages(facesContext);
restoreBeans(facesContext);
}
/**
* @param facesContext
* @param withViewRoot
*/
public void saveRequest(FacesContext facesContext, boolean withViewRoot) {
if (withViewRoot) {
UIViewRoot root = facesContext.getViewRoot();
setViewRoot(root);
if (null != root) {
setViewId(root.getViewId());
}
}
saveBeans(facesContext);
saveMessages(facesContext);
saveRequestParameters(facesContext);
}
/**
* @param facesContext
*/
public void saveMessages(FacesContext facesContext) {
Iterator<String> idsWithMessages = facesContext
.getClientIdsWithMessages();
while (idsWithMessages.hasNext()) {
String id = idsWithMessages.next();
Iterator<FacesMessage> messages = facesContext.getMessages(id);
while (messages.hasNext()) {
FacesMessage message = messages.next();
addMessage(id, message);
}
}
}
/**
* @param facesContext
*/
public void restoreMessages(FacesContext facesContext) {
Iterator<String> idsWithMessages = getClientIdsWithMessages();
while (idsWithMessages.hasNext()) {
String id = idsWithMessages.next();
Iterator<FacesMessage> messages = getMessages(id);
while (messages.hasNext()) {
FacesMessage message = messages.next();
facesContext.addMessage(id, message);
}
}
}
/**
* Save request-scope beans, as described in the JSR-301 5.1
*
* @param facesContext
*/
public void saveBeans(FacesContext facesContext) {
ExternalContext externalContext = facesContext.getExternalContext();
PortletBridgeContext bridgeContext = PortletBridgeContext
.getCurrentInstance(facesContext);
BridgeConfig bridgeConfig = bridgeContext.getBridgeConfig();
Set<String> existingAttributes = null;
if (null != bridgeContext) {
existingAttributes = bridgeContext
.getInitialRequestAttributeNames();
}
if (null == existingAttributes) {
existingAttributes = new HashSet<String>();
} else {
// Create local copy for merge with initial parameters.
existingAttributes = new HashSet<String>(existingAttributes);
}
// Get list of request attributes preventing to save.
// in the JSR 301 PLT 5.1
Set<ExcludedRequestAttribute> excluded = bridgeConfig
.getExcludedAttributes();
Map<String, Object> requestMap = externalContext.getRequestMap();
for (Iterator<Entry<String, Object>> iterator = requestMap.entrySet()
.iterator(); iterator.hasNext();) {
Entry<String, Object> entry = iterator.next();
String attributeName = entry.getKey();
if (!existingAttributes.contains(attributeName)
&& includeAttributeName(attributeName, excluded)) {
Object bean = entry.getValue();
if (null != bean && includeAttributeValue(bean)) {
beans.put(attributeName, bean);
}
}
}
}
private boolean includeAttributeValue(Object bean) {
boolean include = true;
if (bean.getClass().isAnnotationPresent(
ExcludeFromManagedRequestScope.class)) {
include = false;
}
for (int i = 0; (i < excludedClasses.length) && include; i++) {
if (excludedClasses[i].isInstance(bean)) {
include = false;
}
}
return include;
}
private boolean includeAttributeName(String attributeName,Set<ExcludedRequestAttribute> excluded) {
boolean include=true;
// Force error messages to save.
if (!attributeName.startsWith("javax.servlet.error.")) {
for (int i = 0; (i < excludedRequestAttributes.length) && include; i++) {
if (attributeName.startsWith(excludedRequestAttributes[i])) {
include = false;
}
}
} if (include && null != excluded) {
for (ExcludedRequestAttribute excludedRequestAttribute : excluded) {
if (excludedRequestAttribute.match(attributeName)) {
include = false;
break;
}
}
}
return include;
}
public void saveRequestParameters(FacesContext facesContext) {
// Save request parameters ( all or a View state only ) restored as
// requered
// in the JSR 301 PLT 5.1
ExternalContext externalContext = facesContext.getExternalContext();
PortletBridgeContext bridgeContext = PortletBridgeContext
.getCurrentInstance(facesContext);
BridgeConfig bridgeConfig = bridgeContext.getBridgeConfig();
boolean preserveActionParam = bridgeConfig.isPreserveActionParams();
if (preserveActionParam) {
requestParameters.putAll(externalContext
.getRequestParameterValuesMap());
requestParameters.remove(AJAX_PARAMETER_NAME);
requestParameters.remove(ResponseStateManager.VIEW_STATE_PARAM);
}
}
public void restoreBeans(FacesContext facesContext) {
Map<String, Object> requestMap = facesContext.getExternalContext()
.getRequestMap();
requestMap.putAll(beans);
Map<String, String[]> requestParameters = getRequestParameters();
if (requestParameters
.containsKey(ResponseStateManager.VIEW_STATE_PARAM)) {
requestMap.put(Bridge.IS_POSTBACK_ATTRIBUTE, Boolean.TRUE);
}
if (null != conversationId) {
requestParameters.put(conversationIdParameter,
new String[] { conversationId });
}
}
public void reset() {
this.requestParameters = new HashMap<String, String[]>(4);
this.beans = new HashMap<String, Object>();
this.messages = new HashMap<String, List<FacesMessage>>();
this.viewRoot = null;
this.viewId = null;
this.conversationIdParameter = "conversationId";
this.conversationId = null;
}
private void addMessage(String clientId, FacesMessage message) {
List<FacesMessage> list = messages.get(clientId);
if (list == null) {
list = new ArrayList<FacesMessage>();
messages.put(clientId, list);
}
list.add(message);
}
private Iterator<String> getClientIdsWithMessages() {
return (messages.keySet().iterator());
}
private Iterator<FacesMessage> getMessages(String clientId) {
List<FacesMessage> list = messages.get(clientId);
if (list != null) {
return (list.iterator());
} else {
return Collections.<FacesMessage> emptyList().iterator();
}
}
private void writeObject(java.io.ObjectOutputStream out) throws IOException {
// Save all default fields
out.defaultWriteObject();
// Save serializable request-scope beans.
for (Map.Entry<String, Object> entry : beans.entrySet()) {
String key = entry.getKey();
Object bean = entry.getValue();
if (null != key && key.length() > 0 && null != bean
&& bean instanceof Serializable) {
out.writeUTF(key);
out.writeObject(bean);
}
} // End of request scope beans marker.
out.writeUTF("");
}
private void readObject(java.io.ObjectInputStream in) throws IOException,
ClassNotFoundException {
in.defaultReadObject();
String key = in.readUTF();
if (key.length() > 0) {
beans = new HashMap<String, Object>();
while (key.length() > 0) {
Object bean = in.readObject();
beans.put(key, bean);
key = in.readUTF();
}
}
}
}