2 Copyright (C) 2000-2005 SKYRIX Software AG
4 This file is part of SOPE.
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
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.
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
22 #include <NGObjWeb/WOSession.h>
23 #include "WOContext+private.h"
24 #include "NSObject+WO.h"
25 #include "WOComponent+private.h"
26 #include <NGObjWeb/WOApplication.h>
27 #include <NGObjWeb/WOComponent.h>
28 #include <NGObjWeb/WORequest.h>
29 #include <NGObjWeb/WOResponse.h>
30 #include <NGObjWeb/WOStatisticsStore.h>
31 #include <EOControl/EONull.h>
34 #if APPLE_FOUNDATION_LIBRARY || NeXT_Foundation_LIBRARY
35 @interface NSObject(Miss)
36 - (id)notImplemented:(SEL)cmd;
40 struct WOSessionCacheEntry {
47 NSString *WOSessionDidTimeOutNotification = @"WOSessionDidTimeOut";
49 NSString *WOSessionDidRestoreNotification = @"WOSessionDidRestore";
51 NSString *WOSessionDidCreateNotification = @"WOSessionDidCreate";
53 NSString *WOSessionDidTerminateNotification = @"WOSessionDidTerminate";
55 @implementation WOSession
61 static int profileComponents = -1;
62 static int logPageCache = -1;
63 static Class NSDateClass = Nil;
66 if (NSDateClass == Nil)
67 NSDateClass = [NSDate class];
71 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
73 if (NSDateClass == Nil)
74 NSDateClass = [NSDate class];
76 if (profileComponents == -1) {
78 [[ud objectForKey:@"WOProfileComponents"] boolValue] ? 1 : 0;
80 if (logPageCache == -1)
81 logPageCache = [[ud objectForKey:@"WOLogPageCache"] boolValue] ? 1 : 0;
83 if ((self = [super init])) {
84 WOApplication *app = [WOApplication application];
86 if ([[ud objectForKey:@"WORunMultithreaded"] boolValue])
87 self->wosLock = [[NSRecursiveLock allocWithZone:[self zone]] init];
89 /* setup page cache */
91 [self setStoresIDsInURLs:YES];
92 [self setStoresIDsInCookies:YES];
94 self->pageCache.index = 0;
95 self->pageCache.size = [app pageCacheSize];
97 if (self->pageCache.size > 0) {
98 self->pageCache.entries =
99 NGMalloc(sizeof(struct WOSessionCacheEntry) * self->pageCache.size);
100 memset(self->pageCache.entries, 0,
101 sizeof(struct WOSessionCacheEntry) * self->pageCache.size);
104 self->permanentPageCache.index = 0;
105 self->permanentPageCache.size = [app permanentPageCacheSize];
107 if (self->permanentPageCache.size > 0) {
108 self->permanentPageCache.entries =
109 NGMalloc(sizeof(struct WOSessionCacheEntry) *
110 self->permanentPageCache.size);
111 memset(self->permanentPageCache.entries, 0,
112 sizeof(struct WOSessionCacheEntry) *
113 self->permanentPageCache.size);
118 self->wosLanguages = [[ud arrayForKey:@"WODefaultLanguages"] copy];
119 self->isTerminating = NO;
121 [self setTimeOut:[[WOApplication sessionTimeOut] intValue]];
123 /* setup session ID */
126 [[[WOApplication application] createSessionIDForSession:self] copy];
128 if (self->wosSessionId == nil) {
129 /* session-id creation failed ... */
136 if (profileComponents)
137 [self logWithFormat:@"Component profiling is on."];
143 [[NSNotificationCenter defaultCenter]
144 postNotificationName:@"WOSessionWillDeallocate"
146 [self->wosVariables release];
147 [self->wosSessionId release];
148 [self->wosLock release];
149 [self->wosLanguages release];
155 - (NSString *)sessionID {
156 return self->wosSessionId;
159 - (void)setStoresIDsInURLs:(BOOL)_flag {
160 self->wosFlags.storesIDsInURLs = _flag ? 1 : 0;
162 - (BOOL)storesIDsInURLs {
163 return self->wosFlags.storesIDsInURLs ? YES : NO;
166 - (void)setStoresIDsInCookies:(BOOL)_flag {
167 self->wosFlags.storesIDsInCookies = _flag ? 1 : 0;
169 - (BOOL)storesIDsInCookies {
170 return self->wosFlags.storesIDsInCookies ? YES : NO;
173 - (NSString *)domainForIDCookies {
176 - (NSDate *)expirationDateForIDCookies {
177 return [self isTerminating]
178 ? [NSDate dateWithTimeIntervalSinceNow:-15.0]
179 : [NSDate dateWithTimeIntervalSinceNow:([self timeOut] - 5.0)];
182 - (void)setDistributionEnabled:(BOOL)_flag {
183 [self notImplemented:_cmd];
185 - (BOOL)isDistributionEnabled {
189 - (void)setTimeOut:(NSTimeInterval)_timeout {
190 self->wosTimeOut = _timeout;
192 - (NSTimeInterval)timeOut {
193 return self->wosTimeOut;
197 self->isTerminating = YES;
199 - (BOOL)isTerminating {
200 return self->isTerminating;
203 - (WOApplication *)application {
204 if (self->application == nil)
205 self->application = [WOApplication application];
206 return self->application;
208 - (WOContext *)context {
209 if (self->context == nil) {
210 if (self->application == nil)
211 self->application = [WOApplication application];
212 self->context = [self->application context];
214 return self->context;
217 /* editing context */
219 - (id)defaultEditingContext {
220 if (![WOApplication implementsEditingContexts])
223 if (self->wosDefaultEditingContext == nil) {
224 self->wosDefaultEditingContext =
225 [[[WOApplication eoEditingContextClass] alloc] init];
227 return self->wosDefaultEditingContext;
232 - (WOComponent *)restorePageForContextID:(NSString *)_contextID {
235 WOComponent *page = nil;
237 ctxHash = [_contextID hash];
239 /* first scan permanent cache */
241 for (i = 0, page = nil;
242 (page == nil) && (i < self->permanentPageCache.size); i++) {
243 struct WOSessionCacheEntry *entry;
245 entry = &(self->permanentPageCache.entries[i]);
247 if (ctxHash == entry->ctxIdHash) {
248 if ([_contextID isEqualToString:entry->contextID]) {
251 [self debugWithFormat:@"restored permanent page %@ for ctx %@",
252 page ? [page name] : (id)@"<nil>",
253 _contextID ? _contextID : (id)@"<nil>"];
261 return [[page retain] autorelease];
263 /* now scan regular cache */
265 for (i = 0, page = nil; (page == nil) && (i < self->pageCache.size); i++) {
266 struct WOSessionCacheEntry *entry;
268 entry = &(self->pageCache.entries[i]);
270 if (ctxHash == entry->ctxIdHash) {
271 if ([_contextID isEqualToString:entry->contextID]) {
274 [self debugWithFormat:@"restored page %@<0x%08X> for ctx %@",
275 [page name], page, _contextID];
281 return [[page retain] autorelease];
284 - (void)savePage:(WOComponent *)_page {
286 struct WOSessionCacheEntry *entry;
288 cid = [[self context] contextID];
290 [self debugWithFormat:@"storing page %@ for ctx %@", [_page name], cid];
292 /* go to next (fixed) queue entry */
293 self->pageCache.index++;
294 if (self->pageCache.index >= self->pageCache.size)
295 self->pageCache.index = 0;
297 entry = &(self->pageCache.entries[self->pageCache.index]);
299 /* reset old queue entry */
300 entry->ctxIdHash = 0;
301 [entry->contextID release];
302 [entry->page release];
304 /* assign new values */
305 entry->contextID = [cid copyWithZone:[self zone]];
306 entry->ctxIdHash = [entry->contextID hash];
307 entry->page = [_page retain];
310 - (void)savePageInPermanentCache:(WOComponent *)_page {
312 struct WOSessionCacheEntry *entry;
314 cid = [[self context] contextID];
316 [self debugWithFormat:
317 @"permanently storing page %@ for ctx %@", [_page name], cid];
320 /* go to next (fixed) queue entry */
321 self->permanentPageCache.index++;
322 if (self->permanentPageCache.index >= self->permanentPageCache.size)
323 self->permanentPageCache.index = 0;
325 entry = &(self->permanentPageCache.entries[self->permanentPageCache.index]);
327 /* reset old queue entry */
328 entry->ctxIdHash = 0;
329 [entry->contextID release];
330 [entry->page release];
332 /* assign new values */
333 entry->contextID = [cid copyWithZone:[self zone]];
334 entry->ctxIdHash = [entry->contextID hash];
335 entry->page = [_page retain];
340 - (void)languageArrayDidChange {
343 - (void)setLanguages:(NSArray *)_langs {
344 if (![self->wosLanguages isEqual:_langs]) { // check whether they really differ
345 [self->wosLanguages release]; self->wosLanguages = nil;
346 self->wosLanguages = [_langs copyWithZone:[self zone]];
347 [self languageArrayDidChange];
350 - (NSArray *)languages {
351 return self->wosLanguages;
361 - (void)_awakeWithContext:(WOContext *)_ctx {
362 if (self->context == nil)
363 self->context = _ctx;
364 if (self->application == nil)
365 self->application = [WOApplication application];
367 if (!self->wosFlags.isAwake) {
369 self->wosFlags.isAwake = 1;
372 - (void)_sleepWithContext:(WOContext *)_ctx {
373 if (self->wosFlags.isAwake) {
375 self->wosFlags.isAwake = 0;
378 self->application = nil;
383 - (void)takeValuesFromRequest:(WORequest *)_request
384 inContext:(WOContext *)_ctx
389 self->context = _ctx;
390 self->application = [WOApplication application];
392 senderID = [_ctx senderID];
394 if ([senderID length] == 0)
395 /* no element URL is available */
398 if ([[_request method] isEqualToString:@"GET"]) {
401 r = [[_request uri] rangeOfString:@"?"];
403 /* no form content to apply */
407 if ((reqCtxId = [_ctx currentElementID]) == nil)
410 [_ctx appendElementIDComponent:reqCtxId];
414 if ((page = [_ctx page])) {
415 NSTimeInterval st = 0.0;
417 WOContext_enterComponent(_ctx, page, nil);
419 if (profileComponents)
420 st = [[NSDateClass date] timeIntervalSince1970];
422 [page takeValuesFromRequest:_request inContext:_ctx];
424 if (profileComponents) {
427 diff = [[NSDateClass date] timeIntervalSince1970] - st;
428 printf("prof[%s %s]: %0.3fs\n",
429 [[(WOComponent *)page name] cString], sel_get_name(_cmd), diff);
432 WOContext_leaveComponent(_ctx, page);
435 [_ctx deleteLastElementIDComponent];
438 - (id)invokeActionForRequest:(WORequest *)_request inContext:(WOContext *)_ctx {
440 BOOL returnResult = NO;
443 self->context = _ctx;
444 self->application = [WOApplication application];
446 if ((reqCtxId = [_ctx currentElementID]) == nil)
447 /* no sender element ID */
450 [_ctx appendElementIDComponent:reqCtxId];
454 if ((page = [_ctx page])) {
456 -consumeElementID consumes the context id and returns the
457 id of the next element.
458 If there was no next element, the request wasn't active.
460 if (([_ctx consumeElementID])) {
461 NSTimeInterval st = 0.0;
464 WOContext_enterComponent(_ctx, page, nil);
466 if (profileComponents)
467 st = [[NSDateClass date] timeIntervalSince1970];
469 result = [page invokeActionForRequest:_request inContext:_ctx];
471 if (profileComponents) {
474 diff = [[NSDateClass date] timeIntervalSince1970] - st;
475 printf("prof[%s %s]: %0.3fs\n",
476 [[page name] cString], sel_get_name(_cmd), diff);
477 //[page name], sel_get_name(_cmd), diff);
480 WOContext_leaveComponent(_ctx, page);
484 [_ctx deleteLastElementIDComponent];
485 return returnResult ? result : [_ctx page];
488 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
489 self->context = _ctx;
490 self->application = [WOApplication application];
492 /* HTTP/1.1 caching directive, prevents browser from caching dynamic pages */
493 if ([self->application isPageRefreshOnBacktrackEnabled]) {
496 if ((ctype = [_response headerForKey:@"content-type"])) {
497 if ([ctype rangeOfString:@"html"].length > 0)
498 // profiling OSX: 3.1% of append...
499 [_response disableClientCaching];
503 [_ctx deleteAllElementIDComponents];
504 [_ctx appendElementIDComponent:[_ctx contextID]];
508 if ((page = [_ctx page])) {
509 /* let the page append it's content */
510 NSTimeInterval st = 0.0;
512 WOContext_enterComponent(_ctx, page, nil);
514 if (profileComponents)
515 st = [[NSDateClass date] timeIntervalSince1970];
517 [page appendToResponse:_response inContext:_ctx];
519 if (profileComponents) {
522 diff = [[NSDateClass date] timeIntervalSince1970] - st;
523 printf("prof[%s %s]: %0.3fs\n",
524 [[page name] cString], sel_get_name(_cmd), diff);
527 WOContext_leaveComponent(_ctx, page);
530 [self logWithFormat:@"missing page in context for -appendToResponse: !"];
533 [_ctx deleteLastElementIDComponent];
535 /* generate statistics */
536 // profiling OSX: 3.1% of append... (seems to be NSDate!)
537 [[[self application] statisticsStore]
538 recordStatisticsForResponse:_response
545 [self->wosLock lock];
548 [self->wosLock unlock];
552 return [self->wosLock tryLock];
555 /* session variables */
557 - (void)setObject:(id)_obj forKey:(NSString *)_key {
558 if (self->wosVariables == nil)
559 self->wosVariables = [[NSMutableDictionary alloc] initWithCapacity:16];
562 [self->wosVariables setObject:_obj forKey:_key];
564 [self->wosVariables removeObjectForKey:_key];
566 - (id)objectForKey:(NSString *)_key {
567 return [self->wosVariables objectForKey:_key];
570 - (void)removeObjectForKey:(NSString *)_key {
571 [self->wosVariables removeObjectForKey:_key];
574 - (NSDictionary *)variableDictionary {
575 return self->wosVariables;
578 #if LIB_FOUNDATION_LIBRARY /* only override on libFoundation */
580 - (void)takeValue:(id)_value forKey:(NSString *)_key {
581 if (WOSetKVCValueUsingMethod(self, _key, _value))
584 else if (WOGetKVCGetMethod(self, _key) == NULL) {
585 if (self->wosVariables == nil)
586 self->wosVariables = [[NSMutableDictionary alloc] initWithCapacity:16];
588 if (_value) [self->wosVariables setObject:_value forKey:_key];
592 // only a 'get' method is defined for _key !
593 [self handleTakeValue:_value forUnboundKey:_key];
595 - (id)valueForKey:(NSString *)_key {
598 if ((value = WOGetKVCValueUsingMethod(self, _key)))
601 return [self->wosVariables objectForKey:_key];
604 #else /* use fallback methods on other Foundation libraries */
606 - (void)setValue:(id)_value forUndefinedKey:(NSString *)_key {
607 [self setObject:_value forKey:_key];
609 - (id)valueForUndefinedKey:(NSString *)_key {
610 return [self->wosVariables objectForKey:_key];
613 - (void)handleTakeValue:(id)_value forUnboundKey:(NSString *)_key {
614 // deprecated: pre-Panther method
615 [self setValue:_value forUndefinedKey:_key];
617 - (id)handleQueryWithUnboundKey:(NSString *)_key {
618 // deprecated: pre-Panther method
619 return [self valueForUndefinedKey:_key];
626 - (NSArray *)statistics {
627 return [NSArray array];
632 - (NSString *)description {
633 return [NSString stringWithFormat:@"<%@[0x%08X]: id=%@>",
634 NSStringFromClass([self class]), self,
640 @implementation WOSession(NSCoding)
642 - (void)encodeWithCoder:(NSCoder *)_coder {
646 [_coder encodeObject:self->wosLanguages];
647 [_coder encodeObject:[self sessionID]];
648 [_coder encodeObject:self->wosVariables];
649 [_coder encodeValueOfObjCType:@encode(NSTimeInterval) at:&(self->wosTimeOut)];
650 t = [self storesIDsInURLs];
651 [_coder encodeValueOfObjCType:@encode(BOOL) at:&t];
652 t = [self storesIDsInCookies];
653 [_coder encodeValueOfObjCType:@encode(BOOL) at:&t];
655 /* store page caches */
657 [_coder encodeValueOfObjCType:@encode(unsigned short)
658 at:&(self->pageCache.index)];
659 [_coder encodeValueOfObjCType:@encode(unsigned short)
660 at:&(self->pageCache.size)];
661 for (i = 0; i < self->pageCache.size; i++) {
662 [_coder encodeValueOfObjCType:@encode(unsigned)
663 at:&(self->pageCache.entries[i].ctxIdHash)];
664 [_coder encodeObject:self->pageCache.entries[i].contextID];
665 [_coder encodeObject:self->pageCache.entries[i].page];
668 [_coder encodeValueOfObjCType:@encode(unsigned short)
669 at:&(self->permanentPageCache.index)];
670 [_coder encodeValueOfObjCType:@encode(unsigned short)
671 at:&(self->permanentPageCache.size)];
672 for (i = 0; i < self->permanentPageCache.size; i++) {
673 [_coder encodeValueOfObjCType:@encode(unsigned)
674 at:&(self->permanentPageCache.entries[i].ctxIdHash)];
675 [_coder encodeObject:self->permanentPageCache.entries[i].contextID];
676 [_coder encodeObject:self->permanentPageCache.entries[i].page];
680 - (id)initWithCoder:(NSCoder *)_coder {
681 if ((self = [super init])) {
685 self->wosLanguages = [[_coder decodeObject] retain];
686 self->wosSessionId = [[_coder decodeObject] copyWithZone:[self zone]];
687 self->wosVariables = [[_coder decodeObject] copyWithZone:[self zone]];
688 [_coder decodeValueOfObjCType:@encode(NSTimeInterval)
689 at:&(self->wosTimeOut)];
690 [_coder decodeValueOfObjCType:@encode(BOOL) at:&t];
691 [self setStoresIDsInURLs:t];
692 [_coder decodeValueOfObjCType:@encode(BOOL) at:&t];
693 [self setStoresIDsInCookies:t];
695 /* restore page caches */
697 [_coder decodeValueOfObjCType:@encode(unsigned short)
698 at:&(self->pageCache.index)];
699 [_coder decodeValueOfObjCType:@encode(unsigned short)
700 at:&(self->pageCache.size)];
701 self->pageCache.entries =
702 NGMalloc(sizeof(struct WOSessionCacheEntry) * self->pageCache.size);
703 for (i = 0; i < self->pageCache.size; i++) {
704 [_coder decodeValueOfObjCType:@encode(unsigned)
705 at:&(self->pageCache.entries[i].ctxIdHash)];
706 self->pageCache.entries[i].contextID = [[_coder decodeObject] retain];
707 self->pageCache.entries[i].page = [[_coder decodeObject] retain];
710 [_coder decodeValueOfObjCType:@encode(unsigned short)
711 at:&(self->permanentPageCache.index)];
712 [_coder decodeValueOfObjCType:@encode(unsigned short)
713 at:&(self->permanentPageCache.size)];
714 self->permanentPageCache.entries =
715 NGMalloc(sizeof(struct WOSessionCacheEntry) *
716 self->permanentPageCache.size);
717 for (i = 0; i < self->permanentPageCache.size; i++) {
718 [_coder decodeValueOfObjCType:@encode(unsigned)
719 at:&(self->permanentPageCache.entries[i].ctxIdHash)];
720 self->permanentPageCache.entries[i].contextID =
721 [[_coder decodeObject] retain];
722 self->permanentPageCache.entries[i].page = [[_coder decodeObject] retain];
725 self->wosLock = [[NSRecursiveLock allocWithZone:[self zone]] init];
730 @end /* WOSession(NSCoding) */
732 @implementation WOSession(Logging2)
734 - (BOOL)isDebuggingEnabled {
735 static char showDebug = 2;
738 showDebug = [WOApplication isDebuggingEnabled] ? 1 : 0;
739 return showDebug ? YES : NO;
741 - (NSString *)loggingPrefix {
742 return [NSString stringWithFormat:@"(%@)", [self sessionID]];
745 @end /* WOSession(Logging) */
747 NSString *OWSessionLanguagesDidChangeNotificationName =
748 @"OWSnLanguagesDidChangeNotification";
750 @implementation WOSession(Misc)
752 - (void)languageArrayDidChange {
755 c = [[self context] page];
756 if ([c respondsToSelector:@selector(languageArrayDidChange)])
757 [(id)c languageArrayDidChange];
759 [[NSNotificationCenter defaultCenter]
760 postNotificationName:
761 OWSessionLanguagesDidChangeNotificationName
765 @end /* WOSession(Misc) */