]> err.no Git - sope/blob - sope-appserver/NGObjWeb/WOSession.m
changes to DA request handling
[sope] / sope-appserver / NGObjWeb / WOSession.m
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/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>
32 #include "common.h"
33
34 #if APPLE_FOUNDATION_LIBRARY || NeXT_Foundation_LIBRARY
35 @interface NSObject(Miss)
36 - (id)notImplemented:(SEL)cmd;
37 @end
38 #endif
39
40 struct WOSessionCacheEntry {
41   NSString    *contextID;
42   unsigned    ctxIdHash;
43   WOComponent *page;
44 };
45
46 NGObjWeb_DECLARE
47   NSString *WOSessionDidTimeOutNotification   = @"WOSessionDidTimeOut";
48 NGObjWeb_DECLARE
49   NSString *WOSessionDidRestoreNotification   = @"WOSessionDidRestore";
50 NGObjWeb_DECLARE
51   NSString *WOSessionDidCreateNotification    = @"WOSessionDidCreate";
52 NGObjWeb_DECLARE
53   NSString *WOSessionDidTerminateNotification = @"WOSessionDidTerminate";
54
55 @implementation WOSession
56
57 + (int)version {
58   return 5;
59 }
60
61 static int   profileComponents = -1;
62 static int   logPageCache      = -1;
63 static Class NSDateClass = Nil;
64
65 + (void)initialize {
66   if (NSDateClass == Nil)
67     NSDateClass = [NSDate class];
68 }
69
70 - (id)init {
71   NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
72   
73   if (NSDateClass == Nil)
74     NSDateClass = [NSDate class];
75   
76   if (profileComponents == -1) {
77     profileComponents = 
78       [[ud objectForKey:@"WOProfileComponents"] boolValue] ? 1 : 0;
79   }
80   if (logPageCache == -1)
81     logPageCache = [[ud objectForKey:@"WOLogPageCache"] boolValue] ? 1 : 0;
82   
83   if ((self = [super init])) {
84     WOApplication *app = [WOApplication application];
85
86     if ([[ud objectForKey:@"WORunMultithreaded"] boolValue])
87       self->wosLock = [[NSRecursiveLock allocWithZone:[self zone]] init];
88     
89     /* setup page cache */
90
91     [self setStoresIDsInURLs:YES];
92     [self setStoresIDsInCookies:YES];
93     
94     self->pageCache.index = 0;
95     self->pageCache.size = [app pageCacheSize];
96
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);
102     }
103     
104     self->permanentPageCache.index = 0;
105     self->permanentPageCache.size = [app permanentPageCacheSize];
106
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);
114     }
115
116     /* setup misc */
117
118     self->wosLanguages  = [[ud arrayForKey:@"WODefaultLanguages"] copy];
119     self->isTerminating = NO;
120     
121     [self setTimeOut:[[WOApplication sessionTimeOut] intValue]];
122     
123     /* setup session ID */
124     
125     self->wosSessionId =
126       [[[WOApplication application] createSessionIDForSession:self] copy];
127     
128     if (self->wosSessionId == nil) {
129       /* session-id creation failed ... */
130       [self release];
131       return nil;
132     }
133     
134     /* misc logging */
135     
136     if (profileComponents)
137       [self logWithFormat:@"Component profiling is on."];
138   }
139   return self;
140 }
141
142 - (void)dealloc {
143   [[NSNotificationCenter defaultCenter]
144                          postNotificationName:@"WOSessionWillDeallocate"
145                          object:self];
146   [self->wosVariables release];
147   [self->wosSessionId release];
148   [self->wosLock      release];
149   [self->wosLanguages release];
150   [super dealloc];
151 }
152
153 /* session */
154
155 - (NSString *)sessionID {
156   return self->wosSessionId;
157 }
158
159 - (void)setStoresIDsInURLs:(BOOL)_flag {
160   self->wosFlags.storesIDsInURLs = _flag ? 1 : 0;
161 }
162 - (BOOL)storesIDsInURLs {
163   return self->wosFlags.storesIDsInURLs ? YES : NO;
164 }
165
166 - (void)setStoresIDsInCookies:(BOOL)_flag {
167   self->wosFlags.storesIDsInCookies = _flag ? 1 : 0;
168 }
169 - (BOOL)storesIDsInCookies {
170   return self->wosFlags.storesIDsInCookies ? YES : NO;
171 }
172
173 - (NSString *)domainForIDCookies {
174   return nil;
175 }
176 - (NSDate *)expirationDateForIDCookies {
177   return [self isTerminating]
178     ? [NSDate dateWithTimeIntervalSinceNow:-15.0]
179     : [NSDate dateWithTimeIntervalSinceNow:([self timeOut] - 5.0)];
180 }
181
182 - (void)setDistributionEnabled:(BOOL)_flag {
183   [self notImplemented:_cmd];
184 }
185 - (BOOL)isDistributionEnabled {
186   return NO;
187 }
188
189 - (void)setTimeOut:(NSTimeInterval)_timeout {
190   self->wosTimeOut = _timeout;
191 }
192 - (NSTimeInterval)timeOut {
193   return self->wosTimeOut;
194 }
195
196 - (void)terminate {
197   self->isTerminating = YES;
198 }
199 - (BOOL)isTerminating {
200   return self->isTerminating;
201 }
202
203 - (id)application {
204   if (self->application == nil)
205     self->application = [WOApplication application];
206   return self->application;
207 }
208 - (WOContext *)context {
209   if (self->context == nil) {
210     if (self->application == nil)
211       self->application = [WOApplication application];
212     self->context = [self->application context];
213   }
214   return self->context;
215 }
216
217 /* editing context */
218
219 - (id)defaultEditingContext {
220   if (![WOApplication implementsEditingContexts])
221     return nil;
222   
223   if (self->wosDefaultEditingContext == nil) {
224     self->wosDefaultEditingContext = 
225       [[[WOApplication eoEditingContextClass] alloc] init];
226   }
227   return self->wosDefaultEditingContext;
228 }
229
230 /* pages */
231
232 - (id)restorePageForContextID:(NSString *)_contextID {
233   unsigned short i;
234   unsigned       ctxHash;
235   WOComponent    *page = nil;
236   
237   ctxHash = [_contextID hash];
238   
239   /* first scan permanent cache */
240
241   for (i = 0, page = nil;
242        (page == nil) && (i < self->permanentPageCache.size); i++) {
243     struct WOSessionCacheEntry *entry;
244
245     entry = &(self->permanentPageCache.entries[i]);
246     
247     if (ctxHash == entry->ctxIdHash) {
248       if ([_contextID isEqualToString:entry->contextID]) {
249         page = entry->page;
250         if (logPageCache) {
251           [self debugWithFormat:@"restored permanent page %@ for ctx %@",
252                   page ? [page name] : (id)@"<nil>",
253                   _contextID ? _contextID : (id)@"<nil>"];
254         }
255         break;
256       }
257     }
258   }
259
260   if (page)
261     return [[page retain] autorelease];
262   
263   /* now scan regular cache */
264   
265   for (i = 0, page = nil; (page == nil) && (i < self->pageCache.size); i++) {
266     struct WOSessionCacheEntry *entry;
267
268     entry = &(self->pageCache.entries[i]);
269     
270     if (ctxHash == entry->ctxIdHash) {
271       if ([_contextID isEqualToString:entry->contextID]) {
272         page = entry->page;
273         if (logPageCache) {
274           [self debugWithFormat:@"restored page %@<0x%08X> for ctx %@",
275                   [page name], page, _contextID];
276         }
277         break;
278       }
279     }
280   }
281   return [[page retain] autorelease];
282 }
283
284 - (void)savePage:(WOComponent *)_page {
285   NSString *cid;
286   struct WOSessionCacheEntry *entry;
287   
288   cid = [[self context] contextID];
289   if (logPageCache)
290     [self debugWithFormat:@"storing page %@ for ctx %@", [_page name], cid];
291     
292   /* go to next (fixed) queue entry */
293   self->pageCache.index++;
294   if (self->pageCache.index >= self->pageCache.size)
295     self->pageCache.index = 0;
296
297   entry = &(self->pageCache.entries[self->pageCache.index]);
298   
299   /* reset old queue entry */
300   entry->ctxIdHash = 0;
301   [entry->contextID release];
302   [entry->page      release];
303
304   /* assign new values */
305   entry->contextID = [cid copyWithZone:[self zone]];
306   entry->ctxIdHash = [entry->contextID hash];
307   entry->page = [_page retain];
308 }
309
310 - (void)savePageInPermanentCache:(WOComponent *)_page {
311   NSString *cid;
312   struct WOSessionCacheEntry *entry;
313     
314   cid = [[self context] contextID];
315   if (logPageCache) {
316     [self debugWithFormat:
317             @"permanently storing page %@ for ctx %@", [_page name], cid];
318   }
319     
320   /* go to next (fixed) queue entry */
321   self->permanentPageCache.index++;
322   if (self->permanentPageCache.index >= self->permanentPageCache.size)
323     self->permanentPageCache.index = 0;
324
325   entry = &(self->permanentPageCache.entries[self->permanentPageCache.index]);
326
327   /* reset old queue entry */
328   entry->ctxIdHash = 0;
329   [entry->contextID release];
330   [entry->page      release];
331   
332   /* assign new values */
333   entry->contextID = [cid copyWithZone:[self zone]];
334   entry->ctxIdHash = [entry->contextID hash];
335   entry->page = [_page retain];
336 }
337
338 // localization
339
340 - (void)languageArrayDidChange {
341 }
342
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];
348   }
349 }
350 - (NSArray *)languages {
351   return self->wosLanguages;
352 }
353
354 /* notifications */
355
356 - (void)awake {
357 }
358 - (void)sleep {
359 }
360
361 - (void)_awakeWithContext:(WOContext *)_ctx {
362   if (self->context == nil)
363     self->context = _ctx;
364   if (self->application == nil)
365     self->application = [WOApplication application];
366
367   if (!self->wosFlags.isAwake) {
368     [self awake];
369     self->wosFlags.isAwake = 1;
370   }
371 }
372 - (void)_sleepWithContext:(WOContext *)_ctx {
373   if (self->wosFlags.isAwake) {
374     [self sleep];
375     self->wosFlags.isAwake = 0;
376   }
377   self->context     = nil;
378   self->application = nil;
379 }
380
381 /* responder */
382
383 - (void)takeValuesFromRequest:(WORequest *)_request
384   inContext:(WOContext *)_ctx
385 {
386   NSString *senderID;
387   NSString *reqCtxId;
388   
389   self->context     = _ctx;
390   self->application = [WOApplication application];
391
392   senderID = [_ctx senderID];
393
394   if ([senderID length] == 0) {
395     /* no element URL is available */
396     WOComponent *page;
397
398     if ((page = [_ctx page]) != nil) {
399       /* 
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.
403       */
404       NSTimeInterval st = 0.0;
405       
406       WOContext_enterComponent(_ctx, page, nil);
407       
408       if (profileComponents)
409         st = [[NSDateClass date] timeIntervalSince1970];
410       
411       [page takeValuesFromRequest:_request inContext:_ctx];
412       
413       if (profileComponents) {
414         NSTimeInterval diff;
415         
416         diff = [[NSDateClass date] timeIntervalSince1970] - st;
417         printf("prof[%s %s]: %0.3fs\n",
418                [[(WOComponent *)page name] cString], sel_get_name(_cmd), diff);
419       }
420       
421       WOContext_leaveComponent(_ctx, page);
422     }
423     
424     return;
425   }
426
427   if ([[_request method] isEqualToString:@"GET"]) {
428     NSRange r;
429     
430     r = [[_request uri] rangeOfString:@"?"];
431     if (r.length == 0) {
432       /* no form content to apply */
433       // TODO: we should run the takeValues nevertheless to clear values?
434       return;
435     }
436   }
437
438   if ((reqCtxId = [_ctx currentElementID]) == nil)
439     reqCtxId = @"0";
440   
441   [_ctx appendElementIDComponent:reqCtxId];
442   {
443     WOComponent *page;
444
445     if ((page = [_ctx page]) != nil) {
446       NSTimeInterval st = 0.0;
447       
448       WOContext_enterComponent(_ctx, page, nil);
449       
450       if (profileComponents)
451         st = [[NSDateClass date] timeIntervalSince1970];
452       
453       [page takeValuesFromRequest:_request inContext:_ctx];
454       
455       if (profileComponents) {
456         NSTimeInterval diff;
457         
458         diff = [[NSDateClass date] timeIntervalSince1970] - st;
459         printf("prof[%s %s]: %0.3fs\n",
460                [[(WOComponent *)page name] cString], sel_get_name(_cmd), diff);
461       }
462       
463       WOContext_leaveComponent(_ctx, page);
464     }
465   }
466   [_ctx deleteLastElementIDComponent];
467 }
468
469 - (id)invokeActionForRequest:(WORequest *)_request inContext:(WOContext *)_ctx {
470   NSString *reqCtxId;
471   BOOL     returnResult = NO;
472   id       result       = nil;
473   
474   self->context     = _ctx;
475   self->application = [WOApplication application];
476   
477   if ((reqCtxId = [_ctx currentElementID]) == nil)
478     /* no sender element ID */
479     return nil;
480   
481   [_ctx appendElementIDComponent:reqCtxId];
482   {
483     WOComponent *page;
484
485     if ((page = [_ctx page])) {
486       /*
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.
490       */
491       if (([_ctx consumeElementID])) {
492         NSTimeInterval st = 0.0;
493         
494         returnResult = YES;
495         WOContext_enterComponent(_ctx, page, nil);
496         
497         if (profileComponents)
498           st = [[NSDateClass date] timeIntervalSince1970];
499         
500         result = [page invokeActionForRequest:_request inContext:_ctx];
501       
502         if (profileComponents) {
503           NSTimeInterval diff;
504           
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);
509         }
510       
511         WOContext_leaveComponent(_ctx, page);
512       }
513     }
514   }
515   [_ctx deleteLastElementIDComponent];
516   return returnResult ? result : [_ctx page];
517 }
518
519 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
520   self->context     = _ctx;
521   self->application = [WOApplication application];
522   
523   /* HTTP/1.1 caching directive, prevents browser from caching dynamic pages */
524   if ([self->application isPageRefreshOnBacktrackEnabled]) {
525     NSString *ctype;
526     
527     if ((ctype = [_response headerForKey:@"content-type"])) {
528       if ([ctype rangeOfString:@"html"].length > 0)
529         // profiling OSX: 3.1% of append...
530         [_response disableClientCaching];
531     }
532   }
533   
534   [_ctx deleteAllElementIDComponents];
535   [_ctx appendElementIDComponent:[_ctx contextID]];
536   {
537     WOComponent *page;
538
539     if ((page = [_ctx page])) {
540       /* let the page append it's content */
541       NSTimeInterval st = 0.0;
542       
543       WOContext_enterComponent(_ctx, page, nil);
544       
545       if (profileComponents)
546         st = [[NSDateClass date] timeIntervalSince1970];
547       
548       [page appendToResponse:_response inContext:_ctx];
549       
550       if (profileComponents) {
551         NSTimeInterval diff;
552         
553         diff = [[NSDateClass date] timeIntervalSince1970] - st;
554         printf("prof[%s %s]: %0.3fs\n",
555                [[page name] cString], sel_get_name(_cmd), diff);
556       }
557       
558       WOContext_leaveComponent(_ctx, page);
559     }
560     else {
561       [self logWithFormat:@"missing page in context for -appendToResponse: !"];
562     }
563   }
564   [_ctx deleteLastElementIDComponent];
565
566   /* generate statistics */
567   // profiling OSX: 3.1% of append... (seems to be NSDate!)
568   [[[self application] statisticsStore]
569           recordStatisticsForResponse:_response
570           inContext:_ctx];
571 }
572
573 // multithreading
574
575 - (void)lock {
576   [self->wosLock lock];
577 }
578 - (void)unlock {
579   [self->wosLock unlock];
580 }
581
582 - (BOOL)tryLock {
583   return [self->wosLock tryLock];
584 }
585
586 /* session variables */
587
588 - (void)setObject:(id)_obj forKey:(NSString *)_key {
589   if (self->wosVariables == nil)
590     self->wosVariables = [[NSMutableDictionary alloc] initWithCapacity:16];
591   
592   if (_obj)
593     [self->wosVariables setObject:_obj forKey:_key];
594   else
595     [self->wosVariables removeObjectForKey:_key];
596 }
597 - (id)objectForKey:(NSString *)_key {
598   return [self->wosVariables objectForKey:_key];
599 }
600
601 - (void)removeObjectForKey:(NSString *)_key {
602   [self->wosVariables removeObjectForKey:_key];
603 }
604
605 - (NSDictionary *)variableDictionary {
606   return self->wosVariables;
607 }
608
609 #if LIB_FOUNDATION_LIBRARY /* only override on libFoundation */
610
611 - (void)takeValue:(id)_value forKey:(NSString *)_key {
612   if (WOSetKVCValueUsingMethod(self, _key, _value))
613     // method is used
614     return;
615   else if (WOGetKVCGetMethod(self, _key) == NULL) {
616     if (self->wosVariables == nil)
617       self->wosVariables = [[NSMutableDictionary alloc] initWithCapacity:16];
618     
619     if (_value) [self->wosVariables setObject:_value forKey:_key];
620     return;
621   }
622   else
623     // only a 'get' method is defined for _key !
624     [self handleTakeValue:_value forUnboundKey:_key];
625 }
626 - (id)valueForKey:(NSString *)_key {
627   id value;
628   
629   if ((value = WOGetKVCValueUsingMethod(self, _key)))
630     return value;
631   
632   return [self->wosVariables objectForKey:_key];
633 }
634
635 #else /* use fallback methods on other Foundation libraries */
636
637 - (void)setValue:(id)_value forUndefinedKey:(NSString *)_key {
638   [self setObject:_value forKey:_key];
639 }
640 - (id)valueForUndefinedKey:(NSString *)_key {
641   return [self->wosVariables objectForKey:_key];
642 }
643
644 - (void)handleTakeValue:(id)_value forUnboundKey:(NSString *)_key {
645   // deprecated: pre-Panther method
646   [self setValue:_value forUndefinedKey:_key];
647 }
648 - (id)handleQueryWithUnboundKey:(NSString *)_key {
649   // deprecated: pre-Panther method
650   return [self valueForUndefinedKey:_key];
651 }
652
653 #endif
654
655 /* statistics */
656
657 - (NSArray *)statistics {
658   return [NSArray array];
659 }
660
661 /* description */
662
663 - (NSString *)description {
664   return [NSString stringWithFormat:@"<%@[0x%08X]: id=%@>",
665                      NSStringFromClass([self class]), self,
666                      [self sessionID]];
667 }
668
669 @end /* WOSession */
670
671 @implementation WOSession(NSCoding)
672
673 - (void)encodeWithCoder:(NSCoder *)_coder {
674   unsigned short i;
675   BOOL t;
676   
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];
685
686   /* store page caches */
687   
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];
697   }
698
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];
708   }
709 }
710
711 - (id)initWithCoder:(NSCoder *)_coder {
712   if ((self = [super init])) {
713     unsigned short i;
714     BOOL t;
715     
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];
725
726     /* restore page caches */
727     
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];
739     }
740
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];
754     }
755     
756     self->wosLock = [[NSRecursiveLock allocWithZone:[self zone]] init];
757   }
758   return self;
759 }
760
761 @end /* WOSession(NSCoding) */
762
763 @implementation WOSession(Logging2)
764
765 - (BOOL)isDebuggingEnabled {
766   static char showDebug = 2;
767   
768   if (showDebug == 2)
769     showDebug = [WOApplication isDebuggingEnabled] ? 1 : 0;
770   return showDebug ? YES : NO;
771 }
772 - (NSString *)loggingPrefix {
773   return [NSString stringWithFormat:@"(%@)", [self sessionID]];
774 }
775
776 @end /* WOSession(Logging) */
777
778 NSString *OWSessionLanguagesDidChangeNotificationName =
779   @"OWSnLanguagesDidChangeNotification";
780
781 @implementation WOSession(Misc)
782
783 - (void)languageArrayDidChange {
784   WOComponent *c;
785
786   c = [[self context] page];
787   if ([c respondsToSelector:@selector(languageArrayDidChange)])
788     [(id)c languageArrayDidChange];
789   
790   [[NSNotificationCenter defaultCenter]
791                          postNotificationName:
792                            OWSessionLanguagesDidChangeNotificationName
793                          object:self];
794 }
795
796 @end /* WOSession(Misc) */