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