package com.liuxinglanyue.session;
import java.io.IOException;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.Enumeration;
import java.util.Iterator;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.LifecycleState;
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.util.LifecycleSupport;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import com.liuxinglanyue.session.config.SessionPersistPolicy;
import com.liuxinglanyue.session.serializer.Serializer;
public abstract class AbstractSessionManager extends ManagerBase implements Lifecycle {
private final Log log = LogFactory.getLog(AbstractSessionManager.class);
protected byte[] NULL_SESSION = "null".getBytes();
// 是否开启调试
// protected boolean debugEnabled = false;
protected SessionHandlerValve handlerValve;
protected ThreadLocal<CustomSession> currentSession = new ThreadLocal<>();
protected ThreadLocal<SessionMetadata> currentSessionMetadata = new ThreadLocal<>();
protected ThreadLocal<String> currentSessionId = new ThreadLocal<>();
protected ThreadLocal<Boolean> currentSessionIsPersisted = new ThreadLocal<>();
protected Serializer serializer;
protected String serializationStrategyClass = "com.liuxinglanyue.session.serializer.JavaSerializer";
protected EnumSet<SessionPersistPolicy> sessionPersistPoliciesSet = EnumSet.of(SessionPersistPolicy.DEFAULT);
protected LifecycleSupport lifecycle = new LifecycleSupport(this);
@Override
public abstract Session createEmptySession();
protected abstract void initializeDatabaseConnection() throws LifecycleException;
protected abstract void connectionDestroy();
protected abstract String generateCustomSessionId(String requestedSessionId);
public abstract String dbSet(Object db, byte[] key, byte[] value);
public abstract long dbExpire(Object db, byte[] key, int seconds);
public abstract void clear();
public abstract int getSize() throws IOException;
public abstract String[] keys() throws IOException;
public abstract byte[] loadSessionDataFromDB(String id) throws IOException;
public abstract void save(Session session, boolean forceSave) throws IOException;
@Override
public abstract void remove(Session session, boolean update);
@Override
protected synchronized void startInternal() throws LifecycleException {
super.startInternal();
setState(LifecycleState.STARTING);
boolean attachedToValve = false;
for (Valve valve : getContainer().getPipeline().getValves()) {
if (valve instanceof SessionHandlerValve) {
this.handlerValve = (SessionHandlerValve) valve;
this.handlerValve.setSessionManager(this);
log.info("Attached to SessionHandlerValve");
attachedToValve = true;
break;
}
}
if (!attachedToValve) {
String error = "Unable to attach to session handling valve; sessions cannot be saved after the request without the valve starting properly.";
log.fatal(error);
throw new LifecycleException(error);
}
try {
initializeSerializer();
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
log.fatal("Unable to load serializer", e);
throw new LifecycleException(e);
}
log.info("Will expire sessions after " + getMaxInactiveInterval() + " seconds");
initializeDatabaseConnection();
setDistributable(true);
}
@Override
protected synchronized void stopInternal() throws LifecycleException {
if (log.isDebugEnabled()) {
log.debug("Stopping");
}
setState(LifecycleState.STOPPING);
connectionDestroy();
super.stopInternal();
}
@Override
public Session createSession(String requestedSessionId) {
generateCustomSessionId(requestedSessionId);
return currentSession.get();
}
protected boolean initSession(Object db, String sessionId) {
CustomSession session = null;
if (null != sessionId) {
session = (CustomSession) createEmptySession();
session.setNew(true);
session.setValid(true);
session.setCreationTime(System.currentTimeMillis());
session.setMaxInactiveInterval(getMaxInactiveInterval());
session.setId(sessionId);
session.tellNew();
}
currentSession.set(session);
currentSessionId.set(sessionId);
currentSessionIsPersisted.set(false);
currentSessionMetadata.set(new SessionMetadata());
if (null != session) {
try {
return saveInternal(db, session, true);
} catch (IOException ex) {
log.error("Error saving newly created session: " + ex.getMessage());
currentSession.set(null);
currentSessionId.set(null);
session = null;
}
}
return false;
}
@Override
public void add(Session session) {
try {
save(session);
} catch (IOException ex) {
log.warn("Unable to add to session manager store: " + ex.getMessage());
throw new RuntimeException("Unable to add to session manager store.", ex);
}
}
@Override
public Session findSession(String id) throws IOException {
CustomSession session = null;
if (null == id) {
currentSessionIsPersisted.set(false);
currentSession.set(null);
currentSessionMetadata.set(null);
currentSessionId.set(null);
} else if (id.equals(currentSessionId.get())) {
session = currentSession.get();
} else {
byte[] data = loadSessionDataFromDB(id);
if (data != null) {
DeserializedSessionContainer container = sessionFromSerializedData(id, data);
session = container.session;
currentSession.set(session);
currentSessionMetadata.set(container.metadata);
currentSessionIsPersisted.set(true);
currentSessionId.set(id);
} else {
currentSessionIsPersisted.set(false);
currentSession.set(null);
currentSessionMetadata.set(null);
currentSessionId.set(null);
}
}
return session;
}
/**
* 反序列化 得到 session & session attributes
* @param id
* @param data
* @return
* @throws IOException
*/
public DeserializedSessionContainer sessionFromSerializedData(String id, byte[] data) throws IOException {
log.trace("Deserializing session " + id + " from DB");
if (Arrays.equals(NULL_SESSION, data)) {
log.error("Encountered serialized session " + id + " with data equal to NULL_SESSION. This is a bug.");
throw new IOException("Serialized session data was equal to NULL_SESSION");
}
CustomSession session = null;
SessionMetadata metadata = new SessionMetadata();
try {
session = (CustomSession) createEmptySession();
serializer.deserializeInto(data, session, metadata);
session.setId(id);
session.setNew(false);
session.setMaxInactiveInterval(getMaxInactiveInterval() * 1000);
session.access();
session.setValid(true);
session.resetDirtyTracking();
if (log.isTraceEnabled()) {
log.trace("Session Contents [" + id + "]:");
Enumeration<String> en = session.getAttributeNames();
while (en.hasMoreElements()) {
log.trace(" " + en.nextElement());
}
}
} catch (ClassNotFoundException ex) {
log.fatal("Unable to deserialize into session", ex);
throw new IOException("Unable to deserialize into session", ex);
}
return new DeserializedSessionContainer(session, metadata);
}
public void save(Session session) throws IOException {
save(session, false);
}
/**
* 报错session & session attributes
* @param db
* @param session
* @param forceSave
* @return
* @throws IOException
*/
protected boolean saveInternal(Object db, Session session, boolean forceSave) throws IOException {
boolean error = true;
try {
log.trace("Saving session " + session + " into " + db);
CustomSession customSession = (CustomSession) session;
if (log.isTraceEnabled()) {
log.trace("Session Contents [" + customSession.getId() + "]:");
Enumeration<String> en = customSession.getAttributeNames();
while (en.hasMoreElements()) {
log.trace(" " + en.nextElement());
}
}
byte[] binaryId = customSession.getId().getBytes();
Boolean isCurrentSessionPersisted;
SessionMetadata sessionMetadata = currentSessionMetadata.get();
byte[] originalSessionAttributes = sessionMetadata.getSessionAttributes();
byte[] sessionAttributes = null;
if (forceSave || customSession.isDirty() || null == (isCurrentSessionPersisted = this.currentSessionIsPersisted.get())
|| !isCurrentSessionPersisted
|| !Arrays.equals(originalSessionAttributes, (sessionAttributes = serializer.attributesFrom(customSession)))) {
log.trace("Save was determined to be necessary");
if (null == sessionAttributes) {
sessionAttributes = serializer.attributesFrom(customSession);
}
SessionMetadata updatedMetadata = new SessionMetadata();
updatedMetadata.setSessionAttributes(sessionAttributes);
dbSet(db, binaryId, serializer.serializeFrom(customSession, updatedMetadata));
customSession.resetDirtyTracking();
currentSessionMetadata.set(updatedMetadata);
currentSessionIsPersisted.set(true);
} else {
log.trace("Save was determined to be unnecessary");
}
log.trace("Setting expire timeout on session [" + customSession.getId() + "] to " + getMaxInactiveInterval());
dbExpire(db, binaryId, getMaxInactiveInterval());
error = false;
return error;
} catch (IOException e) {
log.error(e.getMessage());
throw e;
}
}
@Override
public void remove(Session session) {
remove(session, false);
}
/*public void afterRequest() {
CustomSession customSession = currentSession.get();
if (customSession != null) {
try {
if (customSession.isValid()) {
log.trace("Request with session completed, saving session " + customSession.getId());
save(customSession, getAlwaysSaveAfterRequest());
} else {
log.trace("HTTP Session has been invalidated, removing :" + customSession.getId());
remove(customSession);
}
} catch (Exception e) {
log.error("Error storing/removing session", e);
} finally {
currentSession.remove();
currentSessionId.remove();
currentSessionIsPersisted.remove();
log.trace("Session removed from ThreadLocal :" + customSession.getIdInternal());
}
}
}*/
@Override
public void processExpires() {
}
/**
* get session策略
* @return
*/
public String getSessionPersistPolicies() {
StringBuilder policies = new StringBuilder();
for (Iterator<SessionPersistPolicy> iter = this.sessionPersistPoliciesSet.iterator(); iter.hasNext();) {
SessionPersistPolicy policy = iter.next();
policies.append(policy.name());
if (iter.hasNext()) {
policies.append(",");
}
}
return policies.toString();
}
/**
* set session策略
* @param sessionPersistPolicies
*/
public void setSessionPersistPolicies(String sessionPersistPolicies) {
String[] policyArray = sessionPersistPolicies.split(",");
EnumSet<SessionPersistPolicy> policySet = EnumSet.of(SessionPersistPolicy.DEFAULT);
for (String policyName : policyArray) {
SessionPersistPolicy policy = SessionPersistPolicy.fromName(policyName);
policySet.add(policy);
}
this.sessionPersistPoliciesSet = policySet;
}
/**
* 初始化序列化
* @throws ClassNotFoundException
* @throws IllegalAccessException
* @throws InstantiationException
*/
private void initializeSerializer() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
log.info("Attempting to use serializer :" + serializationStrategyClass);
serializer = (Serializer) Class.forName(serializationStrategyClass).newInstance();
Loader loader = null;
if (getContainer() != null) {
loader = getContainer().getLoader();
}
ClassLoader classLoader = null;
if (loader != null) {
classLoader = loader.getClassLoader();
}
serializer.setClassLoader(classLoader);
}
/**
* 每次改变 都save
* @return
*/
public boolean getSaveOnChange() {
return this.sessionPersistPoliciesSet.contains(SessionPersistPolicy.SAVE_ON_CHANGE);
}
/**
* request后总是save
* @return
*/
public boolean getAlwaysSaveAfterRequest() {
return this.sessionPersistPoliciesSet.contains(SessionPersistPolicy.ALWAYS_SAVE_AFTER_REQUEST);
}
/**
* set 序列化 class
* @param strategy
*/
public void setSerializationStrategyClass(String strategy) {
this.serializationStrategyClass = strategy;
}
/*
* public void setDebug(String debug) { this.debugEnabled =
* Boolean.parseBoolean(debug); }
*/
@Override
public int getRejectedSessions() {
return 0;
}
public void setRejectedSessions(int i) {
}
@Override
public void load() throws ClassNotFoundException, IOException {
}
@Override
public void unload() throws IOException {
}
@Override
public void addLifecycleListener(LifecycleListener listener) {
lifecycle.addLifecycleListener(listener);
}
@Override
public LifecycleListener[] findLifecycleListeners() {
return lifecycle.findLifecycleListeners();
}
@Override
public void removeLifecycleListener(LifecycleListener listener) {
lifecycle.removeLifecycleListener(listener);
}
}
class DeserializedSessionContainer {
public final CustomSession session;
public final SessionMetadata metadata;
public DeserializedSessionContainer(CustomSession session, SessionMetadata metadata) {
this.session = session;
this.metadata = metadata;
}
}