/* * Copyright (c) 2008-2012, Hazel Bilisim Ltd. All Rights Reserved. * * Licensed under the Apache 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.apache.org/licenses/LICENSE-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 com.hazelcast.web.tomcat; import com.hazelcast.core.IMap; import com.hazelcast.query.SqlPredicate; import org.apache.catalina.Context; import org.apache.catalina.Manager; import org.apache.catalina.Session; import org.apache.catalina.security.SecurityUtil; import org.apache.catalina.session.StandardSession; import org.apache.catalina.util.Enumerator; import javax.servlet.http.*; import java.io.*; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; /** * @author ali */ public class HazelcastSession extends StandardSession { static final String SESSION_MARK = "__hz_ses_mark"; private static final String SESSION_MARK_EXCEPTION = "'" + SESSION_MARK + "' is a reserved key for Hazelcast!"; /** * Construct a new Session associated with the specified Manager. * * @param manager The manager with which this Session is associated */ public HazelcastSession(Manager manager) { super(manager); } /** * Descriptive information describing this Session implementation. */ protected static final String info = "HazelcastSession/1.0"; /** * Return the <code>HttpSession</code> for which this object * is the facade. */ public HttpSession getSession() { if (facade == null) { if (SecurityUtil.isPackageProtectionEnabled()) { final HazelcastSession fsession = this; facade = (HazelcastSessionFacade) AccessController.doPrivileged(new PrivilegedAction() { public Object run() { return new HazelcastSessionFacade(fsession); } }); } else { facade = new HazelcastSessionFacade(this); } } return (facade); } /** * Return the object bound with the specified name in this session, or * <code>null</code> if no object is bound with that name. * * @param name Name of the attribute to be returned * @throws IllegalStateException if this method is called on an * invalidated session */ public Object getAttribute(String name) { if (!isValidInternal()) { throw new IllegalStateException (sm.getString("standardSession.getAttribute.ise")); } // Name cannot be null if (name == null) throw new IllegalArgumentException (sm.getString("standardSession.setAttribute.namenull")); if (SESSION_MARK.equals(name)) { throw new IllegalArgumentException(SESSION_MARK_EXCEPTION); } HazelcastAttribute attribute = (HazelcastAttribute) attributes.get(name); if (attribute == null) { final IMap<String, HazelcastAttribute> sessionMap = HazelcastClusterSupport.get().getAttributesMap(); attribute = (HazelcastAttribute) sessionMap.get(getIdInternal() + "_" + name); if (attribute == null) { attributes.put(name, new HazelcastAttribute(getIdInternal(), name, null)); return null; } } long requestId = LocalRequestId.get(); attribute.touch(requestId); return attribute.getValue(); } /** * Get HazelAttribute directly */ public Object getLocalAttribute(String name) { return super.getAttribute(name); } /** * Bind an object to this session, using the specified name. If an object * of the same name is already bound to this session, the object is * replaced. * <p/> * After this method executes, and if the object implements * <code>HttpSessionBindingListener</code>, the container calls * <code>valueBound()</code> on the object. * * @param name Name to which the object is bound, cannot be null * @param value Object to be bound, cannot be null * @param notify whether to notify session listeners * @throws IllegalArgumentException if an attempt is made to add a * non-serializable object in an environment marked distributable. * @throws IllegalStateException if this method is called on an * invalidated session */ public void setAttribute(String name, Object value, boolean notify) { // Name cannot be null if (name == null) throw new IllegalArgumentException (sm.getString("standardSession.setAttribute.namenull")); if (SESSION_MARK.equals(name)) { throw new IllegalArgumentException(SESSION_MARK_EXCEPTION); } // Null value is the same as removeAttribute() if (value == null) { removeAttribute(name); return; } // Validate our current state if (!isValidInternal()) throw new IllegalStateException (sm.getString("standardSession.setAttribute.ise")); if ((manager != null) && manager.getDistributable() && !(value instanceof Serializable)) throw new IllegalArgumentException (sm.getString("standardSession.setAttribute.iae")); // Construct an event with the new value HttpSessionBindingEvent event = null; // Call the valueBound() method if necessary if (notify && value instanceof HttpSessionBindingListener) { HazelcastAttribute oldAttribute = (HazelcastAttribute) attributes.get(name); // Don't call any notification if replacing with the same value if (oldAttribute != null && value != oldAttribute.getValue()) { event = new HttpSessionBindingEvent(getSession(), name, value); try { ((HttpSessionBindingListener) value).valueBound(event); } catch (Throwable t) { manager.getContainer().getLogger().error (sm.getString("standardSession.bindingEvent"), t); } } } final HazelcastAttribute attribute = new HazelcastAttribute(getIdInternal(), name, value); long requestId = LocalRequestId.get(); attribute.touch(requestId); final HazelcastAttribute unboundAttribute = (HazelcastAttribute) attributes.put(name, attribute); final Object unboundValue = unboundAttribute != null ? unboundAttribute.getValue() : null; // Call the valueUnbound() method if necessary if (notify && (unboundValue != null) && (unboundValue != value) && (unboundValue instanceof HttpSessionBindingListener)) { try { ((HttpSessionBindingListener) unboundValue).valueUnbound (new HttpSessionBindingEvent(getSession(), name)); } catch (Throwable t) { manager.getContainer().getLogger().error (sm.getString("standardSession.bindingEvent"), t); } } if (!notify) return; // Notify interested application event listeners Context context = (Context) manager.getContainer(); Object listeners[] = context.getApplicationEventListeners(); if (listeners == null) return; for (int i = 0; i < listeners.length; i++) { if (!(listeners[i] instanceof HttpSessionAttributeListener)) continue; HttpSessionAttributeListener listener = (HttpSessionAttributeListener) listeners[i]; try { if (unboundValue != null) { fireContainerEvent(context, "beforeSessionAttributeReplaced", listener); if (event == null) { event = new HttpSessionBindingEvent (getSession(), name, unboundValue); } listener.attributeReplaced(event); fireContainerEvent(context, "afterSessionAttributeReplaced", listener); } else { fireContainerEvent(context, "beforeSessionAttributeAdded", listener); if (event == null) { event = new HttpSessionBindingEvent (getSession(), name, value); } listener.attributeAdded(event); fireContainerEvent(context, "afterSessionAttributeAdded", listener); } } catch (Throwable t) { try { if (unboundValue != null) { fireContainerEvent(context, "afterSessionAttributeReplaced", listener); } else { fireContainerEvent(context, "afterSessionAttributeAdded", listener); } } catch (Exception e) { ; } manager.getContainer().getLogger().error (sm.getString("standardSession.attributeEvent"), t); } } } /** * Remove the object bound with the specified name from this session. If * the session does not have an object bound with this name, this method * does nothing. * <p/> * After this method executes, and if the object implements * <code>HttpSessionBindingListener</code>, the container calls * <code>valueUnbound()</code> on the object. * * @param name Name of the object to remove from this session. * @param notify Should we notify interested listeners that this * attribute is being removed? */ protected void removeAttributeInternal(String name, boolean notify) { // Avoid NPE if (name == null) return; if (name.equals(SESSION_MARK)) { throw new IllegalArgumentException(SESSION_MARK_EXCEPTION); } // Remove this attribute from our collection HazelcastAttribute attribute = (HazelcastAttribute) attributes.remove(name); if (attribute == null || attribute.getValue() == null) { return; } Object value = attribute.getValue(); attribute.setValue(null); long requestId = LocalRequestId.get(); attribute.touch(requestId); attributes.put(name, attribute); notifyRemove(name, value, notify); } protected void removeAttributeHard(String name, boolean notify) { // Avoid NPE if (name == null) return; // Remove this attribute from our collection HazelcastAttribute attribute = (HazelcastAttribute) attributes.remove(name); if (attribute == null) { return; } Object value = attribute.getValue(); final IMap<String, HazelcastAttribute> sessionAttrMap = HazelcastClusterSupport.get().getAttributesMap(); sessionAttrMap.remove(attribute.getKey()); notifyRemove(name, value, notify); } protected void notifyRemove(String name, Object value, boolean notify) { // Do we need to do valueUnbound() and attributeRemoved() notification? if (!notify || (value == null)) { return; } // Call the valueUnbound() method if necessary HttpSessionBindingEvent event = null; if (value instanceof HttpSessionBindingListener) { event = new HttpSessionBindingEvent(getSession(), name, value); ((HttpSessionBindingListener) value).valueUnbound(event); } // Notify interested application event listeners Context context = (Context) manager.getContainer(); Object listeners[] = context.getApplicationEventListeners(); if (listeners == null) return; for (int i = 0; i < listeners.length; i++) { if (!(listeners[i] instanceof HttpSessionAttributeListener)) continue; HttpSessionAttributeListener listener = (HttpSessionAttributeListener) listeners[i]; try { fireContainerEvent(context, "beforeSessionAttributeRemoved", listener); if (event == null) { event = new HttpSessionBindingEvent (getSession(), name, value); } listener.attributeRemoved(event); fireContainerEvent(context, "afterSessionAttributeRemoved", listener); } catch (Throwable t) { try { fireContainerEvent(context, "afterSessionAttributeRemoved", listener); } catch (Exception e) { ; } manager.getContainer().getLogger().error (sm.getString("standardSession.attributeEvent"), t); } } } /** * Perform internal processing required to activate this * session. */ public void activate() { // Initialize access count if (ACTIVITY_CHECK) { accessCount = new AtomicInteger(); } // Notify interested session event listeners fireSessionEvent(Session.SESSION_ACTIVATED_EVENT, null); // Notify ActivationListeners HttpSessionEvent event = null; String keys[] = keys(); for (int i = 0; i < keys.length; i++) { Object attribute = ((HazelcastAttribute) attributes.get(keys[i])).getValue(); if (attribute instanceof HttpSessionActivationListener) { if (event == null) event = new HttpSessionEvent(getSession()); try { ((HttpSessionActivationListener) attribute) .sessionDidActivate(event); } catch (Throwable t) { manager.getContainer().getLogger().error (sm.getString("standardSession.attributeEvent"), t); } } } } /** * Perform the internal processing required to passivate * this session. */ public void passivate() { // Notify interested session event listeners fireSessionEvent(Session.SESSION_PASSIVATED_EVENT, null); // Notify ActivationListeners HttpSessionEvent event = null; String keys[] = keys(); for (int i = 0; i < keys.length; i++) { Object attribute = ((HazelcastAttribute) attributes.get(keys[i])).getValue(); if (attribute instanceof HttpSessionActivationListener) { if (event == null) event = new HttpSessionEvent(getSession()); try { ((HttpSessionActivationListener) attribute) .sessionWillPassivate(event); } catch (Throwable t) { manager.getContainer().getLogger().error (sm.getString("standardSession.attributeEvent"), t); } } } } /** * Read a serialized version of this session object from the specified * object input stream. * <p/> * <b>IMPLEMENTATION NOTE</b>: The reference to the owning Manager * is not restored by this method, and must be set explicitly. * * @param stream The input stream to read from * @throws ClassNotFoundException if an unknown class is specified * @throws IOException if an input/output error occurs */ protected void readObject(ObjectInputStream stream) throws ClassNotFoundException, IOException { // Deserialize the scalar instance variables (except Manager) authType = null; // Transient only creationTime = ((Long) stream.readObject()).longValue(); lastAccessedTime = ((Long) stream.readObject()).longValue(); maxInactiveInterval = ((Integer) stream.readObject()).intValue(); isNew = ((Boolean) stream.readObject()).booleanValue(); isValid = ((Boolean) stream.readObject()).booleanValue(); thisAccessedTime = ((Long) stream.readObject()).longValue(); principal = null; // Transient only // setId((String) stream.readObject()); id = (String) stream.readObject(); if (manager.getContainer().getLogger().isDebugEnabled()) manager.getContainer().getLogger().debug ("readObject() loading session " + id); // Deserialize the attribute count and attribute values if (attributes == null) attributes = new Hashtable(); int n = ((Integer) stream.readObject()).intValue(); boolean isValidSave = isValid; isValid = true; for (int i = 0; i < n; i++) { String name = (String) stream.readObject(); Object value = (Object) stream.readObject(); if ((value instanceof String) && (value.equals(NOT_SERIALIZED))) continue; if (manager.getContainer().getLogger().isDebugEnabled()) manager.getContainer().getLogger().debug(" loading attribute '" + name + "' with value '" + value + "'"); attributes.put(name, new HazelcastAttribute(id, name, value)); } attributes.put(SESSION_MARK, new HazelcastAttribute(id, SESSION_MARK, System.currentTimeMillis())); isValid = isValidSave; if (listeners == null) { listeners = new ArrayList(); } if (notes == null) { notes = new Hashtable(); } } /** * Write a serialized version of this session object to the specified * object output stream. * <p/> * <b>IMPLEMENTATION NOTE</b>: The owning Manager will not be stored * in the serialized representation of this Session. After calling * <code>readObject()</code>, you must set the associated Manager * explicitly. * <p/> * <b>IMPLEMENTATION NOTE</b>: Any attribute that is not Serializable * will be unbound from the session, with appropriate actions if it * implements HttpSessionBindingListener. If you do not want any such * attributes, be sure the <code>distributable</code> property of the * associated Manager is set to <code>true</code>. * * @param stream The output stream to write to * @throws IOException if an input/output error occurs */ protected void writeObject(ObjectOutputStream stream) throws IOException { // Write the scalar instance variables (except Manager) stream.writeObject(new Long(creationTime)); stream.writeObject(new Long(lastAccessedTime)); stream.writeObject(new Integer(maxInactiveInterval)); stream.writeObject(new Boolean(isNew)); stream.writeObject(new Boolean(isValid)); stream.writeObject(new Long(thisAccessedTime)); stream.writeObject(id); if (manager.getContainer().getLogger().isDebugEnabled()) manager.getContainer().getLogger().debug ("writeObject() storing session " + id); // Accumulate the names of serializable and non-serializable attributes String keys[] = keys(); ArrayList saveNames = new ArrayList(); ArrayList saveValues = new ArrayList(); for (int i = 0; i < keys.length; i++) { HazelcastAttribute hattribute = (HazelcastAttribute) attributes.get(keys[i]); if (hattribute == null) continue; Object value = hattribute.getValue(); if (value == null) continue; else if ((value instanceof Serializable) && (!exclude(keys[i]))) { saveNames.add(keys[i]); saveValues.add(value); } else { removeAttributeInternal(keys[i], true); } } // Serialize the attribute count and the Serializable attributes int n = saveNames.size(); stream.writeObject(new Integer(n)); for (int i = 0; i < n; i++) { stream.writeObject((String) saveNames.get(i)); try { stream.writeObject(saveValues.get(i)); if (manager.getContainer().getLogger().isDebugEnabled()) manager.getContainer().getLogger().debug (" storing attribute '" + saveNames.get(i) + "' with value '" + saveValues.get(i) + "'"); } catch (NotSerializableException e) { manager.getContainer().getLogger().warn (sm.getString("standardSession.notSerializable", saveNames.get(i), id), e); stream.writeObject(NOT_SERIALIZED); if (manager.getContainer().getLogger().isDebugEnabled()) manager.getContainer().getLogger().debug (" storing attribute '" + saveNames.get(i) + "' with value NOT_SERIALIZED"); } } } /** * Set the session identifier for this session. * * @param id The new session identifier */ public void setId(String id) { if ((this.id != null) && (manager != null)) manager.remove(this); this.id = id; final IMap<String, HazelcastAttribute> sessionAttrMap = HazelcastClusterSupport.get().getAttributesMap(); Collection<HazelcastAttribute> colAttributes = sessionAttrMap.values(new SqlPredicate("sessionId=" + id)); if (colAttributes.size() != 0) { for (HazelcastAttribute hattribute : colAttributes) { attributes.put(hattribute.getName(), hattribute); } } else { HazelcastAttribute mark = new HazelcastAttribute(id, SESSION_MARK, System.currentTimeMillis()); sessionAttrMap.put(mark.getKey(), mark); attributes.put(SESSION_MARK, mark); } if (manager != null) manager.add(this); tellNew(); } /** * Perform the internal processing required to invalidate this session, * without triggering an exception if the session has already expired. * * @param notify Should we notify listeners about the demise of * this session? */ public void expire(boolean notify) { // Mark this session as "being expired" if needed if (expiring) return; synchronized (this) { if (manager == null) return; expiring = true; // Notify interested application event listeners // FIXME - Assumes we call listeners in reverse order Context context = (Context) manager.getContainer(); Object listeners[] = context.getApplicationLifecycleListeners(); if (notify && (listeners != null)) { HttpSessionEvent event = new HttpSessionEvent(getSession()); for (int i = 0; i < listeners.length; i++) { int j = (listeners.length - 1) - i; if (!(listeners[j] instanceof HttpSessionListener)) continue; HttpSessionListener listener = (HttpSessionListener) listeners[j]; try { fireContainerEvent(context, "beforeSessionDestroyed", listener); listener.sessionDestroyed(event); fireContainerEvent(context, "afterSessionDestroyed", listener); } catch (Throwable t) { try { fireContainerEvent(context, "afterSessionDestroyed", listener); } catch (Exception e) { ; } manager.getContainer().getLogger().error (sm.getString("standardSession.sessionEvent"), t); } } } if (ACTIVITY_CHECK) { accessCount.set(0); } setValid(false); /* * Compute how long this session has been alive, and update * session manager's related properties accordingly */ long timeNow = System.currentTimeMillis(); int timeAlive = (int) ((timeNow - creationTime) / 1000); synchronized (manager) { if (timeAlive > manager.getSessionMaxAliveTime()) { manager.setSessionMaxAliveTime(timeAlive); } int numExpired = manager.getExpiredSessions(); numExpired++; manager.setExpiredSessions(numExpired); int average = manager.getSessionAverageAliveTime(); average = ((average * (numExpired - 1)) + timeAlive) / numExpired; manager.setSessionAverageAliveTime(average); } // Remove this session from our manager's active sessions manager.remove(this); // Notify interested session event listeners if (notify) { fireSessionEvent(Session.SESSION_DESTROYED_EVENT, null); } // We have completed expire of this session expiring = false; // Unbind any objects associated with this session String keys[] = keys(); for (int i = 0; i < keys.length; i++) removeAttributeHard(keys[i], notify); removeAttributeHard(SESSION_MARK, notify); } } /** * Return the names of all currently defined session attributes * as an array of Strings. If there are no defined attributes, a * zero-length array is returned. */ protected String[] keys() { Set keySet = attributes.keySet(); keySet.remove(SESSION_MARK); return ((String[]) keySet.toArray(EMPTY_ARRAY)); } /** * Return an <code>Enumeration</code> of <code>String</code> objects * containing the names of the objects bound to this session. * * @throws IllegalStateException if this method is called on an * invalidated session */ public Enumeration getAttributeNames() { if (!isValidInternal()) throw new IllegalStateException (sm.getString("standardSession.getAttributeNames.ise")); Set keySet = attributes.keySet(); keySet.remove(SESSION_MARK); return (new Enumerator(keySet, true)); } }