/********************************************************************************** * $URL: https://source.sakaiproject.org/svn/kernel/trunk/kernel-impl/src/main/java/org/sakaiproject/tool/impl/MySession.java $ * $Id: MySession.java 125958 2013-06-19 02:29:59Z azeckoski@unicon.net $ ********************************************************************************** * * Copyright (c) 2008 The Sakai Foundation. * * Licensed under the Educational Community License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.opensource.org/licenses/ECL-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * **********************************************************************************/ package org.sakaiproject.tool.impl; import java.io.Serializable; import java.util.Collection; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import javax.servlet.ServletContext; import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSessionBindingEvent; import javax.servlet.http.HttpSessionBindingListener; import javax.servlet.http.HttpSessionContext; import org.apache.commons.collections.iterators.IteratorChain; import org.apache.commons.lang.mutable.MutableLong; import org.sakaiproject.id.api.IdManager; import org.sakaiproject.thread_local.api.ThreadLocalManager; import org.sakaiproject.tool.api.ContextSession; import org.sakaiproject.tool.api.NonPortableSession; import org.sakaiproject.tool.api.Session; import org.sakaiproject.tool.api.SessionAttributeListener; import org.sakaiproject.tool.api.SessionBindingEvent; import org.sakaiproject.tool.api.SessionBindingListener; import org.sakaiproject.tool.api.SessionManager; import org.sakaiproject.tool.api.SessionStore; import org.sakaiproject.tool.api.ToolSession; import org.sakaiproject.util.IteratorEnumeration; /************************************************************************************************************************************************* * Entity: Session Also is an HttpSession ************************************************************************************************************************************************/ public class MySession implements Session, HttpSession, Serializable { /** * Value that identifies the version of this class that has been Serialized. * 1 = original definition * 2 = added expirationTimeSuggestion */ private static final long serialVersionUID = 2L; /** * SessionManager */ private transient SessionManager sessionManager; private transient SessionStore sessionStore; private transient ThreadLocalManager threadLocalManager; private transient IdManager idManager; private transient boolean TERRACOTTA_CLUSTER; private transient NonPortableSession m_nonPortalSession; private transient SessionAttributeListener sessionListener; /** Hold attributes in a Map. TODO: ConcurrentHashMap may be better for multiple writers */ protected Map m_attributes = new ConcurrentHashMap(); /** Hold toolSessions in a Map, by placement id. TODO: ConcurrentHashMap may be better for multiple writers */ protected Map m_toolSessions = new ConcurrentHashMap(); /** Hold context toolSessions in a Map, by context (webapp) id. TODO: ConcurrentHashMap may be better for multiple writers */ protected Map m_contextSessions = new ConcurrentHashMap(); /** The creation time of the session. */ protected long m_created = 0; /** The session id. */ protected String m_id = null; /** Time last accessed (via getSession()). */ protected long m_accessed = 0; /** * The possible time this Session may be inactive and available for expiration. * This value is an optimization for Terracotta clustered environments, to avoid * faulting object in, unless we have a best guess that it may be out of date. * We also choose not to use the m_accessed field directly, to avoid updating the * SHARED (on every box) data structure, except every inactive/2 period. */ protected final MutableLong expirationTimeSuggestion; /** Seconds of inactive time before being automatically invalidated - 0 turns off this feature. */ protected int m_inactiveInterval; /** The user id for this session. */ protected String m_userId = null; /** The user enterprise id for this session. */ protected String m_userEid = null; /** True while the session is valid. */ protected boolean m_valid = true; public MySession(SessionManager sessionManager, String id, ThreadLocalManager threadLocalManager, IdManager idManager, SessionStore sessionStore, SessionAttributeListener sessionListener, int inactiveInterval, NonPortableSession nonPortableSession, MutableLong expirationTimeSuggestion) { this.sessionManager = sessionManager; m_id = id; this.threadLocalManager = threadLocalManager; this.idManager = idManager; this.sessionStore = sessionStore; this.sessionListener = sessionListener; m_inactiveInterval = inactiveInterval; m_nonPortalSession = nonPortableSession; m_created = System.currentTimeMillis(); m_accessed = m_created; this.expirationTimeSuggestion = expirationTimeSuggestion; resetExpirationTimeSuggestion(); // set the TERRACOTTA_CLUSTER flag resolveTerracottaClusterProperty(); } protected void resolveTransientFields() { // These are spelled out instead of using imports, to be explicit org.sakaiproject.component.api.ComponentManager compMgr = org.sakaiproject.component.cover.ComponentManager.getInstance(); sessionManager = (SessionManager)compMgr.get(org.sakaiproject.tool.api.SessionManager.class); sessionStore = (SessionStore)compMgr.get(org.sakaiproject.tool.api.SessionStore.class); threadLocalManager = (ThreadLocalManager)compMgr.get(org.sakaiproject.thread_local.api.ThreadLocalManager.class); idManager = (IdManager)compMgr.get(org.sakaiproject.id.api.IdManager.class); // set the TERRACOTTA_CLUSTER flag resolveTerracottaClusterProperty(); m_nonPortalSession = new MyNonPortableSession(); sessionListener = (SessionAttributeListener)compMgr.get(org.sakaiproject.tool.api.SessionBindingListener.class); } protected void resolveTerracottaClusterProperty() { String clusterTerracotta = System.getProperty("sakai.cluster.terracotta"); TERRACOTTA_CLUSTER = "true".equals(clusterTerracotta); } /** * @inheritDoc */ public Object getAttribute(String name) { Object target = m_attributes.get(name); if ((target == null) && (m_nonPortalSession != null)) { target = m_nonPortalSession.getAttribute(name); } return target; } /** * @inheritDoc */ public Enumeration getAttributeNames() { Set<String> nonPortableAttributeNames = m_nonPortalSession.getAllAttributes().keySet(); IteratorChain ic = new IteratorChain(m_attributes.keySet().iterator(),nonPortableAttributeNames.iterator()); return new IteratorEnumeration(ic); } /** * @inheritDoc */ public long getCreationTime() { return m_created; } /** * @inheritDoc */ public String getId() { return m_id; } /** * @inheritDoc */ public long getLastAccessedTime() { return m_accessed; } /** * @inheritDoc */ public int getMaxInactiveInterval() { return m_inactiveInterval; } /** * @inheritDoc */ public String getUserEid() { return m_userEid; } /** * @inheritDoc */ public String getUserId() { return m_userId; } /** * @inheritDoc */ public void invalidate() { m_valid = false; synchronized (this) { clear(); // let it not be found sessionStore.remove(getId()); } // if this is the current session, remove it if (this.equals(this.sessionManager.getCurrentSession())) { this.sessionManager.setCurrentSession(null); } } /** * {@inheritDoc} */ public void clear() { // move the attributes and tool sessions to local maps in a synchronized block so the unbinding happens only on one thread Map unbindMap = null; Map toolMap = null; Map contextMap = null; Map<String,Object> nonPortableMap = null; synchronized (this) { unbindMap = new HashMap(m_attributes); m_attributes.clear(); toolMap = new HashMap(m_toolSessions); m_toolSessions.clear(); contextMap = new HashMap(m_contextSessions); m_contextSessions.clear(); nonPortableMap = m_nonPortalSession.getAllAttributes(); m_nonPortalSession.clear(); } // clear each tool session for (Iterator i = toolMap.entrySet().iterator(); i.hasNext();) { Map.Entry e = (Map.Entry) i.next(); ToolSession t = (ToolSession) e.getValue(); t.clearAttributes(); } // clear each context session for (Iterator i = contextMap.entrySet().iterator(); i.hasNext();) { Map.Entry e = (Map.Entry) i.next(); ToolSession t = (ToolSession) e.getValue(); t.clearAttributes(); } // send unbind events for normal (possibly clustered) session data for (Iterator i = unbindMap.entrySet().iterator(); i.hasNext();) { Map.Entry e = (Map.Entry) i.next(); String name = (String) e.getKey(); Object value = e.getValue(); unBind(name, value); } // send unbind events for non clustered session data (in a clustered environment) for (Map.Entry<String, Object> e: nonPortableMap.entrySet()) { unBind(e.getKey(), e.getValue()); } } /** * {@inheritDoc} */ public void clearExcept(Collection names) { // save any attributes in names Map saveAttributes = new HashMap(); Map<String,Object> saveNonPortableAttributes = new HashMap<String,Object>(); for (Iterator i = names.iterator(); i.hasNext();) { String name = (String) i.next(); Object value = m_attributes.get(name); if (value != null) { // remove, but do NOT unbind m_attributes.remove(name); saveAttributes.put(name, value); } else { value = m_nonPortalSession.removeAttribute(name); if (value != null) { saveNonPortableAttributes.put(name, value); } } } // clear the remaining clear(); // restore the saved attributes for (Iterator i = saveAttributes.entrySet().iterator(); i.hasNext();) { Map.Entry e = (Map.Entry) i.next(); String name = (String) e.getKey(); Object value = e.getValue(); m_attributes.put(name, value); } for(Map.Entry<String,Object> e: saveNonPortableAttributes.entrySet()) { m_nonPortalSession.setAttribute(e.getKey(), e.getValue()); } } /** * @inheritDoc */ public void setActive() { m_accessed = System.currentTimeMillis(); updateExpirationTimeSuggestion(); } protected void updateExpirationTimeSuggestion() { long now = System.currentTimeMillis(); long diff = expirationTimeSuggestion.longValue() - now; long max = getMaxInactiveIntervalMillis(); if (diff < (max/2)) { resetExpirationTimeSuggestion(); } } protected void resetExpirationTimeSuggestion() { expirationTimeSuggestion.setValue(System.currentTimeMillis() + getMaxInactiveIntervalMillis()); } protected long getMaxInactiveIntervalMillis() { return getMaxInactiveInterval() * 1000L; } /** * @inheritDoc */ public void removeAttribute(String name) { // remove Object value = m_attributes.remove(name); if ((value == null) && (m_nonPortalSession != null)) { value = m_nonPortalSession.removeAttribute(name); } // unbind event unBind(name, value); } /** * @inheritDoc */ public void setAttribute(String name, Object value) { // treat a set to null as a remove if (value == null) { removeAttribute(name); } else { Object old = null; // If this is not a terracotta clustered environment then immediately // place the attribute in the normal data structure // Otherwise, if this *IS* a TERRACOTTA_CLUSTER, then check the current // tool id against the tool whitelist, to see if attributes from this // tool should be clustered, or not. if ((!TERRACOTTA_CLUSTER) || (sessionStore.isCurrentToolClusterable())) { old = m_attributes.put(name, value); } else { old = m_nonPortalSession.setAttribute(name, value); } // bind event bind(name, value); // unbind event if old exiss if (old != null) { unBind(name, old); } } } /** * @inheritDoc */ public void setMaxInactiveInterval(int interval) { m_inactiveInterval = interval; resetExpirationTimeSuggestion(); // added for KNL-1088 } /** * @inheritDoc */ public void setUserEid(String eid) { m_userEid = eid; } /** * @inheritDoc */ public void setUserId(String uid) { m_userId = uid; } /** * @inheritDoc */ public ToolSession getToolSession(String placementId) { ToolSession t = (ToolSession) m_toolSessions.get(placementId); if (t == null) { NonPortableSession nPS = new MyNonPortableSession(); t = new MyLittleSession(sessionManager,idManager.createUuid(),this,placementId, threadLocalManager, sessionListener,sessionStore,nPS); m_toolSessions.put(placementId, t); } // mark it as accessed ((MyLittleSession) t).setAccessed(); return t; } /** * @inheritDoc */ public ContextSession getContextSession(String contextId) { ContextSession t = (ContextSession) m_contextSessions.get(contextId); if (t == null) { NonPortableSession nPS = new MyNonPortableSession(); t = new MyLittleSession(sessionManager, idManager.createUuid(),this,contextId, threadLocalManager,sessionListener,sessionStore,nPS); m_contextSessions.put(contextId, t); } // mark it as accessed ((MyLittleSession) t).setAccessed(); return t; } /** * Check if the session has become inactive * * @return true if the session is capable of becoming inactive and has done so, false if not. */ protected boolean isInactive() { return ((m_inactiveInterval > 0) && (System.currentTimeMillis() > (m_accessed + (m_inactiveInterval * 1000)))); } /** * {@inheritDoc} */ public boolean equals(Object obj) { if (!(obj instanceof Session)) { return false; } return ((Session) obj).getId().equals(getId()); } /** * {@inheritDoc} */ public int hashCode() { return getId().hashCode(); } /** * {@inheritDoc} */ public ServletContext getServletContext() { return (ServletContext) threadLocalManager.get(SessionComponent.CURRENT_SERVLET_CONTEXT); } /** * {@inheritDoc} */ public HttpSessionContext getSessionContext() { throw new UnsupportedOperationException(); } /** * {@inheritDoc} */ public Object getValue(String arg0) { throw new UnsupportedOperationException(); } /** * {@inheritDoc} */ public String[] getValueNames() { throw new UnsupportedOperationException(); } /** * {@inheritDoc} */ public void putValue(String arg0, Object arg1) { throw new UnsupportedOperationException(); } /** * {@inheritDoc} */ public void removeValue(String arg0) { throw new UnsupportedOperationException(); } /** * {@inheritDoc} */ public boolean isNew() { return false; } /** * Unbind the value if it's a SessionBindingListener. Also does the HTTP unbinding if it's a HttpSessionBindingListener. * * @param name * The attribute name bound. * @param value * The bond value. */ protected void unBind(String name, Object value) { if (value instanceof SessionBindingListener) { SessionBindingEvent event = new MySessionBindingEvent(name, this, value); ((SessionBindingListener) value).valueUnbound(event); } // also unbind any objects that are regular HttpSessionBindingListeners if (value instanceof HttpSessionBindingListener) { HttpSessionBindingEvent event = new HttpSessionBindingEvent(this, name, value); ((HttpSessionBindingListener) value).valueUnbound(event); } // Added for testing purposes. Very much unsure whether this is a proper // use of MySessionBindingEvent. if ( sessionListener != null ) { sessionListener.attributeRemoved(new MySessionBindingEvent(name, this, value)); } } /** * Bind the value if it's a SessionBindingListener. Also does the HTTP binding if it's a HttpSessionBindingListener. * * @param name * The attribute name bound. * @param value * The bond value. */ protected void bind(String name, Object value) { if (value instanceof SessionBindingListener) { SessionBindingEvent event = new MySessionBindingEvent(name, this, value); ((SessionBindingListener) value).valueBound(event); } // also bind any objects that are regular HttpSessionBindingListeners if (value instanceof HttpSessionBindingListener) { HttpSessionBindingEvent event = new HttpSessionBindingEvent(this, name, value); ((HttpSessionBindingListener) value).valueBound(event); } // Added for testing purposes. Very much unsure whether this is a proper // use of MySessionBindingEvent. if ( sessionListener != null ) { sessionListener.attributeAdded(new MySessionBindingEvent(name, this, value)); } } }