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