2 Copyright (C) 2000-2004 SKYRIX Software AG
4 This file is part of OpenGroupware.org.
6 OGo 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
11 OGo 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.
16 You should have received a copy of the GNU Lesser General Public
17 License along with OGo; see the file COPYING. If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
23 #include <NGObjWeb/WOSession.h>
24 #include "WOContext+private.h"
25 #include "NSObject+WO.h"
26 #include "WOComponent+private.h"
27 #include <NGObjWeb/WOApplication.h>
28 #include <NGObjWeb/WOComponent.h>
29 #include <NGObjWeb/WORequest.h>
30 #include <NGObjWeb/WOResponse.h>
31 #include <NGObjWeb/WOStatisticsStore.h>
32 #include <EOControl/EONull.h>
35 #if APPLE_FOUNDATION_LIBRARY || NeXT_Foundation_LIBRARY
36 @interface NSObject(Miss)
37 - (id)notImplemented:(SEL)cmd;
41 struct WOSessionCacheEntry {
48 NSString *WOSessionDidTimeOutNotification = @"WOSessionDidTimeOut";
50 NSString *WOSessionDidRestoreNotification = @"WOSessionDidRestore";
52 NSString *WOSessionDidCreateNotification = @"WOSessionDidCreate";
54 NSString *WOSessionDidTerminateNotification = @"WOSessionDidTerminate";
56 @implementation WOSession
62 static int profileComponents = -1;
63 static int logPageCache = -1;
64 static Class NSDateClass = Nil;
67 if (NSDateClass == Nil)
68 NSDateClass = [NSDate class];
72 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
74 if (NSDateClass == Nil)
75 NSDateClass = [NSDate class];
77 if (profileComponents == -1) {
79 [[ud objectForKey:@"WOProfileComponents"] boolValue] ? 1 : 0;
81 if (logPageCache == -1)
82 logPageCache = [[ud objectForKey:@"WOLogPageCache"] boolValue] ? 1 : 0;
84 if ((self = [super init])) {
85 WOApplication *app = [WOApplication application];
87 if ([[ud objectForKey:@"WORunMultithreaded"] boolValue])
88 self->wosLock = [[NSRecursiveLock allocWithZone:[self zone]] init];
90 /* setup page cache */
92 [self setStoresIDsInURLs:YES];
93 [self setStoresIDsInCookies:YES];
95 self->pageCache.index = 0;
96 self->pageCache.size = [app pageCacheSize];
98 if (self->pageCache.size > 0) {
99 self->pageCache.entries =
100 NGMalloc(sizeof(struct WOSessionCacheEntry) * self->pageCache.size);
101 memset(self->pageCache.entries, 0,
102 sizeof(struct WOSessionCacheEntry) * self->pageCache.size);
105 self->permanentPageCache.index = 0;
106 self->permanentPageCache.size = [app permanentPageCacheSize];
108 if (self->permanentPageCache.size > 0) {
109 self->permanentPageCache.entries =
110 NGMalloc(sizeof(struct WOSessionCacheEntry) *
111 self->permanentPageCache.size);
112 memset(self->permanentPageCache.entries, 0,
113 sizeof(struct WOSessionCacheEntry) *
114 self->permanentPageCache.size);
119 self->wosLanguages = [[ud arrayForKey:@"WODefaultLanguages"] copy];
120 self->isTerminating = NO;
122 [self setTimeOut:[[WOApplication sessionTimeOut] intValue]];
124 /* setup session ID */
127 [[[WOApplication application] createSessionIDForSession:self] copy];
129 if (self->wosSessionId == nil) {
130 /* session-id creation failed ... */
137 if (profileComponents)
138 [self logWithFormat:@"Component profiling is on."];
144 [[NSNotificationCenter defaultCenter]
145 postNotificationName:@"WOSessionWillDeallocate"
147 [self->wosVariables release];
148 [self->wosSessionId release];
149 [self->wosLock release];
150 [self->wosLanguages release];
156 - (NSString *)sessionID {
157 return self->wosSessionId;
160 - (void)setStoresIDsInURLs:(BOOL)_flag {
161 self->wosFlags.storesIDsInURLs = _flag ? 1 : 0;
163 - (BOOL)storesIDsInURLs {
164 return self->wosFlags.storesIDsInURLs ? YES : NO;
167 - (void)setStoresIDsInCookies:(BOOL)_flag {
168 self->wosFlags.storesIDsInCookies = _flag ? 1 : 0;
170 - (BOOL)storesIDsInCookies {
171 return self->wosFlags.storesIDsInCookies ? YES : NO;
174 - (NSString *)domainForIDCookies {
177 - (NSDate *)expirationDateForIDCookies {
178 return [self isTerminating]
179 ? [NSDate dateWithTimeIntervalSinceNow:-15.0]
180 : [NSDate dateWithTimeIntervalSinceNow:([self timeOut] - 5.0)];
183 - (void)setDistributionEnabled:(BOOL)_flag {
184 [self notImplemented:_cmd];
186 - (BOOL)isDistributionEnabled {
190 - (void)setTimeOut:(NSTimeInterval)_timeout {
191 self->wosTimeOut = _timeout;
193 - (NSTimeInterval)timeOut {
194 return self->wosTimeOut;
198 self->isTerminating = YES;
200 - (BOOL)isTerminating {
201 return self->isTerminating;
204 - (WOApplication *)application {
205 if (self->application == nil)
206 self->application = [WOApplication application];
207 return self->application;
209 - (WOContext *)context {
210 if (self->context == nil) {
211 if (self->application == nil)
212 self->application = [WOApplication application];
213 self->context = [self->application context];
215 return self->context;
218 /* editing context */
220 - (id)defaultEditingContext {
221 if (![WOApplication implementsEditingContexts])
224 if (self->wosDefaultEditingContext == nil) {
225 self->wosDefaultEditingContext =
226 [[[WOApplication eoEditingContextClass] alloc] init];
228 return self->wosDefaultEditingContext;
233 - (WOComponent *)restorePageForContextID:(NSString *)_contextID {
236 WOComponent *page = nil;
238 ctxHash = [_contextID hash];
240 /* first scan permanent cache */
242 for (i = 0, page = nil;
243 (page == nil) && (i < self->permanentPageCache.size); i++) {
244 struct WOSessionCacheEntry *entry;
246 entry = &(self->permanentPageCache.entries[i]);
248 if (ctxHash == entry->ctxIdHash) {
249 if ([_contextID isEqualToString:entry->contextID]) {
252 [self debugWithFormat:@"restored permanent page %@ for ctx %@",
253 page ? [page name] : (id)@"<nil>",
254 _contextID ? _contextID : (id)@"<nil>"];
262 return [[page retain] autorelease];
264 /* now scan regular cache */
266 for (i = 0, page = nil; (page == nil) && (i < self->pageCache.size); i++) {
267 struct WOSessionCacheEntry *entry;
269 entry = &(self->pageCache.entries[i]);
271 if (ctxHash == entry->ctxIdHash) {
272 if ([_contextID isEqualToString:entry->contextID]) {
275 [self debugWithFormat:@"restored page %@<0x%08X> for ctx %@",
276 [page name], page, _contextID];
282 return [[page retain] autorelease];
285 - (void)savePage:(WOComponent *)_page {
287 struct WOSessionCacheEntry *entry;
289 cid = [[self context] contextID];
291 [self debugWithFormat:@"storing page %@ for ctx %@", [_page name], cid];
293 /* go to next (fixed) queue entry */
294 self->pageCache.index++;
295 if (self->pageCache.index >= self->pageCache.size)
296 self->pageCache.index = 0;
298 entry = &(self->pageCache.entries[self->pageCache.index]);
300 /* reset old queue entry */
301 entry->ctxIdHash = 0;
302 [entry->contextID release];
303 [entry->page release];
305 /* assign new values */
306 entry->contextID = [cid copyWithZone:[self zone]];
307 entry->ctxIdHash = [entry->contextID hash];
308 entry->page = [_page retain];
311 - (void)savePageInPermanentCache:(WOComponent *)_page {
313 struct WOSessionCacheEntry *entry;
315 cid = [[self context] contextID];
317 [self debugWithFormat:
318 @"permanently storing page %@ for ctx %@", [_page name], cid];
321 /* go to next (fixed) queue entry */
322 self->permanentPageCache.index++;
323 if (self->permanentPageCache.index >= self->permanentPageCache.size)
324 self->permanentPageCache.index = 0;
326 entry = &(self->permanentPageCache.entries[self->permanentPageCache.index]);
328 /* reset old queue entry */
329 entry->ctxIdHash = 0;
330 [entry->contextID release];
331 [entry->page release];
333 /* assign new values */
334 entry->contextID = [cid copyWithZone:[self zone]];
335 entry->ctxIdHash = [entry->contextID hash];
336 entry->page = [_page retain];
341 - (void)languageArrayDidChange {
344 - (void)setLanguages:(NSArray *)_langs {
345 if (![self->wosLanguages isEqual:_langs]) { // check whether they really differ
346 [self->wosLanguages release]; self->wosLanguages = nil;
347 self->wosLanguages = [_langs copyWithZone:[self zone]];
348 [self languageArrayDidChange];
351 - (NSArray *)languages {
352 return self->wosLanguages;
362 - (void)_awakeWithContext:(WOContext *)_ctx {
363 if (self->context == nil)
364 self->context = _ctx;
365 if (self->application == nil)
366 self->application = [WOApplication application];
368 if (!self->wosFlags.isAwake) {
370 self->wosFlags.isAwake = 1;
373 - (void)_sleepWithContext:(WOContext *)_ctx {
374 if (self->wosFlags.isAwake) {
376 self->wosFlags.isAwake = 0;
379 self->application = nil;
384 - (void)takeValuesFromRequest:(WORequest *)_request
385 inContext:(WOContext *)_ctx
390 self->context = _ctx;
391 self->application = [WOApplication application];
393 senderID = [_ctx senderID];
395 if ([senderID length] == 0)
396 /* no element URL is available */
399 if ([[_request method] isEqualToString:@"GET"]) {
402 r = [[_request uri] rangeOfString:@"?"];
404 /* no form content to apply */
408 if ((reqCtxId = [_ctx currentElementID]) == nil)
411 [_ctx appendElementIDComponent:reqCtxId];
415 if ((page = [_ctx page])) {
416 NSTimeInterval st = 0.0;
418 WOContext_enterComponent(_ctx, page, nil);
420 if (profileComponents)
421 st = [[NSDateClass date] timeIntervalSince1970];
423 [page takeValuesFromRequest:_request inContext:_ctx];
425 if (profileComponents) {
428 diff = [[NSDateClass date] timeIntervalSince1970] - st;
429 printf("prof[%s %s]: %0.3fs\n",
430 [[(WOComponent *)page name] cString], sel_get_name(_cmd), diff);
433 WOContext_leaveComponent(_ctx, page);
436 [_ctx deleteLastElementIDComponent];
439 - (id)invokeActionForRequest:(WORequest *)_request inContext:(WOContext *)_ctx {
441 BOOL returnResult = NO;
444 self->context = _ctx;
445 self->application = [WOApplication application];
447 if ((reqCtxId = [_ctx currentElementID]) == nil)
448 /* no sender element ID */
451 [_ctx appendElementIDComponent:reqCtxId];
455 if ((page = [_ctx page])) {
457 -consumeElementID consumes the context id and returns the
458 id of the next element.
459 If there was no next element, the request wasn't active.
461 if (([_ctx consumeElementID])) {
462 NSTimeInterval st = 0.0;
465 WOContext_enterComponent(_ctx, page, nil);
467 if (profileComponents)
468 st = [[NSDateClass date] timeIntervalSince1970];
470 result = [page invokeActionForRequest:_request inContext:_ctx];
472 if (profileComponents) {
475 diff = [[NSDateClass date] timeIntervalSince1970] - st;
476 printf("prof[%s %s]: %0.3fs\n",
477 [[page name] cString], sel_get_name(_cmd), diff);
478 //[page name], sel_get_name(_cmd), diff);
481 WOContext_leaveComponent(_ctx, page);
485 [_ctx deleteLastElementIDComponent];
486 return returnResult ? result : [_ctx page];
489 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
490 self->context = _ctx;
491 self->application = [WOApplication application];
493 /* HTTP/1.1 caching directive, prevents browser from caching dynamic pages */
494 if ([self->application isPageRefreshOnBacktrackEnabled]) {
497 if ((ctype = [_response headerForKey:@"content-type"])) {
498 if ([ctype rangeOfString:@"html"].length > 0)
499 // profiling OSX: 3.1% of append...
500 [_response disableClientCaching];
504 [_ctx deleteAllElementIDComponents];
505 [_ctx appendElementIDComponent:[_ctx contextID]];
509 if ((page = [_ctx page])) {
510 /* let the page append it's content */
511 NSTimeInterval st = 0.0;
513 WOContext_enterComponent(_ctx, page, nil);
515 if (profileComponents)
516 st = [[NSDateClass date] timeIntervalSince1970];
518 [page appendToResponse:_response inContext:_ctx];
520 if (profileComponents) {
523 diff = [[NSDateClass date] timeIntervalSince1970] - st;
524 printf("prof[%s %s]: %0.3fs\n",
525 [[page name] cString], sel_get_name(_cmd), diff);
528 WOContext_leaveComponent(_ctx, page);
531 [self logWithFormat:@"missing page in context for -appendToResponse: !"];
534 [_ctx deleteLastElementIDComponent];
536 /* generate statistics */
537 // profiling OSX: 3.1% of append... (seems to be NSDate!)
538 [[[self application] statisticsStore]
539 recordStatisticsForResponse:_response
546 [self->wosLock lock];
549 [self->wosLock unlock];
553 return [self->wosLock tryLock];
556 /* session variables */
558 - (void)setObject:(id)_obj forKey:(NSString *)_key {
559 if (self->wosVariables == nil)
560 self->wosVariables = [[NSMutableDictionary alloc] initWithCapacity:16];
563 [self->wosVariables setObject:_obj forKey:_key];
565 [self->wosVariables removeObjectForKey:_key];
567 - (id)objectForKey:(NSString *)_key {
568 return [self->wosVariables objectForKey:_key];
571 - (void)removeObjectForKey:(NSString *)_key {
572 [self->wosVariables removeObjectForKey:_key];
575 - (NSDictionary *)variableDictionary {
576 return self->wosVariables;
579 #if LIB_FOUNDATION_LIBRARY /* only override on libFoundation */
581 - (void)takeValue:(id)_value forKey:(NSString *)_key {
582 if (WOSetKVCValueUsingMethod(self, _key, _value))
585 else if (WOGetKVCGetMethod(self, _key) == NULL) {
586 if (self->wosVariables == nil)
587 self->wosVariables = [[NSMutableDictionary alloc] initWithCapacity:16];
589 if (_value) [self->wosVariables setObject:_value forKey:_key];
593 // only a 'get' method is defined for _key !
594 [self handleTakeValue:_value forUnboundKey:_key];
596 - (id)valueForKey:(NSString *)_key {
599 if ((value = WOGetKVCValueUsingMethod(self, _key)))
602 return [self->wosVariables objectForKey:_key];
605 #else /* use fallback methods on other Foundation libraries */
607 - (void)setValue:(id)_value forUndefinedKey:(NSString *)_key {
608 [self setObject:_value forKey:_key];
610 - (id)valueForUndefinedKey:(NSString *)_key {
611 return [self->wosVariables objectForKey:_key];
614 - (void)handleTakeValue:(id)_value forUnboundKey:(NSString *)_key {
615 // deprecated: pre-Panther method
616 [self setValue:_value forUndefinedKey:_key];
618 - (id)handleQueryWithUnboundKey:(NSString *)_key {
619 // deprecated: pre-Panther method
620 return [self valueForUndefinedKey:_key];
627 - (NSArray *)statistics {
628 return [NSArray array];
633 - (NSString *)description {
634 return [NSString stringWithFormat:@"<%@[0x%08X]: id=%@>",
635 NSStringFromClass([self class]), self,
641 @implementation WOSession(NSCoding)
643 - (void)encodeWithCoder:(NSCoder *)_coder {
647 [_coder encodeObject:self->wosLanguages];
648 [_coder encodeObject:[self sessionID]];
649 [_coder encodeObject:self->wosVariables];
650 [_coder encodeValueOfObjCType:@encode(NSTimeInterval) at:&(self->wosTimeOut)];
651 t = [self storesIDsInURLs];
652 [_coder encodeValueOfObjCType:@encode(BOOL) at:&t];
653 t = [self storesIDsInCookies];
654 [_coder encodeValueOfObjCType:@encode(BOOL) at:&t];
656 /* store page caches */
658 [_coder encodeValueOfObjCType:@encode(unsigned short)
659 at:&(self->pageCache.index)];
660 [_coder encodeValueOfObjCType:@encode(unsigned short)
661 at:&(self->pageCache.size)];
662 for (i = 0; i < self->pageCache.size; i++) {
663 [_coder encodeValueOfObjCType:@encode(unsigned)
664 at:&(self->pageCache.entries[i].ctxIdHash)];
665 [_coder encodeObject:self->pageCache.entries[i].contextID];
666 [_coder encodeObject:self->pageCache.entries[i].page];
669 [_coder encodeValueOfObjCType:@encode(unsigned short)
670 at:&(self->permanentPageCache.index)];
671 [_coder encodeValueOfObjCType:@encode(unsigned short)
672 at:&(self->permanentPageCache.size)];
673 for (i = 0; i < self->permanentPageCache.size; i++) {
674 [_coder encodeValueOfObjCType:@encode(unsigned)
675 at:&(self->permanentPageCache.entries[i].ctxIdHash)];
676 [_coder encodeObject:self->permanentPageCache.entries[i].contextID];
677 [_coder encodeObject:self->permanentPageCache.entries[i].page];
681 - (id)initWithCoder:(NSCoder *)_coder {
682 if ((self = [super init])) {
686 self->wosLanguages = [[_coder decodeObject] retain];
687 self->wosSessionId = [[_coder decodeObject] copyWithZone:[self zone]];
688 self->wosVariables = [[_coder decodeObject] copyWithZone:[self zone]];
689 [_coder decodeValueOfObjCType:@encode(NSTimeInterval)
690 at:&(self->wosTimeOut)];
691 [_coder decodeValueOfObjCType:@encode(BOOL) at:&t];
692 [self setStoresIDsInURLs:t];
693 [_coder decodeValueOfObjCType:@encode(BOOL) at:&t];
694 [self setStoresIDsInCookies:t];
696 /* restore page caches */
698 [_coder decodeValueOfObjCType:@encode(unsigned short)
699 at:&(self->pageCache.index)];
700 [_coder decodeValueOfObjCType:@encode(unsigned short)
701 at:&(self->pageCache.size)];
702 self->pageCache.entries =
703 NGMalloc(sizeof(struct WOSessionCacheEntry) * self->pageCache.size);
704 for (i = 0; i < self->pageCache.size; i++) {
705 [_coder decodeValueOfObjCType:@encode(unsigned)
706 at:&(self->pageCache.entries[i].ctxIdHash)];
707 self->pageCache.entries[i].contextID = [[_coder decodeObject] retain];
708 self->pageCache.entries[i].page = [[_coder decodeObject] retain];
711 [_coder decodeValueOfObjCType:@encode(unsigned short)
712 at:&(self->permanentPageCache.index)];
713 [_coder decodeValueOfObjCType:@encode(unsigned short)
714 at:&(self->permanentPageCache.size)];
715 self->permanentPageCache.entries =
716 NGMalloc(sizeof(struct WOSessionCacheEntry) *
717 self->permanentPageCache.size);
718 for (i = 0; i < self->permanentPageCache.size; i++) {
719 [_coder decodeValueOfObjCType:@encode(unsigned)
720 at:&(self->permanentPageCache.entries[i].ctxIdHash)];
721 self->permanentPageCache.entries[i].contextID =
722 [[_coder decodeObject] retain];
723 self->permanentPageCache.entries[i].page = [[_coder decodeObject] retain];
726 self->wosLock = [[NSRecursiveLock allocWithZone:[self zone]] init];
731 @end /* WOSession(NSCoding) */
733 @implementation WOSession(Logging2)
735 - (BOOL)isDebuggingEnabled {
736 static char showDebug = 2;
739 showDebug = [WOApplication isDebuggingEnabled] ? 1 : 0;
740 return showDebug ? YES : NO;
742 - (NSString *)loggingPrefix {
743 return [NSString stringWithFormat:@"(%@)", [self sessionID]];
746 @end /* WOSession(Logging) */
748 NSString *OWSessionLanguagesDidChangeNotificationName =
749 @"OWSnLanguagesDidChangeNotification";
751 @implementation WOSession(Misc)
753 - (void)languageArrayDidChange {
756 c = [[self context] page];
757 if ([c respondsToSelector:@selector(languageArrayDidChange)])
758 [(id)c languageArrayDidChange];
760 [[NSNotificationCenter defaultCenter]
761 postNotificationName:
762 OWSessionLanguagesDidChangeNotificationName
766 @end /* WOSession(Misc) */