/* * 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.catalina.session; import java.io.IOException; import java.security.AccessController; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.apache.catalina.Lifecycle; import org.apache.catalina.LifecycleException; import org.apache.catalina.LifecycleState; import org.apache.catalina.Session; import org.apache.catalina.Store; import org.apache.catalina.StoreManager; import org.apache.catalina.security.SecurityUtil; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; /** * Extends the {@link ManagerBase} class to implement most of the * functionality required by a Manager which supports any kind of * persistence, even if only for restarts. *
* IMPLEMENTATION NOTE: Correct behavior of session storing and
* reloading depends upon external calls to the {@link Lifecycle#start()}
* and {@link Lifecycle#stop()} methods of this class
* at the correct times.
*
* @author Craig R. McClanahan
*/
public abstract class PersistentManagerBase extends ManagerBase
implements StoreManager {
private final Log log = LogFactory.getLog(PersistentManagerBase.class); // must not be static
// ---------------------------------------------------- Security Classes
private class PrivilegedStoreClear
implements PrivilegedExceptionAction
* Note that this is not a hard limit: sessions are checked
* against this age limit periodically according to {@code processExpiresFrequency}.
* This value should be considered to indicate when a session is
* ripe for backing up.
*
* So it is possible that a session may be idle for {@code maxIdleBackup +
* processExpiresFrequency * engine.backgroundProcessorDelay} seconds, plus the time it takes to handle other
* session expiration, swapping, etc. tasks.
*
* @param backup The number of seconds after their last accessed
* time when they should be written to the Store.
*/
public void setMaxIdleBackup (int backup) {
if (backup == this.maxIdleBackup)
return;
int oldBackup = this.maxIdleBackup;
this.maxIdleBackup = backup;
support.firePropertyChange("maxIdleBackup",
Integer.valueOf(oldBackup),
Integer.valueOf(this.maxIdleBackup));
}
/**
* @return The maximum time in seconds a session may be idle before it is
* eligible to be swapped to disk due to inactivity. A value of {@code -1}
* means sessions should not be swapped out just because of inactivity.
*/
public int getMaxIdleSwap() {
return maxIdleSwap;
}
/**
* Sets the maximum time in seconds a session may be idle before it is
* eligible to be swapped to disk due to inactivity. Setting this to
* {@code -1} means sessions should not be swapped out just because of
* inactivity.
*
* @param max time in seconds to wait for possible swap out
*/
public void setMaxIdleSwap(int max) {
if (max == this.maxIdleSwap)
return;
int oldMaxIdleSwap = this.maxIdleSwap;
this.maxIdleSwap = max;
support.firePropertyChange("maxIdleSwap",
Integer.valueOf(oldMaxIdleSwap),
Integer.valueOf(this.maxIdleSwap));
}
/**
* @return The minimum time in seconds a session must be idle before it is
* eligible to be swapped to disk to keep the active session count below
* maxActiveSessions. A value of {@code -1} means sessions will not be
* swapped out to keep the active session count down.
*/
public int getMinIdleSwap() {
return minIdleSwap;
}
/**
* Sets the minimum time in seconds a session must be idle before it is
* eligible to be swapped to disk to keep the active session count below
* maxActiveSessions. Setting to {@code -1} means sessions will not be
* swapped out to keep the active session count down.
*
* @param min time in seconds before a possible swap out
*/
public void setMinIdleSwap(int min) {
if (this.minIdleSwap == min)
return;
int oldMinIdleSwap = this.minIdleSwap;
this.minIdleSwap = min;
support.firePropertyChange("minIdleSwap",
Integer.valueOf(oldMinIdleSwap),
Integer.valueOf(this.minIdleSwap));
}
/**
* Check, whether a session is loaded in memory
*
* @param id The session id for the session to be searched for
* @return {@code true}, if the session id is loaded in memory
* otherwise {@code false} is returned
*/
public boolean isLoaded( String id ){
try {
if ( super.findSession(id) != null )
return true;
} catch (IOException e) {
log.error("checking isLoaded for id, " + id + ", "+e.getMessage(), e);
}
return false;
}
@Override
public String getName() {
return name;
}
/**
* Set the Store object which will manage persistent Session
* storage for this Manager.
*
* @param store the associated Store
*/
public void setStore(Store store) {
this.store = store;
store.setManager(this);
}
/**
* @return the Store object which manages persistent Session
* storage for this Manager.
*/
@Override
public Store getStore() {
return this.store;
}
/**
* Indicates whether sessions are saved when the Manager is shut down
* properly. This requires the {@link #unload()} method to be called.
*
* @return {@code true}, when sessions should be saved on restart,
* {code false} otherwise
*/
public boolean getSaveOnRestart() {
return saveOnRestart;
}
/**
* Set the option to save sessions to the Store when the Manager is
* shut down, then loaded when the Manager starts again. If set to
* false, any sessions found in the Store may still be picked up when
* the Manager is started again.
*
* @param saveOnRestart {@code true} if sessions should be saved on restart, {@code false} if
* they should be ignored.
*/
public void setSaveOnRestart(boolean saveOnRestart) {
if (saveOnRestart == this.saveOnRestart)
return;
boolean oldSaveOnRestart = this.saveOnRestart;
this.saveOnRestart = saveOnRestart;
support.firePropertyChange("saveOnRestart",
Boolean.valueOf(oldSaveOnRestart),
Boolean.valueOf(this.saveOnRestart));
}
// --------------------------------------------------------- Public Methods
/**
* Clear all sessions from the Store.
*/
public void clearStore() {
if (store == null)
return;
try {
if (SecurityUtil.isPackageProtectionEnabled()){
try{
AccessController.doPrivileged(new PrivilegedStoreClear());
}catch(PrivilegedActionException ex){
Exception exception = ex.getException();
log.error("Exception clearing the Store: " + exception,
exception);
}
} else {
store.clear();
}
} catch (IOException e) {
log.error("Exception clearing the Store: " + e, e);
}
}
/**
* {@inheritDoc}
*
* Direct call to processExpires and processPersistenceChecks
*/
@Override
public void processExpires() {
long timeNow = System.currentTimeMillis();
Session sessions[] = findSessions();
int expireHere = 0 ;
if(log.isDebugEnabled())
log.debug("Start expire sessions " + getName() + " at " + timeNow + " sessioncount " + sessions.length);
for (int i = 0; i < sessions.length; i++) {
if (!sessions[i].isValid()) {
expiredSessions.incrementAndGet();
expireHere++;
}
}
processPersistenceChecks();
if (getStore() instanceof StoreBase) {
((StoreBase) getStore()).processExpires();
}
long timeEnd = System.currentTimeMillis();
if(log.isDebugEnabled())
log.debug("End expire sessions " + getName() + " processingTime " + (timeEnd - timeNow) + " expired sessions: " + expireHere);
processingTime += (timeEnd - timeNow);
}
/**
* Called by the background thread after active sessions have been checked
* for expiration, to allow sessions to be swapped out, backed up, etc.
*/
public void processPersistenceChecks() {
processMaxIdleSwaps();
processMaxActiveSwaps();
processMaxIdleBackups();
}
/**
* {@inheritDoc}
*
* This method checks the persistence store if persistence is enabled,
* otherwise just uses the functionality from ManagerBase.
*/
@Override
public Session findSession(String id) throws IOException {
Session session = super.findSession(id);
// OK, at this point, we're not sure if another thread is trying to
// remove the session or not so the only way around this is to lock it
// (or attempt to) and then try to get it by this session id again. If
// the other code ran swapOut, then we should get a null back during
// this run, and if not, we lock it out so we can access the session
// safely.
if(session != null) {
synchronized(session){
session = super.findSession(session.getIdInternal());
if(session != null){
// To keep any external calling code from messing up the
// concurrency.
session.access();
session.endAccess();
}
}
}
if (session != null)
return session;
// See if the Session is in the Store
session = swapIn(id);
return session;
}
/**
* Remove this Session from the active Sessions for this Manager,
* but not from the Store. (Used by the PersistentValve)
*
* @param session Session to be removed
*/
@Override
public void removeSuper(Session session) {
super.remove(session, false);
}
/**
* Load all sessions found in the persistence mechanism, assuming
* they are marked as valid and have not passed their expiration
* limit. If persistence is not supported, this method returns
* without doing anything.
*
* Note that by default, this method is not called by the MiddleManager
* class. In order to use it, a subclass must specifically call it,
* for example in the start() and/or processPersistenceChecks() methods.
*/
@Override
public void load() {
// Initialize our internal data structures
sessions.clear();
if (store == null)
return;
String[] ids = null;
try {
if (SecurityUtil.isPackageProtectionEnabled()){
try{
ids = AccessController.doPrivileged(
new PrivilegedStoreKeys());
}catch(PrivilegedActionException ex){
Exception exception = ex.getException();
log.error("Exception in the Store during load: "
+ exception, exception);
return;
}
} else {
ids = store.keys();
}
} catch (IOException e) {
log.error("Can't load sessions from store, " + e.getMessage(), e);
return;
}
int n = ids.length;
if (n == 0)
return;
if (log.isDebugEnabled())
log.debug(sm.getString("persistentManager.loading", String.valueOf(n)));
for (int i = 0; i < n; i++)
try {
swapIn(ids[i]);
} catch (IOException e) {
log.error("Failed load session from store, " + e.getMessage(), e);
}
}
/**
* {@inheritDoc}
*
* Remove this Session from the Store.
*/
@Override
public void remove(Session session, boolean update) {
super.remove (session, update);
if (store != null){
removeSession(session.getIdInternal());
}
}
/**
* Remove this Session from the active Sessions for this Manager,
* and from the Store.
*
* @param id Session's id to be removed
*/
protected void removeSession(String id){
try {
if (SecurityUtil.isPackageProtectionEnabled()){
try{
AccessController.doPrivileged(new PrivilegedStoreRemove(id));
}catch(PrivilegedActionException ex){
Exception exception = ex.getException();
log.error("Exception in the Store during removeSession: "
+ exception, exception);
}
} else {
store.remove(id);
}
} catch (IOException e) {
log.error("Exception removing session " + e.getMessage(), e);
}
}
/**
* Save all currently active sessions in the appropriate persistence
* mechanism, if any. If persistence is not supported, this method
* returns without doing anything.
*
* Note that by default, this method is not called by the MiddleManager
* class. In order to use it, a subclass must specifically call it,
* for example in the stop() and/or processPersistenceChecks() methods.
*/
@Override
public void unload() {
if (store == null)
return;
Session sessions[] = findSessions();
int n = sessions.length;
if (n == 0)
return;
if (log.isDebugEnabled())
log.debug(sm.getString("persistentManager.unloading",
String.valueOf(n)));
for (int i = 0; i < n; i++)
try {
swapOut(sessions[i]);
} catch (IOException e) {
// This is logged in writeSession()
}
}
@Override
public int getActiveSessionsFull() {
// In memory session count
int result = getActiveSessions();
try {
// Store session count
result += getStore().getSize();
} catch (IOException ioe) {
log.warn(sm.getString("persistentManager.storeSizeException"));
}
return result;
}
@Override
public Setunload
* and load methods are called.
*/
protected boolean saveOnRestart = true;
/**
* How long a session must be idle before it should be backed up.
* {@code -1} means sessions won't be backed up.
*/
protected int maxIdleBackup = -1;
/**
* The minimum time in seconds a session must be idle before it is eligible
* to be swapped to disk to keep the active session count below
* maxActiveSessions. Setting to {@code -1} means sessions will not be
* swapped out to keep the active session count down.
*/
protected int minIdleSwap = -1;
/**
* The maximum time in seconds a session may be idle before it is eligible
* to be swapped to disk due to inactivity. Setting this to {@code -1} means
* sessions should not be swapped out just because of inactivity.
*/
protected int maxIdleSwap = -1;
/**
* Sessions currently being swapped in and the associated locks
*/
private final Map