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