| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more contributor license |
| * agreements. See the NOTICE file distributed with this work for additional information regarding |
| * copyright ownership. The ASF licenses this file to You 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 org.apache.geode.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.List; |
| 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 org.apache.catalina.Container; |
| import org.apache.catalina.Context; |
| import org.apache.catalina.Lifecycle; |
| import org.apache.catalina.Loader; |
| import org.apache.catalina.Pipeline; |
| 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.juli.logging.Log; |
| import org.apache.juli.logging.LogFactory; |
| |
| import org.apache.geode.cache.Cache; |
| import org.apache.geode.cache.CacheFactory; |
| import org.apache.geode.cache.Region; |
| import org.apache.geode.cache.query.Query; |
| import org.apache.geode.cache.query.QueryService; |
| import org.apache.geode.cache.query.SelectResults; |
| import org.apache.geode.internal.cache.GemFireCacheImpl; |
| import org.apache.geode.modules.session.catalina.internal.DeltaSessionStatistics; |
| import org.apache.geode.modules.util.ContextMapper; |
| import org.apache.geode.modules.util.RegionConfiguration; |
| import org.apache.geode.modules.util.RegionHelper; |
| |
| public abstract class DeltaSessionManager extends ManagerBase |
| implements Lifecycle, PropertyChangeListener, SessionManager { |
| |
| static final String catalinaBaseSystemProperty = "catalina.base"; |
| static final String javaTempDirSystemProperty = "java.io.tmpdir"; |
| static final String fileSeparatorSystemProperty = "file.separator"; |
| /** |
| * The number of rejected sessions. |
| */ |
| private AtomicInteger rejectedSessions; |
| |
| /** |
| * The maximum number of active Sessions allowed, or -1 for no limit. |
| */ |
| private 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; |
| |
| private Valve jvmRouteBinderValve; |
| |
| private Valve commitSessionValve; |
| |
| private SessionCache sessionCache; |
| |
| private static final String DEFAULT_REGION_NAME = RegionHelper.NAME + "_sessions"; |
| |
| private static final boolean DEFAULT_ENABLE_GATEWAY_REPLICATION = false; |
| |
| private static final boolean DEFAULT_ENABLE_DEBUG_LISTENER = false; |
| |
| private static final boolean DEFAULT_ENABLE_COMMIT_VALVE = true; |
| |
| private static final boolean DEFAULT_ENABLE_COMMIT_VALVE_FAILFAST = false; |
| |
| private 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. |
| */ |
| private Log LOGGER; |
| |
| protected String regionName = DEFAULT_REGION_NAME; |
| |
| private String regionAttributesId; // the default is different for client-server and |
| // peer-to-peer |
| |
| private Boolean enableLocalCache; // the default is different for client-server and peer-to-peer |
| |
| private boolean enableCommitValve = DEFAULT_ENABLE_COMMIT_VALVE; |
| |
| private boolean enableCommitValveFailfast = DEFAULT_ENABLE_COMMIT_VALVE_FAILFAST; |
| |
| private boolean enableGatewayReplication = DEFAULT_ENABLE_GATEWAY_REPLICATION; |
| |
| private boolean enableDebugListener = DEFAULT_ENABLE_DEBUG_LISTENER; |
| |
| private 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() { |
| this.rejectedSessions = new AtomicInteger(0); |
| // Create the set to store sessions to be touched after get attribute requests |
| this.sessionsToTouch = Collections.newSetFromMap(new ConcurrentHashMap<>()); |
| } |
| |
| @Override |
| public String getRegionName() { |
| return this.regionName; |
| } |
| |
| public void setRegionName(String regionName) { |
| this.regionName = regionName; |
| } |
| |
| @Override |
| public void setMaxInactiveInterval(final int interval) { |
| super.setMaxInactiveInterval(interval); |
| } |
| |
| @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; |
| } |
| |
| @SuppressWarnings("unused") |
| 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; |
| } |
| |
| @SuppressWarnings("unused") |
| public void setEnableLocalCache(boolean enableLocalCache) { |
| this.enableLocalCache = enableLocalCache; |
| } |
| |
| @SuppressWarnings("unused") |
| public int getMaxActiveSessions() { |
| return this.maxActiveSessions; |
| } |
| |
| @SuppressWarnings("unused") |
| 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 |
| } |
| |
| @SuppressWarnings("unused") |
| public void setEnableGatewayDeltaReplication(boolean enableGatewayDeltaReplication) { |
| // this.enableGatewayDeltaReplication = enableGatewayDeltaReplication; |
| // Disabled. Keeping the method for backward compatibility. |
| } |
| |
| @Override |
| public boolean getEnableGatewayReplication() { |
| return this.enableGatewayReplication; |
| } |
| |
| @SuppressWarnings("unused") |
| public void setEnableGatewayReplication(boolean enableGatewayReplication) { |
| this.enableGatewayReplication = enableGatewayReplication; |
| } |
| |
| @Override |
| public boolean getEnableDebugListener() { |
| return this.enableDebugListener; |
| } |
| |
| @SuppressWarnings("unused") |
| 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; |
| } |
| |
| @SuppressWarnings("unused") |
| public void setEnableCommitValveFailfast(boolean enable) { |
| this.enableCommitValveFailfast = enable; |
| } |
| |
| @Override |
| public boolean isBackingCacheAvailable() { |
| return sessionCache.isBackingCacheAvailable(); |
| } |
| |
| @SuppressWarnings("unused") |
| public void setPreferDeserializedForm(boolean enable) { |
| this.preferDeserializedForm = enable; |
| } |
| |
| @Override |
| public boolean getPreferDeserializedForm() { |
| return this.preferDeserializedForm; |
| } |
| |
| @Override |
| public String getStatisticsName() { |
| return getContextName().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(); |
| } |
| |
| boolean isPeerToPeer() { |
| return getSessionCache().isPeerToPeer(); |
| } |
| |
| @SuppressWarnings("unused") |
| public boolean isClientServer() { |
| return getSessionCache().isClientServer(); |
| } |
| |
| /** |
| * This method was taken from StandardManager to set the default maxInactiveInterval based on the |
| * container (to 30 minutes). |
| * <p> |
| * 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)) { |
| 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); |
| 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()); |
| } |
| DeltaSessionInterface session = (DeltaSessionInterface) 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() |
| && !getContextName().equals(session.getContextName())) { |
| getLogger() |
| .info(this + ": Session " + id + " rejected as container name and context do not match: " |
| + getContextName() + " != " + 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) { |
| session.setOwner(this); |
| session.activate(); |
| } |
| } |
| |
| return session; |
| } |
| |
| protected void initializeSessionCache() { |
| // Retrieve the cache |
| GemFireCacheImpl cache = (GemFireCacheImpl) getAnyCacheInstance(); |
| 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 |
| initSessionCache(); |
| } |
| |
| void initSessionCache() { |
| this.sessionCache.initialize(); |
| } |
| |
| Cache getAnyCacheInstance() { |
| return CacheFactory.getAnyInstance(); |
| } |
| |
| @Override |
| protected StandardSession getNewSession() { |
| return new DeltaSession(this); |
| } |
| |
| @Override |
| public void remove(Session session) { |
| remove(session, false); |
| } |
| |
| public void remove(Session session, @SuppressWarnings("unused") boolean update) { |
| // super.remove(session); |
| // Remove the session from the region if necessary. |
| // It will have already been removed if it expired implicitly. |
| DeltaSessionInterface ds = (DeltaSessionInterface) 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(); |
| } |
| |
| @Override |
| public void setRejectedSessions(int rejectedSessions) { |
| this.rejectedSessions.set(rejectedSessions); |
| } |
| |
| /** |
| * 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. |
| */ |
| void addSessionToTouch(String sessionId) { |
| this.sessionsToTouch.add(sessionId); |
| } |
| |
| protected Set<String> getSessionsToTouch() { |
| return this.sessionsToTouch; |
| } |
| |
| 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; |
| sessionIds = new HashSet<>(getSessionsToTouch()); |
| getSessionsToTouch().clear(); |
| |
| // Touch the sessions we currently have |
| if (!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(getContextName(), this); |
| } |
| |
| @Override |
| public void unload() throws IOException { |
| doUnload(); |
| ContextMapper.removeContext(getContextName()); |
| } |
| |
| protected void registerJvmRouteBinderValve() { |
| if (getLogger().isDebugEnabled()) { |
| getLogger().debug(this + ": Registering JVM route binder valve"); |
| } |
| jvmRouteBinderValve = new JvmRouteBinderValve(); |
| getPipeline().addValve(jvmRouteBinderValve); |
| } |
| |
| Pipeline getPipeline() { |
| return getContainer().getPipeline(); |
| } |
| |
| protected void unregisterJvmRouteBinderValve() { |
| if (getLogger().isDebugEnabled()) { |
| getLogger().debug(this + ": Unregistering JVM route binder valve"); |
| } |
| if (jvmRouteBinderValve != null) { |
| getPipeline().removeValve(jvmRouteBinderValve); |
| } |
| } |
| |
| protected void registerCommitSessionValve() { |
| if (getLogger().isDebugEnabled()) { |
| getLogger().debug(this + ": Registering CommitSessionValve"); |
| } |
| commitSessionValve = new CommitSessionValve(); |
| getPipeline().addValve(commitSessionValve); |
| } |
| |
| protected void unregisterCommitSessionValve() { |
| if (getLogger().isDebugEnabled()) { |
| getLogger().debug(this + ": Unregistering CommitSessionValve"); |
| } |
| if (commitSessionValve != null) { |
| getPipeline().removeValve(commitSessionValve); |
| } |
| } |
| |
| // ------------------------------ Lifecycle Methods |
| |
| /** |
| * Process property change events from our associated Context. |
| * <p> |
| * 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. |
| * <p> |
| * The precedence order for setting the session timeout value is: |
| * <p> |
| * <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; |
| } |
| |
| // Process a relevant property change |
| if (event.getPropertyName().equals("sessionTimeout")) { |
| try { |
| int interval = (Integer) event.getNewValue(); |
| 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(); |
| } |
| // 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. |
| * |
| * @throws IOException if an input/output error occurs |
| */ |
| private void doUnload() throws IOException { |
| QueryService querySvc = getSessionCache().getCache().getQueryService(); |
| Context context = getTheContext(); |
| |
| if (context == null) { |
| 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() + "'"); |
| |
| if (getLogger().isDebugEnabled()) { |
| 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 = getFileOutputStream(store); |
| bos = getBufferedOutputStream(fos); |
| oos = getObjectOutputStream(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<DeltaSessionInterface> list = new ArrayList<>(); |
| @SuppressWarnings("unchecked") |
| Iterator<String> elements = (Iterator<String>) results.iterator(); |
| while (elements.hasNext()) { |
| String id = elements.next(); |
| DeltaSessionInterface session = (DeltaSessionInterface) 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 { |
| writeToObjectOutputStream(oos, list); |
| for (DeltaSessionInterface session : list) { |
| if (session instanceof StandardSession) { |
| StandardSession standardSession = (StandardSession) session; |
| standardSession.passivate(); |
| standardSession.writeObjectData(oos); |
| } else { |
| // All DeltaSessionInterfaces as of Geode 1.0 should be based on StandardSession |
| throw new IOException("Session should be of type StandardSession"); |
| } |
| } |
| } 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 (DeltaSessionInterface session : list) { |
| if (getLogger().isDebugEnabled()) { |
| getLogger().debug("Locally destroying session " + session.getId()); |
| } |
| getSessionCache().getOperatingRegion().localDestroy(session.getId()); |
| } |
| } |
| |
| 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. |
| * |
| * @throws ClassNotFoundException if a serialized class cannot be found during the reload |
| * @throws IOException if an input/output error occurs |
| */ |
| private void doLoad() throws ClassNotFoundException, IOException { |
| Context context = getTheContext(); |
| if (context == null) { |
| 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; |
| Loader loader = null; |
| ClassLoader classLoader = null; |
| try { |
| fis = getFileInputStream(store); |
| bis = getBufferedInputStream(fis); |
| if (getTheContext() != null) { |
| loader = getTheContext().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 = getObjectInputStream(bis); |
| } |
| } catch (FileNotFoundException e) { |
| if (getLogger().isDebugEnabled()) { |
| getLogger().debug("No persisted data file found"); |
| } |
| return; |
| } catch (IOException e) { |
| getLogger().error("Exception loading sessions", e); |
| try { |
| fis.close(); |
| } catch (IOException f) { |
| // Ignore |
| } |
| try { |
| bis.close(); |
| } catch (IOException f) { |
| // Ignore |
| } |
| throw e; |
| } |
| |
| // Load the previously unloaded active sessions |
| try { |
| int n = getSessionCountFromObjectInputStream(ois); |
| 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(); |
| DeltaSessionInterface existingSession = (DeltaSessionInterface) 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 | 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()) { |
| if (!store.delete()) { |
| getLogger().warn("Couldn't delete persistent storage file " + store.getAbsolutePath()); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Return a File object representing the pathname to our persistence file, if any. |
| */ |
| private File sessionStore(String ctxPath) { |
| String storeDir = getSystemPropertyValue(catalinaBaseSystemProperty); |
| if (storeDir == null || storeDir.isEmpty()) { |
| storeDir = getSystemPropertyValue(javaTempDirSystemProperty); |
| } else { |
| storeDir += getSystemPropertyValue(fileSeparatorSystemProperty) + "temp"; |
| } |
| |
| return getFileAtPath(storeDir, ctxPath); |
| } |
| |
| String getSystemPropertyValue(String propertyKey) { |
| return System.getProperty(propertyKey); |
| } |
| |
| File getFileAtPath(String storeDir, String ctxPath) { |
| return (new File(storeDir, ctxPath.replaceAll("/", "_") + ".sessions.ser")); |
| } |
| |
| FileInputStream getFileInputStream(File file) throws FileNotFoundException { |
| return new FileInputStream(file.getAbsolutePath()); |
| } |
| |
| BufferedInputStream getBufferedInputStream(FileInputStream fis) { |
| return new BufferedInputStream(fis); |
| } |
| |
| ObjectInputStream getObjectInputStream(BufferedInputStream bis) throws IOException { |
| return new ObjectInputStream(bis); |
| } |
| |
| FileOutputStream getFileOutputStream(File file) throws FileNotFoundException { |
| return new FileOutputStream(file.getAbsolutePath()); |
| } |
| |
| BufferedOutputStream getBufferedOutputStream(FileOutputStream fos) { |
| return new BufferedOutputStream(fos); |
| } |
| |
| ObjectOutputStream getObjectOutputStream(BufferedOutputStream bos) throws IOException { |
| return new ObjectOutputStream(bos); |
| } |
| |
| void writeToObjectOutputStream(ObjectOutputStream oos, List listToWrite) throws IOException { |
| oos.writeObject(listToWrite.size()); |
| } |
| |
| int getSessionCountFromObjectInputStream(ObjectInputStream ois) |
| throws IOException, ClassNotFoundException { |
| return (Integer) ois.readObject(); |
| } |
| |
| @Override |
| public String toString() { |
| return getClass().getSimpleName() + "[" + "container=" |
| + getTheContext() + "; regionName=" + this.regionName |
| + "; regionAttributesId=" + this.regionAttributesId + "]"; |
| } |
| |
| String getContextName() { |
| return getTheContext().getName(); |
| } |
| |
| public Context getTheContext() { |
| if (getContainer() instanceof Context) { |
| return (Context) getContainer(); |
| } else { |
| getLogger().error("Unable to unload sessions - container is of type " |
| + getContainer().getClass().getName() + " instead of StandardContext"); |
| return null; |
| } |
| } |
| } |