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