]> err.no Git - sope/blob - sope-appserver/NGObjWeb/WOApplication.m
changed #includes into #imports - does compile against MulleEOF out of the box now
[sope] / sope-appserver / NGObjWeb / WOApplication.m
1 /*
2   Copyright (C) 2000-2005 SKYRIX Software AG
3   Copyright (C) 2006      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/WOApplication.h>
24 #include "WOContext+private.h"
25 #include "WOElement+private.h"
26 #include "WOComponent+private.h"
27 #include <NGObjWeb/WOAdaptor.h>
28 #include <NGObjWeb/WORequest.h>
29 #include <NGObjWeb/WORequestHandler.h>
30 #include <NGObjWeb/WOResourceManager.h>
31 #include <NGObjWeb/WOResponse.h>
32 #include <NGObjWeb/WOSession.h>
33 #include <NGObjWeb/WOSessionStore.h>
34 #include <NGObjWeb/WOStatisticsStore.h>
35 #include <NGObjWeb/WODynamicElement.h>
36 #include <NGObjWeb/WOTemplate.h>
37 #import <EOControl/EOControl.h>
38 #include "common.h"
39 #include <time.h>
40
41 #if GNU_RUNTIME
42 #  include <objc/sarray.h>
43 #endif
44
45 @interface WOApplication(PrivateMethods)
46 + (id)logger;
47 - (id)_loadComponentDefinitionWithName:(NSString *)_name
48   language:(NSArray *)_langs;
49 - (NSDictionary *)memoryStatistics;
50 @end
51
52 static NSRecursiveLock *classLock = nil;
53 static NGLogger *perfLogger       = nil;
54 static Class NSDateClass = Nil;
55 static Class WOTemplateClass = Nil;
56 static BOOL  debugOn     = NO;
57 static NSString *rapidTurnAroundPath = nil;
58
59 @interface WOSessionStore(SnStore)
60 - (void)performExpirationCheck:(NSTimer *)_timer;
61 @end
62
63 @implementation WOApplication
64
65 #if 1 // TODO: why is that? why isn't that set by a default?
66 static NSString *defaultCompRqHandlerClassName = @"OWViewRequestHandler";
67 #else
68 static NSString *defaultCompRqHandlerClassName = @"WOComponentRequestHandler";
69 #endif
70
71 + (int)version {
72   return [super version] + 5 /* v6 */;
73 }
74
75 + (void)_setupSNS {
76   Class clazz;
77   id c;
78   
79   clazz = NSClassFromString(@"SNSConnection");
80   c = [(id<NSObject>)clazz performSelector:@selector(defaultSNSConnection)];
81
82   if (c == nil) {
83     [[self logger] fatalWithFormat:@"could not connect SNS, exiting .."];
84     exit(20);
85   }
86   
87   [[self logger] logWithFormat:@"SNS enabled"];
88 }
89
90 + (void)_initializeWOApp {
91   static BOOL isInitialized = NO;
92   NSAutoreleasePool *pool;
93   NSUserDefaults    *ud;
94   NGLoggerManager   *lm;
95
96   if (isInitialized) return;
97
98   isInitialized = YES;
99
100   pool = [[NSAutoreleasePool alloc] init];
101   debugOn = [WOApplication isDebuggingEnabled];
102   if (!debugOn)
103     [[self logger] setLogLevel:NGLogLevelInfo];
104   else
105     NSLog(@"Note: WOApplication debugging is enabled.");
106
107   if (classLock == nil) classLock = [[NSRecursiveLock alloc] init];
108   ud = [NSUserDefaults standardUserDefaults];
109   
110   /* setup SNSConnection */
111   
112   if ([ud boolForKey:@"WOContactSNS"])
113     [self _setupSNS];
114   else
115     [[self logger] logWithFormat:@"SNS support disabled."];
116   
117   NSDateClass = [NSDate class];
118   WOTemplateClass = [WOTemplate class];
119
120   rapidTurnAroundPath   = [[ud stringForKey:@"WOProjectDirectory"] copy];
121
122   lm         = [NGLoggerManager defaultLoggerManager];
123   perfLogger = [lm loggerForDefaultKey:@"WOProfileApplication"];
124
125   [pool release];
126 }
127
128 /* old license checks */
129
130 - (NSCalendarDate *)appExpireDate {
131   // TODO: can we remove that?
132   return nil;
133 }
134 - (BOOL)isLicenseExpired {
135   // TODO: can we remove that?
136   return NO;
137 }
138
139 /* app path */
140
141 - (NSString *)_lookupAppPath {
142   static NSString *suffix = nil;
143   static BOOL    appPathMissing = NO;
144   NSUserDefaults *ud;
145   NSFileManager  *fm;
146   NSString       *cwd;
147   NSString       *result;
148   
149   if (appPathMissing)
150     return nil;
151
152   ud = [NSUserDefaults standardUserDefaults];
153   
154   // Check if appPath has been forced
155   result = [ud stringForKey:@"WOProjectDirectory"];
156   if(result != nil)
157       return result;
158
159   if (suffix == nil)
160     suffix = [ud stringForKey:@"WOApplicationSuffix"];
161   
162   fm  = [NSFileManager defaultManager];
163   cwd = [fm currentDirectoryPath];
164   
165 #if NeXT_Foundation_LIBRARY || COCOA_Foundation_LIBRARY
166   result = [[NGBundle mainBundle] bundlePath];
167   //NSLog(@"%s: check path '%@'", __PRETTY_FUNCTION__, result);
168 #else
169   result = cwd;
170 #endif
171
172   if ([result hasSuffix:suffix]) {
173     /* started app inside of .woa directory */
174 #if NeXT_Foundation_LIBRARY || COCOA_Foundation_LIBRARY
175     result = [[NGBundle mainBundle] bundlePath];
176 #else
177     result = cwd;
178 #endif
179   }
180   else {
181     NSString *wrapperName;
182     
183     wrapperName = [self->name stringByAppendingString:suffix];
184     
185     /* take a look whether ./AppName.woa exists */
186     result = [result stringByAppendingPathComponent:wrapperName];
187     if (![fm fileExistsAtPath:result]) {
188       /* lookup in process-path */
189       NSProcessInfo *pi;
190       NSDictionary  *env;
191       NSString      *ppath;
192       BOOL isFlattened;
193       
194       pi  = [NSProcessInfo processInfo];
195       env = [pi environment];
196       if ([[env objectForKey:@"GNUSTEP_SYSTEM_ROOT"] isNotNull]) {
197         isFlattened = [[[env objectForKey:@"GNUSTEP_FLATTENED"]
198                              lowercaseString] isEqualToString:@"yes"];
199       }
200       else /* default to flattened if no GNUstep runtime is set */
201         isFlattened = YES;
202       
203       ppath = [[pi arguments] objectAtIndex:0];
204       ppath = [ppath stringByDeletingLastPathComponent]; // del exe-name
205       
206       if (!isFlattened) {
207         ppath = [ppath stringByDeletingLastPathComponent]; // lib-combo
208         ppath = [ppath stringByDeletingLastPathComponent]; // os
209         ppath = [ppath stringByDeletingLastPathComponent]; // cpu
210       }
211       if ([ppath hasSuffix:suffix])
212         result = ppath;
213     }
214   }
215   
216   if (![fm fileExistsAtPath:result]) {
217     [self debugWithFormat:@"%s: missing path '%@'", 
218             __PRETTY_FUNCTION__, result];
219     appPathMissing = YES;
220     result = nil;
221   }
222
223   return result;
224 }
225
226 + (NSString *)defaultRequestHandlerClassName {
227   return @"WOComponentRequestHandler";
228 }
229
230 - (void)_logDefaults {
231   NSUserDefaults *ud;
232   NSArray        *keys;
233   NSEnumerator   *e;
234   NSString       *key;
235
236   ud   = [NSUserDefaults standardUserDefaults];
237   keys = [[ud dictionaryRepresentation] allKeys];
238   keys = [keys sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
239
240   e    = [keys objectEnumerator];
241   while((key = [e nextObject]) != nil) {
242     if ([key hasPrefix:@"WO"] || [key isEqualToString:@"NSProjectSearchPath"])
243       [self logWithFormat:@"[default]: %@ = %@",
244         key,
245         [[ud objectForKey:key] description]];
246   }
247 }
248
249 - (id)initWithName:(NSString *)_name {
250   [WOApplication _initializeWOApp];
251   
252   if ((self = [super init]) != nil) {
253     NSUserDefaults   *ud;
254     WORequestHandler *rh;
255     NSString *rk;
256     
257     self->name = [_name copy];
258     
259     ud = [NSUserDefaults standardUserDefaults];
260     
261     [self setPageCacheSize:[ud integerForKey:@"WOPageCacheSize"]];
262     [self setPermanentPageCacheSize:
263             [ud integerForKey:@"WOPermanentPageCacheSize"]];
264     
265     [self setPageRefreshOnBacktrackEnabled:
266             [[ud objectForKey:@"WOPageRefreshOnBacktrack"] boolValue]];
267     
268     [self setCachingEnabled:[WOApplication isCachingEnabled]];
269     
270     /* setup request handlers */
271     
272     self->defaultRequestHandler =
273       [[NSClassFromString([[self class] defaultRequestHandlerClassName])
274                          alloc] init];
275     
276     self->requestHandlerRegistry =
277       NSCreateMapTable(NSObjectMapKeyCallBacks, NSObjectMapValueCallBacks, 8);
278     
279     if ((rk = [WOApplication componentRequestHandlerKey]) == nil) {
280       [self logWithFormat:
281               @"WARNING: no component request handler key is specified, "
282               @"this probably means that share/ngobjweb/Defaults.plist "
283               @"could not get loaded (permissions?)"];
284     }
285     rh = [[NSClassFromString(defaultCompRqHandlerClassName) alloc] init];
286     if ([rk isNotEmpty] && [rh isNotNull])
287       [self registerRequestHandler:rh forKey:rk];
288     [rh release]; rh = nil;
289     
290     rk = [WOApplication directActionRequestHandlerKey];
291     rh = [[NSClassFromString(@"WODirectActionRequestHandler") alloc] init];
292     if ([rk isNotEmpty] && [rh isNotNull])
293       [self registerRequestHandler:rh forKey:rk];
294     [rh release]; rh = nil;
295     
296     if ((rh = [[NSClassFromString(@"WOResourceRequestHandler") alloc] init])) {
297       rk = [WOApplication resourceRequestHandlerKey];
298       if ([rk isNotEmpty])
299         [self registerRequestHandler:rh forKey:rk];
300       [self registerRequestHandler:rh forKey:@"WebServerResources"];
301 #ifdef __APPLE__
302       [self registerRequestHandler:rh forKey:@"Resources"];
303 #endif
304       [rh release]; rh = nil;
305     }
306
307     /* setup session store */
308     
309     self->iSessionStore =
310       [[NSClassFromString([self sessionStoreClassName]) alloc] init];
311     
312     /* setup statistics store */
313     
314     self->iStatisticsStore = [[WOStatisticsStore alloc] init];
315     
316     /* register timers */
317     self->expirationTimer =
318       [[NSTimer scheduledTimerWithTimeInterval:
319                   [[ud objectForKey:@"WOExpirationTimeInterval"] intValue]
320                 target:self
321                 selector:@selector(performExpirationCheck:)
322                 userInfo:nil
323                 repeats:YES]
324                 retain];
325     
326     if ([ud boolForKey:@"WOLogDefaultsOnStartup"])
327       [self _logDefaults];
328     
329     [[NSNotificationCenter defaultCenter]
330                            postNotificationName:
331                              WOApplicationWillFinishLaunchingNotification
332                            object:self];
333   }
334   return self;
335 }
336
337 - (id)init {
338   return [self initWithName:[[[NSProcessInfo processInfo]
339                                              processName]
340                                              stringByDeletingPathExtension]];
341 }
342
343 - (void)dealloc {
344   [[NSNotificationCenter defaultCenter] removeObserver:self];
345   
346   [self->expirationTimer invalidate];
347
348   if (self->requestHandlerRegistry)
349     NSFreeMapTable(self->requestHandlerRegistry);
350   
351   [self->expirationTimer release];
352   [self->resourceManager release];
353   [self->iSessionStore   release];
354   [self->defaultRequestHandler release];
355   [self->path            release];
356   [self->name            release];
357   [self->instanceNumber  release];
358   [super dealloc];
359 }
360
361 - (void)processHupSignal:(int)_signal {
362   /* this isn't called immediatly */
363   [self logWithFormat:@"terminating on SIGHUP ..."];
364   [self terminate];
365 }
366
367 /* accessors */
368
369 - (NSString *)name {
370   return self->name;
371 }
372 - (BOOL)monitoringEnabled {
373   return NO;
374 }
375 - (NSString *)path {
376   static BOOL missingPath = NO;
377   if (missingPath)
378     return nil;
379   
380   if (self->path == nil) {
381     if ((self->path = [[self _lookupAppPath] copy]) == nil) {
382       [self debugWithFormat:@"could not find wrapper of application !"];
383       missingPath = YES;
384       return nil;
385     }
386   }
387   return self->path;
388 }
389
390 - (NSString *)number {
391   if (self->instanceNumber == nil) {
392     id num;
393       
394     if ((num = [[NSUserDefaults standardUserDefaults] objectForKey:@"n"])) {
395       self->instanceNumber = [[num stringValue] copy];
396     }
397     else {
398       unsigned pid;
399 #if defined(__MINGW32__)
400       pid = (unsigned)GetCurrentProcessId();
401 #else                     
402       pid = (unsigned)getpid();
403 #endif
404       self->instanceNumber = [[NSString alloc] initWithFormat:@"%d", pid];
405     }
406   }
407   return self->instanceNumber;
408 }
409
410 - (void)_setCurrentContext:(WOContext *)_ctx {
411   NSMutableDictionary *info;
412
413   info = [[NSThread currentThread] threadDictionary];
414   if (_ctx != nil)
415     [info setObject:_ctx forKey:@"WOContext"];
416   else
417     [info removeObjectForKey:@"WOContext"];
418 }
419 - (WOContext *)context {
420   // deprecated in WO4
421   NSThread     *t;
422   NSDictionary *td;
423   
424   if ((t = [NSThread currentThread]) == nil) {
425     [self errorWithFormat:@"missing current thread !!!"];
426     return nil;
427   }
428   if ((td = [t threadDictionary]) == nil) {
429     [self errorWithFormat:
430             @"missing current thread's dictionary (thread=%@) !!!",
431             t];
432     return nil;
433   }
434   
435   return [td objectForKey:@"WOContext"];
436 }
437
438 /* request handlers */
439
440 - (void)registerRequestHandler:(WORequestHandler *)_hdl
441   forKey:(NSString *)_key
442 {
443   [self lock];
444   NSMapInsert(self->requestHandlerRegistry, _key, _hdl);
445   [self unlock];
446 }
447 - (void)removeRequestHandlerForKey:(NSString *)_key {
448   if (_key == nil) return;
449   [self lock];
450   NSMapRemove(self->requestHandlerRegistry, _key);
451   [self unlock];
452 }
453
454 - (void)setDefaultRequestHandler:(WORequestHandler *)_hdl {
455   [self lock];
456   ASSIGN(self->defaultRequestHandler, _hdl);
457   [self unlock];
458 }
459 - (WORequestHandler *)defaultRequestHandler {
460   return self->defaultRequestHandler;
461 }
462 - (WORequestHandler *)requestHandlerForKey:(NSString *)_key {
463   WORequestHandler *handler;
464   
465   [self lock];
466   handler = [(id)NSMapGet(self->requestHandlerRegistry, _key) retain];
467   if (handler == nil)
468     handler = [[self defaultRequestHandler] retain];
469   [self unlock];
470   
471   return [handler autorelease];
472 }
473
474 - (NSArray *)registeredRequestHandlerKeys {
475   NSMutableArray   *array = [NSMutableArray arrayWithCapacity:16];
476   NSMapEnumerator  e;
477   NSString         *key;
478   WORequestHandler *handler;
479   
480   [self lock];
481   e = NSEnumerateMapTable(self->requestHandlerRegistry);
482   while (NSNextMapEnumeratorPair(&e, (void**)&key, (void**)&handler))
483     [array addObject:key];
484   [self unlock];
485   
486   return [[array copy] autorelease];
487 }
488
489 - (WORequestHandler *)handlerForRequest:(WORequest *)_request {
490   WORequestHandler *handler;
491   NSString         *key;
492   
493   if ((key = [_request requestHandlerKey]) == nil)
494     return [self defaultRequestHandler];
495   
496   handler = NSMapGet(self->requestHandlerRegistry, key);
497   return (handler != nil) ? handler : [self defaultRequestHandler];
498 }
499
500 /* sessions */
501
502 - (WOSession *)_initializeSessionInContext:(WOContext *)_ctx {
503   WOSession *sn;
504
505   sn = [self createSessionForRequest:[_ctx request]];
506   [_ctx setNewSession:sn];
507   
508   if ([sn respondsToSelector:@selector(prepare)]) {
509 #if DEBUG
510     [self debugWithFormat:@"calling -prepare on session .."];
511 #endif
512     [sn performSelector:@selector(prepare)];
513   }
514
515   [sn _awakeWithContext:_ctx];
516   
517   [[NSNotificationCenter defaultCenter]
518                          postNotificationName:WOSessionDidCreateNotification
519                          object:sn];
520   return [sn autorelease];
521 }
522
523 - (NSString *)sessionIDFromRequest:(WORequest *)_request {
524   NSString *sessionId;
525   
526   if (_request == nil) return nil;
527   
528   /* first look into form values */
529   if ((sessionId = [_request formValueForKey:WORequestValueSessionID])!=nil) {
530     if ([sessionId isNotEmpty])
531       return sessionId;
532   }
533   
534   /* now look into the cookies */
535   if ((sessionId = [_request cookieValueForKey:[self name]]) != nil) {
536     if ([sessionId respondsToSelector:@selector(objectEnumerator)]) {
537       NSEnumerator *e;
538       
539       e = [(id)sessionId objectEnumerator];
540       while ((sessionId = [e nextObject]) != nil) {
541         if ([sessionId isNotEmpty] && ![sessionId isEqual:@"nil"])
542           return sessionId;
543       }
544     }
545     else {
546       if ([sessionId isNotEmpty] && ![sessionId isEqual:@"nil"])
547         return sessionId;
548     }
549   }
550   
551   return nil;
552 }
553
554 - (NSString *)createSessionIDForSession:(WOSession *)_session {
555   /* session id must be 18 chars long for snsd to work ! */
556   static unsigned int sessionCount = 0;
557   NSString *wosid;
558   unsigned char buf[20];
559   
560   sessionCount++;
561   sprintf((char *)buf, "%04X%04X%02X%08X",
562           [[self number] intValue], getpid(), sessionCount, 
563           (unsigned int)time(NULL));
564   wosid = [NSString stringWithCString:(char *)buf];
565   return wosid;
566 }
567
568 - (id)createSessionForRequest:(WORequest *)_request {
569   if ([self respondsToSelector:@selector(createSession)]) {
570     /* call deprecated method */
571     [self warnWithFormat:@"calling deprecated -createSession .."];
572     return [self createSession];
573   }
574   else {
575     Class snClass = Nil;
576     
577     if ((snClass = NSClassFromString(@"Session")) == Nil)
578       snClass = [WOSession class];
579     
580     return [[snClass alloc] init];
581   }
582 }
583
584 - (id)restoreSessionWithID:(NSString *)_sid inContext:(WOContext *)_ctx {
585   WOSession *session;
586   
587   *(&session) = nil;
588
589   if ([self respondsToSelector:@selector(restoreSession)]) {
590     /* call deprecated method */
591     [self warnWithFormat:@"calling deprecated -restoreSession .."];
592     return [self restoreSession];
593   }
594   
595   SYNCHRONIZED(self) {
596     WOSessionStore *store;
597     
598     if ((store = [self sessionStore]) == nil) {
599       [self errorWithFormat:@"missing session store ..."];
600     }
601     else {
602       session = [store restoreSessionWithID:_sid request:[_ctx request]];
603       if ([session isNotNull]) {
604         [_ctx setSession:session];
605         [session _awakeWithContext:_ctx];
606       }
607       else {
608         [self debugWithFormat:@"did not find a session for sid '%@'", _sid];
609       }
610     }
611   }
612   END_SYNCHRONIZED;
613   
614   if (session) {
615     [[NSNotificationCenter defaultCenter]
616                            postNotificationName:WOSessionDidRestoreNotification
617                            object:session];
618   }
619   else {
620     if ([_sid hasPrefix:@"("]) {
621       id sid;
622
623       sid = [_sid propertyList];
624       
625       if ([sid respondsToSelector:@selector(objectEnumerator)]) {
626         NSEnumerator *e;
627         
628         [self errorWithFormat:@"got multiple session IDs !"];
629         
630         e = [sid objectEnumerator];
631         while ((_sid = [e nextObject])) {
632           if ([_sid isEqualToString:@"nil"])
633             continue;
634           
635           if ((session = [self restoreSessionWithID:_sid inContext:_ctx]))
636             return session;
637           
638           //[self warnWithFormat:@"did not find session for sid %@", _sid);
639         }
640       }
641     }
642   }
643   return session;
644 }
645 - (void)saveSessionForContext:(WOContext *)_ctx {
646   NSTimeInterval startSave = 0.0;
647
648   if (perfLogger)
649     startSave = [[NSDateClass date] timeIntervalSince1970];
650   
651   if ([self respondsToSelector:@selector(saveSession:)]) {
652     /* call deprecated method */
653     [self warnWithFormat:@"calling deprecated -saveSession: .."];
654     [self saveSession:[_ctx session]];
655     return;
656   }
657   
658   SYNCHRONIZED(self) {
659     WOSession     *sn;
660     NSTimeInterval startSnSleep = 0.0, startStore = 0.0;
661     
662     sn = [_ctx session];
663
664     if (perfLogger)
665       startSnSleep = [[NSDateClass date] timeIntervalSince1970];
666     
667     /* put session to sleep */
668     [sn _sleepWithContext:_ctx];
669     
670     if (perfLogger) {
671       NSTimeInterval rt;
672       rt = [[NSDateClass date] timeIntervalSince1970] - startSnSleep;
673       [perfLogger logWithFormat:@"[woapp]: session -sleep took %4.3fs.",
674                                     rt < 0.0 ? -1.0 : rt];
675     }
676     
677     if ([sn isTerminating]) {
678       [[NSNotificationCenter defaultCenter]
679                              postNotificationName:
680                                WOSessionDidTerminateNotification
681                              object:sn];
682     }
683     
684     if (perfLogger)
685       startStore = [[NSDateClass date] timeIntervalSince1970];
686     
687     [[self sessionStore] saveSessionForContext:_ctx];
688     
689     if (perfLogger) {
690       NSTimeInterval rt;
691       rt = [[NSDateClass date] timeIntervalSince1970] - startStore;
692       [perfLogger logWithFormat:@"[woapp]: storing sn in store took %4.3fs.",
693                                     rt < 0.0 ? -1.0 : rt];
694     }
695   }
696   END_SYNCHRONIZED;
697
698   if (perfLogger) {
699     NSTimeInterval rt;
700     rt = [[NSDateClass date] timeIntervalSince1970] - startSave;
701     [perfLogger logWithFormat:@"[woapp]: saveSessionForContext took %4.3fs.",
702                                   rt < 0.0 ? -1.0 : rt];
703   }
704 }
705
706 - (void)refuseNewSessions:(BOOL)_flag {
707   self->appFlags.doesRefuseNewSessions = _flag ? 1 : 0;
708 }
709 - (BOOL)isRefusingNewSessions {
710   return self->appFlags.doesRefuseNewSessions;
711 }
712 - (int)activeSessionsCount {
713   return [[self sessionStore] activeSessionsCount];
714 }
715
716 - (void)setSessionStore:(WOSessionStore *)_store {
717   ASSIGN(self->iSessionStore, _store);
718 }
719 - (NSString *)sessionStoreClassName {
720   return [[NSUserDefaults standardUserDefaults] stringForKey:@"WOSessionStore"];
721 }
722 - (WOSessionStore *)sessionStore {
723   return self->iSessionStore;
724 }
725
726 - (void)setMinimumActiveSessionsCount:(int)_minimum {
727   self->minimumActiveSessionsCount = _minimum;
728 }
729 - (int)minimumActiveSessionsCount {
730   return self->minimumActiveSessionsCount;
731 }
732
733 - (void)performExpirationCheck:(NSTimer *)_timer {
734   WOSessionStore *ss;
735
736   /* let session store check for expiration ... */
737   
738   ss = [self sessionStore];
739   if ([ss respondsToSelector:@selector(performExpirationCheck:)])
740     [ss performExpirationCheck:_timer];
741   
742   /* check whether application should terminate ... */
743
744   if ([self isRefusingNewSessions] &&
745       ([self activeSessionsCount] < [self minimumActiveSessionsCount])) {
746     /* check whether the application instance is still valid .. */
747     [self debugWithFormat:
748             @"application terminates because it refuses new sessions and "
749             @"the active session count (%i) is below the minimum (%i).",
750             [self activeSessionsCount], [self minimumActiveSessionsCount]];
751     [self terminate];
752   }
753 }
754
755 - (id)session {
756   return [[self context] session];
757 }
758
759 - (WOResponse *)handleSessionCreationErrorInContext:(WOContext *)_ctx {
760   WOResponse *response = [_ctx response];
761   unsigned pid;
762   
763 #ifdef __MINGW32__
764   pid = GetCurrentProcessId();
765 #else
766   pid = getpid();
767 #endif
768   
769   if ([self respondsToSelector:@selector(handleSessionCreationError)]) {
770     [self warnWithFormat:@"called deprecated -handleSessionCreationError method"];
771     return [self handleSessionCreationError];
772   }
773   
774   [self errorWithFormat:@"could not create session for context %@", _ctx];
775   
776   [response setStatus:200];
777   [response appendContentString:@"<h4>Session Creation Error</h4>\n<pre>"];
778   [response appendContentString:
779               @"Application Instance failed to create session."];
780   [response appendContentHTMLString:
781               [NSString stringWithFormat:
782                           @"   application: %@\n"
783                           @"   adaptor:     %@\n"
784                           @"   baseURL:     %@\n"
785                           @"   contextID:   %@\n"
786                           @"   instance:    %i\n"
787                           @"   request:     %@\n",
788                           [self name],
789                           [[_ctx request] adaptorPrefix],
790                           [self baseURL],
791                           [_ctx contextID],
792                           pid,
793                           [[_ctx request] description]]];
794   [response appendContentString:@"</pre>"];
795   return response;
796 }
797
798 - (WOResponse *)handleSessionRestorationErrorInContext:(WOContext *)_ctx {
799   if ([self respondsToSelector:@selector(handleSessionRestorationError)]) {
800     [self warnWithFormat:@"calling deprecated "
801                             @"-handleSessionRestorationError method"];
802     return [self handleSessionRestorationError];
803   }
804   
805   // TODO: is it correct to return nil?
806   // TODO: we should return a page saying sorry with a cookie + redirect
807   [self errorWithFormat:@"could not restore session for context %@", _ctx];
808   return nil;
809 }
810
811 /* statistics */
812
813 - (void)setStatisticsStore:(WOStatisticsStore *)_statStore {
814   ASSIGN(self->iStatisticsStore, _statStore);
815 }
816 - (WOStatisticsStore *)statisticsStore {
817   return self->iStatisticsStore;
818 }
819
820 - (bycopy NSDictionary *)statistics {
821   return [[self statisticsStore] statistics];
822 }
823
824 /* resources */
825
826 - (void)_setupDefaultResourceManager {
827   NSUserDefaults *ud;
828   Class    rmClass;
829   NSString *p;
830   
831   ud = [NSUserDefaults standardUserDefaults];
832   p  = [ud stringForKey:@"WODefaultResourceManager"];
833   rmClass = [p isNotEmpty]
834     ? NSClassFromString(p)
835     : [WOResourceManager class];
836   
837   if (rmClass == Nil) {
838     [self errorWithFormat:
839             @"failed to locate class of resource manager: '%@'", p];
840     return;
841   }
842   
843   if ([rmClass instancesRespondToSelector:@selector(initWithPath:)])
844     self->resourceManager = [[rmClass alloc] init];
845   else {
846     self->resourceManager = 
847       [(WOResourceManager *)[rmClass alloc] initWithPath:[self path]];
848   }
849 }
850
851 - (void)setResourceManager:(WOResourceManager *)_manager {
852   ASSIGN(self->resourceManager, _manager);
853 }
854 - (WOResourceManager *)resourceManager {
855   if (self->resourceManager == nil)
856     [self _setupDefaultResourceManager];
857   
858   return self->resourceManager;
859 }
860
861 - (NSURL *)baseURL {
862   NSString  *n;
863   WOContext *ctx = [self context];
864   
865   n = [[ctx request] applicationName];
866   n = [@"/" stringByAppendingString:n ? n : [self name]];
867   
868   return [NSURL URLWithString:n relativeToURL:[ctx baseURL]];
869 }
870
871 - (NSString *)pathForResourceNamed:(NSString *)_name ofType:(NSString *)_type {
872   IS_DEPRECATED;
873   return [[self resourceManager] pathForResourceNamed:_name ofType:_type];
874 }
875
876 - (NSString *)stringForKey:(NSString *)_key
877   inTableNamed:(NSString *)_tableName
878   withDefaultValue:(NSString *)_default
879 {
880   IS_DEPRECATED;
881   return [[self resourceManager] stringForKey:_key
882                                  inTableNamed:_tableName
883                                  withDefaultValue:_default
884                                  languages:
885                                    [(WOSession *)[self session] languages]];
886 }
887
888 /* notifications */
889
890 - (void)awake {
891 }
892 - (void)sleep {
893 #if DEBUG && PRINT_NSSTRING_STATISTICS
894   if ([NSString respondsToSelector:@selector(printStatistics)])
895     [NSString printStatistics];
896 #endif
897   
898 #if DEBUG && PRINT_OBJC_STATISTICS
899 extern int __objc_selector_max_index;
900   printf("nbuckets=%i, nindices=%i, narrays=%i, idxsize=%i\n",
901 nbuckets, nindices, narrays, idxsize);
902   printf("maxsel=%i\n", __objc_selector_max_index);
903 #endif
904 }
905
906 /* responder */
907
908 - (void)takeValuesFromRequest:(WORequest *)_req inContext:(WOContext *)_ctx {
909   if ([_ctx hasSession])
910     [[_ctx session] takeValuesFromRequest:_req inContext:_ctx];
911   else {
912     WOComponent *page;
913     
914     if ((page = [_ctx page]) != nil) {
915       WOContext_enterComponent(_ctx, page, nil);
916       [page takeValuesFromRequest:_req inContext:_ctx];
917       WOContext_leaveComponent(_ctx, page);
918     }
919   }
920 }
921
922 - (id)invokeActionForRequest:(WORequest *)_rq inContext:(WOContext *)_ctx {
923   id result;
924   
925   if ([_ctx hasSession])
926     result = [[_ctx session] invokeActionForRequest:_rq inContext:_ctx];
927   else {
928     WOComponent *page;
929     
930     if ((page = [_ctx page])) {
931       WOContext_enterComponent(_ctx, page, nil);
932       result = [[_ctx page] invokeActionForRequest:_rq inContext:_ctx];
933       WOContext_leaveComponent(_ctx, page);
934     }
935     else
936       result = nil;
937   }
938   return result;
939 }
940
941 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
942   if ([_ctx hasSession])
943     [[_ctx session] appendToResponse:_response inContext:_ctx];
944   else {
945     WOComponent *page;
946     
947     if ((page = [_ctx page])) {
948       WOContext_enterComponent(_ctx, page, nil);
949       [page appendToResponse:_response inContext:_ctx];
950       WOContext_leaveComponent(_ctx, page);
951     }
952   }
953
954   if(rapidTurnAroundPath != nil) {
955       WOComponent *page;
956       
957       if((page = [_ctx page])) {
958           WOElement *template;
959           
960           template = [page _woComponentTemplate];
961           if([template isKindOfClass:WOTemplateClass]) {
962               NSString *_path;
963               
964               _path = [[(WOTemplate *)template url] path];
965               [_response setHeader:_path
966                             forKey:@"x-sope-template-path"];
967           }
968
969       }
970   }
971 }
972
973 // dynamic elements
974
975 - (WOElement *)dynamicElementWithName:(NSString *)_name
976   associations:(NSDictionary *)_associations
977   template:(WOElement *)_template
978   languages:(NSArray *)_languages
979 {
980   WOElement *element            = nil;
981   Class     dynamicElementClass = NSClassFromString(_name);
982
983   if (dynamicElementClass == Nil) {
984     [self warnWithFormat:@"did not find dynamic element class %@ !", _name];
985     return nil;
986   }
987   if (![dynamicElementClass isDynamicElement]) {
988     [self warnWithFormat:@"class %@ is not a dynamic element class !", _name];
989     return nil;
990   }
991   
992   element = [[dynamicElementClass allocWithZone:[_template zone]]
993                                   initWithName:_name
994                                   associations:_associations
995                                   template:_template];
996   return element;
997 }
998 - (WOElement *)dynamicElementWithName:(NSString *)_name
999   associations:(NSDictionary *)_associations
1000   template:(WOElement *)_template
1001 {
1002   return [self dynamicElementWithName:_name
1003                associations:_associations
1004                template:_template
1005                languages:[(WOSession *)[self session] languages]];
1006 }
1007
1008 // pages
1009
1010 - (void)setPageRefreshOnBacktrackEnabled:(BOOL)_flag {
1011   self->appFlags.isPageRefreshOnBacktrackEnabled = _flag ? 1 : 0;
1012 }
1013 - (BOOL)isPageRefreshOnBacktrackEnabled {
1014   return self->appFlags.isPageRefreshOnBacktrackEnabled ? YES : NO;
1015 }
1016
1017 - (void)setCachingEnabled:(BOOL)_flag {
1018   self->appFlags.isCachingEnabled = _flag ? 1 : 0;
1019 }
1020 - (BOOL)isCachingEnabled {
1021   // component definition caching
1022   return self->appFlags.isCachingEnabled ? YES : NO;
1023 }
1024
1025 - (void)setPageCacheSize:(int)_size {
1026   self->pageCacheSize = _size;
1027 }
1028 - (int)pageCacheSize {
1029   return self->pageCacheSize;
1030 }
1031 - (void)setPermanentPageCacheSize:(int)_size {
1032   self->permanentPageCacheSize = _size;
1033 }
1034 - (int)permanentPageCacheSize {
1035   return self->permanentPageCacheSize;
1036 }
1037
1038 - (id)pageWithName:(NSString *)_name {
1039   // deprecated in WO4
1040   return [self pageWithName:_name inContext:[self context]];
1041 }
1042
1043 - (WOComponent *)_pageWithName:(NSString *)_name inContext:(WOContext *)_ctx {
1044   /*
1045     OSX profiling: 3.4% of dispatchRequest?
1046       3.0%  rm -pageWithName..
1047         1.5%  def instantiate
1048           1.3% initWithName:...
1049             0.76% initWithContent:.. (0.43 addobserver)
1050         0.76% rm  defForComp (0.43% touch)
1051         0.54% pool
1052       0.11% ctx -component
1053       0.11% pool
1054   */
1055   NSArray           *languages;
1056   WOComponent       *page;
1057   NSAutoreleasePool *pool;
1058   WOResourceManager *rm;
1059
1060 #if MEM_DEBUG
1061   NSDictionary *start, *stop;
1062   start = [self memoryStatistics];
1063 #endif
1064   
1065   pool      = [[NSAutoreleasePool alloc] init];
1066   
1067   languages = [_ctx resourceLookupLanguages];
1068
1069   if ((rm = [[_ctx component] resourceManager]) == nil)
1070     rm = [self resourceManager];
1071   
1072   /*  TODO:
1073    *  the following ignores the fact that the passed context may be different
1074    *  from that of WOApplication. During the course of template instantiation
1075    *  WOApplication's current context gets attached to page which is definitely
1076    *  wrong. We workaround this problem by using the private API of WOComponent
1077    *  to explicitly set it. However all accompanied methods should be
1078    *  extended to pass the correct context where needed.
1079    */
1080   page      = [rm pageWithName:(_name != nil ? _name : @"Main")
1081                   languages:languages];
1082   [page _setContext:_ctx];
1083   [page ensureAwakeInContext:_ctx];
1084   
1085   page = [page retain];
1086   [pool release];
1087
1088 #if MEM_DEBUG
1089   {
1090     int rss, vmsize, lib;
1091     stop = [self memoryStatistics];
1092     rss    = [[stop objectForKey:@"VmRSS"] intValue] -
1093              [[start objectForKey:@"VmRSS"] intValue];
1094     vmsize = [[stop objectForKey:@"VmSize"] intValue] -
1095              [[start objectForKey:@"VmSize"] intValue];
1096     lib    = [[stop objectForKey:@"VmLib"] intValue] -
1097              [[start objectForKey:@"VmLib"] intValue];
1098     [self debugWithFormat:@"loaded component %@; rss=%i vm=%i lib=%i.",
1099             _name, rss,vmsize,lib];
1100   }
1101 #endif
1102   
1103   return [page autorelease];
1104 }
1105 - (id)pageWithName:(NSString *)_name inContext:(WOContext *)_ctx {
1106   return [self _pageWithName:_name inContext:_ctx];
1107 }
1108 - (id)pageWithName:(NSString *)_name forRequest:(WORequest *)_req {
1109   WOResourceManager *rm;
1110
1111   if ((rm = [self resourceManager]) == nil)
1112     return nil;
1113   
1114   return [rm pageWithName:(_name != nil) ? _name : @"Main"
1115              languages:[_req browserLanguages]];
1116 }
1117
1118 - (void)savePage:(WOComponent *)_page {
1119   IS_DEPRECATED;
1120   [[[self context] session] savePage:_page];
1121 }
1122 - (id)restorePageForContextID:(NSString *)_ctxId {
1123   IS_DEPRECATED;
1124   return [[[self context] session] restorePageForContextID:_ctxId];
1125 }
1126
1127 - (WOResponse *)handlePageRestorationErrorInContext:(WOContext *)_ctx {
1128   [self errorWithFormat:
1129           @"could not restore page for context-id %@\n  in context %@",
1130           [_ctx currentElementID], _ctx];
1131   
1132   /* return main page ... */
1133   return [[self pageWithName:nil inContext:_ctx] generateResponse];
1134 }
1135 - (WOResponse *)handlePageRestorationError {
1136   IS_DEPRECATED;
1137   return [self handlePageRestorationErrorInContext:[self context]];
1138 }
1139
1140 /* exceptions */
1141
1142 - (WOResponse *)handleException:(NSException *)_exc
1143   inContext:(WOContext *)_ctx
1144 {
1145   WORequest  *rq = [_ctx request];
1146   WOResponse *r  = nil;
1147   
1148   if ([self respondsToSelector:@selector(handleException:)]) {
1149     [self warnWithFormat:@"calling deprecated -handleException method !"];
1150     return [self handleException:_exc];
1151   }
1152   
1153 #if DEBUG
1154   {
1155     static int doCore = -1;
1156     if (doCore == -1) {
1157       doCore = [[NSUserDefaults standardUserDefaults] 
1158                  boolForKey:@"WOCoreOnApplicationException"]
1159         ? 1 : 0;
1160     }
1161     if (doCore) {
1162       [self fatalWithFormat:@"%@: caught (ctx=%@):\n  %@.",
1163               self, _ctx, _exc];
1164       abort();
1165     }
1166   }
1167 #endif
1168   
1169   if (_ctx == nil) {
1170     [self fatalWithFormat:@"%@: caught (without context):\n  %@.",
1171             self, _exc];
1172     [self terminate];
1173   }
1174   else if (rq == nil) {
1175     [self fatalWithFormat:@"%@: caught (without request):\n  %@.",
1176             self, _exc];
1177     [self terminate];
1178   }
1179   else {
1180     static NSString *pageFormat =
1181       @"Application Server caught exception:\n\n"
1182       @"  session:   %@\n"
1183       @"  element:   %@\n"
1184       @"  context:   %@\n"
1185       @"  request:   %@\n\n"
1186       @"  class:     %@\n"
1187       @"  name:      %@\n"
1188       @"  reason:    %@\n"
1189       @"  info:\n    %@\n"
1190       @"  backtrace:\n%@\n";
1191     NSString *str = nil;
1192     NSString *bt  = nil;
1193     
1194     [self errorWithFormat:@"%@: caught:\n  %@\nin context:\n  %@.",
1195             self, _exc, _ctx];
1196
1197 #if LIB_FOUNDATION_LIBRARY
1198     if ([NSException respondsToSelector:@selector(backtrace)])
1199       bt = [NSException backtrace];
1200 #endif
1201     
1202     if ((r = [WOResponse responseWithRequest:rq]) == nil)
1203       [self errorWithFormat:@"could not create response !"];
1204     
1205     [r setHeader:@"text/html" forKey:@"content-type"];
1206     [r setHeader:@"no-cache" forKey:@"cache-control"];
1207     if (rapidTurnAroundPath != nil) {
1208         NSURL *templateURL;
1209         
1210         templateURL = [[_exc userInfo] objectForKey:@"templateURL"];
1211         if(templateURL != nil)
1212             [r setHeader:[templateURL path] forKey:@"x-sope-template-path"];
1213     }
1214
1215     str = [NSString stringWithFormat:pageFormat,
1216                       [_ctx hasSession] 
1217                         ? [[_ctx session] sessionID]
1218                         : @"[no session]",
1219                       [_ctx elementID],
1220                       [_ctx description],
1221                       [rq description],
1222                       NSStringFromClass([_exc class]),
1223                       [_exc name],
1224                       [_exc reason],
1225                       [[_exc userInfo] description],
1226                       bt];
1227     
1228     [r appendContentString:@"<html><head><title>Caught exception</title></head><body><pre>\n"];
1229     [r appendContentHTMLString:str];
1230     [r appendContentString:@"</pre></body></html>\n"];
1231   }
1232   return r;
1233 }
1234
1235 /* runloop */
1236
1237 - (BOOL)shouldTerminate {
1238   if (![self isRefusingNewSessions])
1239     return NO;
1240   if ([self activeSessionsCount] >= [self minimumActiveSessionsCount])
1241     return NO;
1242
1243   /* check whether the application instance is still valid .. */
1244   [self debugWithFormat:
1245           @"application terminates because it refuses new sessions and "
1246           @"the active session count (%i) is below the minimum (%i).",
1247           [self activeSessionsCount], [self minimumActiveSessionsCount]];
1248   return YES;
1249 }
1250
1251 - (void)terminate {
1252   [self debugWithFormat:
1253           @"application terminates:\n"
1254           @"  %i active sessions\n"
1255           @"  %i minimum active sessions\n"
1256           @"  refuses new session: %s",
1257           [self activeSessionsCount],
1258           [self minimumActiveSessionsCount],
1259           [self isRefusingNewSessions] ? "yes" : "no"];
1260   [super terminate];
1261 }
1262
1263 /* logging */
1264
1265 - (BOOL)isDebuggingEnabled {
1266   return debugOn;
1267 }
1268 - (NSString *)loggingPrefix {
1269   return [NSString stringWithFormat:@"|%@%@|", 
1270                      [self name],
1271                      [self isTerminating] ? @" terminating" : @""];
1272 }
1273
1274 /* KVC */
1275
1276 #if !LIB_FOUNDATION_LIBRARY
1277 - (id)valueForUndefinedKey:(NSString *)_key {
1278   [self warnWithFormat:@"tried to access undefined KVC key: '%@'",
1279           _key];
1280   return nil;
1281 }
1282 #endif
1283
1284 /* configuration */
1285
1286 + (Class)eoEditingContextClass {
1287   static Class eoEditingContextClass = Nil;
1288   static BOOL  lookedUpForEOEditingContextClass = NO;
1289   
1290   if (!lookedUpForEOEditingContextClass) {
1291     if ((eoEditingContextClass = NSClassFromString(@"EOEditingContext")) ==nil)
1292       eoEditingContextClass = NSClassFromString(@"NSManagedObjectContext");
1293     lookedUpForEOEditingContextClass = YES;
1294   }
1295   return eoEditingContextClass;
1296 }
1297
1298 + (BOOL)implementsEditingContexts {
1299   return [self eoEditingContextClass] != NULL ? YES : NO;
1300 }
1301
1302 /* description */
1303
1304 - (NSString *)description {
1305   return [NSString stringWithFormat:@"<%@[0x%08X]: name=%@%@>",
1306                      NSStringFromClass([self class]), self,
1307                      [self name],
1308                      [self isTerminating] ? @" terminating" : @""
1309                    ];
1310 }
1311
1312 @end /* WOApplication */