| /*========================================================================= |
| * Copyright (c) 2010-2014 Pivotal Software, Inc. All Rights Reserved. |
| * This product is protected by U.S. and international copyright |
| * and intellectual property laws. Pivotal products are covered by |
| * one or more patents listed at http://www.pivotal.io/patents. |
| *========================================================================= |
| */ |
| package com.gemstone.gemfire.modules.session.catalina; |
| |
| import java.io.DataInput; |
| import java.io.DataOutput; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.security.AccessController; |
| import java.security.Principal; |
| import java.security.PrivilegedAction; |
| import java.util.ArrayList; |
| import java.util.Enumeration; |
| import java.util.Hashtable; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.ConcurrentHashMap; |
| |
| import javax.servlet.http.HttpSession; |
| |
| import com.gemstone.gemfire.internal.cache.lru.Sizeable; |
| import org.apache.catalina.Manager; |
| import org.apache.catalina.ha.session.SerializablePrincipal; |
| import org.apache.catalina.realm.GenericPrincipal; |
| import org.apache.catalina.security.SecurityUtil; |
| import org.apache.catalina.session.StandardSession; |
| |
| import com.gemstone.gemfire.DataSerializable; |
| import com.gemstone.gemfire.DataSerializer; |
| import com.gemstone.gemfire.Delta; |
| import com.gemstone.gemfire.InvalidDeltaException; |
| import com.gemstone.gemfire.cache.Region; |
| import com.gemstone.gemfire.internal.util.BlobHelper; |
| import com.gemstone.gemfire.modules.gatewaydelta.GatewayDelta; |
| import com.gemstone.gemfire.modules.gatewaydelta.GatewayDeltaEvent; |
| import com.gemstone.gemfire.modules.session.catalina.internal.DeltaSessionAttributeEvent; |
| import com.gemstone.gemfire.modules.session.catalina.internal.DeltaSessionAttributeEventBatch; |
| import com.gemstone.gemfire.modules.session.catalina.internal.DeltaSessionDestroyAttributeEvent; |
| import com.gemstone.gemfire.modules.session.catalina.internal.DeltaSessionUpdateAttributeEvent; |
| import org.apache.juli.logging.Log; |
| import org.apache.juli.logging.LogFactory; |
| |
| @SuppressWarnings("serial") |
| public class DeltaSession extends StandardSession implements DataSerializable, Delta, GatewayDelta, Sizeable { |
| |
| private transient Region<String,HttpSession> operatingRegion; |
| |
| private String sessionRegionName; |
| |
| private String contextName; |
| |
| private boolean hasDelta; |
| |
| private boolean applyRemotely; |
| |
| private boolean enableGatewayDeltaReplication; |
| |
| private transient final Object changeLock = new Object(); |
| |
| private final List<DeltaSessionAttributeEvent> eventQueue = new ArrayList<DeltaSessionAttributeEvent>(); |
| |
| private transient GatewayDeltaEvent currentGatewayDeltaEvent; |
| |
| private transient boolean expired = false; |
| |
| private transient boolean preferDeserializedForm = true; |
| |
| private byte[] serializedPrincipal; |
| |
| private final Log LOG = LogFactory.getLog(DeltaSession.class.getName()); |
| |
| /** |
| * The string manager for this package. |
| */ |
| // protected static StringManager STRING_MANAGER = |
| // StringManager.getManager("com.gemstone.gemfire.modules.session.catalina"); |
| |
| /** |
| * Construct a new <code>Session</code> associated with no |
| * <code>Manager</code>. The <code>Manager</code> will be assigned later using |
| * {@link #setOwner(Object)}. |
| */ |
| public DeltaSession() { |
| super(null); |
| } |
| |
| /** |
| * Construct a new Session associated with the specified Manager. |
| * |
| * @param manager |
| * The manager with which this Session is associated |
| */ |
| public DeltaSession(Manager manager) { |
| super(manager); |
| setOwner(manager); |
| } |
| |
| /** |
| * Return the <code>HttpSession</code> for which this object |
| * is the facade. |
| */ |
| @SuppressWarnings("unchecked") |
| public HttpSession getSession() { |
| if (facade == null) { |
| if (SecurityUtil.isPackageProtectionEnabled()) { |
| final DeltaSession fsession = this; |
| facade = (DeltaSessionFacade) AccessController |
| .doPrivileged(new PrivilegedAction() { |
| public Object run() { |
| return new DeltaSessionFacade(fsession); |
| } |
| }); |
| } else { |
| facade = new DeltaSessionFacade(this); |
| } |
| } |
| return (facade); |
| } |
| |
| public Principal getPrincipal() { |
| if (this.principal == null && this.serializedPrincipal != null) { |
| SerializablePrincipal sp = null; |
| try { |
| sp = (SerializablePrincipal) BlobHelper.deserializeBlob(this.serializedPrincipal); |
| } catch (Exception e) { |
| StringBuilder builder = new StringBuilder(); |
| builder |
| .append(this) |
| .append(": Serialized principal contains a byte[] that cannot be deserialized due to the following exception"); |
| ((DeltaSessionManager) getManager()).getLogger().warn(builder.toString(), e); |
| return null; |
| } |
| this.principal = sp.getPrincipal(this.manager.getContainer().getRealm()); |
| if (getManager() != null) { |
| DeltaSessionManager mgr = (DeltaSessionManager) getManager(); |
| if (mgr.getLogger().isDebugEnabled()) { |
| mgr.getLogger().debug(this + ": Deserialized principal: " + this.principal); |
| //mgr.logCurrentStack(); |
| } |
| } |
| } |
| return this.principal; |
| } |
| |
| public void setPrincipal(Principal principal) { |
| super.setPrincipal(principal); |
| |
| // Put the session into the region to serialize the principal |
| if (getManager() != null) { |
| // TODO convert this to a delta |
| getManager().add(this); |
| DeltaSessionManager mgr = (DeltaSessionManager) getManager(); |
| if (mgr.getLogger().isDebugEnabled()) { |
| mgr.getLogger().debug(this + ": Cached principal: " + principal); |
| //mgr.logCurrentStack(); |
| } |
| } |
| } |
| |
| private byte[] getSerializedPrincipal() { |
| if (this.serializedPrincipal == null) { |
| if (this.principal != null && this.principal instanceof GenericPrincipal) { |
| GenericPrincipal gp = (GenericPrincipal) this.principal; |
| SerializablePrincipal sp = SerializablePrincipal.createPrincipal(gp); |
| this.serializedPrincipal = serialize(sp); |
| if (manager != null) { |
| DeltaSessionManager mgr = (DeltaSessionManager) getManager(); |
| if (mgr.getLogger().isDebugEnabled()) { |
| mgr.getLogger().debug(this + ": Serialized principal: " + sp); |
| //mgr.logCurrentStack(); |
| } |
| } |
| } |
| } |
| return this.serializedPrincipal; |
| } |
| |
| protected Region<String,HttpSession> getOperatingRegion() { |
| // This region shouldn't be null when it is needed. |
| // It should have been set by the setOwner method. |
| return this.operatingRegion; |
| } |
| |
| public boolean isCommitEnabled() { |
| DeltaSessionManager mgr = (DeltaSessionManager) getManager(); |
| return mgr.isCommitValveEnabled(); |
| } |
| |
| public GatewayDeltaEvent getCurrentGatewayDeltaEvent() { |
| return this.currentGatewayDeltaEvent; |
| } |
| |
| public void setCurrentGatewayDeltaEvent(GatewayDeltaEvent currentGatewayDeltaEvent) { |
| this.currentGatewayDeltaEvent = currentGatewayDeltaEvent; |
| } |
| |
| @SuppressWarnings("unchecked") |
| public void setOwner(Object manager) { |
| if (manager instanceof DeltaSessionManager) { |
| DeltaSessionManager sessionManager = (DeltaSessionManager) manager; |
| this.manager = sessionManager; |
| initializeRegion(sessionManager); |
| this.hasDelta = false; |
| this.applyRemotely = false; |
| this.enableGatewayDeltaReplication = sessionManager.getEnableGatewayDeltaReplication(); |
| this.preferDeserializedForm = sessionManager.getPreferDeserializedForm(); |
| |
| // Initialize transient variables |
| if (this.listeners == null) { |
| this.listeners = new ArrayList(); |
| } |
| |
| if (this.notes == null) { |
| this.notes = new Hashtable(); |
| } |
| |
| contextName = ((DeltaSessionManager) manager).getContainer().getName(); |
| } else { |
| throw new IllegalArgumentException(this + ": The Manager must be an AbstractManager"); |
| } |
| } |
| |
| private void checkBackingCacheAvailable() { |
| if (! ((SessionManager)getManager()).isBackingCacheAvailable()) { |
| throw new IllegalStateException("No backing cache server is available."); |
| } |
| } |
| |
| public void setAttribute(String name, Object value, boolean notify) { |
| checkBackingCacheAvailable(); |
| synchronized (this.changeLock) { |
| // Serialize the value |
| byte[] serializedValue = serialize(value); |
| |
| // Store the attribute locally |
| if (this.preferDeserializedForm) { |
| super.setAttribute(name, value, true); |
| } else { |
| super.setAttribute(name, serializedValue, true); |
| } |
| |
| if (serializedValue == null) { |
| return; |
| } |
| |
| // Create the update attribute message |
| DeltaSessionAttributeEvent event = new DeltaSessionUpdateAttributeEvent(name, serializedValue); |
| queueAttributeEvent(event, true); |
| |
| // Distribute the update |
| if (! isCommitEnabled()) { |
| putInRegion(getOperatingRegion(), true, null); |
| } |
| } |
| } |
| |
| public void removeAttribute(String name, boolean notify) { |
| checkBackingCacheAvailable(); |
| synchronized (this.changeLock) { |
| // Remove the attribute locally |
| super.removeAttribute(name, true); |
| |
| // Create the destroy attribute message |
| DeltaSessionAttributeEvent event = new DeltaSessionDestroyAttributeEvent(name); |
| queueAttributeEvent(event, true); |
| |
| // Distribute the update |
| if (! isCommitEnabled()) { |
| putInRegion(getOperatingRegion(), true, null); |
| } |
| } |
| } |
| |
| public Object getAttribute(String name) { |
| checkBackingCacheAvailable(); |
| Object value = super.getAttribute(name); |
| |
| // If the attribute is a byte[] (meaning it came from the server), |
| // deserialize it and add it to attributes map before returning it. |
| if (value instanceof byte[]) { |
| try { |
| value = BlobHelper.deserializeBlob((byte[]) value); |
| } catch (Exception e) { |
| StringBuilder builder = new StringBuilder(); |
| builder |
| .append(this) |
| .append(": Attribute named ") |
| .append(name) |
| .append(" contains a byte[] that cannot be deserialized due to the following exception"); |
| ((DeltaSessionManager) getManager()).getLogger().warn(builder.toString(), e); |
| } |
| if (this.preferDeserializedForm) { |
| localUpdateAttribute(name, value); |
| } |
| } |
| |
| // Touch the session region if necessary. This is an asynchronous operation |
| // that prevents the session region from prematurely expiring a session that |
| // is only getting attributes. |
| ((DeltaSessionManager) getManager()).addSessionToTouch(getId()); |
| |
| return value; |
| } |
| |
| public void invalidate() { |
| super.invalidate(); |
| //getOperatingRegion().destroy(this.id, true); // already done in super (remove) |
| ((DeltaSessionManager) getManager()).getStatistics().incSessionsInvalidated(); |
| } |
| |
| public void processExpired() { |
| if (((DeltaSessionManager) getManager()).getLogger().isDebugEnabled()) { |
| ((DeltaSessionManager) getManager()).getLogger().debug(this + ": Expired"); |
| } |
| // Set expired (so region.destroy is not called again) |
| setExpired(true); |
| |
| // Do expire processing |
| expire(); |
| |
| // Update statistics |
| DeltaSessionManager manager = (DeltaSessionManager) getManager(); |
| if (manager != null) { |
| manager.getStatistics().incSessionsExpired(); |
| } |
| } |
| |
| public void setMaxInactiveInterval(int interval) { |
| super.setMaxInactiveInterval(interval); |
| } |
| |
| public void localUpdateAttribute(String name, Object value) { |
| super.setAttribute(name, value, false); // don't do notification since this is a replication |
| } |
| |
| public void localDestroyAttribute(String name) { |
| super.removeAttribute(name, false); // don't do notification since this is a replication |
| } |
| |
| public void applyAttributeEvents(Region<String,DeltaSession> region, List<DeltaSessionAttributeEvent> events) { |
| for (DeltaSessionAttributeEvent event : events) { |
| event.apply(this); |
| queueAttributeEvent(event, false); |
| } |
| |
| putInRegion(region, false, true); |
| } |
| |
| private void initializeRegion(DeltaSessionManager sessionManager) { |
| // Get the session region name |
| this.sessionRegionName = sessionManager.getRegionName(); |
| |
| // Get the operating region. |
| // If a P2P manager is used, then this will be a local region fronting the |
| // session region if local cache is enabled; otherwise, it will be the |
| // session region itself. |
| // If a CS manager is used, it will be the session proxy region. |
| this.operatingRegion = sessionManager.getSessionCache().getOperatingRegion(); |
| if (sessionManager.getLogger().isDebugEnabled()) { |
| sessionManager.getLogger().debug(this + ": Set operating region: " + this.operatingRegion); |
| } |
| } |
| |
| private void queueAttributeEvent(DeltaSessionAttributeEvent event, boolean checkAddToCurrentGatewayDelta) { |
| // Add to current gateway delta if necessary |
| if (checkAddToCurrentGatewayDelta) { |
| // If the manager has enabled gateway delta replication and is a P2P |
| // manager, the GatewayDeltaForwardCacheListener will be invoked in this |
| // VM. Add the event to the currentDelta. |
| DeltaSessionManager mgr = (DeltaSessionManager) this.manager; |
| if (this.enableGatewayDeltaReplication && mgr.isPeerToPeer()) { |
| // If commit is not enabled, add the event to the current batch; else, |
| // the current batch will be initialized to the events in the queue will |
| // be added at commit time. |
| if (! isCommitEnabled()) { |
| List<DeltaSessionAttributeEvent> events = new ArrayList<DeltaSessionAttributeEvent>(); |
| events.add(event); |
| this.currentGatewayDeltaEvent = new DeltaSessionAttributeEventBatch(this.sessionRegionName, this.id, events); |
| } |
| } |
| } |
| this.eventQueue.add(event); |
| } |
| |
| @SuppressWarnings("unchecked") |
| private void putInRegion(Region region, boolean applyRemotely, Object callbackArgument) { |
| this.hasDelta = true; |
| this.applyRemotely = applyRemotely; |
| region.put(this.id, this, callbackArgument); |
| this.eventQueue.clear(); |
| } |
| |
| public void commit() { |
| if (!isValidInternal()) |
| throw new IllegalStateException("commit: Session " + getId() + |
| " already invalidated"); |
| // (STRING_MANAGER.getString("deltaSession.commit.ise", getId())); |
| |
| synchronized (this.changeLock) { |
| // Jens - there used to be a check to only perform this if the queue is |
| // empty, but we want this to always run so that the lastAccessedTime |
| // will be updated even when no attributes have been changed. |
| DeltaSessionManager mgr = (DeltaSessionManager) this.manager; |
| if (this.enableGatewayDeltaReplication && mgr.isPeerToPeer()) { |
| setCurrentGatewayDeltaEvent(new DeltaSessionAttributeEventBatch(this.sessionRegionName, this.id, this.eventQueue)); |
| } |
| this.hasDelta = true; |
| this.applyRemotely = true; |
| putInRegion(getOperatingRegion(), true, null); |
| this.eventQueue.clear(); |
| } |
| } |
| |
| public void abort() { |
| synchronized (this.changeLock) { |
| this.eventQueue.clear(); |
| } |
| } |
| |
| private void setExpired(boolean expired) { |
| this.expired = expired; |
| } |
| |
| protected boolean getExpired() { |
| return this.expired; |
| } |
| |
| public String getContextName() { |
| return contextName; |
| } |
| |
| public boolean hasDelta() { |
| return this.hasDelta; |
| } |
| |
| public void toDelta(DataOutput out) throws IOException { |
| // Write whether to apply the changes to another DS if necessary |
| out.writeBoolean(this.applyRemotely); |
| |
| // Write the events |
| DataSerializer.writeArrayList((ArrayList) this.eventQueue, out); |
| |
| out.writeLong(this.lastAccessedTime); |
| out.writeInt(this.maxInactiveInterval); |
| } |
| |
| public void fromDelta(DataInput in) throws IOException, InvalidDeltaException { |
| // Read whether to apply the changes to another DS if necessary |
| this.applyRemotely = in.readBoolean(); |
| |
| // Read the events |
| List<DeltaSessionAttributeEvent> events = null; |
| try { |
| events = DataSerializer.readArrayList(in); |
| } catch (ClassNotFoundException e) { |
| throw new InvalidDeltaException(e); |
| } |
| |
| // This allows for backwards compatibility with 2.1 clients |
| if (((InputStream)in).available() > 0) { |
| this.lastAccessedTime = in.readLong(); |
| this.maxInactiveInterval = in.readInt(); |
| } |
| |
| // Iterate and apply the events |
| for (DeltaSessionAttributeEvent event : events) { |
| event.apply(this); |
| } |
| |
| // Add the events to the gateway delta region if necessary |
| if (this.enableGatewayDeltaReplication && this.applyRemotely) { |
| setCurrentGatewayDeltaEvent(new DeltaSessionAttributeEventBatch(this.sessionRegionName, this.id, events)); |
| } |
| |
| // Access it to set the last accessed time. End access it to set not new. |
| access(); |
| endAccess(); |
| } |
| |
| @Override |
| public void toData(DataOutput out) throws IOException { |
| // Write the StandardSession state |
| DataSerializer.writeString(this.id, out); |
| out.writeLong(this.creationTime); |
| out.writeLong(this.lastAccessedTime); |
| out.writeLong(this.thisAccessedTime); |
| out.writeInt(this.maxInactiveInterval); |
| out.writeBoolean(this.isNew); |
| out.writeBoolean(this.isValid); |
| DataSerializer.writeObject(getSerializedAttributes(), out); |
| DataSerializer.writeByteArray(getSerializedPrincipal(), out); |
| |
| // Write the DeltaSession state |
| out.writeBoolean(this.enableGatewayDeltaReplication); |
| DataSerializer.writeString(this.sessionRegionName, out); |
| |
| DataSerializer.writeString(this.contextName, out); |
| } |
| |
| @Override |
| public void fromData(DataInput in) throws IOException, ClassNotFoundException { |
| // Read the StandardSession state |
| this.id = DataSerializer.readString(in); |
| this.creationTime = in.readLong(); |
| this.lastAccessedTime = in.readLong(); |
| this.thisAccessedTime = in.readLong(); |
| this.maxInactiveInterval = in.readInt(); |
| this.isNew = in.readBoolean(); |
| this.isValid = in.readBoolean(); |
| this.attributes = DataSerializer.readObject(in); |
| this.serializedPrincipal = DataSerializer.readByteArray(in); |
| |
| // Read the DeltaSession state |
| this.enableGatewayDeltaReplication = in.readBoolean(); |
| this.sessionRegionName = DataSerializer.readString(in); |
| |
| // This allows for backwards compatibility with 2.1 clients |
| if (((InputStream)in).available() > 0) { |
| this.contextName = DataSerializer.readString(in); |
| } |
| |
| // Initialize the transients if necessary |
| if (this.listeners == null) { |
| this.listeners = new ArrayList(); |
| } |
| |
| if (this.notes == null) { |
| this.notes = new Hashtable(); |
| } |
| } |
| |
| @Override |
| public int getSizeInBytes() { |
| int size = 0; |
| for (Enumeration<String> e = getAttributeNames(); e.hasMoreElements(); ) { |
| // Don't use this.getAttribute() because we don't want to deserialize |
| // the value. |
| Object value = super.getAttribute(e.nextElement()); |
| if (value instanceof byte[]) { |
| size += ((byte[]) value).length; |
| } |
| } |
| |
| return size; |
| } |
| |
| @SuppressWarnings({ "unchecked", "rawtypes" }) |
| private Map<String,byte[]> getSerializedAttributes() { |
| // Iterate the values and serialize them if necessary before sending them to the server. This makes the application classes unnecessary on the server. |
| Map<String,byte[]> serializedAttributes = new ConcurrentHashMap<String,byte[]>(); |
| for (Iterator i= this.attributes.entrySet().iterator(); i.hasNext();) { |
| Map.Entry<String,Object> entry = (Map.Entry<String,Object>) i.next(); |
| Object value = entry.getValue(); |
| byte[] serializedValue = value instanceof byte[] ? (byte[]) value : serialize(value); |
| serializedAttributes.put(entry.getKey(), serializedValue); |
| } |
| return serializedAttributes; |
| } |
| |
| private byte[] serialize(Object obj) { |
| byte[] serializedValue = null; |
| try { |
| serializedValue = BlobHelper.serializeToBlob(obj); |
| } catch (IOException e) { |
| StringBuilder builder = new StringBuilder(); |
| builder |
| .append(this) |
| .append(": Object ") |
| .append(obj) |
| .append(" cannot be serialized due to the following exception"); |
| ((DeltaSessionManager) getManager()).getLogger().warn(builder.toString(), e); |
| } |
| return serializedValue; |
| } |
| |
| @Override |
| public String toString() { |
| return new StringBuilder() |
| .append("DeltaSession[") |
| .append("id=") |
| .append(getId()) |
| .append("; context=") |
| .append(this.contextName) |
| .append("; sessionRegionName=") |
| .append(this.sessionRegionName) |
| .append("; operatingRegionName=") |
| .append(getOperatingRegion() == null ? "unset" : getOperatingRegion().getFullPath()) |
| .append("]") |
| .toString(); |
| } |
| } |