/* * 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;
/** * Minimal implementation of the <b>Manager</b> interface that supports no session persistence or distributable * capabilities. This class may be subclassed to create more sophisticated Manager implementations. * * @author Craig R. McClanahan
*/ publicabstractclass ManagerBase extends LifecycleMBeanBase implements Manager {
privatefinal Log log = LogFactory.getLog(ManagerBase.class); // must not be static
/** * The Context with which this Manager is associated.
*/ private Context context;
/** * The descriptive name of this Manager implementation (for logging).
*/ privatestaticfinal String name = "ManagerBase";
/** * The Java class name of the secure random number generator class to be used when generating session identifiers. * The random number generator class must be self-seeding and have a zero-argument constructor. If not specified, an * instance of {@link java.security.SecureRandom} will be generated.
*/ protected String secureRandomClass = null;
/** * The name of the algorithm to use to create instances of {@link java.security.SecureRandom} which are used to * generate session IDs. If no algorithm is specified, SHA1PRNG is used. If SHA1PRNG is not available, the platform * default will be used. To use the platform default (which may be SHA1PRNG), specify the empty string. If an * invalid algorithm and/or provider is specified the SecureRandom instances will be created using the defaults. If * that fails, the SecureRandom instances will be created using platform defaults.
*/ protected String secureRandomAlgorithm = SessionIdGeneratorBase.DEFAULT_SECURE_RANDOM_ALGORITHM;
/** * The name of the provider to use to create instances of {@link java.security.SecureRandom} which are used to * generate session IDs. If no provider is specified the platform default is used. If an invalid algorithm and/or * provider is specified the SecureRandom instances will be created using the defaults. If that fails, the * SecureRandom instances will be created using platform defaults.
*/ protected String secureRandomProvider = null;
/** * The longest time (in seconds) that an expired session had been alive.
*/ protectedvolatileint sessionMaxAliveTime; privatefinal Object sessionMaxAliveTimeUpdateLock = new Object();
// Use LinkedList as the Deques are initialised by filling with null protectedfinal Deque<SessionTiming> sessionCreationTiming = new LinkedList<>(); protectedfinal Deque<SessionTiming> sessionExpirationTiming = new LinkedList<>();
/** * Number of sessions that have expired.
*/ protectedfinal AtomicLong expiredSessions = new AtomicLong(0);
/** * The set of currently active Sessions for this Manager, keyed by session identifier.
*/ protected Map<String,Session> sessions = new ConcurrentHashMap<>();
// Number of sessions created by this manager protectedlong sessionCounter = 0;
protectedvolatileint maxActive = 0;
privatefinal Object maxActiveUpdateLock = new Object();
/** * The maximum number of active Sessions allowed, or -1 for no limit.
*/ protectedint maxActiveSessions = -1;
/** * Number of session creations that failed due to maxActiveSessions.
*/ protectedint rejectedSessions = 0;
// number of duplicated session ids - anything >0 means we have problems protectedvolatileint duplicates = 0;
/** * Processing time during session expiration.
*/ protectedlong processingTime = 0;
/** * Frequency of the session expiration, and related manager operations. Manager operations will be done once for the * specified amount of backgroundProcess calls (ie, the lower the amount, the most often the checks will occur).
*/ protectedint processExpiresFrequency = 6;
/** * The string manager for this package.
*/ protectedstaticfinal StringManager sm = StringManager.getManager(ManagerBase.class);
/** * The property change support for this component.
*/ protectedfinal PropertyChangeSupport support = new PropertyChangeSupport(this);
public ManagerBase() { if (Globals.IS_SECURITY_ENABLED) { // Minimum set required for default distribution/persistence to work // plus String // plus SerializablePrincipal and String[] (required for authentication persistence)
setSessionAttributeValueClassNameFilter("java\\.lang\\.(?:Boolean|Integer|Long|Number|String)" + "|org\\.apache\\.catalina\\.realm\\.GenericPrincipal\\$SerializablePrincipal" + "|\\[Ljava.lang.String;");
setWarnOnSessionAttributeFilterFailure(true);
}
}
/** * Obtain the regular expression used to filter session attribute based on attribute name. The regular expression is * anchored so it must match the entire name * * @return The regular expression currently used to filter attribute names. {@code null} means no filter is applied. * If an empty string is specified then no names will match the filter and all attributes will be * blocked.
*/ public String getSessionAttributeNameFilter() { if (sessionAttributeNamePattern == null) { returnnull;
} return sessionAttributeNamePattern.toString();
}
/** * Set the regular expression to use to filter session attributes based on attribute name. The regular expression is * anchored so it must match the entire name. * * @param sessionAttributeNameFilter The regular expression to use to filter session attributes based on attribute * name. Use {@code null} if no filtering is required. If an empty string is * specified then no names will match the filter and all attributes will be * blocked. * * @throws PatternSyntaxException If the expression is not valid
*/ publicvoid setSessionAttributeNameFilter(String sessionAttributeNameFilter) throws PatternSyntaxException { if (sessionAttributeNameFilter == null || sessionAttributeNameFilter.length() == 0) {
sessionAttributeNamePattern = null;
} else {
sessionAttributeNamePattern = Pattern.compile(sessionAttributeNameFilter);
}
}
/** * Provides {@link #getSessionAttributeNameFilter()} as a pre-compiled regular expression pattern. * * @return The pre-compiled pattern used to filter session attributes based on attribute name. {@code null} means no * filter is applied.
*/ protected Pattern getSessionAttributeNamePattern() { return sessionAttributeNamePattern;
}
/** * Obtain the regular expression used to filter session attribute based on the implementation class of the value. * The regular expression is anchored and must match the fully qualified class name. * * @return The regular expression currently used to filter class names. {@code null} means no filter is applied. If * an empty string is specified then no names will match the filter and all attributes will be blocked.
*/ public String getSessionAttributeValueClassNameFilter() { if (sessionAttributeValueClassNamePattern == null) { returnnull;
} return sessionAttributeValueClassNamePattern.toString();
}
/** * Provides {@link #getSessionAttributeValueClassNameFilter()} as a pre-compiled regular expression pattern. * * @return The pre-compiled pattern used to filter session attributes based on the implementation class name of the * value. {@code null} means no filter is applied.
*/ protected Pattern getSessionAttributeValueClassNamePattern() { return sessionAttributeValueClassNamePattern;
}
/** * Set the regular expression to use to filter classes used for session attributes. The regular expression is * anchored and must match the fully qualified class name. * * @param sessionAttributeValueClassNameFilter The regular expression to use to filter session attributes based on * class name. Use {@code * null} if no filtering is required. If an empty string is specified then no * names will match the filter and all attributes will be blocked. * * @throws PatternSyntaxException If the expression is not valid
*/ publicvoid setSessionAttributeValueClassNameFilter(String sessionAttributeValueClassNameFilter) throws PatternSyntaxException { if (sessionAttributeValueClassNameFilter == null || sessionAttributeValueClassNameFilter.length() == 0) {
sessionAttributeValueClassNamePattern = null;
} else {
sessionAttributeValueClassNamePattern = Pattern.compile(sessionAttributeValueClassNameFilter);
}
}
/** * Should a warn level log message be generated if a session attribute is not persisted / replicated / restored. * * @return {@code true} if a warn level log message should be generated
*/ publicboolean getWarnOnSessionAttributeFilterFailure() { return warnOnSessionAttributeFilterFailure;
}
/** * Configure whether or not a warn level log message should be generated if a session attribute is not persisted / * replicated / restored. * * @param warnOnSessionAttributeFilterFailure {@code true} if the warn level message should be generated
*/ publicvoid setWarnOnSessionAttributeFilterFailure(boolean warnOnSessionAttributeFilterFailure) { this.warnOnSessionAttributeFilterFailure = warnOnSessionAttributeFilterFailure;
}
@Override public Context getContext() { return context;
}
/** * @return The descriptive short name of this Manager implementation.
*/ public String getName() { return name;
}
/** * @return The secure random number generator class name.
*/ public String getSecureRandomClass() { returnthis.secureRandomClass;
}
/** * Set the secure random number generator class name. * * @param secureRandomClass The new secure random number generator class name
*/ publicvoid setSecureRandomClass(String secureRandomClass) {
/** * @return The secure random number generator algorithm name.
*/ public String getSecureRandomAlgorithm() { return secureRandomAlgorithm;
}
/** * Set the secure random number generator algorithm name. * * @param secureRandomAlgorithm The new secure random number generator algorithm name
*/ publicvoid setSecureRandomAlgorithm(String secureRandomAlgorithm) { this.secureRandomAlgorithm = secureRandomAlgorithm;
}
/** * @return The secure random number generator provider name.
*/ public String getSecureRandomProvider() { return secureRandomProvider;
}
/** * Set the secure random number generator provider name. * * @param secureRandomProvider The new secure random number generator provider name
*/ publicvoid setSecureRandomProvider(String secureRandomProvider) { this.secureRandomProvider = secureRandomProvider;
}
/** * @return The frequency of manager checks.
*/ publicint getProcessExpiresFrequency() { returnthis.processExpiresFrequency;
}
/** * Set the manager checks frequency. * * @param processExpiresFrequency the new manager checks frequency
*/ publicvoid setProcessExpiresFrequency(int processExpiresFrequency) {
if (processExpiresFrequency <= 0) { return;
}
int oldProcessExpiresFrequency = this.processExpiresFrequency; this.processExpiresFrequency = processExpiresFrequency;
support.firePropertyChange("processExpiresFrequency", Integer.valueOf(oldProcessExpiresFrequency),
Integer.valueOf(this.processExpiresFrequency));
}
/** * Return whether sessions managed by this manager shall persist authentication information or not. * * @return {@code true}, sessions managed by this manager shall persist authentication information; {@code false} * otherwise
*/ publicboolean getPersistAuthentication() { returnthis.persistAuthentication;
}
/** * Set whether sessions managed by this manager shall persist authentication information or not. * * @param persistAuthentication if {@code true}, sessions managed by this manager shall persist authentication * information
*/ publicvoid setPersistAuthentication(boolean persistAuthentication) { this.persistAuthentication = persistAuthentication;
}
// --------------------------------------------------------- Public Methods
// Ensure caches for timing stats are the right size by filling with // nulls. synchronized (sessionCreationTiming) { while (sessionCreationTiming.size() < TIMING_STATS_CACHE_SIZE) {
sessionCreationTiming.add(null);
}
} synchronized (sessionExpirationTiming) { while (sessionExpirationTiming.size() < TIMING_STATS_CACHE_SIZE) {
sessionExpirationTiming.add(null);
}
}
/* Create sessionIdGenerator if not explicitly configured */
SessionIdGenerator sessionIdGenerator = getSessionIdGenerator(); if (sessionIdGenerator == null) {
sessionIdGenerator = new StandardSessionIdGenerator();
setSessionIdGenerator(sessionIdGenerator);
}
sessionIdGenerator.setJvmRoute(getJvmRoute()); if (sessionIdGenerator instanceof SessionIdGeneratorBase) {
SessionIdGeneratorBase sig = (SessionIdGeneratorBase) sessionIdGenerator;
sig.setSecureRandomAlgorithm(getSecureRandomAlgorithm());
sig.setSecureRandomClass(getSecureRandomClass());
sig.setSecureRandomProvider(getSecureRandomProvider());
}
if (sessionIdGenerator instanceof Lifecycle) {
((Lifecycle) sessionIdGenerator).start();
} else { // Force initialization of the random number generator if (log.isDebugEnabled()) {
log.debug("Force random number initialization starting");
}
sessionIdGenerator.generateSessionId(); if (log.isDebugEnabled()) {
log.debug("Force random number initialization completed");
}
}
}
// Recycle or create a Session instance
Session session = createEmptySession();
// Initialize the properties of the new session and return it
session.setNew(true);
session.setValid(true);
session.setCreationTime(System.currentTimeMillis());
session.setMaxInactiveInterval(getContext().getSessionTimeout() * 60);
String id = sessionId; if (id == null) {
id = generateSessionId();
}
session.setId(id);
sessionCounter++;
@Override publicvoid remove(Session session, boolean update) { // If the session has expired - as opposed to just being removed from // the manager because it is being persisted - update the expired stats if (update) { long timeNow = System.currentTimeMillis(); int timeAlive = (int) (timeNow - session.getCreationTimeInternal()) / 1000;
updateSessionMaxAliveTime(timeAlive);
expiredSessions.incrementAndGet();
SessionTiming timing = new SessionTiming(timeNow, timeAlive); synchronized (sessionExpirationTiming) {
sessionExpirationTiming.add(timing);
sessionExpirationTiming.poll();
}
}
if (session.getIdInternal() != null) {
sessions.remove(session.getIdInternal());
}
}
/** * Get new session class to be used in the doLoad() method. * * @return a new session for use with this manager
*/ protected StandardSession getNewSession() { returnnew StandardSession(this);
}
/** * Generate and return a new session identifier. * * @return a new session id
*/ protected String generateSessionId() {
String result = null;
do { if (result != null) { // Not thread-safe but if one of multiple increments is lost // that is not a big deal since the fact that there was any // duplicate is a much bigger issue.
duplicates++;
}
/** * Retrieve the enclosing Engine for this Manager. * * @return an Engine object (or null).
*/ public Engine getEngine() {
Engine e = null; for (Container c = getContext(); e == null && c != null; c = c.getParent()) { if (c instanceof Engine) {
e = (Engine) c;
}
} return e;
}
/** * Retrieve the JvmRoute for the enclosing Engine. * * @return the JvmRoute or null.
*/ public String getJvmRoute() {
Engine e = getEngine(); return e == null ? null : e.getJvmRoute();
}
/** * Number of duplicated session IDs generated by the random source. Anything bigger than 0 means problems. * * @return The count of duplicates
*/ publicint getDuplicates() { return duplicates;
}
/** * @return The maximum number of active Sessions allowed, or -1 for no limit.
*/ publicint getMaxActiveSessions() { returnthis.maxActiveSessions;
}
/** * Set the maximum number of active Sessions allowed, or -1 for no limit. * * @param max The new maximum number of sessions
*/ publicvoid setMaxActiveSessions(int max) {
int oldMaxActiveSessions = this.maxActiveSessions; this.maxActiveSessions = max;
support.firePropertyChange("maxActiveSessions", Integer.valueOf(oldMaxActiveSessions),
Integer.valueOf(this.maxActiveSessions));
/** * Updates the sessionMaxAliveTime attribute if the candidate value is larger than the current value. * * @param sessionAliveTime The candidate value (in seconds) for the new sessionMaxAliveTime value.
*/ publicvoid updateSessionMaxAliveTime(int sessionAliveTime) { if (sessionAliveTime > this.sessionMaxAliveTime) { synchronized (sessionMaxAliveTimeUpdateLock) { if (sessionAliveTime > this.sessionMaxAliveTime) { this.sessionMaxAliveTime = sessionAliveTime;
}
}
}
}
/** * {@inheritDoc} * <p> * Based on the last 100 sessions to expire. If less than 100 sessions have expired then all available data is used.
*/
@Override publicint getSessionAverageAliveTime() { // Copy current stats
List<SessionTiming> copy; synchronized (sessionExpirationTiming) {
copy = new ArrayList<>(sessionExpirationTiming);
}
// Init int counter = 0; int result = 0;
// Calculate average for (SessionTiming timing : copy) { if (timing != null) { int timeAlive = timing.getDuration();
counter++; // Very careful not to overflow - probably not necessary
result = (result * ((counter - 1) / counter)) + (timeAlive / counter);
}
} return result;
}
/** * {@inheritDoc} * <p> * Based on the creation time of the previous 100 sessions created. If less than 100 sessions have been created then * all available data is used.
*/
@Override publicint getSessionCreateRate() { // Copy current stats
List<SessionTiming> copy; synchronized (sessionCreationTiming) {
copy = new ArrayList<>(sessionCreationTiming);
}
return calculateRate(copy);
}
/** * {@inheritDoc} * <p> * Based on the expiry time of the previous 100 sessions expired. If less than 100 sessions have expired then all * available data is used. * * @return The current rate (in sessions per minute) of session expiration
*/
@Override publicint getSessionExpireRate() { // Copy current stats
List<SessionTiming> copy; synchronized (sessionExpirationTiming) {
copy = new ArrayList<>(sessionExpirationTiming);
}
return calculateRate(copy);
}
privatestaticint calculateRate(List<SessionTiming> sessionTiming) { // Init long now = System.currentTimeMillis(); long oldest = now; int counter = 0; int result = 0;
// Calculate rate for (SessionTiming timing : sessionTiming) { if (timing != null) {
counter++; if (timing.getTimestamp() < oldest) {
oldest = timing.getTimestamp();
}
}
} if (counter > 0) { if (oldest < now) {
result = (1000 * 60 * counter) / (int) (now - oldest);
} else { // Better than reporting zero
result = Integer.MAX_VALUE;
}
} return result;
}
/** * For debugging. * * @return A space separated list of all session IDs currently active
*/ public String listSessionIds() {
StringBuilder sb = new StringBuilder(); for (String s : sessions.keySet()) {
sb.append(s).append(' ');
} return sb.toString();
}
/** * For debugging. * * @param sessionId The ID for the session of interest * @param key The key for the attribute to obtain * * @return The attribute value for the specified session, if found, null otherwise
*/ public String getSessionAttribute(String sessionId, String key) {
Session s = sessions.get(sessionId); if (s == null) { if (log.isInfoEnabled()) {
log.info(sm.getString("managerBase.sessionNotFound", sessionId));
} returnnull;
}
Object o = s.getSession().getAttribute(key); if (o == null) { returnnull;
} return o.toString();
}
/** * Returns information about the session with the given session id. * <p> * The session information is organized as a HashMap, mapping session attribute names to the String representation * of their values. * * @param sessionId Session id * * @return HashMap mapping session attribute names to the String representation of their values, or null if no * session with the specified id exists, or if the session does not have any attributes
*/ public HashMap<String,String> getSession(String sessionId) {
Session s = sessions.get(sessionId); if (s == null) { if (log.isInfoEnabled()) {
log.info(sm.getString("managerBase.sessionNotFound", sessionId));
} returnnull;
}
Enumeration<String> ee = s.getSession().getAttributeNames(); if (ee == null || !ee.hasMoreElements()) { returnnull;
}
HashMap<String,String> map = new HashMap<>(); while (ee.hasMoreElements()) {
String attrName = ee.nextElement();
map.put(attrName, getSessionAttribute(sessionId, attrName));
}
return map;
}
publicvoid expireSession(String sessionId) {
Session s = sessions.get(sessionId); if (s == null) { if (log.isInfoEnabled()) {
log.info(sm.getString("managerBase.sessionNotFound", sessionId));
} return;
}
s.expire();
}
publiclong getThisAccessedTimestamp(String sessionId) {
Session s = sessions.get(sessionId); if (s == null) { if (log.isInfoEnabled()) {
log.info(sm.getString("managerBase.sessionNotFound", sessionId));
} return -1;
} return s.getThisAccessedTime();
}
public String getThisAccessedTime(String sessionId) {
Session s = sessions.get(sessionId); if (s == null) { if (log.isInfoEnabled()) {
log.info(sm.getString("managerBase.sessionNotFound", sessionId));
} return"";
} returnnew Date(s.getThisAccessedTime()).toString();
}
publiclong getLastAccessedTimestamp(String sessionId) {
Session s = sessions.get(sessionId); if (s == null) { if (log.isInfoEnabled()) {
log.info(sm.getString("managerBase.sessionNotFound", sessionId));
} return -1;
} return s.getLastAccessedTime();
}
public String getLastAccessedTime(String sessionId) {
Session s = sessions.get(sessionId); if (s == null) { if (log.isInfoEnabled()) {
log.info(sm.getString("managerBase.sessionNotFound", sessionId));
} return"";
} returnnew Date(s.getLastAccessedTime()).toString();
}
public String getCreationTime(String sessionId) {
Session s = sessions.get(sessionId); if (s == null) { if (log.isInfoEnabled()) {
log.info(sm.getString("managerBase.sessionNotFound", sessionId));
} return"";
} returnnew Date(s.getCreationTime()).toString();
}
publiclong getCreationTimestamp(String sessionId) {
Session s = sessions.get(sessionId); if (s == null) { if (log.isInfoEnabled()) {
log.info(sm.getString("managerBase.sessionNotFound", sessionId));
} return -1;
} return s.getCreationTime();
}
@Override public String toString() { return ToStringUtil.toString(this, context);
}
// -------------------- JMX and Registration --------------------
@Override public String getObjectNameKeyProperties() {
StringBuilder name = new StringBuilder("type=Manager");
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.