View Javadoc
1   /*
2    * This file is part of ***  M y C o R e  ***
3    * See http://www.mycore.de/ for details.
4    *
5    * MyCoRe is free software: you can redistribute it and/or modify
6    * it under the terms of the GNU General Public License as published by
7    * the Free Software Foundation, either version 3 of the License, or
8    * (at your option) any later version.
9    *
10   * MyCoRe is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with MyCoRe.  If not, see <http://www.gnu.org/licenses/>.
17   */
18  
19  package org.mycore.common;
20  
21  import static org.mycore.common.events.MCRSessionEvent.Type.created;
22  import static org.mycore.common.events.MCRSessionEvent.Type.destroyed;
23  
24  import java.util.ArrayList;
25  import java.util.Collection;
26  import java.util.Collections;
27  import java.util.HashMap;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.stream.Collectors;
31  
32  import org.apache.logging.log4j.LogManager;
33  import org.apache.logging.log4j.Logger;
34  import org.mycore.common.events.MCRSessionListener;
35  import org.mycore.util.concurrent.MCRReadWriteGuard;
36  
37  /**
38   * Manages sessions for a MyCoRe system. This class is backed by a ThreadLocal variable, so every Thread is guaranteed
39   * to get a unique instance of MCRSession. Care must be taken when using an environment utilizing a Thread pool, such as
40   * many Servlet engines. In this case it is possible for the session object to stay attached to a thread where it should
41   * not be. Use the {@link #releaseCurrentSession()}method to reset the session object for a Thread to its default
42   * values. The basic idea for the implementation of this class is taken from an apache project, namely the class
43   * org.apache.common.latka.LatkaProperties.java written by Morgan Delagrange.
44   * 
45   * @author Detlev Degenhardt
46   * @author Thomas Scheffler (yagee)
47   * @version $Revision$ $Date$
48   */
49  public class MCRSessionMgr {
50  
51      private static Map<String, MCRSession> sessions = Collections.synchronizedMap(new HashMap<String, MCRSession>());
52  
53      private static List<MCRSessionListener> listeners = new ArrayList<>();
54  
55      private static MCRReadWriteGuard listenersGuard = new MCRReadWriteGuard();
56  
57      private static final Logger LOGGER = LogManager.getLogger();
58  
59      /**
60       * This ThreadLocal is automatically instantiated per thread with a MyCoRe session object containing the default
61       * session parameters which are set in the constructor of MCRSession.
62       * 
63       * @see ThreadLocal
64       */
65      private static ThreadLocal<MCRSession> theThreadLocalSession = ThreadLocal.withInitial(MCRSession::new);
66  
67      private static ThreadLocal<Boolean> isSessionAttached = ThreadLocal.withInitial(() -> Boolean.FALSE);
68  
69      private static ThreadLocal<Boolean> isSessionCreationLocked = new ThreadLocal<>() {
70          @Override
71          protected Boolean initialValue() {
72              return Boolean.TRUE;
73          }
74      };
75  
76      /**
77       * This method returns the unique MyCoRe session object for the current Thread. The session object is initialized
78       * with the default MyCoRe session data.
79       * 
80       * @return MyCoRe MCRSession object
81       * @throws MCRException if the current Thread {@link #isLocked()}
82       */
83      public static MCRSession getCurrentSession() {
84          if (isSessionCreationLocked.get()) {
85              throw new MCRException("Session creation is locked!");
86          }
87          isSessionAttached.set(Boolean.TRUE);
88          return theThreadLocalSession.get();
89      }
90  
91      private static void checkSessionLock() {
92          if (isSessionCreationLocked.get()) {
93              throw new MCRException("Session creation is locked!");
94          }
95      }
96  
97      /**
98       * This method sets a MyCoRe session object for the current Thread. This method fires a "activated" event, when
99       * called the first time for this session and thread.
100      * Calling this method also unlocks the current Thread for MCRSession handling.
101      * 
102      * @see org.mycore.common.events.MCRSessionEvent.Type#activated
103      */
104     public static void setCurrentSession(MCRSession theSession) {
105         if (hasCurrentSession()) {
106             MCRSession currentSession = getCurrentSession();
107             if (currentSession != theSession && currentSession.getID() != null) {
108                 LOGGER.error("Current session will be released: " + currentSession,
109                     new MCRException("Current thread already has a session attached!"));
110                 releaseCurrentSession();
111             }
112 
113         }
114         unlock();
115         theSession.activate();
116         theThreadLocalSession.set(theSession);
117         isSessionAttached.set(Boolean.TRUE);
118     }
119 
120     /**
121      * Releases the MyCoRe session from its current thread. Subsequent calls of getCurrentSession() will return a
122      * different MCRSession object than before for the current Thread. One use for this method is to reset the session
123      * inside a Thread-pooling environment like Servlet engines. This method fires a "passivated" event, when called the
124      * last time for this session and thread.
125      * 
126      * @see org.mycore.common.events.MCRSessionEvent.Type#passivated
127      */
128     public static void releaseCurrentSession() {
129         //theThreadLocalSession maybe null if called after close()
130         if (theThreadLocalSession != null && hasCurrentSession()) {
131             MCRSession session = theThreadLocalSession.get();
132             session.passivate();
133             MCRSession.LOGGER.debug("MCRSession released {}", session.getID());
134             theThreadLocalSession.remove();
135             isSessionAttached.remove();
136             lock();
137         }
138     }
139 
140     /**
141      * Returns a boolean indicating if a {@link MCRSession} is bound to the current thread.
142      * 
143      * @return true if a session is bound to the current thread
144      */
145     public static boolean hasCurrentSession() {
146         return isSessionAttached.get();
147     }
148 
149     /**
150      * Returns the current session ID. This method does not spawn a new session as {@link #getCurrentSession()} would
151      * do.
152      * 
153      * @return current session ID or <code>null</code> if current thread has no session attached.
154      */
155     public static String getCurrentSessionID() {
156         if (hasCurrentSession()) {
157             return getCurrentSession().getID();
158         }
159         return null;
160     }
161 
162     /**
163      * Locks the MCRSessionMgr and no {@link MCRSession}s can be attached to the current Thread.
164      */
165     public static void lock() {
166         isSessionCreationLocked.set(true);
167     }
168 
169     /**
170      * Unlocks the MCRSessionMgr to allow management of {@link MCRSession}s on the current Thread.
171      */
172     public static void unlock() {
173         isSessionCreationLocked.set(false);
174     }
175 
176     /**
177      * @return the lock status of MCRSessionMgr, defaults to <code>true</code> on new Threads
178      */
179     public static boolean isLocked() {
180         return isSessionCreationLocked.get();
181     }
182 
183     /**
184      * Returns the MCRSession for the given sessionID.
185      */
186     public static MCRSession getSession(String sessionID) {
187         MCRSession s = sessions.get(sessionID);
188         if (s == null) {
189             MCRSession.LOGGER.warn("MCRSession with ID {} not cached any more", sessionID);
190         }
191         return s;
192     }
193 
194     /**
195      * Add MCRSession to a static Map that manages all sessions. This method fires a "created" event and is invoked by
196      * MCRSession constructor.
197      * 
198      * @see MCRSession#MCRSession()
199      * @see org.mycore.common.events.MCRSessionEvent.Type#created
200      */
201     static void addSession(MCRSession session) {
202         sessions.put(session.getID(), session);
203         session.fireSessionEvent(created, session.concurrentAccess.get());
204     }
205 
206     /**
207      * Remove MCRSession from a static Map that manages all sessions. This method fires a "destroyed" event and is
208      * invoked by MCRSession.close().
209      * 
210      * @see MCRSession#close()
211      * @see org.mycore.common.events.MCRSessionEvent.Type#destroyed
212      */
213     static void removeSession(MCRSession session) {
214         sessions.remove(session.getID());
215         session.fireSessionEvent(destroyed, session.concurrentAccess.get());
216     }
217 
218     /**
219      * Add a MCRSessionListener, that gets infomed about MCRSessionEvents.
220      * 
221      * @see #removeSessionListener(MCRSessionListener)
222      */
223     public static void addSessionListener(MCRSessionListener listener) {
224         listenersGuard.write(() -> listeners.add(listener));
225     }
226 
227     /**
228      * Removes a MCRSessionListener from the list.
229      * 
230      * @see #addSessionListener(MCRSessionListener)
231      */
232     public static void removeSessionListener(MCRSessionListener listener) {
233         listenersGuard.write(() -> listeners.remove(listener));
234     }
235 
236     /**
237      * Allows access to all MCRSessionListener instances. Mainly for internal use of MCRSession.
238      */
239     static List<MCRSessionListener> getListeners() {
240         return listenersGuard.read(() -> listeners.stream().collect(Collectors.toList()));
241     }
242 
243     public static void close() {
244         listenersGuard.write(() -> {
245             Collection<MCRSession> var = sessions.values();
246             for (MCRSession session : var.toArray(new MCRSession[var.size()])) {
247                 session.close();
248             }
249             LogManager.getLogger(MCRSessionMgr.class).info("Removing thread locals...");
250             isSessionAttached = null;
251             theThreadLocalSession = null;
252             listeners.clear();
253             LogManager.getLogger(MCRSessionMgr.class).info("...done.");
254         });
255         listenersGuard = null;
256     }
257 
258     public static Map<String, MCRSession> getAllSessions() {
259         return sessions;
260     }
261 
262 }