]> err.no Git - sope/blob - sope-appserver/NGObjWeb/WOApplication.m
minor code cleanups
[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(buf, "%04X%04X%02X%08X",
561           [[self number] intValue], getpid(), sessionCount, 
562           (unsigned int)time(NULL));
563   wosid = [NSString stringWithCString:buf];
564   return wosid;
565 }
566
567 - (WOSession *)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 - (WOSession *)restoreSessionWithID:(NSString *)_sid
584   inContext:(WOContext *)_ctx
585 {
586   WOSession *session;
587   
588   *(&session) = nil;
589
590   if ([self respondsToSelector:@selector(restoreSession)]) {
591     /* call deprecated method */
592     [self warnWithFormat:@"calling deprecated -restoreSession .."];
593     return [self restoreSession];
594   }
595   
596   SYNCHRONIZED(self) {
597     WOSessionStore *store;
598     
599     if ((store = [self sessionStore]) == nil) {
600       [self errorWithFormat:@"missing session store ..."];
601     }
602     else {
603       session = [store restoreSessionWithID:_sid request:[_ctx request]];
604       if (session) {
605         [_ctx setSession:session];
606         [session _awakeWithContext:_ctx];
607       }
608       else {
609         [self debugWithFormat:@"did not find a session for sid '%@'", _sid];
610       }
611     }
612   }
613   END_SYNCHRONIZED;
614   
615   if (session) {
616     [[NSNotificationCenter defaultCenter]
617                            postNotificationName:WOSessionDidRestoreNotification
618                            object:session];
619   }
620   else {
621     if ([_sid hasPrefix:@"("]) {
622       id sid;
623
624       sid = [_sid propertyList];
625       
626       if ([sid respondsToSelector:@selector(objectEnumerator)]) {
627         NSEnumerator *e;
628         
629         [self errorWithFormat:@"got multiple session IDs !"];
630         
631         e = [sid objectEnumerator];
632         while ((_sid = [e nextObject])) {
633           if ([_sid isEqualToString:@"nil"])
634             continue;
635           
636           if ((session = [self restoreSessionWithID:_sid inContext:_ctx]))
637             return session;
638           
639           //[self warnWithFormat:@"did not find session for sid %@", _sid);
640         }
641       }
642     }
643   }
644   return session;
645 }
646 - (void)saveSessionForContext:(WOContext *)_ctx {
647   NSTimeInterval startSave = 0.0;
648
649   if (perfLogger)
650     startSave = [[NSDateClass date] timeIntervalSince1970];
651   
652   if ([self respondsToSelector:@selector(saveSession:)]) {
653     /* call deprecated method */
654     [self warnWithFormat:@"calling deprecated -saveSession: .."];
655     [self saveSession:[_ctx session]];
656     return;
657   }
658   
659   SYNCHRONIZED(self) {
660     WOSession     *sn;
661     NSTimeInterval startSnSleep = 0.0, startStore = 0.0;
662     
663     sn = [_ctx session];
664
665     if (perfLogger)
666       startSnSleep = [[NSDateClass date] timeIntervalSince1970];
667     
668     /* put session to sleep */
669     [sn _sleepWithContext:_ctx];
670     
671     if (perfLogger) {
672       NSTimeInterval rt;
673       rt = [[NSDateClass date] timeIntervalSince1970] - startSnSleep;
674       [perfLogger logWithFormat:@"[woapp]: session -sleep took %4.3fs.",
675                                     rt < 0.0 ? -1.0 : rt];
676     }
677     
678     if ([sn isTerminating]) {
679       [[NSNotificationCenter defaultCenter]
680                              postNotificationName:
681                                WOSessionDidTerminateNotification
682                              object:sn];
683     }
684     
685     if (perfLogger)
686       startStore = [[NSDateClass date] timeIntervalSince1970];
687     
688     [[self sessionStore] saveSessionForContext:_ctx];
689     
690     if (perfLogger) {
691       NSTimeInterval rt;
692       rt = [[NSDateClass date] timeIntervalSince1970] - startStore;
693       [perfLogger logWithFormat:@"[woapp]: storing sn in store took %4.3fs.",
694                                     rt < 0.0 ? -1.0 : rt];
695     }
696   }
697   END_SYNCHRONIZED;
698
699   if (perfLogger) {
700     NSTimeInterval rt;
701     rt = [[NSDateClass date] timeIntervalSince1970] - startSave;
702     [perfLogger logWithFormat:@"[woapp]: saveSessionForContext took %4.3fs.",
703                                   rt < 0.0 ? -1.0 : rt];
704   }
705 }
706
707 - (void)refuseNewSessions:(BOOL)_flag {
708   self->appFlags.doesRefuseNewSessions = _flag ? 1 : 0;
709 }
710 - (BOOL)isRefusingNewSessions {
711   return self->appFlags.doesRefuseNewSessions;
712 }
713 - (int)activeSessionsCount {
714   return [[self sessionStore] activeSessionsCount];
715 }
716
717 - (void)setSessionStore:(WOSessionStore *)_store {
718   ASSIGN(self->iSessionStore, _store);
719 }
720 - (NSString *)sessionStoreClassName {
721   return [[NSUserDefaults standardUserDefaults] stringForKey:@"WOSessionStore"];
722 }
723 - (WOSessionStore *)sessionStore {
724   return self->iSessionStore;
725 }
726
727 - (void)setMinimumActiveSessionsCount:(int)_minimum {
728   self->minimumActiveSessionsCount = _minimum;
729 }
730 - (int)minimumActiveSessionsCount {
731   return self->minimumActiveSessionsCount;
732 }
733
734 - (void)performExpirationCheck:(NSTimer *)_timer {
735   WOSessionStore *ss;
736
737   /* let session store check for expiration ... */
738   
739   ss = [self sessionStore];
740   if ([ss respondsToSelector:@selector(performExpirationCheck:)])
741     [ss performExpirationCheck:_timer];
742   
743   /* check whether application should terminate ... */
744
745   if ([self isRefusingNewSessions] &&
746       ([self activeSessionsCount] < [self minimumActiveSessionsCount])) {
747     /* check whether the application instance is still valid .. */
748     [self debugWithFormat:
749             @"application terminates because it refuses new sessions and "
750             @"the active session count (%i) is below the minimum (%i).",
751             [self activeSessionsCount], [self minimumActiveSessionsCount]];
752     [self terminate];
753   }
754 }
755
756 - (WOSession *)session {
757   return [[self context] session];
758 }
759
760 - (WOResponse *)handleSessionCreationErrorInContext:(WOContext *)_ctx {
761   WOResponse *response = [_ctx response];
762   unsigned pid;
763   
764 #ifdef __MINGW32__
765   pid = GetCurrentProcessId();
766 #else
767   pid = getpid();
768 #endif
769   
770   if ([self respondsToSelector:@selector(handleSessionCreationError)]) {
771     [self warnWithFormat:@"called deprecated -handleSessionCreationError method"];
772     return [self handleSessionCreationError];
773   }
774   
775   [self errorWithFormat:@"could not create session for context %@", _ctx];
776   
777   [response setStatus:200];
778   [response appendContentString:@"<h4>Session Creation Error</h4>\n<pre>"];
779   [response appendContentString:
780               @"Application Instance failed to create session."];
781   [response appendContentHTMLString:
782               [NSString stringWithFormat:
783                           @"   application: %@\n"
784                           @"   adaptor:     %@\n"
785                           @"   baseURL:     %@\n"
786                           @"   contextID:   %@\n"
787                           @"   instance:    %i\n"
788                           @"   request:     %@\n",
789                           [self name],
790                           [[_ctx request] adaptorPrefix],
791                           [self baseURL],
792                           [_ctx contextID],
793                           pid,
794                           [[_ctx request] description]]];
795   [response appendContentString:@"</pre>"];
796   return response;
797 }
798
799 - (WOResponse *)handleSessionRestorationErrorInContext:(WOContext *)_ctx {
800   if ([self respondsToSelector:@selector(handleSessionRestorationError)]) {
801     [self warnWithFormat:@"calling deprecated "
802                             @"-handleSessionRestorationError method"];
803     return [self handleSessionRestorationError];
804   }
805   
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 - (WOComponent *)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   page      = [rm pageWithName:(_name != nil ? _name : @"Main")
1072                   languages:languages];
1073   [page ensureAwakeInContext:_ctx];
1074   
1075   page = [page retain];
1076   [pool release];
1077
1078 #if MEM_DEBUG
1079   {
1080     int rss, vmsize, lib;
1081     stop = [self memoryStatistics];
1082     rss    = [[stop objectForKey:@"VmRSS"] intValue] -
1083              [[start objectForKey:@"VmRSS"] intValue];
1084     vmsize = [[stop objectForKey:@"VmSize"] intValue] -
1085              [[start objectForKey:@"VmSize"] intValue];
1086     lib    = [[stop objectForKey:@"VmLib"] intValue] -
1087              [[start objectForKey:@"VmLib"] intValue];
1088     [self debugWithFormat:@"loaded component %@; rss=%i vm=%i lib=%i.",
1089             _name, rss,vmsize,lib];
1090   }
1091 #endif
1092   
1093   return [page autorelease];
1094 }
1095 - (WOComponent *)pageWithName:(NSString *)_name inContext:(WOContext *)_ctx {
1096   return [self _pageWithName:_name inContext:_ctx];
1097 }
1098 - (WOComponent *)pageWithName:(NSString *)_name forRequest:(WORequest *)_req {
1099   WOResourceManager *rm;
1100
1101   if ((rm = [self resourceManager]) == nil)
1102     return nil;
1103   
1104   return [rm pageWithName:(_name != nil) ? _name : @"Main"
1105              languages:[_req browserLanguages]];
1106 }
1107
1108 - (void)savePage:(WOComponent *)_page {
1109   IS_DEPRECATED;
1110   [[[self context] session] savePage:_page];
1111 }
1112 - (id)restorePageForContextID:(NSString *)_ctxId {
1113   IS_DEPRECATED;
1114   return [[[self context] session] restorePageForContextID:_ctxId];
1115 }
1116
1117 - (WOResponse *)handlePageRestorationErrorInContext:(WOContext *)_ctx {
1118   [self errorWithFormat:
1119           @"could not restore page for context-id %@\n  in context %@",
1120           [_ctx currentElementID], _ctx];
1121   
1122   /* return main page ... */
1123   return [[self pageWithName:nil inContext:_ctx] generateResponse];
1124 }
1125 - (WOResponse *)handlePageRestorationError {
1126   IS_DEPRECATED;
1127   return [self handlePageRestorationErrorInContext:[self context]];
1128 }
1129
1130 /* exceptions */
1131
1132 - (WOResponse *)handleException:(NSException *)_exc
1133   inContext:(WOContext *)_ctx
1134 {
1135   WORequest  *rq = [_ctx request];
1136   WOResponse *r  = nil;
1137   
1138   if ([self respondsToSelector:@selector(handleException:)]) {
1139     [self warnWithFormat:@"calling deprecated -handleException method !"];
1140     return [self handleException:_exc];
1141   }
1142   
1143 #if DEBUG
1144   {
1145     static int doCore = -1;
1146     if (doCore == -1) {
1147       doCore = [[NSUserDefaults standardUserDefaults] 
1148                  boolForKey:@"WOCoreOnApplicationException"]
1149         ? 1 : 0;
1150     }
1151     if (doCore) {
1152       [self fatalWithFormat:@"%@: caught (ctx=%@):\n  %@.",
1153               self, _ctx, _exc];
1154       abort();
1155     }
1156   }
1157 #endif
1158   
1159   if (_ctx == nil) {
1160     [self fatalWithFormat:@"%@: caught (without context):\n  %@.",
1161             self, _exc];
1162     [self terminate];
1163   }
1164   else if (rq == nil) {
1165     [self fatalWithFormat:@"%@: caught (without request):\n  %@.",
1166             self, _exc];
1167     [self terminate];
1168   }
1169   else {
1170     static NSString *pageFormat =
1171       @"Application Server caught exception:\n\n"
1172       @"  session:   %@\n"
1173       @"  element:   %@\n"
1174       @"  context:   %@\n"
1175       @"  request:   %@\n\n"
1176       @"  class:     %@\n"
1177       @"  name:      %@\n"
1178       @"  reason:    %@\n"
1179       @"  info:\n    %@\n"
1180       @"  backtrace:\n%@\n";
1181     NSString *str = nil;
1182     NSString *bt  = nil;
1183     
1184     [self errorWithFormat:@"%@: caught:\n  %@\nin context:\n  %@.",
1185             self, _exc, _ctx];
1186
1187 #if LIB_FOUNDATION_LIBRARY
1188     if ([NSException respondsToSelector:@selector(backtrace)])
1189       bt = [NSException backtrace];
1190 #endif
1191     
1192     if ((r = [WOResponse responseWithRequest:rq]) == nil)
1193       [self errorWithFormat:@"could not create response !"];
1194     
1195     [r setHeader:@"text/html" forKey:@"content-type"];
1196     [r setHeader:@"no-cache" forKey:@"cache-control"];
1197     if(rapidTurnAroundPath != nil) {
1198         NSURL *templateURL;
1199         
1200         templateURL = [[_exc userInfo] objectForKey:@"templateURL"];
1201         if(templateURL != nil)
1202             [r setHeader:[templateURL path] forKey:@"x-sope-template-path"];
1203     }
1204
1205     str = [NSString stringWithFormat:pageFormat,
1206                       [_ctx hasSession] 
1207                         ? [[_ctx session] sessionID]
1208                         : @"[no session]",
1209                       [_ctx elementID],
1210                       [_ctx description],
1211                       [rq description],
1212                       NSStringFromClass([_exc class]),
1213                       [_exc name],
1214                       [_exc reason],
1215                       [[_exc userInfo] description],
1216                       bt];
1217     
1218     [r appendContentString:@"<html><head><title>Caught exception</title></head><body><pre>\n"];
1219     [r appendContentHTMLString:str];
1220     [r appendContentString:@"</pre></body></html>\n"];
1221   }
1222   return r;
1223 }
1224
1225 /* runloop */
1226
1227 - (BOOL)shouldTerminate {
1228   if (![self isRefusingNewSessions])
1229     return NO;
1230   if ([self activeSessionsCount] >= [self minimumActiveSessionsCount])
1231     return NO;
1232
1233   /* check whether the application instance is still valid .. */
1234   [self debugWithFormat:
1235           @"application terminates because it refuses new sessions and "
1236           @"the active session count (%i) is below the minimum (%i).",
1237           [self activeSessionsCount], [self minimumActiveSessionsCount]];
1238   return YES;
1239 }
1240
1241 - (void)terminate {
1242   [self debugWithFormat:
1243           @"application terminates:\n"
1244           @"  %i active sessions\n"
1245           @"  %i minimum active sessions\n"
1246           @"  refuses new session: %s",
1247           [self activeSessionsCount],
1248           [self minimumActiveSessionsCount],
1249           [self isRefusingNewSessions] ? "yes" : "no"];
1250   [super terminate];
1251 }
1252
1253 /* logging */
1254
1255 - (BOOL)isDebuggingEnabled {
1256   return debugOn;
1257 }
1258 - (NSString *)loggingPrefix {
1259   return [NSString stringWithFormat:@"|%@%@|", 
1260                      [self name],
1261                      [self isTerminating] ? @" terminating" : @""];
1262 }
1263
1264 /* KVC */
1265
1266 #if !LIB_FOUNDATION_LIBRARY
1267 - (id)valueForUndefinedKey:(NSString *)_key {
1268   [self warnWithFormat:@"tried to access undefined KVC key: '%@'",
1269           _key];
1270   return nil;
1271 }
1272 #endif
1273
1274 /* configuration */
1275
1276 + (Class)eoEditingContextClass {
1277   static Class eoEditingContextClass = Nil;
1278   static BOOL  lookedUpForEOEditingContextClass = NO;
1279   
1280   if (!lookedUpForEOEditingContextClass) {
1281     eoEditingContextClass = NSClassFromString(@"EOEditingContext");
1282     lookedUpForEOEditingContextClass = YES;
1283   }
1284   return eoEditingContextClass;
1285 }
1286
1287 + (BOOL)implementsEditingContexts {
1288   return [self eoEditingContextClass] != NULL ? YES : NO;
1289 }
1290
1291 /* description */
1292
1293 - (NSString *)description {
1294   return [NSString stringWithFormat:@"<%@[0x%08X]: name=%@%@>",
1295                      NSStringFromClass([self class]), self,
1296                      [self name],
1297                      [self isTerminating] ? @" terminating" : @""
1298                    ];
1299 }
1300
1301 @end /* WOApplication */