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