KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > terracotta > session > TerracottaSessionManager


1 /*
2  * All content copyright (c) 2003-2006 Terracotta, Inc., except as may otherwise be noted in a separate copyright
3  * notice. All rights reserved.
4  */

5 package com.terracotta.session;
6
7 import com.tc.logging.TCLogger;
8 import com.tc.management.beans.sessions.SessionMonitorMBean;
9 import com.tc.management.beans.sessions.SessionMonitorMBean.SessionsComptroller;
10 import com.tc.object.bytecode.ManagerUtil;
11 import com.tc.object.bytecode.hook.impl.ClassProcessorHelper;
12 import com.terracotta.session.util.Assert;
13 import com.terracotta.session.util.ConfigProperties;
14 import com.terracotta.session.util.ContextMgr;
15 import com.terracotta.session.util.DefaultContextMgr;
16 import com.terracotta.session.util.LifecycleEventMgr;
17 import com.terracotta.session.util.Lock;
18 import com.terracotta.session.util.SessionCookieWriter;
19 import com.terracotta.session.util.SessionIdGenerator;
20 import com.terracotta.session.util.Timestamp;
21
22 import javax.servlet.http.HttpServletRequest JavaDoc;
23 import javax.servlet.http.HttpServletResponse JavaDoc;
24
25 public class TerracottaSessionManager {
26
27   private final SessionMonitorMBean mBean;
28   private final SessionIdGenerator idGenerator;
29   private final SessionCookieWriter cookieWriter;
30   private final SessionDataStore store;
31   private final LifecycleEventMgr eventMgr;
32   private final ContextMgr contextMgr;
33   private final boolean reqeustLogEnabled;
34   private final boolean invalidatorLogEnabled;
35   private final TCLogger logger;
36   private final RequestResponseFactory factory;
37   private final RequestTracker tracker;
38   private final boolean debugServerHops;
39   private final int debugServerHopsInterval;
40   private int serverHopsDetected = 0;
41
42   public TerracottaSessionManager(SessionIdGenerator sig, SessionCookieWriter scw, LifecycleEventMgr eventMgr,
43                                   ContextMgr contextMgr, RequestResponseFactory factory, ConfigProperties cp) {
44
45     Assert.pre(sig != null);
46     Assert.pre(scw != null);
47     Assert.pre(eventMgr != null);
48     Assert.pre(contextMgr != null);
49
50     this.idGenerator = sig;
51     this.cookieWriter = scw;
52     this.eventMgr = eventMgr;
53     this.contextMgr = contextMgr;
54     this.factory = factory;
55     this.store = new SessionDataStore(contextMgr.getAppName(), cp.getSessionTimeoutSeconds(), eventMgr, contextMgr);
56     this.logger = ManagerUtil.getLogger("com.tc.tcsession." + contextMgr.getAppName());
57     this.reqeustLogEnabled = cp.getRequestLogBenchEnabled();
58     this.invalidatorLogEnabled = cp.getInvalidatorLogBenchEnabled();
59
60     // XXX: If reasonable, we should move this out of the constructor -- leaking a reference to "this" to another thread
61
// within a constructor is a bad practice (note: althought "this" isn't explicitly based as arg, it is available and
62
// acessed by the non-static inner class)
63
Thread JavaDoc invalidator = new Thread JavaDoc(new SessionInvalidator(store, cp.getInvalidatorSleepSeconds(), cp
64         .getSessionTimeoutSeconds()), "SessionInvalidator - " + contextMgr.getAppName());
65     invalidator.setDaemon(true);
66     invalidator.start();
67     Assert.post(invalidator.isAlive());
68
69     // This is disgusting, but right now we have to do this because we don't have an event
70
// management infrastructure to boot stuff up
71
mBean = ManagerUtil.getSessionMonitorMBean();
72
73     mBean.registerSessionsController(new SessionsComptroller() {
74       public boolean killSession(final String JavaDoc browserSessionId) {
75         SessionId id = idGenerator.makeInstanceFromBrowserId(browserSessionId);
76         if (id == null) {
77           // that, potentially, was not *browser* id, try to recover...
78
id = idGenerator.makeInstanceFromInternalKey(browserSessionId);
79         }
80         expire(id);
81         return true;
82       }
83     });
84
85     if (cp.isRequestTrackingEnabled()) {
86       tracker = new StuckRequestTracker(cp.getRequestTrackerSleepMillis(), cp.getRequestTrackerStuckThresholdMillis(),
87                                         cp.isDumpThreadsOnStuckRequests());
88       ((StuckRequestTracker) tracker).start();
89     } else {
90       tracker = new NullRequestTracker();
91     }
92
93     this.debugServerHops = cp.isDebugServerHops();
94     this.debugServerHopsInterval = cp.getDebugServerHopsInterval();
95   }
96
97   public TerracottaRequest preprocess(HttpServletRequest JavaDoc req, HttpServletResponse JavaDoc res) {
98     tracker.begin(req);
99     TerracottaRequest terracottaRequest = basicPreprocess(req, res);
100     tracker.recordSessionId(terracottaRequest);
101     return terracottaRequest;
102   }
103
104   private TerracottaRequest basicPreprocess(HttpServletRequest JavaDoc req, HttpServletResponse JavaDoc res) {
105     Assert.pre(req != null);
106     Assert.pre(res != null);
107
108     SessionId sessionId = findSessionId(req);
109
110     if (debugServerHops) {
111       if (sessionId != null && sessionId.isServerHop()) {
112         synchronized (this) {
113           serverHopsDetected++;
114           if ((serverHopsDetected % debugServerHopsInterval) == 0) {
115             logger.info(serverHopsDetected + " server hops detected");
116           }
117         }
118       }
119     }
120
121     TerracottaRequest rw = wrapRequest(sessionId, req, res);
122
123     Assert.post(rw != null);
124     return rw;
125   }
126
127   public TerracottaResponse createResponse(TerracottaRequest req, HttpServletResponse JavaDoc res) {
128     return factory.createResponse(req, res);
129   }
130
131   public void postprocess(TerracottaRequest req) {
132     try {
133       basicPostprocess(req);
134     } finally {
135       tracker.end();
136     }
137   }
138
139   private void basicPostprocess(TerracottaRequest req) {
140     Assert.pre(req != null);
141
142     // don't do anything for forwarded requests
143
if (req.isForwarded()) return;
144
145     Assert.inv(!req.isForwarded());
146
147     mBean.requestProcessed();
148
149     try {
150       if (req.isSessionOwner()) postprocessSession(req);
151     } finally {
152       if (reqeustLogEnabled) {
153         logRequestBench(req);
154       }
155     }
156   }
157
158   private void logRequestBench(TerracottaRequest req) {
159     final String JavaDoc msgPrefix = "REQUEST BENCH: url=[" + req.getRequestURL() + "]";
160     String JavaDoc sessionInfo = "";
161     if (req.isSessionOwner()) {
162       final SessionId id = req.getTerracottaSession(false).getSessionId();
163       sessionInfo = " sid=[" + id.getKey() + "] -> [" + id.getLock().getLockTimer().elapsed() + ":"
164                     + id.getLock().getUnlockTimer().elapsed() + "]";
165     }
166     final String JavaDoc msg = msgPrefix + sessionInfo + " -> " + (System.currentTimeMillis() - req.getRequestStartMillis());
167     logger.info(msg);
168   }
169
170   private void postprocessSession(TerracottaRequest req) {
171     Assert.pre(req != null);
172     Assert.pre(!req.isForwarded());
173     Assert.pre(req.isSessionOwner());
174     final Session session = req.getTerracottaSession(false);
175     Assert.inv(session != null);
176     final SessionId id = session.getSessionId();
177     final SessionData sd = session.getSessionData();
178     try {
179       if (!session.isValid()) store.remove(id);
180       else {
181         sd.finishRequest();
182         store.updateTimestampIfNeeded(sd);
183       }
184     } finally {
185       id.commitLock();
186     }
187   }
188
189   /**
190    * The only use for this method [currently] is by Struts' Include Tag, which can generate a nested request. In this
191    * case we have to release session lock, so that nested request (running, potentially, in another JVM) can acquire it.
192    * {@link TerracottaSessionManager#resumeRequest(Session)} method will re-aquire the lock.
193    */

194   public static void pauseRequest(final Session sess) {
195     Assert.pre(sess != null);
196     final SessionId id = sess.getSessionId();
197     final SessionData sd = sess.getSessionData();
198     sd.finishRequest();
199     id.commitLock();
200   }
201
202   /**
203    * See {@link TerracottaSessionManager#resumeRequest(Session)} for details
204    */

205   public static void resumeRequest(final Session sess) {
206     Assert.pre(sess != null);
207     final SessionId id = sess.getSessionId();
208     final SessionData sd = sess.getSessionData();
209     id.getWriteLock();
210     sd.startRequest();
211   }
212
213   private TerracottaRequest wrapRequest(SessionId sessionId, HttpServletRequest JavaDoc req, HttpServletResponse JavaDoc res) {
214     TerracottaRequest request = factory.createRequest(sessionId, req, res);
215     request.setSessionManager(this);
216     return request;
217   }
218
219   /**
220    * This method always returns a valid session. If data for the requestedSessionId found and is valid, it is returned.
221    * Otherwise, we must create a new session id, a new session data, a new sessiono, and cookie the response.
222    */

223   protected Session getSession(final SessionId requestedSessionId, final HttpServletRequest JavaDoc req,
224                                final HttpServletResponse JavaDoc res) {
225     Assert.pre(req != null);
226     Assert.pre(res != null);
227     Session rv = doGetSession(requestedSessionId, req, res);
228     Assert.post(rv != null);
229     return rv;
230   }
231
232   protected Session getSessionIfExists(SessionId requestedSessionId, HttpServletRequest JavaDoc req, HttpServletResponse JavaDoc res) {
233     if (requestedSessionId == null) return null;
234     SessionData sd = store.find(requestedSessionId);
235     if (sd == null) return null;
236     Assert.inv(sd.isValid());
237     if (requestedSessionId.isServerHop()) cookieWriter.writeCookie(req, res, requestedSessionId);
238     return sd;
239   }
240
241   protected SessionCookieWriter getCookieWriter() {
242     return this.cookieWriter;
243   }
244
245   private void expire(SessionId id) {
246     SessionData sd = null;
247     try {
248       sd = store.find(id);
249       if (sd != null) {
250         expire(id, sd);
251       }
252     } finally {
253       if (sd != null) id.commitLock();
254     }
255   }
256
257   private void expire(SessionId id, SessionData sd) {
258     try {
259       sd.invalidate();
260     } catch (Throwable JavaDoc t) {
261       logger.error("unhandled exception during invalidate() for session " + id.getKey());
262     }
263     store.remove(id);
264     mBean.sessionDestroyed();
265   }
266
267   private Session doGetSession(final SessionId requestedSessionId, final HttpServletRequest JavaDoc req,
268                                final HttpServletResponse JavaDoc res) {
269     Assert.pre(req != null);
270     Assert.pre(res != null);
271
272     if (requestedSessionId == null) { return createNewSession(req, res); }
273     final SessionData sd = store.find(requestedSessionId);
274     if (sd == null) { return createNewSession(req, res); }
275     Assert.inv(sd.isValid());
276     if (requestedSessionId.isServerHop()) cookieWriter.writeCookie(req, res, requestedSessionId);
277
278     return sd;
279   }
280
281   private Session createNewSession(HttpServletRequest JavaDoc req, HttpServletResponse JavaDoc res) {
282     Assert.pre(req != null);
283     Assert.pre(res != null);
284
285     SessionId id = idGenerator.generateNewId();
286     SessionData sd = store.createSessionData(id);
287     cookieWriter.writeCookie(req, res, id);
288     eventMgr.fireSessionCreatedEvent(sd);
289     mBean.sessionCreated();
290     Assert.post(sd != null);
291     return sd;
292   }
293
294   private SessionId findSessionId(HttpServletRequest JavaDoc httpRequest) {
295     Assert.pre(httpRequest != null);
296
297     String JavaDoc requestedSessionId = httpRequest.getRequestedSessionId();
298     if (requestedSessionId == null) return null;
299     else return idGenerator.makeInstanceFromBrowserId(requestedSessionId);
300   }
301
302   private class SessionInvalidator implements Runnable JavaDoc {
303
304     private final long sleepMillis;
305
306     public SessionInvalidator(final SessionDataStore store, final long sleepSeconds,
307                               final long defaultSessionIdleSeconds) {
308       this.sleepMillis = sleepSeconds * 1000;
309     }
310
311     public void run() {
312       final String JavaDoc invalidatorLock = "tc:session_invalidator_lock_" + contextMgr.getAppName();
313
314       while (true) {
315         sleep(sleepMillis);
316         if (Thread.interrupted()) {
317           break;
318         } else {
319           try {
320             final Lock lock = new Lock(invalidatorLock);
321             lock.tryWriteLock();
322             if (!lock.isLocked()) continue;
323             try {
324               invalidateSessions();
325             } finally {
326               lock.commitLock();
327             }
328           } catch (Throwable JavaDoc t) {
329             logger.error("Unhandled exception occurred during session invalidation", t);
330           }
331         }
332       }
333     }
334
335     private void invalidateSessions() {
336       final long startMillis = System.currentTimeMillis();
337       final String JavaDoc keys[] = store.getAllKeys();
338       int totalCnt = 0;
339       int invalCnt = 0;
340       int evaled = 0;
341       int notEvaled = 0;
342       int errors = 0;
343
344       if (invalidatorLogEnabled) {
345         logger.info("SESSION INVALIDATOR: started");
346       }
347
348       for (int i = 0, n = keys.length; i < n; i++) {
349         final String JavaDoc key = keys[i];
350         try {
351           final SessionId id = idGenerator.makeInstanceFromInternalKey(key);
352           final Timestamp dtm = store.findTimestampUnlocked(id);
353           if (dtm == null) continue;
354           totalCnt++;
355           if (dtm.getMillis() < System.currentTimeMillis()) {
356             evaled++;
357             if (evaluateSession(dtm, id)) invalCnt++;
358           } else {
359             notEvaled++;
360           }
361         } catch (Throwable JavaDoc t) {
362           errors++;
363           logger.error("Unhandled exception inspecting session " + key + " for invalidation", t);
364         }
365       }
366       if (invalidatorLogEnabled) {
367         final String JavaDoc msg = "SESSION INVALIDATOR BENCH: " + " -> total=" + totalCnt + ", evaled=" + evaled
368                            + ", notEvaled=" + notEvaled + ", errors=" + errors + ", invalidated=" + invalCnt
369                            + " -> elapsed=" + (System.currentTimeMillis() - startMillis);
370         logger.info(msg);
371       }
372     }
373
374     private boolean evaluateSession(final Timestamp dtm, final SessionId id) {
375       Assert.pre(id != null);
376
377       boolean rv = false;
378       id.tryWriteLock();
379       if (!id.getLock().isLocked()) { return rv; }
380
381       try {
382         final SessionData sd = store.findSessionDataUnlocked(id);
383         if (sd == null) return rv;
384         if (!sd.isValid()) {
385           expire(id, sd);
386           rv = true;
387         } else {
388           store.updateTimestampIfNeeded(sd);
389         }
390       } finally {
391         id.commitLock();
392       }
393       return rv;
394     }
395
396     private void sleep(long l) {
397       try {
398         Thread.sleep(l);
399       } catch (InterruptedException JavaDoc ignore) {
400         // nothing to do
401
}
402     }
403   }
404
405   public static boolean isDsoSessionApp(HttpServletRequest JavaDoc request) {
406     Assert.pre(request != null);
407     final String JavaDoc appName = DefaultContextMgr.computeAppName(request);
408     return ClassProcessorHelper.isDSOSessions(appName);
409   }
410
411   private static final class NullRequestTracker implements RequestTracker {
412
413     public final boolean end() {
414       return true;
415     }
416
417     public final void begin(HttpServletRequest JavaDoc req) {
418       //
419
}
420
421     public final void recordSessionId(TerracottaRequest terracottaRequest) {
422       //
423
}
424
425   }
426
427 }
428
Popular Tags