
com.aspectran.core.component.session.ManagedSession Maven / Gradle / Ivy
The newest version!
/*
* Copyright (c) 2008-2025 The Aspectran Project
*
* Licensed 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 com.aspectran.core.component.session;
import com.aspectran.utils.ToStringBuilder;
import com.aspectran.utils.annotation.jsr305.NonNull;
import com.aspectran.utils.thread.AutoLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* A default {@link Session} implementation.
*
* Created: 2017. 6. 13.
*/
public class ManagedSession implements Session {
private static final Logger logger = LoggerFactory.getLogger(ManagedSession.class);
private final AutoLock autoLock = new AutoLock();
private final AbstractSessionManager sessionManager;
private final SessionInactivityTimer inactivityTimer;
private SessionData sessionData;
private boolean newSession;
private boolean justLoaded;
private boolean resident;
private int requests;
private State state = State.VALID;
/**
* state of the session: valid, invalid or being invalidated
*/
private enum State {
VALID,
INVALID,
INVALIDATING
}
private Session.DestroyedReason destroyedReason;
protected ManagedSession(AbstractSessionManager sessionManager,
@NonNull SessionData sessionData, boolean newSession) {
this.sessionManager = sessionManager;
this.inactivityTimer = new SessionInactivityTimer(sessionManager, this);
this.sessionData = sessionData;
this.newSession = newSession;
if (newSession) {
// new sessions will skip access() call
this.sessionData.setDirty(true);
this.requests = 1;
reduceInactiveInterval();
} else {
this.justLoaded = true;
}
}
public SessionManager getSessionManager() {
return sessionManager;
}
protected SessionData getSessionData() {
return sessionData;
}
protected void setSessionData(SessionData sessionData) {
this.sessionData = sessionData;
}
@Override
public String getId() {
try (AutoLock ignored = autoLock.lock()) {
return sessionData.getId();
}
}
@Override
public T getAttribute(String name) {
try (AutoLock ignored = autoLock.lock()) {
checkValidForRead();
return NonPersistentValue.unwrap(sessionData.getAttribute(name));
}
}
@Override
public Set getAttributeNames() {
try (AutoLock ignored = autoLock.lock()) {
checkValidForRead();
return sessionData.getKeys();
}
}
@Override
public Object setAttribute(String name, Object value) {
try (AutoLock ignored = autoLock.lock()) {
// if session is not valid, don't accept the set
checkValidForWrite();
Object old = sessionData.setAttribute(name, value);
if (value == null && old == null) {
return null; // if same as remove attribute but attribute was already removed, no change
}
Object newValue = NonPersistentValue.unwrap(value);
Object oldValue = NonPersistentValue.unwrap(old);
onSessionAttributeUpdate(name, oldValue, newValue);
return oldValue;
}
}
@Override
public Object removeAttribute(String name) {
return setAttribute(name, null);
}
@Override
public long getCreationTime() {
try (AutoLock ignored = autoLock.lock()) {
checkValidForRead();
return sessionData.getCreated();
}
}
@Override
public long getLastAccessedTime() {
try (AutoLock ignored = autoLock.lock()) {
return sessionData.getLastAccessed();
}
}
@Override
public int getMaxInactiveInterval() {
try (AutoLock ignored = autoLock.lock()) {
if (sessionData.getInactiveInterval() > 0L) {
return (int)TimeUnit.MILLISECONDS.toSeconds(sessionData.getInactiveInterval());
} else {
return -1;
}
}
}
@Override
public void setMaxInactiveInterval(int inactiveIntervalSecs) {
try (AutoLock ignored = autoLock.lock()) {
sessionData.restoreInactiveInterval();
long inactiveInterval = Math.max(TimeUnit.SECONDS.toMillis(inactiveIntervalSecs), -1L);
sessionData.setInactiveInterval(inactiveInterval);
sessionData.calcAndSetExpiry(System.currentTimeMillis());
sessionData.setDirty(true);
if (logger.isDebugEnabled()) {
if (inactiveIntervalSecs <= 0) {
logger.debug("Session {} is now immortal (maxInactiveInterval={})",
sessionData.getId(), inactiveIntervalSecs);
} else {
logger.debug("Session {} maxInactiveInterval={}", sessionData.getId(), inactiveIntervalSecs);
}
}
}
}
private void reduceInactiveInterval() {
int maxIdleSecs = sessionManager.getMaxIdleSecsForNew();
if (maxIdleSecs > 0) {
long inactiveInterval = TimeUnit.SECONDS.toMillis(maxIdleSecs);
sessionData.reduceInactiveInterval(inactiveInterval);
}
}
protected int getEvictionIdleSecs() {
if (sessionData.getExtraInactiveInterval() > 0) {
return sessionManager.getSessionCache().getEvictionIdleSecsForNew();
} else {
return sessionManager.getSessionCache().getEvictionIdleSecs();
}
}
@Override
public boolean isNew() {
try (AutoLock ignored = autoLock.lock()) {
checkValidForRead();
return newSession;
}
}
protected boolean isResident() {
return resident;
}
protected void setResident(boolean resident) {
this.resident = resident;
if (!resident) {
inactivityTimer.destroy();
}
}
@Override
public boolean isTempResident() {
try (AutoLock ignored = autoLock.lock()) {
return (resident && sessionData.getExtraInactiveInterval() > 0);
}
}
/**
* Returns the current number of requests that are active in the Session.
* @return the number of active requests for this session
*/
protected long getRequests() {
try (AutoLock ignored = autoLock.lock()) {
return requests;
}
}
@Override
public boolean isValid() {
try (AutoLock ignored = autoLock.lock()) {
return (state == State.VALID);
}
}
@Override
public boolean access() {
try (AutoLock ignored = autoLock.lock()) {
if (state != State.VALID) {
return false;
}
if (requests == 0 && !justLoaded) {
// if in clustering mode, session data will be updated
sessionManager.refreshSession(this);
}
newSession = false;
justLoaded = false;
// new sessions will have full inactivity interval upon second access
if (sessionData.restoreInactiveInterval()) {
sessionData.setDirty(true);
sessionManager.onSessionResided(this);
}
long now = System.currentTimeMillis();
sessionData.setAccessed(now);
sessionData.calcAndSetExpiry(now);
if (isExpiredAt(now)) {
invalidate();
return false;
}
requests++;
// temporarily stop the idle timer
if (logger.isDebugEnabled()) {
logger.debug("Session {} accessed, stopping timer, active requests={}", getId(), requests);
}
inactivityTimer.cancel();
return true;
}
}
@Override
public void complete() {
try (AutoLock ignored = autoLock.lock()) {
requests--;
if (requests < 0) {
int temp = requests;
requests = 0;
throw new IllegalStateException("Incomplete session transaction; requests=" + temp);
}
if (logger.isDebugEnabled()) {
logger.debug("Session {} complete, active requests={}", getId(), requests);
}
// start the inactivity timer if necessary
if (requests == 0) {
// update the expiry time to take account of the time all requests spent inside of the session
long now = System.currentTimeMillis();
sessionData.calcAndSetExpiry(now);
sessionData.setLastAccessed(sessionData.getAccessed());
sessionManager.releaseSession(this);
inactivityTimer.schedule(calculateInactivityTimeout(now));
}
}
}
/**
* Calculate what the session timer setting should be based on:
* the time remaining before the session expires
* and any idle eviction time configured.
* The timer value will be the lesser of the above.
* @param now the time at which to calculate remaining expiry
* @return the time remaining before expiry or inactivity timeout
*/
protected long calculateInactivityTimeout(long now) {
long result;
try (AutoLock ignored = autoLock.lock()) {
long remaining = sessionData.getExpiry() - now;
long inactiveInterval = sessionData.getInactiveInterval();
int evictionIdleSecs = getEvictionIdleSecs();
if (inactiveInterval <= 0L) {
// sessions are immortal, they never expire
if (evictionIdleSecs < SessionCache.EVICT_ON_INACTIVITY) {
// we do not want to evict inactive sessions
result = -1L;
if (logger.isTraceEnabled()) {
logger.trace("Session id={} is immortal && no inactivity eviction", getId());
}
} else {
// sessions are immortal but we want to evict after inactivity
result = TimeUnit.SECONDS.toMillis(evictionIdleSecs);
if (logger.isTraceEnabled()) {
logger.trace("Session {} is immortal; evict after {} second(s) inactivity",
getId(), evictionIdleSecs);
}
}
} else {
// sessions are not immortal
if (evictionIdleSecs == SessionCache.NEVER_EVICT) {
// timeout is the time remaining until its expiry
result = Math.max(remaining, 0L);
if (logger.isTraceEnabled()) {
logger.trace("Session id={} no eviction", getId());
}
} else if (evictionIdleSecs == SessionCache.EVICT_ON_SESSION_EXIT) {
// session will not remain in the cache, so no timeout
result = -1L;
if (logger.isTraceEnabled()) {
logger.trace("Session id={} evict on exit", getId());
}
} else {
// want to evict on idle: timer is the lesser of the session's
// expiration remaining and the time to evict
if (remaining > 0L) {
result = Math.min(inactiveInterval, TimeUnit.SECONDS.toMillis(evictionIdleSecs));
} else {
result = 0L;
}
if (logger.isTraceEnabled()) {
logger.trace("Session id={} timer set to lesser of maxIdleSeconds={} and evictionIdleSeconds={}",
getId(), TimeUnit.MILLISECONDS.toSeconds(inactiveInterval), evictionIdleSecs);
}
}
}
}
return result;
}
/**
* Called by users to invalidate a session, or called by the
* access method as a request enters the session if the session
* has expired, or called by manager as a result of scavenger
* expiring session.
*/
@Override
public void invalidate() {
boolean result = beginInvalidate();
// if the session was not already invalid, or in process of being
// invalidated, do invalidate
if (result) {
try {
try {
// do the invalidation
try (AutoLock ignored = autoLock.lock()) {
if (getDestroyedReason() == null) {
setDestroyedReason(DestroyedReason.INVALIDATED);
}
sessionManager.onSessionDestroyed(this);
}
} catch (Exception e) {
logger.warn("Error during Session destroy listener", e);
} finally {
// call the attribute removed listeners and finally mark it as invalid
finishInvalidate();
sessionManager.removeSession(sessionData.getId(), false);
}
} catch (Exception e) {
logger.warn("Unable to invalidate session {}", this, e);
}
}
}
protected boolean beginInvalidate() {
boolean result = false;
try (AutoLock ignored = autoLock.lock()) {
switch (state) {
case INVALID:
// spec does not allow invalidation of already invalid session
throw new IllegalStateException();
case INVALIDATING:
if (logger.isDebugEnabled()) {
logger.debug("Session {} already being invalidated", sessionData.getId());
}
break;
case VALID:
// only first change from valid to invalidating should be actionable
state = State.INVALIDATING;
result = true;
break;
default:
throw new IllegalStateException();
}
}
return result;
}
protected void finishInvalidate() {
try (AutoLock ignored = autoLock.lock()) {
try {
if (state == State.VALID || state == State.INVALIDATING) {
if (logger.isDebugEnabled()) {
logger.debug("Invalidate session id={}", sessionData.getId());
}
Set keys;
do {
keys = sessionData.getKeys();
for (String key : keys) {
Object old = sessionData.setAttribute(key, null);
if (old != null) {
onSessionAttributeUpdate(key, old, null);
}
}
} while (!keys.isEmpty());
}
} finally {
// mark as invalid
state = State.INVALID;
sessionManager.getStatistics().sessionExpired();
sessionManager.recordSessionTime(this);
}
}
}
@Override
public DestroyedReason getDestroyedReason() {
return destroyedReason;
}
protected void setDestroyedReason(DestroyedReason destroyedReason) {
this.destroyedReason = destroyedReason;
}
/**
* Check to see if session has expired as at the time given.
* @param time the time since the epoch in ms
* @return true if expired
*/
protected boolean isExpiredAt(long time) {
try (AutoLock ignored = autoLock.lock()) {
checkValidForRead();
return sessionData.isExpiredAt(time);
}
}
/**
* Check if the Session has been idle longer than a number of seconds.
* @param sec the number of seconds
* @return true if the session has been idle longer than the interval
*/
protected boolean isIdleLongerThan(int sec) {
long now = System.currentTimeMillis();
try (AutoLock ignored = autoLock.lock()) {
return ((sessionData.getAccessed() + TimeUnit.SECONDS.toMillis(sec)) <= now);
}
}
/**
* Call binding and attribute listeners based on the new and old values of
* the attribute.
* @param name name of the attribute
* @param newValue new value of the attribute
* @param oldValue previous value of the attribute
*/
protected void onSessionAttributeUpdate(String name, Object oldValue, Object newValue) {
if (newValue == null || !newValue.equals(oldValue)) {
if (oldValue != null) {
unbindValue(name, oldValue);
}
if (newValue != null) {
bindValue(name, newValue);
}
}
sessionManager.onSessionAttributeUpdate(this, name, oldValue, newValue);
}
/**
* Unbind value if value implements {@link SessionBindingListener}
* (calls {@link SessionBindingListener#valueUnbound(Session, String, Object)})
* @param name the name with which the object is bound or unbound
* @param value the bound value
*/
protected void unbindValue(String name, Object value) {
if (value instanceof SessionBindingListener listener) {
listener.valueUnbound(this, name, value);
}
}
/**
* Bind value if value implements {@link SessionBindingListener}
* (calls {@link SessionBindingListener#valueBound(Session, String, Object)})
* @param name the name with which the object is bound or unbound
* @param value the bound value
*/
protected void bindValue(String name, Object value) {
if (value instanceof SessionBindingListener listener) {
listener.valueBound(this, name, value);
}
}
/**
* Check that the session can be modified.
* @throws IllegalStateException if the session is invalid
*/
protected void checkValidForWrite() {
if (state == State.INVALID) {
throw new IllegalStateException("Not valid for write; session " + this);
}
if (state == State.INVALIDATING) {
return; // in the process of being invalidated, listeners may try to remove attributes
}
if (!isResident()) {
throw new IllegalStateException("Not valid for write; session " + this);
}
}
/**
* Check that the session data can be read.
* @throws IllegalStateException if the session is invalid
*/
protected void checkValidForRead() {
if (state == State.INVALID) {
throw new IllegalStateException("Invalid for read; session " + this);
}
if (state == State.INVALIDATING) {
return;
}
if (!isResident()) {
throw new IllegalStateException("Invalid for read; session id=" + sessionData.getId() + " not resident");
}
}
/**
* Grab the lock on the session.
* @return the lock
*/
protected AutoLock lock() {
return autoLock.lock();
}
@Override
public String toString() {
try (AutoLock ignored = autoLock.lock()) {
ToStringBuilder tsb = new ToStringBuilder();
tsb.append("id", sessionData.getId());
tsb.append("state", state);
tsb.append("requests", requests);
tsb.appendForce("resident", resident);
return tsb.toString();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy