org.sakaiproject.tool.impl.MySession.java Source code

Java tutorial

Introduction

Here is the source code for org.sakaiproject.tool.impl.MySession.java

Source

/**********************************************************************************
 * $URL$
 * $Id$
 **********************************************************************************
 *
 * 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 org.apache.commons.collections.iterators.IteratorChain;
import org.apache.commons.lang.mutable.MutableLong;
import org.sakaiproject.event.api.UsageSession;
import org.sakaiproject.id.api.IdManager;
import org.sakaiproject.thread_local.api.ThreadLocalManager;
import org.sakaiproject.tool.api.*;
import org.sakaiproject.util.IteratorEnumeration;
import org.sakaiproject.util.RequestFilter;
import org.sakaiproject.util.ResourceLoader;

import javax.servlet.ServletContext;
import javax.servlet.http.*;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/*************************************************************************************************************************************************
 * 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;
    /**
     * 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;
    /** Hold attributes in a Map. */
    protected Map<String, Object> m_attributes = new ConcurrentHashMap<String, Object>();
    /** Hold toolSessions in a Map, by placement id. */
    protected Map<String, MyLittleSession> m_toolSessions = new ConcurrentHashMap<String, MyLittleSession>();
    /** Hold context toolSessions in a Map, by context (webapp) id. */
    protected Map<String, MyLittleSession> m_contextSessions = new ConcurrentHashMap<String, MyLittleSession>();
    /** 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;
    /** 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;
    /**
     * 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;
    private transient RebuildBreakdownService rebuildBreakdownService;

    public MySession(SessionManager sessionManager, String id, ThreadLocalManager threadLocalManager,
            IdManager idManager, SessionStore sessionStore, SessionAttributeListener sessionListener,
            int inactiveInterval, NonPortableSession nonPortableSession, MutableLong expirationTimeSuggestion,
            RebuildBreakdownService rebuildBreakdownService) {
        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();
        this.rebuildBreakdownService = rebuildBreakdownService;
    }

    /**
     * @return true if the session is valid OR false otherwise
     */
    public boolean isValid() {
        return m_valid;
    }

    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 void setMaxInactiveInterval(int interval) {
        m_inactiveInterval = interval;
        resetExpirationTimeSuggestion(); // added for KNL-1088
    }

    /**
     * @inheritDoc
     */
    public String getUserEid() {
        return m_userEid;
    }

    /**
     * @inheritDoc
     */
    public void setUserEid(String eid) {
        m_userEid = eid;
    }

    /**
     * @inheritDoc
     */
    public String getUserId() {
        return m_userId;
    }

    /**
     * @inheritDoc
     */
    public void setUserId(String uid) {
        m_userId = uid;
    }

    /**
     * @inheritDoc
     */
    public void invalidate() {
        String sessionId = getId();
        destroy();
        // ensure that the session cache is cleared when session is invalidated
        if (rebuildBreakdownService != null) {
            rebuildBreakdownService.purgeSessionFromStorageById(sessionId);
        }
    }

    /**
     * TESTING ONLY
     * Special method to destroy a session without purging the sessions storage data
     */
    public void destroy() {
        m_valid = false;
        String sessionId = getId();
        synchronized (this) {
            clear();
            sessionStore.remove(sessionId);
        }
        // if this is the current session, remove it
        if (this.equals(this.sessionManager.getCurrentSession())) {
            this.sessionManager.setCurrentSession(null);
        }
    }

    /**
     * @return the current request associated with this Session
     */
    public HttpServletRequest currentRequest() {
        HttpServletRequest req = (HttpServletRequest) this.threadLocalManager
                .get(RequestFilter.CURRENT_HTTP_REQUEST);
        return req;
    }

    /**
     * @return info about the map attributes for debugging purposes mostly
     */
    public Map<String, String> currentAttributesSummary() {
        LinkedHashMap<String, String> data = new LinkedHashMap<String, String>();
        data.put("ID", this.m_id);
        data.put("userId", this.m_userId);
        data.put("userEid", this.m_userEid);
        data.put("created", this.m_created + "");
        data.put("accessed", this.m_accessed + "");
        data.put("valid", this.m_valid + "");
        for (Map.Entry<String, Object> entry : ((Map<String, Object>) m_attributes).entrySet()) {
            data.put(entry.getKey(), convertValueToString(entry.getValue()));
        }
        for (Iterator i = m_toolSessions.entrySet().iterator(); i.hasNext();) {
            Map.Entry e = (Map.Entry) i.next();
            ToolSession s = (ToolSession) e.getValue();
            String sKey = "TS_" + s.getId() + "_";
            data.put(sKey + "placementId", s.getPlacementId());
            Enumeration<String> keys = s.getAttributeNames();
            while (keys.hasMoreElements()) {
                String key = keys.nextElement();
                Object val = s.getAttribute(key);
                data.put(sKey + key, convertValueToString(val));
            }
        }
        for (Iterator i = m_contextSessions.entrySet().iterator(); i.hasNext();) {
            Map.Entry e = (Map.Entry) i.next();
            ContextSession s = (ContextSession) e.getValue();
            String sKey = "CS_" + s.getId() + "_";
            data.put(sKey + "contextId", s.getContextId());
            Enumeration<String> keys = s.getAttributeNames();
            while (keys.hasMoreElements()) {
                String key = keys.nextElement();
                Object val = s.getAttribute(key);
                data.put(sKey + key, convertValueToString(val));
            }
        }
        return data;
    }

    private String convertValueToString(Object value) {
        if (value == null) {
            return "NULL";
        }
        try {
            if (value.getClass().isPrimitive() || value instanceof String || value instanceof Number
                    || value instanceof Boolean) {
                return value.toString();
            } else if (value.getClass().isArray()) {
                return value.getClass().getCanonicalName() + "(" + Array.getLength(value) + ")";
            } else if (value instanceof ResourceLoader) {
                // have to do this since the ResourceLoader looks like a map but doesn't have most of the methods implemented (like size)
                return value.toString();
            } else if (value instanceof UsageSession) {
                UsageSession us = (UsageSession) value;
                return value.getClass().getCanonicalName() + ":(" + us.getId() + ")";
            } else if (value instanceof Collection) {
                Collection c = (Collection) value;
                return value.getClass().getCanonicalName() + "[" + c.size() + "]";
            } else if (value instanceof Map) {
                Map m = (Map) value;
                return value.getClass().getCanonicalName() + "[" + m.size() + "]";
            } else {
                return value.getClass().getCanonicalName();
            }
        } catch (Exception e) {
            return value.getClass().getCanonicalName() + ":[e=" + e.getMessage() + "]";
        }
    }

    /**
    * {@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 ToolSession getToolSession(String placementId) {
        MyLittleSession t = m_toolSessions.get(placementId);
        if (t == null) {
            NonPortableSession nPS = new MyNonPortableSession();
            t = new MyLittleSession(MyLittleSession.TYPE_TOOL, sessionManager, idManager.createUuid(), this,
                    placementId, threadLocalManager, sessionListener, sessionStore, nPS);
            m_toolSessions.put(placementId, t);
            // try to populate the tool id and context when the session is created
            if (sessionStore instanceof SessionComponent) {
                String sakaiToolId = ((SessionComponent) sessionStore).identifyCurrentTool();
                t.setSessionToolId(sakaiToolId);
                String context = ((SessionComponent) sessionStore).identifyCurrentContext();
                t.setSessionContextId(context);
            }
        }

        // mark it as accessed
        t.setAccessed();

        return t;
    }

    /**
     * @inheritDoc
     */
    public ContextSession getContextSession(String contextId) {
        MyLittleSession t = m_contextSessions.get(contextId);
        if (t == null) {
            NonPortableSession nPS = new MyNonPortableSession();
            t = new MyLittleSession(MyLittleSession.TYPE_CONTEXT, sessionManager, idManager.createUuid(), this,
                    contextId, threadLocalManager, sessionListener, sessionStore, nPS);
            m_contextSessions.put(contextId, t);
            t.setSessionContextId(contextId);
        }

        // mark it as accessed
        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));
        }

    }

    @Override
    public String toString() {
        return "MyS_" + m_userEid + "{" + m_id + ", userId='" + m_userId + '\'' + ", at="
                + (m_attributes != null ? m_attributes.size() : 0) + ", ts="
                + (m_toolSessions != null ? m_toolSessions.size() : 0) + ", cs="
                + (m_contextSessions != null ? m_contextSessions.size() : 0) + ", "
                + (m_created > 0 ? new Date(m_created) : "?") + '}';
    }

}