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