]> err.no Git - sope/blob - sope-appserver/NGObjWeb/WOApplication.m
hotfix
[sope] / sope-appserver / NGObjWeb / WOApplication.m
1 /*
2   Copyright (C) 2000-2005 SKYRIX Software AG
3   Copyright (C) 2006      Helge Hess
4
5   This file is part of SOPE.
6
7   SOPE is free software; you can redistribute it and/or modify it under
8   the terms of the GNU Lesser General Public License as published by the
9   Free Software Foundation; either version 2, or (at your option) any
10   later version.
11
12   SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
13   WARRANTY; without even the implied warranty of MERCHANTABILITY or
14   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
15   License for more details.
16
17   You should have received a copy of the GNU Lesser General Public
18   License along with SOPE; see the file COPYING.  If not, write to the
19   Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
20   02111-1307, USA.
21 */
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 #if GNU_RUNTIME
42 #  include <objc/sarray.h>
43 #endif
44
45 @interface WOApplication(PrivateMethods)
46 + (id)logger;
47 - (id)_loadComponentDefinitionWithName:(NSString *)_name
48   language:(NSArray *)_langs;
49 - (NSDictionary *)memoryStatistics;
50 - (void)_setupSNS;
51 @end
52
53 static NSRecursiveLock *classLock           = nil;
54 static NGLogger        *perfLogger          = nil;
55 static Class           NSDateClass          = Nil;
56 static Class           WOTemplateClass      = Nil;
57 static BOOL            debugOn              = NO;
58 static NSString        *rapidTurnAroundPath = nil;
59
60 @interface WOSessionStore(SnStore)
61 - (void)performExpirationCheck:(NSTimer *)_timer;
62 @end
63
64 @implementation WOApplication
65
66 #if 1 // TODO: why is that? why isn't that set by a default?
67 static NSString *defaultCompRqHandlerClassName = @"OWViewRequestHandler";
68 #else
69 static NSString *defaultCompRqHandlerClassName = @"WOComponentRequestHandler";
70 #endif
71
72 + (int)version {
73   return [super version] + 5 /* v6 */;
74 }
75
76 + (void)_setupSNS {
77   Class clazz;
78   id c;
79   
80   clazz = NSClassFromString(@"SNSConnection");
81   c = [(id<NSObject>)clazz performSelector:@selector(defaultSNSConnection)];
82
83   if (c == nil) {
84     [[self logger] fatalWithFormat:@"could not connect SNS, exiting .."];
85     exit(20);
86   }
87   
88   [[self logger] logWithFormat:@"SNS enabled"];
89 }
90
91 /* old license checks */
92
93 - (NSCalendarDate *)appExpireDate {
94   // TODO: can we remove that?
95   return nil;
96 }
97 - (BOOL)isLicenseExpired {
98   // TODO: can we remove that?
99   return NO;
100 }
101
102 /* app path */
103
104 - (NSString *)_lookupAppPath {
105   static NSString *suffix = nil;
106   static BOOL    appPathMissing = NO;
107   NSUserDefaults *ud;
108   NSFileManager  *fm;
109   NSString       *cwd;
110   NSString       *result;
111   
112   if (appPathMissing)
113     return nil;
114
115   ud = [NSUserDefaults standardUserDefaults];
116   
117   // Check if appPath has been forced
118   result = [ud stringForKey:@"WOProjectDirectory"];
119   if(result != nil)
120       return result;
121
122   if (suffix == nil)
123     suffix = [ud stringForKey:@"WOApplicationSuffix"];
124   
125   fm  = [NSFileManager defaultManager];
126   cwd = [fm currentDirectoryPath];
127   
128 #if NeXT_Foundation_LIBRARY || COCOA_Foundation_LIBRARY
129   result = [[NGBundle mainBundle] bundlePath];
130   //NSLog(@"%s: check path '%@'", __PRETTY_FUNCTION__, result);
131 #else
132   result = cwd;
133 #endif
134
135   if ([result hasSuffix:suffix]) {
136     /* started app inside of .woa directory */
137 #if NeXT_Foundation_LIBRARY || COCOA_Foundation_LIBRARY
138     result = [[NGBundle mainBundle] bundlePath];
139 #else
140     result = cwd;
141 #endif
142   }
143   else {
144     NSString *wrapperName;
145     
146     wrapperName = [self->name stringByAppendingString:suffix];
147     
148     /* take a look whether ./AppName.woa exists */
149     result = [result stringByAppendingPathComponent:wrapperName];
150     if (![fm fileExistsAtPath:result]) {
151       /* lookup in process-path */
152       NSProcessInfo *pi;
153       NSDictionary  *env;
154       NSString      *ppath;
155       BOOL isFlattened;
156       
157       pi  = [NSProcessInfo processInfo];
158       env = [pi environment];
159       if ([[env objectForKey:@"GNUSTEP_SYSTEM_ROOT"] isNotNull]) {
160         isFlattened = [[[env objectForKey:@"GNUSTEP_FLATTENED"]
161                              lowercaseString] isEqualToString:@"yes"];
162       }
163       else /* default to flattened if no GNUstep runtime is set */
164         isFlattened = YES;
165       
166       ppath = [[pi arguments] objectAtIndex:0];
167       ppath = [ppath stringByDeletingLastPathComponent]; // del exe-name
168       
169       if (!isFlattened) {
170         ppath = [ppath stringByDeletingLastPathComponent]; // lib-combo
171         ppath = [ppath stringByDeletingLastPathComponent]; // os
172         ppath = [ppath stringByDeletingLastPathComponent]; // cpu
173       }
174       if ([ppath hasSuffix:suffix])
175         result = ppath;
176     }
177   }
178   
179   if (![fm fileExistsAtPath:result]) {
180     [self debugWithFormat:@"%s: missing path '%@'", 
181             __PRETTY_FUNCTION__, result];
182     appPathMissing = YES;
183     result = nil;
184   }
185
186   return result;
187 }
188
189 + (NSString *)defaultRequestHandlerClassName {
190   return @"WOComponentRequestHandler";
191 }
192
193 - (void)_logDefaults {
194   NSUserDefaults *ud;
195   NSArray        *keys;
196   NSEnumerator   *e;
197   NSString       *key;
198
199   ud   = [NSUserDefaults standardUserDefaults];
200   keys = [[ud dictionaryRepresentation] allKeys];
201   keys = [keys sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
202
203   e    = [keys objectEnumerator];
204   while((key = [e nextObject]) != nil) {
205     if ([key hasPrefix:@"WO"] || [key isEqualToString:@"NSProjectSearchPath"])
206       [self logWithFormat:@"[default]: %@ = %@",
207         key,
208         [[ud objectForKey:key] description]];
209   }
210 }
211
212 - (id)initWithName:(NSString *)_name {
213   if ((self = [super init]) != nil) {
214     NSUserDefaults   *ud;
215     NGLoggerManager  *lm;
216     WORequestHandler *rh;
217     NSString         *rk;
218
219     self->name = [_name copy];
220     ud         = [NSUserDefaults standardUserDefaults];
221
222     debugOn = [WOApplication isDebuggingEnabled];
223     if (!debugOn)
224       [[self logger] setLogLevel:NGLogLevelInfo];
225     else
226       [[self logger] logWithFormat:@"WOApplication debugging is enabled."];
227     
228     if (classLock == nil) classLock = [[NSRecursiveLock alloc] init];
229     
230     /* setup SNSConnection */
231     
232     if ([ud boolForKey:@"WOContactSNS"])
233       [self _setupSNS];
234     else
235       [[self logger] logWithFormat:@"SNS support disabled."];
236     
237     NSDateClass         = [NSDate class];
238     WOTemplateClass     = [WOTemplate class];
239     
240     rapidTurnAroundPath = [[ud stringForKey:@"WOProjectDirectory"] copy];
241     
242     lm                  = [NGLoggerManager defaultLoggerManager];
243     perfLogger          = [lm loggerForDefaultKey:@"WOProfileApplication"];
244     
245     
246     [self setPageCacheSize:[ud integerForKey:@"WOPageCacheSize"]];
247     [self setPermanentPageCacheSize:
248             [ud integerForKey:@"WOPermanentPageCacheSize"]];
249     
250     [self setPageRefreshOnBacktrackEnabled:
251             [[ud objectForKey:@"WOPageRefreshOnBacktrack"] boolValue]];
252     
253     [self setCachingEnabled:[WOApplication isCachingEnabled]];
254     
255     /* setup request handlers */
256     
257     self->defaultRequestHandler =
258       [[NSClassFromString([[self class] defaultRequestHandlerClassName])
259                          alloc] init];
260     
261     self->requestHandlerRegistry =
262       NSCreateMapTable(NSObjectMapKeyCallBacks, NSObjectMapValueCallBacks, 8);
263     
264     if ((rk = [WOApplication componentRequestHandlerKey]) == nil) {
265       [self logWithFormat:
266               @"WARNING: no component request handler key is specified, "
267               @"this probably means that share/ngobjweb/Defaults.plist "
268               @"could not get loaded (permissions?)"];
269     }
270     rh = [[NSClassFromString(defaultCompRqHandlerClassName) alloc] init];
271     if ([rk isNotEmpty] && [rh isNotNull])
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 isNotEmpty] && [rh isNotNull])
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 isNotEmpty])
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 logWithFormat:@"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 debugWithFormat:@"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 errorWithFormat:@"missing current thread !!!"];
411     return nil;
412   }
413   if ((td = [t threadDictionary]) == nil) {
414     [self errorWithFormat:
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 setNewSession: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])!=nil) {
515     if ([sessionId isNotEmpty])
516       return sessionId;
517   }
518   
519   /* now look into the cookies */
520   if ((sessionId = [_request cookieValueForKey:[self name]]) != nil) {
521     if ([sessionId respondsToSelector:@selector(objectEnumerator)]) {
522       NSEnumerator *e;
523       
524       e = [(id)sessionId objectEnumerator];
525       while ((sessionId = [e nextObject]) != nil) {
526         if ([sessionId isNotEmpty] && ![sessionId isEqual:@"nil"])
527           return sessionId;
528       }
529     }
530     else {
531       if ([sessionId isNotEmpty] && ![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((char *)buf, "%04X%04X%02X%08X",
547           [[self number] intValue], getpid(), sessionCount, 
548           (unsigned int)time(NULL));
549   wosid = [NSString stringWithCString:(char *)buf];
550   return wosid;
551 }
552
553 - (id)createSessionForRequest:(WORequest *)_request {
554   if ([self respondsToSelector:@selector(createSession)]) {
555     /* call deprecated method */
556     [self warnWithFormat:@"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 - (id)restoreSessionWithID:(NSString *)_sid inContext:(WOContext *)_ctx {
570   WOSession *session;
571   
572   *(&session) = nil;
573
574   if ([self respondsToSelector:@selector(restoreSession)]) {
575     /* call deprecated method */
576     [self warnWithFormat:@"calling deprecated -restoreSession .."];
577     return [self restoreSession];
578   }
579   
580   SYNCHRONIZED(self) {
581     WOSessionStore *store;
582     
583     if ((store = [self sessionStore]) == nil) {
584       [self errorWithFormat:@"missing session store ..."];
585     }
586     else {
587       session = [store restoreSessionWithID:_sid request:[_ctx request]];
588       if ([session isNotNull]) {
589         [_ctx setSession:session];
590         [session _awakeWithContext:_ctx];
591       }
592       else {
593         [self debugWithFormat:@"did not find a session for sid '%@'", _sid];
594       }
595     }
596   }
597   END_SYNCHRONIZED;
598   
599   if (session) {
600     [[NSNotificationCenter defaultCenter]
601                            postNotificationName:WOSessionDidRestoreNotification
602                            object:session];
603   }
604   else {
605     if ([_sid hasPrefix:@"("]) {
606       id sid;
607
608       sid = [_sid propertyList];
609       
610       if ([sid respondsToSelector:@selector(objectEnumerator)]) {
611         NSEnumerator *e;
612         
613         [self errorWithFormat:@"got multiple session IDs !"];
614         
615         e = [sid objectEnumerator];
616         while ((_sid = [e nextObject])) {
617           if ([_sid isEqualToString:@"nil"])
618             continue;
619           
620           if ((session = [self restoreSessionWithID:_sid inContext:_ctx]))
621             return session;
622           
623           //[self warnWithFormat:@"did not find session for sid %@", _sid);
624         }
625       }
626     }
627   }
628   return session;
629 }
630 - (void)saveSessionForContext:(WOContext *)_ctx {
631   NSTimeInterval startSave = 0.0;
632
633   if (perfLogger)
634     startSave = [[NSDateClass date] timeIntervalSince1970];
635   
636   if ([self respondsToSelector:@selector(saveSession:)]) {
637     /* call deprecated method */
638     [self warnWithFormat:@"calling deprecated -saveSession: .."];
639     [self saveSession:[_ctx session]];
640     return;
641   }
642   
643   SYNCHRONIZED(self) {
644     WOSession     *sn;
645     NSTimeInterval startSnSleep = 0.0, startStore = 0.0;
646     
647     sn = [_ctx session];
648
649     if (perfLogger)
650       startSnSleep = [[NSDateClass date] timeIntervalSince1970];
651     
652     /* put session to sleep */
653     [sn _sleepWithContext:_ctx];
654     
655     if (perfLogger) {
656       NSTimeInterval rt;
657       rt = [[NSDateClass date] timeIntervalSince1970] - startSnSleep;
658       [perfLogger logWithFormat:@"[woapp]: session -sleep took %4.3fs.",
659                                     rt < 0.0 ? -1.0 : rt];
660     }
661     
662     if ([sn isTerminating]) {
663       [[NSNotificationCenter defaultCenter]
664                              postNotificationName:
665                                WOSessionDidTerminateNotification
666                              object:sn];
667     }
668     
669     if (perfLogger)
670       startStore = [[NSDateClass date] timeIntervalSince1970];
671     
672     [[self sessionStore] saveSessionForContext:_ctx];
673     
674     if (perfLogger) {
675       NSTimeInterval rt;
676       rt = [[NSDateClass date] timeIntervalSince1970] - startStore;
677       [perfLogger logWithFormat:@"[woapp]: storing sn in store took %4.3fs.",
678                                     rt < 0.0 ? -1.0 : rt];
679     }
680   }
681   END_SYNCHRONIZED;
682
683   if (perfLogger) {
684     NSTimeInterval rt;
685     rt = [[NSDateClass date] timeIntervalSince1970] - startSave;
686     [perfLogger logWithFormat:@"[woapp]: saveSessionForContext took %4.3fs.",
687                                   rt < 0.0 ? -1.0 : rt];
688   }
689 }
690
691 - (void)refuseNewSessions:(BOOL)_flag {
692   self->appFlags.doesRefuseNewSessions = _flag ? 1 : 0;
693 }
694 - (BOOL)isRefusingNewSessions {
695   return self->appFlags.doesRefuseNewSessions;
696 }
697 - (int)activeSessionsCount {
698   return [[self sessionStore] activeSessionsCount];
699 }
700
701 - (void)setSessionStore:(WOSessionStore *)_store {
702   ASSIGN(self->iSessionStore, _store);
703 }
704 - (NSString *)sessionStoreClassName {
705   return [[NSUserDefaults standardUserDefaults] stringForKey:@"WOSessionStore"];
706 }
707 - (WOSessionStore *)sessionStore {
708   return self->iSessionStore;
709 }
710
711 - (void)setMinimumActiveSessionsCount:(int)_minimum {
712   self->minimumActiveSessionsCount = _minimum;
713 }
714 - (int)minimumActiveSessionsCount {
715   return self->minimumActiveSessionsCount;
716 }
717
718 - (void)performExpirationCheck:(NSTimer *)_timer {
719   WOSessionStore *ss;
720
721   /* let session store check for expiration ... */
722   
723   ss = [self sessionStore];
724   if ([ss respondsToSelector:@selector(performExpirationCheck:)])
725     [ss performExpirationCheck:_timer];
726   
727   /* check whether application should terminate ... */
728
729   if ([self isRefusingNewSessions] &&
730       ([self activeSessionsCount] < [self minimumActiveSessionsCount])) {
731     /* check whether the application instance is still valid .. */
732     [self debugWithFormat:
733             @"application terminates because it refuses new sessions and "
734             @"the active session count (%i) is below the minimum (%i).",
735             [self activeSessionsCount], [self minimumActiveSessionsCount]];
736     [self terminate];
737   }
738 }
739
740 - (id)session {
741   return [[self context] session];
742 }
743
744 - (WOResponse *)handleSessionCreationErrorInContext:(WOContext *)_ctx {
745   WOResponse *response = [_ctx response];
746   unsigned pid;
747   
748 #ifdef __MINGW32__
749   pid = GetCurrentProcessId();
750 #else
751   pid = getpid();
752 #endif
753   
754   if ([self respondsToSelector:@selector(handleSessionCreationError)]) {
755     [self warnWithFormat:@"called deprecated -handleSessionCreationError method"];
756     return [self handleSessionCreationError];
757   }
758   
759   [self errorWithFormat:@"could not create session for context %@", _ctx];
760   
761   [response setStatus:200];
762   [response appendContentString:@"<h4>Session Creation Error</h4>\n<pre>"];
763   [response appendContentString:
764               @"Application Instance failed to create session."];
765   [response appendContentHTMLString:
766               [NSString stringWithFormat:
767                           @"   application: %@\n"
768                           @"   adaptor:     %@\n"
769                           @"   baseURL:     %@\n"
770                           @"   contextID:   %@\n"
771                           @"   instance:    %i\n"
772                           @"   request:     %@\n",
773                           [self name],
774                           [[_ctx request] adaptorPrefix],
775                           [self baseURL],
776                           [_ctx contextID],
777                           pid,
778                           [[_ctx request] description]]];
779   [response appendContentString:@"</pre>"];
780   return response;
781 }
782
783 - (WOResponse *)handleSessionRestorationErrorInContext:(WOContext *)_ctx {
784   if ([self respondsToSelector:@selector(handleSessionRestorationError)]) {
785     [self warnWithFormat:@"calling deprecated "
786                             @"-handleSessionRestorationError method"];
787     return [self handleSessionRestorationError];
788   }
789   
790   // TODO: is it correct to return nil?
791   // TODO: we should return a page saying sorry with a cookie + redirect
792   [self errorWithFormat:@"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)_setupDefaultResourceManager {
812   NSUserDefaults *ud;
813   Class    rmClass;
814   NSString *p;
815   
816   ud = [NSUserDefaults standardUserDefaults];
817   p  = [ud stringForKey:@"WODefaultResourceManager"];
818   rmClass = [p isNotEmpty]
819     ? NSClassFromString(p)
820     : [WOResourceManager class];
821   
822   if (rmClass == Nil) {
823     [self errorWithFormat:
824             @"failed to locate class of resource manager: '%@'", p];
825     return;
826   }
827   
828   if ([rmClass instancesRespondToSelector:@selector(initWithPath:)])
829     self->resourceManager = [[rmClass alloc] init];
830   else {
831     self->resourceManager = 
832       [(WOResourceManager *)[rmClass alloc] initWithPath:[self path]];
833   }
834 }
835
836 - (void)setResourceManager:(WOResourceManager *)_manager {
837   ASSIGN(self->resourceManager, _manager);
838 }
839 - (WOResourceManager *)resourceManager {
840   if (self->resourceManager == nil)
841     [self _setupDefaultResourceManager];
842   
843   return self->resourceManager;
844 }
845
846 - (NSURL *)baseURL {
847   NSString  *n;
848   WOContext *ctx = [self context];
849   
850   n = [[ctx request] applicationName];
851   n = [@"/" stringByAppendingString:n ? n : [self name]];
852   
853   return [NSURL URLWithString:n relativeToURL:[ctx baseURL]];
854 }
855
856 - (NSString *)pathForResourceNamed:(NSString *)_name ofType:(NSString *)_type {
857   IS_DEPRECATED;
858   return [[self resourceManager] pathForResourceNamed:_name ofType:_type];
859 }
860
861 - (NSString *)stringForKey:(NSString *)_key
862   inTableNamed:(NSString *)_tableName
863   withDefaultValue:(NSString *)_default
864 {
865   IS_DEPRECATED;
866   return [[self resourceManager] stringForKey:_key
867                                  inTableNamed:_tableName
868                                  withDefaultValue:_default
869                                  languages:
870                                    [(WOSession *)[self session] languages]];
871 }
872
873 /* notifications */
874
875 - (void)awake {
876 }
877 - (void)sleep {
878 #if DEBUG && PRINT_NSSTRING_STATISTICS
879   if ([NSString respondsToSelector:@selector(printStatistics)])
880     [NSString printStatistics];
881 #endif
882   
883 #if DEBUG && PRINT_OBJC_STATISTICS
884 extern int __objc_selector_max_index;
885   printf("nbuckets=%i, nindices=%i, narrays=%i, idxsize=%i\n",
886 nbuckets, nindices, narrays, idxsize);
887   printf("maxsel=%i\n", __objc_selector_max_index);
888 #endif
889 }
890
891 /* responder */
892
893 - (void)takeValuesFromRequest:(WORequest *)_req inContext:(WOContext *)_ctx {
894   if ([_ctx hasSession])
895     [[_ctx session] takeValuesFromRequest:_req inContext:_ctx];
896   else {
897     WOComponent *page;
898     
899     if ((page = [_ctx page]) != nil) {
900       WOContext_enterComponent(_ctx, page, nil);
901       [page takeValuesFromRequest:_req inContext:_ctx];
902       WOContext_leaveComponent(_ctx, page);
903     }
904   }
905 }
906
907 - (id)invokeActionForRequest:(WORequest *)_rq inContext:(WOContext *)_ctx {
908   id result;
909   
910   if ([_ctx hasSession])
911     result = [[_ctx session] invokeActionForRequest:_rq inContext:_ctx];
912   else {
913     WOComponent *page;
914     
915     if ((page = [_ctx page])) {
916       WOContext_enterComponent(_ctx, page, nil);
917       result = [[_ctx page] invokeActionForRequest:_rq inContext:_ctx];
918       WOContext_leaveComponent(_ctx, page);
919     }
920     else
921       result = nil;
922   }
923   return result;
924 }
925
926 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
927   if ([_ctx hasSession])
928     [[_ctx session] appendToResponse:_response inContext:_ctx];
929   else {
930     WOComponent *page;
931     
932     if ((page = [_ctx page])) {
933       WOContext_enterComponent(_ctx, page, nil);
934       [page appendToResponse:_response inContext:_ctx];
935       WOContext_leaveComponent(_ctx, page);
936     }
937   }
938
939   if(rapidTurnAroundPath != nil) {
940       WOComponent *page;
941       
942       if((page = [_ctx page])) {
943           WOElement *template;
944           
945           template = [page _woComponentTemplate];
946           if([template isKindOfClass:WOTemplateClass]) {
947               NSString *_path;
948               
949               _path = [[(WOTemplate *)template url] path];
950               [_response setHeader:_path
951                             forKey:@"x-sope-template-path"];
952           }
953
954       }
955   }
956 }
957
958 // dynamic elements
959
960 - (WOElement *)dynamicElementWithName:(NSString *)_name
961   associations:(NSDictionary *)_associations
962   template:(WOElement *)_template
963   languages:(NSArray *)_languages
964 {
965   WOElement *element            = nil;
966   Class     dynamicElementClass = NSClassFromString(_name);
967
968   if (dynamicElementClass == Nil) {
969     [self warnWithFormat:@"did not find dynamic element class %@ !", _name];
970     return nil;
971   }
972   if (![dynamicElementClass isDynamicElement]) {
973     [self warnWithFormat:@"class %@ is not a dynamic element class !", _name];
974     return nil;
975   }
976   
977   element = [[dynamicElementClass allocWithZone:[_template zone]]
978                                   initWithName:_name
979                                   associations:_associations
980                                   template:_template];
981   return element;
982 }
983 - (WOElement *)dynamicElementWithName:(NSString *)_name
984   associations:(NSDictionary *)_associations
985   template:(WOElement *)_template
986 {
987   return [self dynamicElementWithName:_name
988                associations:_associations
989                template:_template
990                languages:[(WOSession *)[self session] languages]];
991 }
992
993 // pages
994
995 - (void)setPageRefreshOnBacktrackEnabled:(BOOL)_flag {
996   self->appFlags.isPageRefreshOnBacktrackEnabled = _flag ? 1 : 0;
997 }
998 - (BOOL)isPageRefreshOnBacktrackEnabled {
999   return self->appFlags.isPageRefreshOnBacktrackEnabled ? YES : NO;
1000 }
1001
1002 - (void)setCachingEnabled:(BOOL)_flag {
1003   self->appFlags.isCachingEnabled = _flag ? 1 : 0;
1004 }
1005 - (BOOL)isCachingEnabled {
1006   // component definition caching
1007   return self->appFlags.isCachingEnabled ? YES : NO;
1008 }
1009
1010 - (void)setPageCacheSize:(int)_size {
1011   self->pageCacheSize = _size;
1012 }
1013 - (int)pageCacheSize {
1014   return self->pageCacheSize;
1015 }
1016 - (void)setPermanentPageCacheSize:(int)_size {
1017   self->permanentPageCacheSize = _size;
1018 }
1019 - (int)permanentPageCacheSize {
1020   return self->permanentPageCacheSize;
1021 }
1022
1023 - (id)pageWithName:(NSString *)_name {
1024   // deprecated in WO4
1025   return [self pageWithName:_name inContext:[self context]];
1026 }
1027
1028 - (WOComponent *)_pageWithName:(NSString *)_name inContext:(WOContext *)_ctx {
1029   /*
1030     OSX profiling: 3.4% of dispatchRequest?
1031       3.0%  rm -pageWithName..
1032         1.5%  def instantiate
1033           1.3% initWithName:...
1034             0.76% initWithContent:.. (0.43 addobserver)
1035         0.76% rm  defForComp (0.43% touch)
1036         0.54% pool
1037       0.11% ctx -component
1038       0.11% pool
1039   */
1040   NSArray           *languages;
1041   WOComponent       *page;
1042   NSAutoreleasePool *pool;
1043   WOResourceManager *rm;
1044
1045 #if MEM_DEBUG
1046   NSDictionary *start, *stop;
1047   start = [self memoryStatistics];
1048 #endif
1049   
1050   pool      = [[NSAutoreleasePool alloc] init];
1051   
1052   languages = [_ctx resourceLookupLanguages];
1053
1054   if ((rm = [[_ctx component] resourceManager]) == nil)
1055     rm = [self resourceManager];
1056   
1057   /*  TODO:
1058    *  the following ignores the fact that the passed context may be different
1059    *  from that of WOApplication. During the course of template instantiation
1060    *  WOApplication's current context gets attached to page which is definitely
1061    *  wrong. We workaround this problem by using the private API of WOComponent
1062    *  to explicitly set it. However all accompanied methods should be
1063    *  extended to pass the correct context where needed.
1064    */
1065   page      = [rm pageWithName:(_name != nil ? _name : (NSString *)@"Main")
1066                   languages:languages];
1067   [page _setContext:_ctx];
1068   [page ensureAwakeInContext:_ctx];
1069   
1070   page = [page retain];
1071   [pool release];
1072
1073 #if MEM_DEBUG
1074   {
1075     int rss, vmsize, lib;
1076     stop = [self memoryStatistics];
1077     rss    = [[stop objectForKey:@"VmRSS"] intValue] -
1078              [[start objectForKey:@"VmRSS"] intValue];
1079     vmsize = [[stop objectForKey:@"VmSize"] intValue] -
1080              [[start objectForKey:@"VmSize"] intValue];
1081     lib    = [[stop objectForKey:@"VmLib"] intValue] -
1082              [[start objectForKey:@"VmLib"] intValue];
1083     [self debugWithFormat:@"loaded component %@; rss=%i vm=%i lib=%i.",
1084             _name, rss,vmsize,lib];
1085   }
1086 #endif
1087   
1088   return [page autorelease];
1089 }
1090 - (id)pageWithName:(NSString *)_name inContext:(WOContext *)_ctx {
1091   return [self _pageWithName:_name inContext:_ctx];
1092 }
1093 - (id)pageWithName:(NSString *)_name forRequest:(WORequest *)_req {
1094   WOResourceManager *rm;
1095
1096   if ((rm = [self resourceManager]) == nil)
1097     return nil;
1098   
1099   return [rm pageWithName:(_name != nil) ? _name : (NSString *)@"Main"
1100              languages:[_req browserLanguages]];
1101 }
1102
1103 - (void)savePage:(WOComponent *)_page {
1104   IS_DEPRECATED;
1105   [[[self context] session] savePage:_page];
1106 }
1107 - (id)restorePageForContextID:(NSString *)_ctxId {
1108   IS_DEPRECATED;
1109   return [[[self context] session] restorePageForContextID:_ctxId];
1110 }
1111
1112 - (WOResponse *)handlePageRestorationErrorInContext:(WOContext *)_ctx {
1113   [self errorWithFormat:
1114           @"could not restore page for context-id %@\n  in context %@",
1115           [_ctx currentElementID], _ctx];
1116   
1117   /* return main page ... */
1118   return [[self pageWithName:nil inContext:_ctx] generateResponse];
1119 }
1120 - (WOResponse *)handlePageRestorationError {
1121   IS_DEPRECATED;
1122   return [self handlePageRestorationErrorInContext:[self context]];
1123 }
1124
1125 /* exceptions */
1126
1127 - (WOResponse *)handleException:(NSException *)_exc
1128   inContext:(WOContext *)_ctx
1129 {
1130   WORequest  *rq = [_ctx request];
1131   WOResponse *r  = nil;
1132   
1133   if ([self respondsToSelector:@selector(handleException:)]) {
1134     [self warnWithFormat:@"calling deprecated -handleException method !"];
1135     return [self handleException:_exc];
1136   }
1137   
1138 #if DEBUG
1139   {
1140     static int doCore = -1;
1141     if (doCore == -1) {
1142       doCore = [[NSUserDefaults standardUserDefaults] 
1143                  boolForKey:@"WOCoreOnApplicationException"]
1144         ? 1 : 0;
1145     }
1146     if (doCore) {
1147       [self fatalWithFormat:@"%@: caught (ctx=%@):\n  %@.",
1148               self, _ctx, _exc];
1149       abort();
1150     }
1151   }
1152 #endif
1153   
1154   if (_ctx == nil) {
1155     [self fatalWithFormat:@"%@: caught (without context):\n  %@.",
1156             self, _exc];
1157     [self terminate];
1158   }
1159   else if (rq == nil) {
1160     [self fatalWithFormat:@"%@: caught (without request):\n  %@.",
1161             self, _exc];
1162     [self terminate];
1163   }
1164   else {
1165     static NSString *pageFormat =
1166       @"Application Server caught exception:\n\n"
1167       @"  session:   %@\n"
1168       @"  element:   %@\n"
1169       @"  context:   %@\n"
1170       @"  request:   %@\n\n"
1171       @"  class:     %@\n"
1172       @"  name:      %@\n"
1173       @"  reason:    %@\n"
1174       @"  info:\n    %@\n"
1175       @"  backtrace:\n%@\n";
1176     NSString *str = nil;
1177     NSString *bt  = nil;
1178     
1179     [self errorWithFormat:@"%@: caught:\n  %@\nin context:\n  %@.",
1180             self, _exc, _ctx];
1181
1182 #if LIB_FOUNDATION_LIBRARY
1183     if ([NSException respondsToSelector:@selector(backtrace)])
1184       bt = [NSException backtrace];
1185 #endif
1186     
1187     if ((r = [WOResponse responseWithRequest:rq]) == nil)
1188       [self errorWithFormat:@"could not create response !"];
1189     
1190     [r setHeader:@"text/html" forKey:@"content-type"];
1191     [r setHeader:@"no-cache" forKey:@"cache-control"];
1192     if (rapidTurnAroundPath != nil) {
1193         NSURL *templateURL;
1194         
1195         templateURL = [[_exc userInfo] objectForKey:@"templateURL"];
1196         if(templateURL != nil)
1197             [r setHeader:[templateURL path] forKey:@"x-sope-template-path"];
1198     }
1199
1200     str = [NSString stringWithFormat:pageFormat,
1201                       [_ctx hasSession] 
1202                         ? [[_ctx session] sessionID]
1203                         : (NSString *)@"[no session]",
1204                       [_ctx elementID],
1205                       [_ctx description],
1206                       [rq description],
1207                       NSStringFromClass([_exc class]),
1208                       [_exc name],
1209                       [_exc reason],
1210                       [[_exc userInfo] description],
1211                       bt];
1212     
1213     [r appendContentString:@"<html><head><title>Caught exception</title></head><body><pre>\n"];
1214     [r appendContentHTMLString:str];
1215     [r appendContentString:@"</pre></body></html>\n"];
1216   }
1217   return r;
1218 }
1219
1220 /* runloop */
1221
1222 - (BOOL)shouldTerminate {
1223   if (![self isRefusingNewSessions])
1224     return NO;
1225   if ([self activeSessionsCount] >= [self minimumActiveSessionsCount])
1226     return NO;
1227
1228   /* check whether the application instance is still valid .. */
1229   [self debugWithFormat:
1230           @"application terminates because it refuses new sessions and "
1231           @"the active session count (%i) is below the minimum (%i).",
1232           [self activeSessionsCount], [self minimumActiveSessionsCount]];
1233   return YES;
1234 }
1235
1236 - (void)terminate {
1237   [self debugWithFormat:
1238           @"application terminates:\n"
1239           @"  %i active sessions\n"
1240           @"  %i minimum active sessions\n"
1241           @"  refuses new session: %s",
1242           [self activeSessionsCount],
1243           [self minimumActiveSessionsCount],
1244           [self isRefusingNewSessions] ? "yes" : "no"];
1245   [super terminate];
1246 }
1247
1248 /* logging */
1249
1250 - (BOOL)isDebuggingEnabled {
1251   return debugOn;
1252 }
1253 - (NSString *)loggingPrefix {
1254   return [NSString stringWithFormat:@"|%@%@|", 
1255                      [self name],
1256                      [self isTerminating] ? @" terminating" : @""];
1257 }
1258
1259 /* KVC */
1260
1261 #if !LIB_FOUNDATION_LIBRARY
1262 - (id)valueForUndefinedKey:(NSString *)_key {
1263   [self warnWithFormat:@"tried to access undefined KVC key: '%@'",
1264           _key];
1265   return nil;
1266 }
1267 #endif
1268
1269 /* configuration */
1270
1271 + (Class)eoEditingContextClass {
1272   static Class eoEditingContextClass = Nil;
1273   static BOOL  lookedUpForEOEditingContextClass = NO;
1274   
1275   if (!lookedUpForEOEditingContextClass) {
1276     if ((eoEditingContextClass = NSClassFromString(@"EOEditingContext")) ==nil)
1277       eoEditingContextClass = NSClassFromString(@"NSManagedObjectContext");
1278     lookedUpForEOEditingContextClass = YES;
1279   }
1280   return eoEditingContextClass;
1281 }
1282
1283 + (BOOL)implementsEditingContexts {
1284   return [self eoEditingContextClass] != NULL ? YES : NO;
1285 }
1286
1287 /* description */
1288
1289 - (NSString *)description {
1290   return [NSString stringWithFormat:@"<%@[0x%p]: name=%@%@>",
1291                      NSStringFromClass([self class]), self,
1292                      [self name],
1293                      [self isTerminating] ? @" terminating" : @""
1294                    ];
1295 }
1296
1297 @end /* WOApplication */