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;
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 - (id)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 ((page = [_ctx page]) != nil) {
400 But we do have a page set in the context. This usually means that the
401 -takeValues got triggered by the WODirectActionRequestHandler in
402 combination with a WOComponent being the DirectAction object.
404 NSTimeInterval st = 0.0;
406 WOContext_enterComponent(_ctx, page, nil);
408 if (profileComponents)
409 st = [[NSDateClass date] timeIntervalSince1970];
411 [page takeValuesFromRequest:_request inContext:_ctx];
413 if (profileComponents) {
416 diff = [[NSDateClass date] timeIntervalSince1970] - st;
417 printf("prof[%s %s]: %0.3fs\n",
418 [[(WOComponent *)page name] cString], sel_get_name(_cmd), diff);
421 WOContext_leaveComponent(_ctx, page);
427 if ([[_request method] isEqualToString:@"GET"]) {
430 r = [[_request uri] rangeOfString:@"?"];
432 /* no form content to apply */
433 // TODO: we should run the takeValues nevertheless to clear values?
438 if ((reqCtxId = [_ctx currentElementID]) == nil)
441 [_ctx appendElementIDComponent:reqCtxId];
445 if ((page = [_ctx page]) != nil) {
446 NSTimeInterval st = 0.0;
448 WOContext_enterComponent(_ctx, page, nil);
450 if (profileComponents)
451 st = [[NSDateClass date] timeIntervalSince1970];
453 [page takeValuesFromRequest:_request inContext:_ctx];
455 if (profileComponents) {
458 diff = [[NSDateClass date] timeIntervalSince1970] - st;
459 printf("prof[%s %s]: %0.3fs\n",
460 [[(WOComponent *)page name] cString], sel_get_name(_cmd), diff);
463 WOContext_leaveComponent(_ctx, page);
466 [_ctx deleteLastElementIDComponent];
469 - (id)invokeActionForRequest:(WORequest *)_request inContext:(WOContext *)_ctx {
471 BOOL returnResult = NO;
474 self->context = _ctx;
475 self->application = [WOApplication application];
477 if ((reqCtxId = [_ctx currentElementID]) == nil)
478 /* no sender element ID */
481 [_ctx appendElementIDComponent:reqCtxId];
485 if ((page = [_ctx page])) {
487 -consumeElementID consumes the context id and returns the
488 id of the next element.
489 If there was no next element, the request wasn't active.
491 if (([_ctx consumeElementID])) {
492 NSTimeInterval st = 0.0;
495 WOContext_enterComponent(_ctx, page, nil);
497 if (profileComponents)
498 st = [[NSDateClass date] timeIntervalSince1970];
500 result = [page invokeActionForRequest:_request inContext:_ctx];
502 if (profileComponents) {
505 diff = [[NSDateClass date] timeIntervalSince1970] - st;
506 printf("prof[%s %s]: %0.3fs\n",
507 [[page name] cString], sel_get_name(_cmd), diff);
508 //[page name], sel_get_name(_cmd), diff);
511 WOContext_leaveComponent(_ctx, page);
515 [_ctx deleteLastElementIDComponent];
516 return returnResult ? result : [_ctx page];
519 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
520 self->context = _ctx;
521 self->application = [WOApplication application];
523 /* HTTP/1.1 caching directive, prevents browser from caching dynamic pages */
524 if ([self->application isPageRefreshOnBacktrackEnabled]) {
527 if ((ctype = [_response headerForKey:@"content-type"])) {
528 if ([ctype rangeOfString:@"html"].length > 0)
529 // profiling OSX: 3.1% of append...
530 [_response disableClientCaching];
534 [_ctx deleteAllElementIDComponents];
535 [_ctx appendElementIDComponent:[_ctx contextID]];
539 if ((page = [_ctx page])) {
540 /* let the page append it's content */
541 NSTimeInterval st = 0.0;
543 WOContext_enterComponent(_ctx, page, nil);
545 if (profileComponents)
546 st = [[NSDateClass date] timeIntervalSince1970];
548 [page appendToResponse:_response inContext:_ctx];
550 if (profileComponents) {
553 diff = [[NSDateClass date] timeIntervalSince1970] - st;
554 printf("prof[%s %s]: %0.3fs\n",
555 [[page name] cString], sel_get_name(_cmd), diff);
558 WOContext_leaveComponent(_ctx, page);
561 [self logWithFormat:@"missing page in context for -appendToResponse: !"];
564 [_ctx deleteLastElementIDComponent];
566 /* generate statistics */
567 // profiling OSX: 3.1% of append... (seems to be NSDate!)
568 [[[self application] statisticsStore]
569 recordStatisticsForResponse:_response
576 [self->wosLock lock];
579 [self->wosLock unlock];
583 return [self->wosLock tryLock];
586 /* session variables */
588 - (void)setObject:(id)_obj forKey:(NSString *)_key {
589 if (self->wosVariables == nil)
590 self->wosVariables = [[NSMutableDictionary alloc] initWithCapacity:16];
593 [self->wosVariables setObject:_obj forKey:_key];
595 [self->wosVariables removeObjectForKey:_key];
597 - (id)objectForKey:(NSString *)_key {
598 return [self->wosVariables objectForKey:_key];
601 - (void)removeObjectForKey:(NSString *)_key {
602 [self->wosVariables removeObjectForKey:_key];
605 - (NSDictionary *)variableDictionary {
606 return self->wosVariables;
609 #if LIB_FOUNDATION_LIBRARY /* only override on libFoundation */
611 - (void)takeValue:(id)_value forKey:(NSString *)_key {
612 if (WOSetKVCValueUsingMethod(self, _key, _value))
615 else if (WOGetKVCGetMethod(self, _key) == NULL) {
616 if (self->wosVariables == nil)
617 self->wosVariables = [[NSMutableDictionary alloc] initWithCapacity:16];
619 if (_value) [self->wosVariables setObject:_value forKey:_key];
623 // only a 'get' method is defined for _key !
624 [self handleTakeValue:_value forUnboundKey:_key];
626 - (id)valueForKey:(NSString *)_key {
629 if ((value = WOGetKVCValueUsingMethod(self, _key)))
632 return [self->wosVariables objectForKey:_key];
635 #else /* use fallback methods on other Foundation libraries */
637 - (void)setValue:(id)_value forUndefinedKey:(NSString *)_key {
638 [self setObject:_value forKey:_key];
640 - (id)valueForUndefinedKey:(NSString *)_key {
641 return [self->wosVariables objectForKey:_key];
644 - (void)handleTakeValue:(id)_value forUnboundKey:(NSString *)_key {
645 // deprecated: pre-Panther method
646 [self setValue:_value forUndefinedKey:_key];
648 - (id)handleQueryWithUnboundKey:(NSString *)_key {
649 // deprecated: pre-Panther method
650 return [self valueForUndefinedKey:_key];
657 - (NSArray *)statistics {
658 return [NSArray array];
663 - (NSString *)description {
664 return [NSString stringWithFormat:@"<%@[0x%08X]: id=%@>",
665 NSStringFromClass([self class]), self,
671 @implementation WOSession(NSCoding)
673 - (void)encodeWithCoder:(NSCoder *)_coder {
677 [_coder encodeObject:self->wosLanguages];
678 [_coder encodeObject:[self sessionID]];
679 [_coder encodeObject:self->wosVariables];
680 [_coder encodeValueOfObjCType:@encode(NSTimeInterval) at:&(self->wosTimeOut)];
681 t = [self storesIDsInURLs];
682 [_coder encodeValueOfObjCType:@encode(BOOL) at:&t];
683 t = [self storesIDsInCookies];
684 [_coder encodeValueOfObjCType:@encode(BOOL) at:&t];
686 /* store page caches */
688 [_coder encodeValueOfObjCType:@encode(unsigned short)
689 at:&(self->pageCache.index)];
690 [_coder encodeValueOfObjCType:@encode(unsigned short)
691 at:&(self->pageCache.size)];
692 for (i = 0; i < self->pageCache.size; i++) {
693 [_coder encodeValueOfObjCType:@encode(unsigned)
694 at:&(self->pageCache.entries[i].ctxIdHash)];
695 [_coder encodeObject:self->pageCache.entries[i].contextID];
696 [_coder encodeObject:self->pageCache.entries[i].page];
699 [_coder encodeValueOfObjCType:@encode(unsigned short)
700 at:&(self->permanentPageCache.index)];
701 [_coder encodeValueOfObjCType:@encode(unsigned short)
702 at:&(self->permanentPageCache.size)];
703 for (i = 0; i < self->permanentPageCache.size; i++) {
704 [_coder encodeValueOfObjCType:@encode(unsigned)
705 at:&(self->permanentPageCache.entries[i].ctxIdHash)];
706 [_coder encodeObject:self->permanentPageCache.entries[i].contextID];
707 [_coder encodeObject:self->permanentPageCache.entries[i].page];
711 - (id)initWithCoder:(NSCoder *)_coder {
712 if ((self = [super init])) {
716 self->wosLanguages = [[_coder decodeObject] retain];
717 self->wosSessionId = [[_coder decodeObject] copyWithZone:[self zone]];
718 self->wosVariables = [[_coder decodeObject] copyWithZone:[self zone]];
719 [_coder decodeValueOfObjCType:@encode(NSTimeInterval)
720 at:&(self->wosTimeOut)];
721 [_coder decodeValueOfObjCType:@encode(BOOL) at:&t];
722 [self setStoresIDsInURLs:t];
723 [_coder decodeValueOfObjCType:@encode(BOOL) at:&t];
724 [self setStoresIDsInCookies:t];
726 /* restore page caches */
728 [_coder decodeValueOfObjCType:@encode(unsigned short)
729 at:&(self->pageCache.index)];
730 [_coder decodeValueOfObjCType:@encode(unsigned short)
731 at:&(self->pageCache.size)];
732 self->pageCache.entries =
733 NGMalloc(sizeof(struct WOSessionCacheEntry) * self->pageCache.size);
734 for (i = 0; i < self->pageCache.size; i++) {
735 [_coder decodeValueOfObjCType:@encode(unsigned)
736 at:&(self->pageCache.entries[i].ctxIdHash)];
737 self->pageCache.entries[i].contextID = [[_coder decodeObject] retain];
738 self->pageCache.entries[i].page = [[_coder decodeObject] retain];
741 [_coder decodeValueOfObjCType:@encode(unsigned short)
742 at:&(self->permanentPageCache.index)];
743 [_coder decodeValueOfObjCType:@encode(unsigned short)
744 at:&(self->permanentPageCache.size)];
745 self->permanentPageCache.entries =
746 NGMalloc(sizeof(struct WOSessionCacheEntry) *
747 self->permanentPageCache.size);
748 for (i = 0; i < self->permanentPageCache.size; i++) {
749 [_coder decodeValueOfObjCType:@encode(unsigned)
750 at:&(self->permanentPageCache.entries[i].ctxIdHash)];
751 self->permanentPageCache.entries[i].contextID =
752 [[_coder decodeObject] retain];
753 self->permanentPageCache.entries[i].page = [[_coder decodeObject] retain];
756 self->wosLock = [[NSRecursiveLock allocWithZone:[self zone]] init];
761 @end /* WOSession(NSCoding) */
763 @implementation WOSession(Logging2)
765 - (BOOL)isDebuggingEnabled {
766 static char showDebug = 2;
769 showDebug = [WOApplication isDebuggingEnabled] ? 1 : 0;
770 return showDebug ? YES : NO;
772 - (NSString *)loggingPrefix {
773 return [NSString stringWithFormat:@"(%@)", [self sessionID]];
776 @end /* WOSession(Logging) */
778 NSString *OWSessionLanguagesDidChangeNotificationName =
779 @"OWSnLanguagesDidChangeNotification";
781 @implementation WOSession(Misc)
783 - (void)languageArrayDidChange {
786 c = [[self context] page];
787 if ([c respondsToSelector:@selector(languageArrayDidChange)])
788 [(id)c languageArrayDidChange];
790 [[NSNotificationCenter defaultCenter]
791 postNotificationName:
792 OWSessionLanguagesDidChangeNotificationName
796 @end /* WOSession(Misc) */