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