2 Copyright (C) 2000-2007 SKYRIX Software AG
3 Copyright (C) 2007 Helge Hess
5 This file is part of SOPE.
7 SOPE is free software; you can redistribute it and/or modify it under
8 the terms of the GNU Lesser General Public License as published by the
9 Free Software Foundation; either version 2, or (at your option) any
12 SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
13 WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
15 License for more details.
17 You should have received a copy of the GNU Lesser General Public
18 License along with SOPE; see the file COPYING. If not, write to the
19 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 #import <EOControl/EONull.h>
36 #if APPLE_FOUNDATION_LIBRARY || NeXT_Foundation_LIBRARY
37 @interface NSObject(Miss)
38 - (id)notImplemented:(SEL)cmd;
42 struct WOSessionCacheEntry {
49 NSString *WOSessionDidTimeOutNotification = @"WOSessionDidTimeOut";
51 NSString *WOSessionDidRestoreNotification = @"WOSessionDidRestore";
53 NSString *WOSessionDidCreateNotification = @"WOSessionDidCreate";
55 NSString *WOSessionDidTerminateNotification = @"WOSessionDidTerminate";
57 @implementation WOSession
63 static int profileComponents = -1;
64 static int logPageCache = -1;
65 static Class NSDateClass = Nil;
68 if (NSDateClass == Nil)
69 NSDateClass = [NSDate class];
73 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
75 if (NSDateClass == Nil)
76 NSDateClass = [NSDate class];
78 if (profileComponents == -1) {
80 [[ud objectForKey:@"WOProfileComponents"] boolValue] ? 1 : 0;
82 if (logPageCache == -1)
83 logPageCache = [[ud objectForKey:@"WOLogPageCache"] boolValue] ? 1 : 0;
85 if ((self = [super init])) {
86 WOApplication *app = [WOApplication application];
88 if ([[ud objectForKey:@"WORunMultithreaded"] boolValue])
89 self->wosLock = [[NSRecursiveLock allocWithZone:[self zone]] init];
91 /* setup page cache */
93 [self setStoresIDsInURLs:YES];
94 [self setStoresIDsInCookies:YES];
96 self->pageCache.index = 0;
97 self->pageCache.size = [app pageCacheSize];
99 if (self->pageCache.size > 0) {
100 self->pageCache.entries =
101 NGMalloc(sizeof(struct WOSessionCacheEntry) * self->pageCache.size);
102 memset(self->pageCache.entries, 0,
103 sizeof(struct WOSessionCacheEntry) * self->pageCache.size);
106 self->permanentPageCache.index = 0;
107 self->permanentPageCache.size = [app permanentPageCacheSize];
109 if (self->permanentPageCache.size > 0) {
110 self->permanentPageCache.entries =
111 NGMalloc(sizeof(struct WOSessionCacheEntry) *
112 self->permanentPageCache.size);
113 memset(self->permanentPageCache.entries, 0,
114 sizeof(struct WOSessionCacheEntry) *
115 self->permanentPageCache.size);
120 self->wosLanguages = [[ud arrayForKey:@"WODefaultLanguages"] copy];
121 self->isTerminating = NO;
123 [self setTimeOut:[[WOApplication sessionTimeOut] intValue]];
125 /* setup session ID */
128 [[[WOApplication application] createSessionIDForSession:self] copy];
130 if (self->wosSessionId == nil) {
131 /* session-id creation failed ... */
138 if (profileComponents)
139 [self logWithFormat:@"Component profiling is on."];
145 [[NSNotificationCenter defaultCenter]
146 postNotificationName:@"WOSessionWillDeallocate"
148 [self->wosVariables release];
149 [self->wosSessionId release];
150 [self->wosLock release];
151 [self->wosLanguages release];
157 - (NSString *)sessionID {
158 return self->wosSessionId;
161 - (void)setStoresIDsInURLs:(BOOL)_flag {
162 self->wosFlags.storesIDsInURLs = _flag ? 1 : 0;
164 - (BOOL)storesIDsInURLs {
165 return self->wosFlags.storesIDsInURLs ? YES : NO;
168 - (void)setStoresIDsInCookies:(BOOL)_flag {
169 self->wosFlags.storesIDsInCookies = _flag ? 1 : 0;
171 - (BOOL)storesIDsInCookies {
172 return self->wosFlags.storesIDsInCookies ? YES : NO;
175 - (NSString *)domainForIDCookies {
178 - (NSDate *)expirationDateForIDCookies {
179 return [self isTerminating]
180 ? [NSDate dateWithTimeIntervalSinceNow:-15.0]
181 : [NSDate dateWithTimeIntervalSinceNow:([self timeOut] - 5.0)];
184 - (void)setDistributionEnabled:(BOOL)_flag {
185 [self notImplemented:_cmd];
187 - (BOOL)isDistributionEnabled {
191 - (void)setTimeOut:(NSTimeInterval)_timeout {
192 self->wosTimeOut = _timeout;
194 - (NSTimeInterval)timeOut {
195 return self->wosTimeOut;
199 self->isTerminating = YES;
201 - (BOOL)isTerminating {
202 return self->isTerminating;
206 if (self->application == nil)
207 self->application = [WOApplication application];
208 return self->application;
210 - (WOContext *)context {
211 if (self->context == nil) {
212 if (self->application == nil)
213 self->application = [WOApplication application];
214 self->context = [self->application context];
216 return self->context;
219 /* editing context */
221 - (id)defaultEditingContext {
222 if (![WOApplication implementsEditingContexts])
225 if (self->wosDefaultEditingContext == nil) {
226 self->wosDefaultEditingContext =
227 [[[WOApplication eoEditingContextClass] alloc] init];
229 return self->wosDefaultEditingContext;
234 - (id)restorePageForContextID:(NSString *)_contextID {
237 WOComponent *page = nil;
239 ctxHash = [_contextID hash];
241 /* first scan permanent cache */
243 for (i = 0, page = nil;
244 (page == nil) && (i < self->permanentPageCache.size); i++) {
245 struct WOSessionCacheEntry *entry;
247 entry = &(self->permanentPageCache.entries[i]);
249 if (ctxHash == entry->ctxIdHash) {
250 if ([_contextID isEqualToString:entry->contextID]) {
253 [self debugWithFormat:@"restored permanent page %@ for ctx %@",
254 page != nil ? [page name] : (NSString *)@"<nil>",
255 _contextID != nil ? _contextID : (NSString *)@"<nil>"];
263 return [[page retain] autorelease];
265 /* now scan regular cache */
267 for (i = 0, page = nil; (page == nil) && (i < self->pageCache.size); i++) {
268 struct WOSessionCacheEntry *entry;
270 entry = &(self->pageCache.entries[i]);
272 if (ctxHash == entry->ctxIdHash) {
273 if ([_contextID isEqualToString:entry->contextID]) {
276 [self debugWithFormat:@"restored page %@<0x%p> for ctx %@",
277 [page name], page, _contextID];
283 return [[page retain] autorelease];
286 - (void)savePage:(WOComponent *)_page {
288 struct WOSessionCacheEntry *entry;
290 cid = [[self context] contextID];
292 [self debugWithFormat:@"storing page %@ for ctx %@", [_page name], cid];
294 /* go to next (fixed) queue entry */
295 self->pageCache.index++;
296 if (self->pageCache.index >= self->pageCache.size)
297 self->pageCache.index = 0;
299 entry = &(self->pageCache.entries[self->pageCache.index]);
301 /* reset old queue entry */
302 entry->ctxIdHash = 0;
303 [entry->contextID release];
304 [entry->page release];
306 /* assign new values */
307 entry->contextID = [cid copyWithZone:[self zone]];
308 entry->ctxIdHash = [entry->contextID hash];
309 entry->page = [_page retain];
312 - (void)savePageInPermanentCache:(WOComponent *)_page {
314 struct WOSessionCacheEntry *entry;
316 cid = [[self context] contextID];
318 [self debugWithFormat:
319 @"permanently storing page %@ for ctx %@", [_page name], cid];
322 /* go to next (fixed) queue entry */
323 self->permanentPageCache.index++;
324 if (self->permanentPageCache.index >= self->permanentPageCache.size)
325 self->permanentPageCache.index = 0;
327 entry = &(self->permanentPageCache.entries[self->permanentPageCache.index]);
329 /* reset old queue entry */
330 entry->ctxIdHash = 0;
331 [entry->contextID release];
332 [entry->page release];
334 /* assign new values */
335 entry->contextID = [cid copyWithZone:[self zone]];
336 entry->ctxIdHash = [entry->contextID hash];
337 entry->page = [_page retain];
342 - (void)languageArrayDidChange {
345 - (void)setLanguages:(NSArray *)_langs {
346 if (![self->wosLanguages isEqual:_langs]) { // check whether they really differ
347 [self->wosLanguages release]; self->wosLanguages = nil;
348 self->wosLanguages = [_langs copyWithZone:[self zone]];
349 [self languageArrayDidChange];
352 - (NSArray *)languages {
353 return self->wosLanguages;
363 - (void)_awakeWithContext:(WOContext *)_ctx {
364 if (self->context == nil)
365 self->context = _ctx;
366 if (self->application == nil)
367 self->application = [WOApplication application];
369 if (!self->wosFlags.isAwake) {
371 self->wosFlags.isAwake = 1;
374 - (void)_sleepWithContext:(WOContext *)_ctx {
375 if (self->wosFlags.isAwake) {
377 self->wosFlags.isAwake = 0;
380 self->application = nil;
385 - (void)takeValuesFromRequest:(WORequest *)_request
386 inContext:(WOContext *)_ctx
391 self->context = _ctx;
392 self->application = [WOApplication application];
394 senderID = [_ctx senderID];
396 if ([senderID length] == 0) {
397 /* no element URL is available */
400 if ((page = [_ctx page]) != nil) {
402 But we do have a page set in the context. This usually means that the
403 -takeValues got triggered by the WODirectActionRequestHandler in
404 combination with a WOComponent being the DirectAction object.
406 NSTimeInterval st = 0.0;
408 WOContext_enterComponent(_ctx, page, nil);
410 if (profileComponents)
411 st = [[NSDateClass date] timeIntervalSince1970];
413 [page takeValuesFromRequest:_request inContext:_ctx];
415 if (profileComponents) {
418 diff = [[NSDateClass date] timeIntervalSince1970] - st;
419 printf("prof[%s %s]: %0.3fs\n",
420 [[(WOComponent *)page name] cString], sel_get_name(_cmd), diff);
423 WOContext_leaveComponent(_ctx, page);
429 if ([[_request method] isEqualToString:@"GET"]) {
432 r = [[_request uri] rangeOfString:@"?"];
434 /* no form content to apply */
435 // TODO: we should run the takeValues nevertheless to clear values?
440 if ((reqCtxId = [_ctx currentElementID]) == nil)
443 [_ctx appendElementIDComponent:reqCtxId];
447 if ((page = [_ctx page]) != nil) {
448 NSTimeInterval st = 0.0;
450 WOContext_enterComponent(_ctx, page, nil);
452 if (profileComponents)
453 st = [[NSDateClass date] timeIntervalSince1970];
455 [page takeValuesFromRequest:_request inContext:_ctx];
457 if (profileComponents) {
460 diff = [[NSDateClass date] timeIntervalSince1970] - st;
461 printf("prof[%s %s]: %0.3fs\n",
462 [[(WOComponent *)page name] cString], sel_get_name(_cmd), diff);
465 WOContext_leaveComponent(_ctx, page);
468 [_ctx deleteLastElementIDComponent];
471 - (id)invokeActionForRequest:(WORequest *)_request inContext:(WOContext *)_ctx {
473 BOOL returnResult = NO;
476 self->context = _ctx;
477 self->application = [WOApplication application];
479 if ((reqCtxId = [_ctx currentElementID]) == nil)
480 /* no sender element ID */
483 [_ctx appendElementIDComponent:reqCtxId];
487 if ((page = [_ctx page])) {
489 -consumeElementID consumes the context id and returns the
490 id of the next element.
491 If there was no next element, the request wasn't active.
493 if (([_ctx consumeElementID])) {
494 NSTimeInterval st = 0.0;
497 WOContext_enterComponent(_ctx, page, nil);
499 if (profileComponents)
500 st = [[NSDateClass date] timeIntervalSince1970];
502 result = [page invokeActionForRequest:_request inContext:_ctx];
504 if (profileComponents) {
507 diff = [[NSDateClass date] timeIntervalSince1970] - st;
508 printf("prof[%s %s]: %0.3fs\n",
509 [[page name] cString], sel_get_name(_cmd), diff);
510 //[page name], sel_get_name(_cmd), diff);
513 WOContext_leaveComponent(_ctx, page);
517 [_ctx deleteLastElementIDComponent];
518 return returnResult ? result : [_ctx page];
521 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
522 self->context = _ctx;
523 self->application = [WOApplication application];
525 /* HTTP/1.1 caching directive, prevents browser from caching dynamic pages */
526 if ([self->application isPageRefreshOnBacktrackEnabled]) {
529 if ((ctype = [_response headerForKey:@"content-type"])) {
530 if ([ctype rangeOfString:@"html"].length > 0)
531 // profiling OSX: 3.1% of append...
532 [_response disableClientCaching];
536 [_ctx deleteAllElementIDComponents];
537 [_ctx appendElementIDComponent:[_ctx contextID]];
541 if ((page = [_ctx page])) {
542 /* let the page append it's content */
543 NSTimeInterval st = 0.0;
545 WOContext_enterComponent(_ctx, page, nil);
547 if (profileComponents)
548 st = [[NSDateClass date] timeIntervalSince1970];
550 [page appendToResponse:_response inContext:_ctx];
552 if (profileComponents) {
555 diff = [[NSDateClass date] timeIntervalSince1970] - st;
556 printf("prof[%s %s]: %0.3fs\n",
557 [[page name] cString], sel_get_name(_cmd), diff);
560 WOContext_leaveComponent(_ctx, page);
563 [self logWithFormat:@"missing page in context for -appendToResponse: !"];
566 [_ctx deleteLastElementIDComponent];
568 /* generate statistics */
569 // profiling OSX: 3.1% of append... (seems to be NSDate!)
570 [[[self application] statisticsStore]
571 recordStatisticsForResponse:_response
578 [self->wosLock lock];
581 [self->wosLock unlock];
585 return [self->wosLock tryLock];
588 /* session variables */
590 - (void)setObject:(id)_obj forKey:(NSString *)_key {
592 [self warnWithFormat:@"%s: got no key for extra variable.",
593 __PRETTY_FUNCTION__];
597 if (self->wosVariables == nil)
598 self->wosVariables = [[NSMutableDictionary alloc] initWithCapacity:16];
601 [self->wosVariables setObject:_obj forKey:_key];
603 [self->wosVariables removeObjectForKey:_key];
606 - (id)objectForKey:(NSString *)_key {
607 return _key != nil ? [self->wosVariables objectForKey:_key] : nil;
610 - (void)removeObjectForKey:(NSString *)_key {
612 [self warnWithFormat:@"%s: got no key of extra variable to be removed.",
613 __PRETTY_FUNCTION__];
617 [self->wosVariables removeObjectForKey:_key];
620 - (NSDictionary *)variableDictionary {
621 return self->wosVariables;
624 #if LIB_FOUNDATION_LIBRARY /* only override on libFoundation */
626 - (void)takeValue:(id)_value forKey:(NSString *)_key {
627 if (WOSetKVCValueUsingMethod(self, _key, _value))
630 else if (WOGetKVCGetMethod(self, _key) == NULL) {
631 if (self->wosVariables == nil)
632 self->wosVariables = [[NSMutableDictionary alloc] initWithCapacity:16];
634 if (_value) [self->wosVariables setObject:_value forKey:_key];
638 // only a 'get' method is defined for _key !
639 [self handleTakeValue:_value forUnboundKey:_key];
641 - (id)valueForKey:(NSString *)_key {
644 if ((value = WOGetKVCValueUsingMethod(self, _key)))
647 return [self->wosVariables objectForKey:_key];
650 #else /* use fallback methods on other Foundation libraries */
652 - (void)setValue:(id)_value forUndefinedKey:(NSString *)_key {
653 [self setObject:_value forKey:_key];
655 - (id)valueForUndefinedKey:(NSString *)_key {
656 return [self->wosVariables objectForKey:_key];
659 - (void)handleTakeValue:(id)_value forUnboundKey:(NSString *)_key {
660 // deprecated: pre-Panther method
661 [self setValue:_value forUndefinedKey:_key];
663 - (id)handleQueryWithUnboundKey:(NSString *)_key {
664 // deprecated: pre-Panther method
665 return [self valueForUndefinedKey:_key];
672 - (NSArray *)statistics {
673 return [NSArray array];
678 - (NSString *)description {
679 return [NSString stringWithFormat:@"<%@[0x%p]: id=%@>",
680 NSStringFromClass([self class]), self,
686 @implementation WOSession(NSCoding)
688 - (void)encodeWithCoder:(NSCoder *)_coder {
692 [_coder encodeObject:self->wosLanguages];
693 [_coder encodeObject:[self sessionID]];
694 [_coder encodeObject:self->wosVariables];
695 [_coder encodeValueOfObjCType:@encode(NSTimeInterval) at:&(self->wosTimeOut)];
696 t = [self storesIDsInURLs];
697 [_coder encodeValueOfObjCType:@encode(BOOL) at:&t];
698 t = [self storesIDsInCookies];
699 [_coder encodeValueOfObjCType:@encode(BOOL) at:&t];
701 /* store page caches */
703 [_coder encodeValueOfObjCType:@encode(unsigned short)
704 at:&(self->pageCache.index)];
705 [_coder encodeValueOfObjCType:@encode(unsigned short)
706 at:&(self->pageCache.size)];
707 for (i = 0; i < self->pageCache.size; i++) {
708 [_coder encodeValueOfObjCType:@encode(unsigned)
709 at:&(self->pageCache.entries[i].ctxIdHash)];
710 [_coder encodeObject:self->pageCache.entries[i].contextID];
711 [_coder encodeObject:self->pageCache.entries[i].page];
714 [_coder encodeValueOfObjCType:@encode(unsigned short)
715 at:&(self->permanentPageCache.index)];
716 [_coder encodeValueOfObjCType:@encode(unsigned short)
717 at:&(self->permanentPageCache.size)];
718 for (i = 0; i < self->permanentPageCache.size; i++) {
719 [_coder encodeValueOfObjCType:@encode(unsigned)
720 at:&(self->permanentPageCache.entries[i].ctxIdHash)];
721 [_coder encodeObject:self->permanentPageCache.entries[i].contextID];
722 [_coder encodeObject:self->permanentPageCache.entries[i].page];
726 - (id)initWithCoder:(NSCoder *)_coder {
727 if ((self = [super init])) {
731 self->wosLanguages = [[_coder decodeObject] retain];
732 self->wosSessionId = [[_coder decodeObject] copyWithZone:[self zone]];
733 self->wosVariables = [[_coder decodeObject] copyWithZone:[self zone]];
734 [_coder decodeValueOfObjCType:@encode(NSTimeInterval)
735 at:&(self->wosTimeOut)];
736 [_coder decodeValueOfObjCType:@encode(BOOL) at:&t];
737 [self setStoresIDsInURLs:t];
738 [_coder decodeValueOfObjCType:@encode(BOOL) at:&t];
739 [self setStoresIDsInCookies:t];
741 /* restore page caches */
743 [_coder decodeValueOfObjCType:@encode(unsigned short)
744 at:&(self->pageCache.index)];
745 [_coder decodeValueOfObjCType:@encode(unsigned short)
746 at:&(self->pageCache.size)];
747 self->pageCache.entries =
748 NGMalloc(sizeof(struct WOSessionCacheEntry) * self->pageCache.size);
749 for (i = 0; i < self->pageCache.size; i++) {
750 [_coder decodeValueOfObjCType:@encode(unsigned)
751 at:&(self->pageCache.entries[i].ctxIdHash)];
752 self->pageCache.entries[i].contextID = [[_coder decodeObject] retain];
753 self->pageCache.entries[i].page = [[_coder decodeObject] retain];
756 [_coder decodeValueOfObjCType:@encode(unsigned short)
757 at:&(self->permanentPageCache.index)];
758 [_coder decodeValueOfObjCType:@encode(unsigned short)
759 at:&(self->permanentPageCache.size)];
760 self->permanentPageCache.entries =
761 NGMalloc(sizeof(struct WOSessionCacheEntry) *
762 self->permanentPageCache.size);
763 for (i = 0; i < self->permanentPageCache.size; i++) {
764 [_coder decodeValueOfObjCType:@encode(unsigned)
765 at:&(self->permanentPageCache.entries[i].ctxIdHash)];
766 self->permanentPageCache.entries[i].contextID =
767 [[_coder decodeObject] retain];
768 self->permanentPageCache.entries[i].page = [[_coder decodeObject] retain];
771 self->wosLock = [[NSRecursiveLock allocWithZone:[self zone]] init];
776 @end /* WOSession(NSCoding) */
778 @implementation WOSession(Logging2)
780 - (BOOL)isDebuggingEnabled {
781 static char showDebug = 2;
784 showDebug = [WOApplication isDebuggingEnabled] ? 1 : 0;
785 return showDebug ? YES : NO;
787 - (NSString *)loggingPrefix {
788 return [NSString stringWithFormat:@"(%@)", [self sessionID]];
791 @end /* WOSession(Logging) */
793 NSString *OWSessionLanguagesDidChangeNotificationName =
794 @"OWSnLanguagesDidChangeNotification";
796 @implementation WOSession(Misc)
798 - (void)languageArrayDidChange {
801 c = [[self context] page];
802 if ([c respondsToSelector:@selector(languageArrayDidChange)])
803 [(id)c languageArrayDidChange];
805 [[NSNotificationCenter defaultCenter]
806 postNotificationName:
807 OWSessionLanguagesDidChangeNotificationName
811 @end /* WOSession(Misc) */