/******************************************************************************
* 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.application;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
import java.util.Map.Entry;
import javax.faces.FacesException;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.portlet.ActionRequest;
import javax.portlet.PortletContext;
import javax.portlet.PortletMode;
import javax.portlet.PortletRequest;
import javax.portlet.PortletSession;
import javax.portlet.PortletSessionUtil;
import javax.portlet.RenderRequest;
import javax.portlet.faces.Bridge;
import javax.portlet.faces.BridgeException;
import javax.servlet.http.HttpSessionActivationListener;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
import javax.servlet.http.HttpSessionEvent;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.portletbridge.StateId;
import org.jboss.portletbridge.context.PortalActionURL;
/**
* @author asmirnov
*
*/
public class PortletStateHolder {
public static final String STATE_HOLDER = PortletStateHolder.class
.getName();
private static final String DEFAULT = "default-";
private static final Log log = LogFactory.getLog(PortletStateHolder.class);
private PortletMode lastMode = PortletMode.VIEW;
public static final String WINDOW_ID_RETRIVER = "org.jboss.portletbridge.WINDOW_ID_RETRIVER";
public static final String STATE_ID_PARAMETER = "javax.faces.portletbridge.STATE_ID";
private static final int DEFAULT_MAX_MANAGED_SCOPES = 1000;
/**
* View states for a different portlet modes, Views and serial numbers.
*/
private final Map<StateId, PortletWindowState> states;
/**
* Private constructor - instance must be stored in the session, and created
* on nessesary by factory method.
*
* @param context
*/
private PortletStateHolder(int max) {
states = new LRUMap<StateId, PortletWindowState>(max);
}
public static PortletStateHolder init(PortletContext context) {
PortletStateHolder stateHolder;
synchronized (context) {
stateHolder = (PortletStateHolder) context
.getAttribute(STATE_HOLDER);
if (null == stateHolder) {
int maxScopes = DEFAULT_MAX_MANAGED_SCOPES;
String maxScopesParameter = context
.getInitParameter(Bridge.MAX_MANAGED_REQUEST_SCOPES);
if (null != maxScopesParameter) {
maxScopes = Integer.parseInt(maxScopesParameter);
}
stateHolder = new PortletStateHolder(
maxScopes);
context.setAttribute(STATE_HOLDER, stateHolder);
}
}
return stateHolder;
}
/**
* Get instance of portlet states, stored in the session. If no previsious
* instance in session, create default value.
*
* @param context
* @return
*/
public static PortletStateHolder getInstance(FacesContext context) {
ExternalContext externalContext = context.getExternalContext();
PortletStateHolder stateHolder = (PortletStateHolder) externalContext
.getApplicationMap().get(STATE_HOLDER);
if (null == stateHolder) {
throw new BridgeException(
"Jsf portletbridge bridge not initialized");
}
return stateHolder;
}
public void addWindowState(StateId stateId, PortletWindowState state) {
states.put(stateId, state);
}
/**
* @param stateId
* @return
*/
public PortletWindowState getWindowState(StateId stateId) {
PortletWindowState state = null;
if (null != stateId) {
state = (PortletWindowState) states.get(stateId);
}
return state;
}
public StateId getStateId(String portletName, ActionRequest actionRequest) {
UUID uuid = UUID.randomUUID();
PortletMode portletMode = actionRequest.getPortletMode();
StateId stateId = new StateId(getScopeId(portletName, actionRequest),portletMode ,uuid.toString());
return stateId;
}
public StateId getStateId(String portletName, RenderRequest renderRequest, String namespace) {
PortletMode portletMode = renderRequest.getPortletMode();
String scopeId = getScopeId(portletName, renderRequest);
StateId stateId = null ;
PortletSession session = renderRequest.getPortletSession(false);
String stateIdParameter = renderRequest.getParameter(STATE_ID_PARAMETER);
if (null == stateIdParameter) {
stateId = getStateIdFromViewHistory(portletMode,
session);
} else {
stateId = new StateId(stateIdParameter);
// Check portlet mode for a changes:
if (!portletMode.equals(stateId.getMode())) {
StateId historyStateId = getStateIdFromViewHistory(portletMode,
session);
if(null != historyStateId){
stateId = historyStateId;
} else {
stateId.setMode(portletMode);
}
}
}
if (null == stateId) {
stateId = new StateId(scopeId,portletMode,namespace);
}
return stateId;
}
private StateId getStateIdFromViewHistory(PortletMode portletModeName,
PortletSession session) {
StateId stateId = null;
if(null != session){
String modeViewId = (String) session.getAttribute(Bridge.VIEWID_HISTORY+"."+portletModeName);
if(null != modeViewId){
try {
PortalActionURL viewUrl = new PortalActionURL(modeViewId);
String stateIdParameter = viewUrl.getParameter(STATE_ID_PARAMETER);
if(null != stateIdParameter){
stateId = new StateId(stateIdParameter);
}
} catch (MalformedURLException e) {
// Ignore.
}
}
}
return stateId;
}
public synchronized String getScopeId(String portletName, PortletRequest request) {
PortletSession portletSession = request.getPortletSession(true);
// String scopeId = portletName.replace(':', '_') + portletSession.getId().replace(':', '_');
// Check portlet SessionId retriver attribute.
WindowIDRetriver idRetriver = (WindowIDRetriver) portletSession
.getAttribute(WINDOW_ID_RETRIVER);
if (null == idRetriver) {
synchronized (this) {
idRetriver = (WindowIDRetriver) portletSession
.getAttribute(WINDOW_ID_RETRIVER);
if (null == idRetriver) {
UUID uuid = UUID.randomUUID();
idRetriver = new WindowIDRetriver(uuid.toString());
portletSession.setAttribute(WINDOW_ID_RETRIVER, idRetriver);
}
}
}
return idRetriver.getScopeId();
}
private void removeSessionStates(String scopeId) {
synchronized (states) {
// Iterate over copy of the keys, so LinkedHashSet do not support
// concurrent modifications.
for (Iterator<StateId> keysIterator = states.keySet().iterator(); keysIterator
.hasNext();) {
StateId key = keysIterator.next();
if (key.getScopeId().equals(scopeId)) {
keysIterator.remove();
}
}
}
}
/**
* Based on the Julien Viet idea.
*
* @author asmirnov
*
*/
public static final class WindowIDRetriver implements HttpSessionBindingListener, HttpSessionActivationListener, Serializable {
/**
*
*/
private static final long serialVersionUID = 3821055037900834237L;
public static final String PORTLET_SCOPE_PREFIX = "javax.portlet.p.";
private final String scopeId;
private String windowID;
/**
* @param scopeId
*/
private WindowIDRetriver(String scopeId) {
this.scopeId = scopeId;
}
/*
* (non-Javadoc)
*
* @see
* javax.servlet.http.HttpSessionBindingListener#valueBound(javax.servlet
* .http.HttpSessionBindingEvent)
*/
public void valueBound(HttpSessionBindingEvent event) {
String name = event.getName();
if (null == name || 0 == name.length()) {
throw new FacesException(
"WindowIDRetriver bind to session without name");
}
if (PortletSession.PORTLET_SCOPE != PortletSessionUtil
.decodeScope(name)) {
throw new FacesException(
"WindowIDRetriver bind to APPLICATION_SCOPE. PORTLET_SCOPE is required");
}
windowID = name.substring(PORTLET_SCOPE_PREFIX.length(), name
.indexOf('?'));
if (log.isDebugEnabled()) {
log
.debug("WindowIDRetriver have been bind to session for a portletbridge window "
+ windowID);
}
}
/*
* (non-Javadoc)
*
* @see
* javax.servlet.http.HttpSessionBindingListener#valueUnbound(javax.
* servlet.http.HttpSessionBindingEvent)
*/
public void valueUnbound(HttpSessionBindingEvent event) {
PortletStateHolder stateHolder = (PortletStateHolder) event
.getSession().getServletContext()
.getAttribute(STATE_HOLDER);
if (null != stateHolder) {
stateHolder.removeSessionStates(scopeId);
}
}
/**
* @return the windowID
*/
public String getWindowID() {
return windowID;
}
/**
* @return the scopeId
*/
public String getScopeId() {
return scopeId;
}
public void sessionDidActivate(HttpSessionEvent se) {
// ??? restore window states for that session.
}
public void sessionWillPassivate(HttpSessionEvent se) {
// ??? save window states for that session.
}
}
/**
* Last Recent Used Map cache. See {@link LinkedHashMap} for details.
*
* @author asmirnov
*
*/
private static class LRUMap<K, V> extends LinkedHashMap<K, V> implements
Serializable {
/**
*
*/
private static final long serialVersionUID = -7232885382582796665L;
private int capacity;
/**
* @param capacity
* - maximal cache capacity.
*/
public LRUMap(int capacity) {
super(capacity, 1.0f, true);
this.capacity = capacity;
}
protected boolean removeEldestEntry(Entry<K, V> entry) {
// Remove last entry if size exceeded.
return size() > capacity;
}
/**
* Get most recent used element
*
* @return the most Recent value
*/
public Object getMostRecent() {
Iterator<V> iterator = values().iterator();
Object mostRecent = null;
while (iterator.hasNext()) {
mostRecent = iterator.next();
}
return mostRecent;
}
}
}