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