]> err.no Git - sope/blob - sope-appserver/NGObjWeb/WOSession.m
minor code cleanups
[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 - (WOApplication *)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 - (WOComponent *)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     return;
397
398   if ([[_request method] isEqualToString:@"GET"]) {
399     NSRange r;
400     
401     r = [[_request uri] rangeOfString:@"?"];
402     if (r.length == 0)
403       /* no form content to apply */
404       return;
405   }
406
407   if ((reqCtxId = [_ctx currentElementID]) == nil)
408     reqCtxId = @"0";
409   
410   [_ctx appendElementIDComponent:reqCtxId];
411   {
412     WOComponent *page;
413
414     if ((page = [_ctx page])) {
415       NSTimeInterval st = 0.0;
416       
417       WOContext_enterComponent(_ctx, page, nil);
418       
419       if (profileComponents)
420         st = [[NSDateClass date] timeIntervalSince1970];
421       
422       [page takeValuesFromRequest:_request inContext:_ctx];
423       
424       if (profileComponents) {
425         NSTimeInterval diff;
426         
427         diff = [[NSDateClass date] timeIntervalSince1970] - st;
428         printf("prof[%s %s]: %0.3fs\n",
429                [[(WOComponent *)page name] cString], sel_get_name(_cmd), diff);
430       }
431       
432       WOContext_leaveComponent(_ctx, page);
433     }
434   }
435   [_ctx deleteLastElementIDComponent];
436 }
437
438 - (id)invokeActionForRequest:(WORequest *)_request inContext:(WOContext *)_ctx {
439   NSString *reqCtxId;
440   BOOL     returnResult = NO;
441   id       result       = nil;
442   
443   self->context     = _ctx;
444   self->application = [WOApplication application];
445   
446   if ((reqCtxId = [_ctx currentElementID]) == nil)
447     /* no sender element ID */
448     return nil;
449   
450   [_ctx appendElementIDComponent:reqCtxId];
451   {
452     WOComponent *page;
453
454     if ((page = [_ctx page])) {
455       /*
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.
459       */
460       if (([_ctx consumeElementID])) {
461         NSTimeInterval st = 0.0;
462         
463         returnResult = YES;
464         WOContext_enterComponent(_ctx, page, nil);
465         
466         if (profileComponents)
467           st = [[NSDateClass date] timeIntervalSince1970];
468         
469         result = [page invokeActionForRequest:_request inContext:_ctx];
470       
471         if (profileComponents) {
472           NSTimeInterval diff;
473           
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);
478         }
479       
480         WOContext_leaveComponent(_ctx, page);
481       }
482     }
483   }
484   [_ctx deleteLastElementIDComponent];
485   return returnResult ? result : [_ctx page];
486 }
487
488 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
489   self->context     = _ctx;
490   self->application = [WOApplication application];
491   
492   /* HTTP/1.1 caching directive, prevents browser from caching dynamic pages */
493   if ([self->application isPageRefreshOnBacktrackEnabled]) {
494     NSString *ctype;
495     
496     if ((ctype = [_response headerForKey:@"content-type"])) {
497       if ([ctype rangeOfString:@"html"].length > 0)
498         // profiling OSX: 3.1% of append...
499         [_response disableClientCaching];
500     }
501   }
502   
503   [_ctx deleteAllElementIDComponents];
504   [_ctx appendElementIDComponent:[_ctx contextID]];
505   {
506     WOComponent *page;
507
508     if ((page = [_ctx page])) {
509       /* let the page append it's content */
510       NSTimeInterval st = 0.0;
511       
512       WOContext_enterComponent(_ctx, page, nil);
513       
514       if (profileComponents)
515         st = [[NSDateClass date] timeIntervalSince1970];
516       
517       [page appendToResponse:_response inContext:_ctx];
518       
519       if (profileComponents) {
520         NSTimeInterval diff;
521         
522         diff = [[NSDateClass date] timeIntervalSince1970] - st;
523         printf("prof[%s %s]: %0.3fs\n",
524                [[page name] cString], sel_get_name(_cmd), diff);
525       }
526       
527       WOContext_leaveComponent(_ctx, page);
528     }
529     else {
530       [self logWithFormat:@"missing page in context for -appendToResponse: !"];
531     }
532   }
533   [_ctx deleteLastElementIDComponent];
534
535   /* generate statistics */
536   // profiling OSX: 3.1% of append... (seems to be NSDate!)
537   [[[self application] statisticsStore]
538           recordStatisticsForResponse:_response
539           inContext:_ctx];
540 }
541
542 // multithreading
543
544 - (void)lock {
545   [self->wosLock lock];
546 }
547 - (void)unlock {
548   [self->wosLock unlock];
549 }
550
551 - (BOOL)tryLock {
552   return [self->wosLock tryLock];
553 }
554
555 /* session variables */
556
557 - (void)setObject:(id)_obj forKey:(NSString *)_key {
558   if (self->wosVariables == nil)
559     self->wosVariables = [[NSMutableDictionary alloc] initWithCapacity:16];
560   
561   if (_obj)
562     [self->wosVariables setObject:_obj forKey:_key];
563   else
564     [self->wosVariables removeObjectForKey:_key];
565 }
566 - (id)objectForKey:(NSString *)_key {
567   return [self->wosVariables objectForKey:_key];
568 }
569
570 - (void)removeObjectForKey:(NSString *)_key {
571   [self->wosVariables removeObjectForKey:_key];
572 }
573
574 - (NSDictionary *)variableDictionary {
575   return self->wosVariables;
576 }
577
578 #if LIB_FOUNDATION_LIBRARY /* only override on libFoundation */
579
580 - (void)takeValue:(id)_value forKey:(NSString *)_key {
581   if (WOSetKVCValueUsingMethod(self, _key, _value))
582     // method is used
583     return;
584   else if (WOGetKVCGetMethod(self, _key) == NULL) {
585     if (self->wosVariables == nil)
586       self->wosVariables = [[NSMutableDictionary alloc] initWithCapacity:16];
587     
588     if (_value) [self->wosVariables setObject:_value forKey:_key];
589     return;
590   }
591   else
592     // only a 'get' method is defined for _key !
593     [self handleTakeValue:_value forUnboundKey:_key];
594 }
595 - (id)valueForKey:(NSString *)_key {
596   id value;
597   
598   if ((value = WOGetKVCValueUsingMethod(self, _key)))
599     return value;
600   
601   return [self->wosVariables objectForKey:_key];
602 }
603
604 #else /* use fallback methods on other Foundation libraries */
605
606 - (void)setValue:(id)_value forUndefinedKey:(NSString *)_key {
607   [self setObject:_value forKey:_key];
608 }
609 - (id)valueForUndefinedKey:(NSString *)_key {
610   return [self->wosVariables objectForKey:_key];
611 }
612
613 - (void)handleTakeValue:(id)_value forUnboundKey:(NSString *)_key {
614   // deprecated: pre-Panther method
615   [self setValue:_value forUndefinedKey:_key];
616 }
617 - (id)handleQueryWithUnboundKey:(NSString *)_key {
618   // deprecated: pre-Panther method
619   return [self valueForUndefinedKey:_key];
620 }
621
622 #endif
623
624 /* statistics */
625
626 - (NSArray *)statistics {
627   return [NSArray array];
628 }
629
630 /* description */
631
632 - (NSString *)description {
633   return [NSString stringWithFormat:@"<%@[0x%08X]: id=%@>",
634                      NSStringFromClass([self class]), self,
635                      [self sessionID]];
636 }
637
638 @end /* WOSession */
639
640 @implementation WOSession(NSCoding)
641
642 - (void)encodeWithCoder:(NSCoder *)_coder {
643   unsigned short i;
644   BOOL t;
645   
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];
654
655   /* store page caches */
656   
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];
666   }
667
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];
677   }
678 }
679
680 - (id)initWithCoder:(NSCoder *)_coder {
681   if ((self = [super init])) {
682     unsigned short i;
683     BOOL t;
684     
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];
694
695     /* restore page caches */
696     
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];
708     }
709
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];
723     }
724     
725     self->wosLock = [[NSRecursiveLock allocWithZone:[self zone]] init];
726   }
727   return self;
728 }
729
730 @end /* WOSession(NSCoding) */
731
732 @implementation WOSession(Logging2)
733
734 - (BOOL)isDebuggingEnabled {
735   static char showDebug = 2;
736   
737   if (showDebug == 2)
738     showDebug = [WOApplication isDebuggingEnabled] ? 1 : 0;
739   return showDebug ? YES : NO;
740 }
741 - (NSString *)loggingPrefix {
742   return [NSString stringWithFormat:@"(%@)", [self sessionID]];
743 }
744
745 @end /* WOSession(Logging) */
746
747 NSString *OWSessionLanguagesDidChangeNotificationName =
748   @"OWSnLanguagesDidChangeNotification";
749
750 @implementation WOSession(Misc)
751
752 - (void)languageArrayDidChange {
753   WOComponent *c;
754
755   c = [[self context] page];
756   if ([c respondsToSelector:@selector(languageArrayDidChange)])
757     [(id)c languageArrayDidChange];
758   
759   [[NSNotificationCenter defaultCenter]
760                          postNotificationName:
761                            OWSessionLanguagesDidChangeNotificationName
762                          object:self];
763 }
764
765 @end /* WOSession(Misc) */