1/*
2  Copyright (C) 2000-2005 SKYRIX Software AG
3
4  This file is part of SOPE.
5
6  SOPE is free software; you can redistribute it and/or modify it under
7  the terms of the GNU Lesser General Public License as published by the
8  Free Software Foundation; either version 2, or (at your option) any
9  later version.
10
11  SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
12  WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14  License for more details.
15
16  You should have received a copy of the GNU Lesser General Public
17  License along with SOPE; see the file COPYING.  If not, write to the
18  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19  02111-1307, USA.
20*/
21
22#include <NGObjWeb/WOSessionStore.h>
23
24/*
25  The default session store. It stores all the sessions in memory
26  (it's basically a simple hashmap of the session-id to the session object).
27
28  If the application goes down, all sessions will go down - this store
29  doesn't provide "session-failover".
30*/
31
32@interface WOServerSessionStore : WOSessionStore
33{
34  NSMapTable *idToSession;
35  NSMapTable *activeSessions;
36}
37@end
38
39#include <NGObjWeb/WOContext.h>
40#include <NGObjWeb/WOApplication.h>
41#include <NGObjWeb/WOSession.h>
42#include "common.h"
43
44/* hh: moved in here from application, check whether it's required ... */
45@interface WOSessionInfo : NSObject
46{
47@private
48  NSString *sessionID;
49  NSDate   *timeoutDate;
50}
51
52+ (WOSessionInfo *)infoForSession:(WOSession *)_session;
53
54- (NSString *)sessionID;
55- (NSDate *)timeoutDate;
56
57@end
58
59@implementation WOServerSessionStore
60
61static BOOL logExpiredSessions = NO;
62
63- (id)init {
64  if ((self = [super init])) {
65    self->idToSession = NSCreateMapTable(NSObjectMapKeyCallBacks,
66                                         NSObjectMapValueCallBacks,
67                                         128);
68    self->activeSessions = NSCreateMapTable(NSObjectMapKeyCallBacks,
69                                            NSObjectMapValueCallBacks,
70                                            128);
71    self->checkedOutSessions = [[NSMutableSet allocWithZone:[self zone]]
72                                              initWithCapacity:64];
73
74    if ([[[NSUserDefaults standardUserDefaults]
75                          objectForKey:@"WORunMultithreaded"]
76                          boolValue]) {
77      self->lock         = [[NSRecursiveLock allocWithZone:[self zone]] init];
78      self->checkoutLock = [[NSConditionLock allocWithZone:[self zone]]
79                                             initWithCondition:0];
80    }
81  }
82  return self;
83}
84
85- (void)dealloc {
86  if (self->activeSessions) {
87    NSFreeMapTable(self->activeSessions);
88    self->activeSessions = NULL;
89  }
90  if (self->idToSession) {
91    NSFreeMapTable(self->idToSession);
92    self->idToSession = NULL;
93  }
94  [self->checkedOutSessions release];
95  [self->checkoutLock       release];
96  [self->lock               release];
97  [super dealloc];
98}
99
100/* accessors */
101
102- (int)activeSessionsCount {
103  int count;
104
105  [self->lock lock];
106  count = NSCountMapTable(self->idToSession);
107  [self->lock unlock];
108
109  return count;
110}
111
112/* store */
113
114- (void)saveSessionForContext:(WOContext *)_context {
115  if (![_context hasSession])
116    return;
117
118  [self->lock lock];
119  {
120    WOSession *sn = [_context session];
121
122    if ([sn isTerminating]) {
123      sn = [sn retain];
124
125      NSMapRemove(self->idToSession,    [sn sessionID]);
126      NSMapRemove(self->activeSessions, [sn sessionID]);
127
128      NSLog(@"session %@ terminated at %@ ..",
129	    [sn sessionID], [NSCalendarDate calendarDate]);
130
131      [sn release]; sn = nil;
132    }
133    else {
134      WOSessionInfo *info;
135
136      NSMapInsert(self->idToSession, [sn sessionID], sn);
137
138      info = [WOSessionInfo infoForSession:sn];
139      NSMapInsert(self->activeSessions, [sn sessionID], info);
140    }
141  }
142  [self->lock unlock];
143}
144
145- (id)restoreSessionWithID:(NSString *)_sid request:(WORequest *)_request {
146  WOSession *session = nil;
147
148  if ([_sid length] == 0)
149    return nil;
150
151  if (![_sid isKindOfClass:[NSString class]]) {
152    [self warnWithFormat:@"%s: got invalid session id (expected string !): %@",
153            __PRETTY_FUNCTION__, _sid];
154    return nil;
155  }
156
157  if ([_sid isEqualToString:@"expired"])
158    return nil;
159
160  [self->lock lock];
161  session = NSMapGet(self->idToSession, _sid);
162  [self->lock unlock];
163
164  if (logExpiredSessions) {
165    if (session == nil)
166      [self logWithFormat:@"session with id %@ expired.", _sid];
167  }
168
169  return session;
170}
171
172/* termination */
173
174- (void)sessionExpired:(NSString *)_sessionID {
175  [self->lock lock];
176  NSMapRemove(self->idToSession, _sessionID);
177  [self->lock unlock];
178}
179
180- (void)sessionTerminated:(WOSession *)_session {
181  _session = [_session retain];
182  [self->lock lock];
183  NSMapRemove(self->idToSession, [_session sessionID]);
184  [self->lock unlock];
185  [_session release];
186
187  [[WOApplication application]
188                  logWithFormat:
189                    @"WOServerSessionStore: session %@ terminated.",
190                    [_session sessionID]];
191}
192
193/* expiration check */
194
195- (void)performExpirationCheck:(NSTimer *)_timer {
196  NSNotificationCenter *nc;
197  NSMutableArray  *timedOut = nil;
198  NSMapEnumerator e;
199  NSString        *sid  = nil;
200  WOSessionInfo   *info = nil;
201  NSDate          *now;
202  unsigned cnt, count;
203
204  //NSLog(@"%s: perform expiration check ...", __PRETTY_FUNCTION__);
205
206  if (self->activeSessions == NULL)
207    count = 0;
208  else
209    count = NSCountMapTable(self->activeSessions);
210
211  if (!(count > 0 && (self->activeSessions != NULL)))
212    return;
213
214  e   = NSEnumerateMapTable(self->activeSessions);
215  now = [NSDate date];
216
217  /* search for expired sessions */
218  while (NSNextMapEnumeratorPair(&e, (void **)&sid, (void **)&info)) {
219    NSDate *timeOutDate = [info timeoutDate];
220
221    if (timeOutDate == nil) continue;
222
223    if ([now compare:timeOutDate] != NSOrderedAscending) {
224        [self logWithFormat:@"session %@ expired at %@.", sid, now];
225
226        if (timedOut == nil)
227          timedOut = [NSMutableArray arrayWithCapacity:32];
228        [timedOut addObject:info];
229    }
230  }
231
232  /* Expire sessions */
233  if (!timedOut)
234    return;
235
236  nc = [NSNotificationCenter defaultCenter];
237
238  for (cnt = 0, count = [timedOut count]; cnt < count; cnt++) {
239    NSString *sid;
240
241    info = [timedOut objectAtIndex:cnt];
242    sid  = [[info sessionID] copy];
243
244    NSMapRemove(self->activeSessions, sid);
245    NSMapRemove(self->idToSession,    sid);
246
247    [nc postNotificationName:WOSessionDidTimeOutNotification
248	object:sid];
249
250    [sid release];
251  }
252}
253
254/* description */
255
256- (NSString *)description {
257  return [NSString stringWithFormat:@"<%@[0x%p]: active=%i>",
258                     NSStringFromClass([self class]), self,
259                     [self activeSessionsCount]
260                   ];
261}
262
263@end /* WOServerSessionStore */
264
265@implementation WOSessionInfo
266
267- (id)initWithSession:(WOSession *)_session {
268  self->sessionID = RETAIN([_session sessionID]);
269
270  if ([_session respondsToSelector:@selector(timeoutDate)]) {
271    self->timeoutDate = [(id)_session timeoutDate];
272  }
273  else {
274    NSTimeInterval timeOut = [_session timeOut];
275
276    self->timeoutDate = (timeOut > 0.0)
277      ? [NSDate dateWithTimeIntervalSinceNow:(timeOut + 1.0)]
278      : [NSDate distantFuture];
279  }
280  self->timeoutDate = RETAIN(self->timeoutDate);
281
282  return self;
283}
284
285+ (WOSessionInfo *)infoForSession:(WOSession *)_session {
286  return AUTORELEASE([[self alloc] initWithSession:_session]);
287}
288
289- (void)dealloc {
290  [self->sessionID   release];
291  [self->timeoutDate release];
292  [super dealloc];
293}
294
295- (NSString *)sessionID {
296  return self->sessionID;
297}
298- (NSDate *)timeoutDate {
299  return self->timeoutDate;
300}
301
302@end /* WOSessionInfo */
303