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