org.mycore.common.MCRSessionMgr Maven / Gradle / Ivy
/*
* This file is part of *** M y C o R e ***
* See http://www.mycore.de/ for details.
*
* MyCoRe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MyCoRe is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with MyCoRe. If not, see .
*/
package org.mycore.common;
import static org.mycore.common.events.MCRSessionEvent.Type.created;
import static org.mycore.common.events.MCRSessionEvent.Type.destroyed;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.mycore.common.events.MCRSessionListener;
import org.mycore.util.concurrent.MCRReadWriteGuard;
/**
* Manages sessions for a MyCoRe system. This class is backed by a ThreadLocal variable, so every Thread is guaranteed
* to get a unique instance of MCRSession. Care must be taken when using an environment utilizing a Thread pool, such as
* many Servlet engines. In this case it is possible for the session object to stay attached to a thread where it should
* not be. Use the {@link #releaseCurrentSession()}method to reset the session object for a Thread to its default
* values. The basic idea for the implementation of this class is taken from an apache project, namely the class
* org.apache.common.latka.LatkaProperties.java written by Morgan Delagrange.
*
* @author Detlev Degenhardt
* @author Thomas Scheffler (yagee)
* @version $Revision$ $Date$
*/
public class MCRSessionMgr {
private static Map sessions = Collections.synchronizedMap(new HashMap());
private static List listeners = new ArrayList<>();
private static MCRReadWriteGuard listenersGuard = new MCRReadWriteGuard();
private static final Logger LOGGER = LogManager.getLogger();
/**
* This ThreadLocal is automatically instantiated per thread with a MyCoRe session object containing the default
* session parameters which are set in the constructor of MCRSession.
*
* @see ThreadLocal
*/
private static ThreadLocal theThreadLocalSession = ThreadLocal.withInitial(MCRSession::new);
private static ThreadLocal isSessionAttached = ThreadLocal.withInitial(() -> Boolean.FALSE);
private static ThreadLocal isSessionCreationLocked = new ThreadLocal<>() {
@Override
protected Boolean initialValue() {
return Boolean.TRUE;
}
};
/**
* This method returns the unique MyCoRe session object for the current Thread. The session object is initialized
* with the default MyCoRe session data.
*
* @return MyCoRe MCRSession object
* @throws MCRException if the current Thread {@link #isLocked()}
*/
public static MCRSession getCurrentSession() {
if (isSessionCreationLocked.get()) {
throw new MCRException("Session creation is locked!");
}
isSessionAttached.set(Boolean.TRUE);
return theThreadLocalSession.get();
}
private static void checkSessionLock() {
if (isSessionCreationLocked.get()) {
throw new MCRException("Session creation is locked!");
}
}
/**
* This method sets a MyCoRe session object for the current Thread. This method fires a "activated" event, when
* called the first time for this session and thread.
* Calling this method also unlocks the current Thread for MCRSession handling.
*
* @see org.mycore.common.events.MCRSessionEvent.Type#activated
*/
public static void setCurrentSession(MCRSession theSession) {
if (hasCurrentSession()) {
MCRSession currentSession = getCurrentSession();
if (currentSession != theSession && currentSession.getID() != null) {
LOGGER.error("Current session will be released: " + currentSession,
new MCRException("Current thread already has a session attached!"));
releaseCurrentSession();
}
}
unlock();
theSession.activate();
theThreadLocalSession.set(theSession);
isSessionAttached.set(Boolean.TRUE);
}
/**
* Releases the MyCoRe session from its current thread. Subsequent calls of getCurrentSession() will return a
* different MCRSession object than before for the current Thread. One use for this method is to reset the session
* inside a Thread-pooling environment like Servlet engines. This method fires a "passivated" event, when called the
* last time for this session and thread.
*
* @see org.mycore.common.events.MCRSessionEvent.Type#passivated
*/
public static void releaseCurrentSession() {
//theThreadLocalSession maybe null if called after close()
if (theThreadLocalSession != null && hasCurrentSession()) {
MCRSession session = theThreadLocalSession.get();
session.passivate();
MCRSession.LOGGER.debug("MCRSession released {}", session.getID());
theThreadLocalSession.remove();
isSessionAttached.remove();
lock();
}
}
/**
* Returns a boolean indicating if a {@link MCRSession} is bound to the current thread.
*
* @return true if a session is bound to the current thread
*/
public static boolean hasCurrentSession() {
return isSessionAttached.get();
}
/**
* Returns the current session ID. This method does not spawn a new session as {@link #getCurrentSession()} would
* do.
*
* @return current session ID or null
if current thread has no session attached.
*/
public static String getCurrentSessionID() {
if (hasCurrentSession()) {
return getCurrentSession().getID();
}
return null;
}
/**
* Locks the MCRSessionMgr and no {@link MCRSession}s can be attached to the current Thread.
*/
public static void lock() {
isSessionCreationLocked.set(true);
}
/**
* Unlocks the MCRSessionMgr to allow management of {@link MCRSession}s on the current Thread.
*/
public static void unlock() {
isSessionCreationLocked.set(false);
}
/**
* @return the lock status of MCRSessionMgr, defaults to true
on new Threads
*/
public static boolean isLocked() {
return isSessionCreationLocked.get();
}
/**
* Returns the MCRSession for the given sessionID.
*/
public static MCRSession getSession(String sessionID) {
MCRSession s = sessions.get(sessionID);
if (s == null) {
MCRSession.LOGGER.warn("MCRSession with ID {} not cached any more", sessionID);
}
return s;
}
/**
* Add MCRSession to a static Map that manages all sessions. This method fires a "created" event and is invoked by
* MCRSession constructor.
*
* @see MCRSession#MCRSession()
* @see org.mycore.common.events.MCRSessionEvent.Type#created
*/
static void addSession(MCRSession session) {
sessions.put(session.getID(), session);
session.fireSessionEvent(created, session.concurrentAccess.get());
}
/**
* Remove MCRSession from a static Map that manages all sessions. This method fires a "destroyed" event and is
* invoked by MCRSession.close().
*
* @see MCRSession#close()
* @see org.mycore.common.events.MCRSessionEvent.Type#destroyed
*/
static void removeSession(MCRSession session) {
sessions.remove(session.getID());
session.fireSessionEvent(destroyed, session.concurrentAccess.get());
}
/**
* Add a MCRSessionListener, that gets infomed about MCRSessionEvents.
*
* @see #removeSessionListener(MCRSessionListener)
*/
public static void addSessionListener(MCRSessionListener listener) {
listenersGuard.write(() -> listeners.add(listener));
}
/**
* Removes a MCRSessionListener from the list.
*
* @see #addSessionListener(MCRSessionListener)
*/
public static void removeSessionListener(MCRSessionListener listener) {
listenersGuard.write(() -> listeners.remove(listener));
}
/**
* Allows access to all MCRSessionListener instances. Mainly for internal use of MCRSession.
*/
static List getListeners() {
return listenersGuard.read(() -> listeners.stream().collect(Collectors.toList()));
}
public static void close() {
listenersGuard.write(() -> {
Collection var = sessions.values();
for (MCRSession session : var.toArray(new MCRSession[var.size()])) {
session.close();
}
LogManager.getLogger(MCRSessionMgr.class).info("Removing thread locals...");
isSessionAttached = null;
theThreadLocalSession = null;
listeners.clear();
LogManager.getLogger(MCRSessionMgr.class).info("...done.");
});
listenersGuard = null;
}
public static Map getAllSessions() {
return sessions;
}
}