blob: c5befb512d22c14b53e7a51c8f0e67733518c2c2 [file] [log] [blame]
/*=========================================================================
* 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.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import com.gemstone.gemfire.cache.Region;
import com.gemstone.gemfire.modules.util.ContextMapper;
import org.apache.catalina.Container;
import org.apache.catalina.Context;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Loader;
import org.apache.catalina.Session;
import org.apache.catalina.Valve;
import org.apache.catalina.session.ManagerBase;
import org.apache.catalina.session.StandardSession;
import org.apache.catalina.util.CustomObjectInputStream;
import org.apache.catalina.util.LifecycleSupport;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import com.gemstone.gemfire.cache.query.Query;
import com.gemstone.gemfire.cache.query.QueryService;
import com.gemstone.gemfire.cache.query.SelectResults;
import com.gemstone.gemfire.cache.CacheFactory;
import com.gemstone.gemfire.internal.cache.GemFireCacheImpl;
import com.gemstone.gemfire.modules.session.catalina.internal.DeltaSessionStatistics;
import com.gemstone.gemfire.modules.util.RegionConfiguration;
import com.gemstone.gemfire.modules.util.RegionHelper;
abstract public class DeltaSessionManager extends ManagerBase implements Lifecycle,
PropertyChangeListener, SessionManager {
/**
* The <code>LifecycleSupport</code> for this component.
*/
protected LifecycleSupport lifecycle = new LifecycleSupport(this);
/**
* The number of rejected sessions.
*/
private AtomicInteger rejectedSessions;
/**
* The maximum number of active Sessions allowed, or -1 for no limit.
*/
protected int maxActiveSessions = -1;
/**
* Has this <code>Manager</code> been started?
*/
protected AtomicBoolean started = new AtomicBoolean(false);
/**
* The name of this <code>Manager</code>
*/
protected String name;
protected Valve jvmRouteBinderValve;
protected Valve commitSessionValve;
protected SessionCache sessionCache;
protected static final String DEFAULT_REGION_NAME = RegionHelper.NAME + "_sessions";
protected static final boolean DEFAULT_ENABLE_GATEWAY_DELTA_REPLICATION = false;
protected static final boolean DEFAULT_ENABLE_GATEWAY_REPLICATION = false;
protected static final boolean DEFAULT_ENABLE_DEBUG_LISTENER = false;
protected static final boolean DEFAULT_ENABLE_COMMIT_VALVE = true;
protected static final boolean DEFAULT_ENABLE_COMMIT_VALVE_FAILFAST = false;
protected static final boolean DEFAULT_PREFER_DESERIALIZED_FORM = true;
/*
* This *MUST* only be assigned during start/startInternal otherwise it will be associated
* with the incorrect context class loader.
*/
protected Log LOGGER;
protected String regionName = DEFAULT_REGION_NAME;
protected String regionAttributesId; // the default is different for client-server and peer-to-peer
protected Boolean enableLocalCache; // the default is different for client-server and peer-to-peer
protected boolean enableCommitValve = DEFAULT_ENABLE_COMMIT_VALVE;
protected boolean enableCommitValveFailfast = DEFAULT_ENABLE_COMMIT_VALVE_FAILFAST;
protected boolean enableGatewayDeltaReplication = DEFAULT_ENABLE_GATEWAY_DELTA_REPLICATION;
protected boolean enableGatewayReplication = DEFAULT_ENABLE_GATEWAY_REPLICATION;
protected boolean enableDebugListener = DEFAULT_ENABLE_DEBUG_LISTENER;
protected boolean preferDeserializedForm = DEFAULT_PREFER_DESERIALIZED_FORM;
private Timer timer;
private final Set<String> sessionsToTouch;
private static final long TIMER_TASK_PERIOD =
Long.getLong("gemfiremodules.sessionTimerTaskPeriod", 10000);
private static final long TIMER_TASK_DELAY =
Long.getLong("gemfiremodules.sessionTimerTaskDelay", 10000);
public DeltaSessionManager() {
// Create the set to store sessions to be touched after get attribute requests
this.sessionsToTouch = Collections.newSetFromMap(
new ConcurrentHashMap<String, Boolean>());
}
@Override
public String getRegionName() {
return this.regionName;
}
public void setRegionName(String regionName) {
this.regionName = regionName;
}
@Override
public String getRegionAttributesId() {
// This property will be null if it hasn't been set in the context.xml file.
// Since its default is dependent on the session cache, get the default from
// the session cache.
if (this.regionAttributesId == null) {
this.regionAttributesId = getSessionCache().getDefaultRegionAttributesId();
}
return this.regionAttributesId;
}
public void setRegionAttributesId(String regionType) {
this.regionAttributesId = regionType;
}
@Override
public boolean getEnableLocalCache() {
// This property will be null if it hasn't been set in the context.xml file.
// Since its default is dependent on the session cache, get the default from
// the session cache.
if (this.enableLocalCache == null) {
this.enableLocalCache = getSessionCache().getDefaultEnableLocalCache();
}
return this.enableLocalCache;
}
public void setEnableLocalCache(boolean enableLocalCache) {
this.enableLocalCache = enableLocalCache;
}
public int getMaxActiveSessions() {
return this.maxActiveSessions;
}
public void setMaxActiveSessions(int maxActiveSessions) {
int oldMaxActiveSessions = this.maxActiveSessions;
this.maxActiveSessions = maxActiveSessions;
support.firePropertyChange("maxActiveSessions",
new Integer(oldMaxActiveSessions), new Integer(this.maxActiveSessions));
}
@Override
public boolean getEnableGatewayDeltaReplication() {
//return this.enableGatewayDeltaReplication;
return false; // disabled
}
public void setEnableGatewayDeltaReplication(boolean enableGatewayDeltaReplication) {
this.enableGatewayDeltaReplication = enableGatewayDeltaReplication;
}
@Override
public boolean getEnableGatewayReplication() {
return this.enableGatewayReplication;
}
public void setEnableGatewayReplication(boolean enableGatewayReplication) {
this.enableGatewayReplication = enableGatewayReplication;
}
@Override
public boolean getEnableDebugListener() {
return this.enableDebugListener;
}
public void setEnableDebugListener(boolean enableDebugListener) {
this.enableDebugListener = enableDebugListener;
}
@Override
public boolean isCommitValveEnabled() {
return this.enableCommitValve;
}
public void setEnableCommitValve(boolean enable) {
this.enableCommitValve = enable;
}
@Override
public boolean isCommitValveFailfastEnabled() {
return this.enableCommitValveFailfast;
}
public void setEnableCommitValveFailfast(boolean enable) {
this.enableCommitValveFailfast = enable;
}
@Override
public boolean isBackingCacheAvailable() {
return sessionCache.isBackingCacheAvailable();
}
public void setPreferDeserializedForm(boolean enable) {
this.preferDeserializedForm = enable;
}
@Override
public boolean getPreferDeserializedForm() {
return this.preferDeserializedForm;
}
@Override
public String getStatisticsName() {
return getContainer().getName().replace("/", "");
}
@Override
public Log getLogger() {
if (LOGGER == null) {
LOGGER = LogFactory.getLog(DeltaSessionManager.class);
}
return LOGGER;
}
public SessionCache getSessionCache() {
return this.sessionCache;
}
public DeltaSessionStatistics getStatistics() {
return getSessionCache().getStatistics();
}
public boolean isPeerToPeer() {
return getSessionCache().isPeerToPeer();
}
public boolean isClientServer() {
return getSessionCache().isClientServer();
}
/**
* This method was taken from StandardManager to set the default
* maxInactiveInterval based on the container (to 30 minutes).
*
* Set the Container with which this Manager has been associated. If it is a
* Context (the usual case), listen for changes to the session timeout
* property.
*
* @param container
* The associated Container
*/
@Override
public void setContainer(Container container) {
// De-register from the old Container (if any)
if ((this.container != null) && (this.container instanceof Context)) {
((Context) this.container).removePropertyChangeListener(this);
}
// Default processing provided by our superclass
super.setContainer(container);
// Register with the new Container (if any)
if ((this.container != null) && (this.container instanceof Context)) {
// Overwrite the max inactive interval with the context's session timeout.
setMaxInactiveInterval(((Context) this.container).getSessionTimeout() * 60);
((Context) this.container).addPropertyChangeListener(this);
}
}
@Override
public Session findSession(String id) throws IOException {
if (id == null) {
return null;
}
if (getLogger().isDebugEnabled()) {
getLogger().debug(this + ": Finding session " + id + " in " + getSessionCache().getOperatingRegionName());
}
DeltaSession session = (DeltaSession) getSessionCache().getSession(id);
/*
* Check that the context name for this session is the same as this manager's.
* This comes into play when multiple versions of a webapp are deployed and
* active at the same time; the context name will contain an embedded
* version number; something like /test###2.
*/
if (session != null &&
! session.getContextName().isEmpty() &&
! getContainer().getName().equals(session.getContextName())) {
getLogger().info(this + ": Session " + id +
" rejected as container name and context do not match: " +
getContainer().getName() + " != " + session.getContextName());
session = null;
}
if (session == null) {
if (getLogger().isDebugEnabled()) {
getLogger().debug(this + ": Did not find session " + id + " in " + getSessionCache().getOperatingRegionName());
}
} else {
if (getLogger().isDebugEnabled()) {
getLogger().debug(this + ": Found session " + id + " in " + getSessionCache().getOperatingRegionName() + ": " + session);
}
// The session was previously stored. Set new to false.
session.setNew(false);
// Check the manager.
// If the manager is null, the session was replicated and this is a
// failover situation. Reset the manager and activate the session.
if (session.getManager() == null) {
DeltaSession ds = (DeltaSession) session;
ds.setOwner(this);
ds.activate();
}
}
return session;
}
protected void initializeSessionCache() {
// Retrieve the cache
GemFireCacheImpl cache = (GemFireCacheImpl) CacheFactory.getAnyInstance();
if (cache == null) {
throw new IllegalStateException("No cache exists. Please configure either a PeerToPeerCacheLifecycleListener or ClientServerCacheLifecycleListener in the server.xml file.");
}
// Create the appropriate session cache
this.sessionCache = cache.isClient()
? new ClientServerSessionCache(this, cache)
: new PeerToPeerSessionCache(this, cache);
// Initialize the session cache
this.sessionCache.initialize();
}
@Override
protected StandardSession getNewSession() {
return new DeltaSession(this);
}
@Override
public void remove(Session session) {
remove(session, false);
}
public void remove(Session session, boolean update) {
//super.remove(session);
// Remove the session from the region if necessary.
// It will have already been removed if it expired implicitly.
DeltaSession ds = (DeltaSession) session;
if (ds.getExpired()) {
if (getLogger().isDebugEnabled()) {
getLogger().debug(this + ": Expired session " + session.getId() + " from " + getSessionCache().getOperatingRegionName());
}
} else {
if (getLogger().isDebugEnabled()) {
getLogger().debug(this + ": Destroying session " + session.getId() + " from " + getSessionCache().getOperatingRegionName());
}
getSessionCache().destroySession(session.getId());
if (getLogger().isDebugEnabled()) {
getLogger().debug(this + ": Destroyed session " + session.getId() + " from " + getSessionCache().getOperatingRegionName());
}
}
}
@Override
public void add(Session session) {
//super.add(session);
if (getLogger().isDebugEnabled()) {
getLogger().debug(this + ": Storing session " + session.getId() + " into " + getSessionCache().getOperatingRegionName());
}
getSessionCache().putSession(session);
if (getLogger().isDebugEnabled()) {
getLogger().debug(this + ": Stored session " + session.getId() + " into " + getSessionCache().getOperatingRegionName());
}
getSessionCache().getStatistics().incSessionsCreated();
}
@Override
public int getRejectedSessions() {
return this.rejectedSessions.get();
}
public void setRejectedSessions(int rejectedSessions) {
this.rejectedSessions.set(rejectedSessions);
}
private void incrementRejectedSessions() {
this.rejectedSessions.incrementAndGet();
}
/**
* Returns the number of active sessions
*
* @return number of sessions active
*/
@Override
public int getActiveSessions() {
return getSessionCache().size();
}
/**
* For debugging: return a list of all session ids currently active
*
*/
@Override
public String listSessionIds() {
StringBuilder builder = new StringBuilder();
Iterator<String> sessionIds = getSessionCache().keySet().iterator();
while (sessionIds.hasNext()) {
builder.append(sessionIds.next());
if (sessionIds.hasNext()) {
builder.append(" ");
}
}
return builder.toString();
}
/*
* If local caching is enabled, add the session to the set of sessions to be
* touched. A timer task will be periodically invoked to get the session in
* the session region to update its last accessed time. This prevents the
* session from expiring in the case where the application is only getting
* attributes from the session and never putting attributes into the
* session. If local caching is disabled. the session's last accessed time
* would already have been updated properly in the sessions region.
*
* Note: Due to issues in GemFire expiry, sessions are always asynchronously
* touched using a function regardless whether or not local caching is
* enabled. This prevents premature expiration.
*/
protected void addSessionToTouch(String sessionId) {
this.sessionsToTouch.add(sessionId);
}
protected Set<String> getSessionsToTouch() {
return this.sessionsToTouch;
}
protected boolean removeTouchedSession(String sessionId) {
return this.sessionsToTouch.remove(sessionId);
}
protected void scheduleTimerTasks() {
// Create the timer
this.timer = new Timer("Timer for " + toString(), true);
// Schedule the task to handle sessions to be touched
scheduleTouchSessionsTask();
// Schedule the task to maintain the maxActive sessions
scheduleDetermineMaxActiveSessionsTask();
}
private void scheduleTouchSessionsTask() {
TimerTask task = new TimerTask() {
@Override
public void run() {
// Get the sessionIds to touch and clear the set inside synchronization
Set<String> sessionIds = null;
sessionIds = new HashSet<String>(getSessionsToTouch());
getSessionsToTouch().clear();
// Touch the sessions we currently have
if (sessionIds != null && (!sessionIds.isEmpty())) {
getSessionCache().touchSessions(sessionIds);
if (getLogger().isDebugEnabled()) {
getLogger().debug(DeltaSessionManager.this + ": Touched sessions: " + sessionIds);
}
}
}
};
this.timer.schedule(task, TIMER_TASK_DELAY, TIMER_TASK_PERIOD);
}
protected void cancelTimer() {
if (timer != null) {
this.timer.cancel();
}
}
private void scheduleDetermineMaxActiveSessionsTask() {
TimerTask task = new TimerTask() {
@Override
public void run() {
int currentActiveSessions = getSessionCache().size();
if (currentActiveSessions > getMaxActive()) {
setMaxActive(currentActiveSessions);
if (getLogger().isDebugEnabled()) {
getLogger().debug(DeltaSessionManager.this + ": Set max active sessions: " + currentActiveSessions);
}
}
}
};
this.timer.schedule(task, TIMER_TASK_DELAY, TIMER_TASK_PERIOD);
}
@Override
public void load() throws ClassNotFoundException, IOException {
doLoad();
ContextMapper.addContext(getContainer().getName(), this);
}
@Override
public void unload() throws IOException {
doUnload();
ContextMapper.removeContext(getContainer().getName());
}
protected void registerJvmRouteBinderValve() {
if (getLogger().isDebugEnabled()) {
getLogger().debug(this + ": Registering JVM route binder valve");
}
jvmRouteBinderValve = new JvmRouteBinderValve();
getContainer().getPipeline().addValve(jvmRouteBinderValve);
}
protected void unregisterJvmRouteBinderValve() {
if (getLogger().isDebugEnabled()) {
getLogger().debug(this + ": Unregistering JVM route binder valve");
}
if (jvmRouteBinderValve != null) {
getContainer().getPipeline().removeValve(jvmRouteBinderValve);
}
}
protected void registerCommitSessionValve() {
if (getLogger().isDebugEnabled()) {
getLogger().debug(this + ": Registering CommitSessionValve");
}
commitSessionValve = new CommitSessionValve();
getContainer().getPipeline().addValve(commitSessionValve);
}
protected void unregisterCommitSessionValve() {
if (getLogger().isDebugEnabled()) {
getLogger().debug(this + ": Unregistering CommitSessionValve");
}
if (commitSessionValve != null) {
getContainer().getPipeline().removeValve(commitSessionValve);
}
}
// ------------------------------ Lifecycle Methods
/**
* Add a lifecycle event listener to this component.
*
* @param listener The listener to add
*/
@Override
public void addLifecycleListener(LifecycleListener listener) {
this.lifecycle.addLifecycleListener(listener);
}
/**
* Get the lifecycle listeners associated with this lifecycle. If this
* Lifecycle has no listeners registered, a zero-length array is returned.
*/
@Override
public LifecycleListener[] findLifecycleListeners() {
return this.lifecycle.findLifecycleListeners();
}
/**
* Remove a lifecycle event listener from this component.
*
* @param listener The listener to remove
*/
@Override
public void removeLifecycleListener(LifecycleListener listener) {
this.lifecycle.removeLifecycleListener(listener);
}
/**
* Process property change events from our associated Context.
*
* Part of this method implementation was taken from StandardManager. The
* sessionTimeout can be changed in the web.xml which is processed after the
* context.xml. The context (and the default session timeout) would already
* have been set in this Manager. This is the way to get the new session
* timeout value specified in the web.xml.
*
* The precedence order for setting the session timeout value is:
*
* <ol>
* <li>the max inactive interval is set based on the Manager defined in the
* context.xml
* <li>the max inactive interval is then overwritten by the value of the
* Context's session timeout when setContainer is called
* <li>the max inactive interval is then overwritten by the value of the
* session-timeout specified in the web.xml (if any)
* </ol>
*
* @param event
* The property change event that has occurred
*/
@Override
public void propertyChange(PropertyChangeEvent event) {
// Validate the source of this event
if (!(event.getSource() instanceof Context)) {
return;
}
Context context = (Context) event.getSource();
// Process a relevant property change
if (event.getPropertyName().equals("sessionTimeout")) {
try {
int interval = ((Integer) event.getNewValue()).intValue();
if (interval < RegionConfiguration.DEFAULT_MAX_INACTIVE_INTERVAL) {
getLogger().warn("The configured session timeout of " + interval + " minutes is invalid. Using the original value of " + event.getOldValue() + " minutes.");
interval = ((Integer) event.getOldValue()).intValue();;
}
// StandardContext.setSessionTimeout passes -1 if the configured timeout
// is 0; otherwise it passes the value set in web.xml. If the interval
// parameter equals the default, set the max inactive interval to the
// default (no expiration); otherwise set it in seconds.
setMaxInactiveInterval(interval == RegionConfiguration.DEFAULT_MAX_INACTIVE_INTERVAL
? RegionConfiguration.DEFAULT_MAX_INACTIVE_INTERVAL
: interval * 60);
} catch (NumberFormatException e) {
getLogger().error(sm.getString("standardManager.sessionTimeout", event
.getNewValue().toString()));
}
}
}
/**
* Save any currently active sessions in the appropriate persistence
* mechanism, if any. If persistence is not supported, this method
* returns without doing anything.
*
* @exception IOException if an input/output error occurs
*/
protected void doUnload() throws IOException {
QueryService querySvc = sessionCache.getCache().getQueryService();
Context context;
if (getContainer() instanceof Context) {
context = (Context) getContainer();
} else {
getLogger().error("Unable to unload sessions - container is of type " +
getContainer().getClass().getName() + " instead of StandardContext");
return;
}
String regionName;
if (getRegionName().startsWith("/")) {
regionName = getRegionName();
} else {
regionName = "/" + getRegionName();
}
Query query = querySvc.newQuery("select s.id from " + regionName +
" as s where s.contextName = '" + context.getPath() + "'");
getLogger().debug("Query: " + query.getQueryString());
SelectResults results;
try {
results = (SelectResults) query.execute();
} catch (Exception ex) {
getLogger().error("Unable to perform query during doUnload", ex);
return;
}
if (results.isEmpty()) {
getLogger().debug("No sessions to unload for context " + context.getPath());
return; // nothing to do
}
// Open an output stream to the specified pathname, if any
File store = sessionStore(context.getPath());
if (store == null) {
return;
}
if (getLogger().isDebugEnabled()) {
getLogger().debug("Unloading sessions to " + store.getAbsolutePath());
}
FileOutputStream fos = null;
BufferedOutputStream bos = null;
ObjectOutputStream oos = null;
boolean error = false;
try {
fos = new FileOutputStream(store.getAbsolutePath());
bos = new BufferedOutputStream(fos);
oos = new ObjectOutputStream(bos);
} catch (IOException e) {
error = true;
getLogger().error("Exception unloading sessions", e);
throw e;
} finally {
if (error) {
if (oos != null) {
try {
oos.close();
} catch (IOException ioe) {
// Ignore
}
}
if (bos != null) {
try {
bos.close();
} catch (IOException ioe) {
// Ignore
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException ioe) {
// Ignore
}
}
}
}
ArrayList<StandardSession> list = new ArrayList<StandardSession>();
Iterator<String> elements = results.iterator();
while (elements.hasNext()) {
String id = elements.next();
DeltaSession session = (DeltaSession) findSession(id);
if (session != null) {
list.add(session);
}
}
// Write the number of active sessions, followed by the details
if (getLogger().isDebugEnabled())
getLogger().debug("Unloading " + list.size() + " sessions");
try {
oos.writeObject(new Integer(list.size()));
for (StandardSession session : list) {
session.passivate();
session.writeObjectData(oos);
}
} catch (IOException e) {
getLogger().error("Exception unloading sessions", e);
try {
oos.close();
} catch (IOException f) {
// Ignore
}
throw e;
}
// Flush and close the output stream
try {
oos.flush();
} finally {
try {
oos.close();
} catch (IOException f) {
// Ignore
}
}
// Locally destroy the sessions we just wrote
if (getSessionCache().isClientServer()) {
for (StandardSession session : list) {
if (getLogger().isDebugEnabled()) {
getLogger().debug("Locally destroying session " + session.getId());
}
getSessionCache().getOperatingRegion().localDestroy(session.getId());
}
}
// // Expire all the sessions we just wrote
// if (getLogger().isDebugEnabled()) {
// getLogger().debug("Expiring " + list.size() + " persisted sessions");
// }
// Iterator<StandardSession> expires = list.iterator();
// while (expires.hasNext()) {
// StandardSession session = expires.next();
// try {
// session.expire(false);
// } catch (Throwable t) {
//// ExceptionUtils.handleThrowable(t);
// } finally {
// session.recycle();
// }
// }
if (getLogger().isDebugEnabled()) {
getLogger().debug("Unloading complete");
}
}
/**
* Load any currently active sessions that were previously unloaded
* to the appropriate persistence mechanism, if any. If persistence is not
* supported, this method returns without doing anything.
*
* @exception ClassNotFoundException if a serialized class cannot be
* found during the reload
* @exception IOException if an input/output error occurs
*/
protected void doLoad() throws ClassNotFoundException, IOException {
Context context;
if (getContainer() instanceof Context) {
context = (Context) getContainer();
} else {
getLogger().error("Unable to unload sessions - container is of type " +
getContainer().getClass().getName() + " instead of StandardContext");
return;
}
// Open an input stream to the specified pathname, if any
File store = sessionStore(context.getPath());
if (store == null) {
getLogger().debug("No session store file found");
return;
}
if (getLogger().isDebugEnabled()) {
getLogger().debug("Loading sessions from " + store.getAbsolutePath());
}
FileInputStream fis = null;
BufferedInputStream bis = null;
ObjectInputStream ois = null;
Loader loader = null;
ClassLoader classLoader = null;
try {
fis = new FileInputStream(store.getAbsolutePath());
bis = new BufferedInputStream(fis);
if (container != null) {
loader = container.getLoader();
}
if (loader != null) {
classLoader = loader.getClassLoader();
}
if (classLoader != null) {
if (getLogger().isDebugEnabled()) {
getLogger().debug("Creating custom object input stream for class loader");
}
ois = new CustomObjectInputStream(bis, classLoader);
} else {
if (getLogger().isDebugEnabled()) {
getLogger().debug("Creating standard object input stream");
}
ois = new ObjectInputStream(bis);
}
} catch (FileNotFoundException e) {
if (getLogger().isDebugEnabled()) {
getLogger().debug("No persisted data file found");
}
return;
} catch (IOException e) {
getLogger().error("Exception loading sessions", e);
if (fis != null) {
try {
fis.close();
} catch (IOException f) {
// Ignore
}
}
if (bis != null) {
try {
bis.close();
} catch (IOException f) {
// Ignore
}
}
throw e;
}
// Load the previously unloaded active sessions
try {
Integer count = (Integer) ois.readObject();
int n = count.intValue();
if (getLogger().isDebugEnabled()) {
getLogger().debug("Loading " + n + " persisted sessions");
}
for (int i = 0; i < n; i++) {
StandardSession session = getNewSession();
session.readObjectData(ois);
session.setManager(this);
Region region = getSessionCache().getOperatingRegion();
DeltaSession existingSession = (DeltaSession) region.get(session.getId());
// Check whether the existing session is newer
if (existingSession != null &&
existingSession.getLastAccessedTime() > session.getLastAccessedTime()) {
if (getLogger().isDebugEnabled()) {
getLogger().debug("Loaded session " + session.getId() + " is older than cached copy");
}
continue;
}
// Check whether the new session has already expired
if (! session.isValid()) {
if (getLogger().isDebugEnabled()) {
getLogger().debug("Loaded session " + session.getId() + " is invalid");
}
continue;
}
getLogger().debug("Loading session " + session.getId());
session.activate();
add(session);
}
} catch (ClassNotFoundException e) {
getLogger().error(e);
try {
ois.close();
} catch (IOException f) {
// Ignore
}
throw e;
} catch (IOException e) {
getLogger().error(e);
try {
ois.close();
} catch (IOException f) {
// Ignore
}
throw e;
} finally {
// Close the input stream
try {
ois.close();
} catch (IOException f) {
// ignored
}
// Delete the persistent storage file
if (store.exists()) {
store.delete();
}
}
}
/**
* Return a File object representing the pathname to our
* persistence file, if any.
*/
private File sessionStore(String ctxPath) {
String storeDir = System.getProperty("catalina.base");
if (storeDir == null || storeDir.isEmpty()) {
storeDir = System.getProperty("java.io.tmpdir");
} else {
storeDir += System.getProperty("file.separator") + "temp";
}
File file = new File(storeDir,
ctxPath.replaceAll("/", "_") + ".sessions.ser");
return (file);
}
@Override
public String toString() {
return new StringBuilder()
.append(getClass().getSimpleName())
.append("[")
.append("container=")
.append(getContainer())
.append("; regionName=")
.append(this.regionName)
.append("; regionAttributesId=")
.append(this.regionAttributesId)
.append("]")
.toString();
}
}