2 Copyright (C) 2000-2004 SKYRIX Software AG
4 This file is part of OpenGroupware.org.
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
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.
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
22 #include <NGObjWeb/WOCoreApplication.h>
23 #include <NGObjWeb/WOAdaptor.h>
24 #include <NGObjWeb/WORequest.h>
25 #include <NGObjWeb/WORequestHandler.h>
26 #include <NGObjWeb/WOResponse.h>
27 #include <NGObjWeb/WOContext.h>
28 #include <EOControl/EOControl.h>
29 #include <NGStreams/NGStreams.h>
30 #include <NGStreams/NGNet.h>
31 #include "WORunLoop.h"
34 #if LIB_FOUNDATION_LIBRARY
35 # import <Foundation/UnixSignalHandler.h>
37 # include "UnixSignalHandler.h"
40 NGObjWeb_DECLARE NSString *WOApplicationDidFinishLaunchingNotification =
41 @"WOApplicationDidFinishLaunching";
42 NGObjWeb_DECLARE NSString *WOApplicationWillFinishLaunchingNotification =
43 @"WOApplicationWillFinishLaunching";
44 NGObjWeb_DECLARE NSString *WOApplicationWillTerminateNotification =
45 @"WOApplicationWillTerminate";
46 NGObjWeb_DECLARE NSString *WOApplicationDidTerminateNotification =
47 @"WOApplicationDidTerminate";
49 @interface WOCoreApplication(PrivateMethods)
50 + (void)_initDefaults;
51 - (NSDictionary *)memoryStatistics;
52 - (WOResponse *)handleException:(NSException *)_exc;
55 #if COCOA_Foundation_LIBRARY || NeXT_Foundation_LIBRARY
56 @interface NSObject(KVCWarn)
57 + (void)suppressCapitalizedKeyWarning;
58 - (void)notImplemented:(SEL)cmd;
62 @implementation WOCoreApplication
64 static BOOL perflog = NO;
65 static BOOL outputValidateOn = NO;
66 static Class NSDateClass = Nil;
72 NGObjWeb_DECLARE id WOApp = nil;
73 static NSMutableArray *activeApps = nil; // THREAD
77 NSLog(@"WARNING(%s): some code called +application without "
78 @"an active app !", __PRETTY_FUNCTION__);
80 # warning REMOVE THAT ABORT IN PRODUCTION CODE !!!
86 - (void)activateApplication {
88 if (activeApps == nil)
89 activeApps = [[NSMutableArray alloc] init];
90 [activeApps addObject:WOApp];
94 - (void)deactivateApplication {
99 @"tried to deactivate inactive application !\n"
108 if ((idx = [activeApps count]) > 0) {
110 WOApp = [[activeApps objectAtIndex:idx] retain];
111 [activeApps removeObjectAtIndex:idx];
115 + (void)_initializeClass {
117 this must be called in -init, since the environment is not setup
118 properly if this is called first.
120 static BOOL didInit = NO;
124 [self _initDefaults];
126 ud = [NSUserDefaults standardUserDefaults];
127 perflog = [ud boolForKey:@"WOProfileApplication"];
128 outputValidateOn = [ud boolForKey:@"WOOutputValidationEnabled"];
129 NSDateClass = [NSDate class];
133 [[self class] _initializeClass];
134 #if COCOA_Foundation_LIBRARY
136 NSKeyBinding Warning: <Application 0xc1f70> was accessed using a capitalized key
137 'NSFileSubject'. Keys should normally start with a lowercase character. A
138 typographical error like this could cause a crash or an infinite loop. Use
139 +[NSKeyBinding suppressCapitalizedKeyWarning] to suppress this warning.
141 [NSClassFromString(@"NSKeyBinding") suppressCapitalizedKeyWarning];
144 if ((self = [super init])) {
145 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
147 [self activateApplication];
149 if ([[ud objectForKey:@"WORunMultithreaded"] boolValue]) {
150 self->lock = [[NSRecursiveLock alloc] init];
151 self->requestLock = [[NSLock alloc] init];
155 #if !defined(__MINGW32__) && !defined(NeXT_Foundation_LIBRARY)
157 UnixSignalHandler *us = [UnixSignalHandler sharedHandler];
159 [us addObserver:self selector:@selector(terminateOnSignal:)
160 forSignal:SIGTERM immediatelyNotifyOnSignal:YES];
161 [us addObserver:self selector:@selector(terminateOnSignal:)
162 forSignal:SIGINT immediatelyNotifyOnSignal:YES];
163 [us addObserver:self selector:@selector(terminateOnSignal:)
164 forSignal:SIGQUIT immediatelyNotifyOnSignal:YES];
165 [us addObserver:self selector:@selector(terminateOnSignal:)
166 forSignal:SIGILL immediatelyNotifyOnSignal:YES];
168 [us addObserver:self selector:@selector(processHupSignal:)
169 forSignal:SIGHUP immediatelyNotifyOnSignal:NO];
177 #if !defined(__MINGW32__) && !defined(NeXT_Foundation_LIBRARY)
178 [[UnixSignalHandler sharedHandler] removeObserver:self];
180 [[NSNotificationCenter defaultCenter] removeObserver:self];
181 [self->adaptors release];
182 [self->requestLock release];
183 [self->lock release];
189 - (void)processHupSignal:(int)_signal {
190 /* this isn't called immediatly */
193 - (void)terminateOnSignal:(int)_signal {
194 /* STDIO is forbidden in signal handlers !!! no malloc !!! */
196 self->cappFlags.isTerminating = 1;
198 static int termCount = 0;
202 pid = (unsigned)GetCurrentProcessId();
204 pid = (unsigned)getpid();
207 if ([self isTerminating]) {
213 fprintf(stderr, "%d: forcing termination because of signal %i\n",
221 fprintf(stderr, "%i: terminating because of signal %i\n", pid, _signal);
230 - (NSArray *)adaptors {
231 return self->adaptors;
233 - (WOAdaptor *)adaptorWithName:(NSString *)_name
234 arguments:(NSDictionary *)_args
236 Class adaptorClass = Nil;
237 WOAdaptor *adaptor = nil;
239 adaptorClass = NSClassFromString(_name);
240 if (adaptorClass == Nil) {
241 [self logWithFormat:@"ERROR: did not find adaptor class %@", _name];
245 adaptor = [[adaptorClass allocWithZone:[self zone]]
250 return [adaptor autorelease];
253 - (BOOL)allowsConcurrentRequestHandling {
256 - (BOOL)adaptorsDispatchRequestsConcurrently {
260 /* request recording */
262 - (void)setRecordingPath:(NSString *)_path {
263 [self notImplemented:_cmd];
265 - (NSString *)recordingPath {
266 static NSString *rp = nil;
268 rp = [[[NSUserDefaults standardUserDefaults]
269 stringForKey:@"WORecordingPath"]
278 return NSStringFromClass([self class]);
281 - (WOResponse *)handleException:(NSException *)_exc
282 inContext:(WOContext *)_ctx
284 WORequest *rq = [_ctx request];
287 if ([self respondsToSelector:@selector(handleException:)]) {
288 NSLog(@"WARNING: calling deprecated -handleException method !");
289 return [self handleException:_exc];
293 [self logWithFormat:@"%@: caught (without context):\n %@.", self, _exc];
298 [self logWithFormat:@"%@: caught (without context):\n %@.", self, _exc];
301 else if (rq == nil) {
302 [self logWithFormat:@"%@: caught (without request):\n %@.", self, _exc];
306 [self logWithFormat:@"%@: caught:\n %@\nin context:\n %@.",
311 if ((r = [WOResponse responseWithRequest:rq]) == nil)
312 [self logWithFormat:@"could not create response !"];
327 return [self->lock tryLock];
330 - (void)lockRequestHandling {
331 [self->requestLock lock];
333 - (void)unlockRequestHandling {
334 [self->requestLock unlock];
346 - (void)_loadAdaptors {
347 NSMutableArray *ads = nil;
351 args = [[NSProcessInfo processInfo] arguments];
353 for (i = 0, count = [args count]; i < count; i++) {
356 arg = [args objectAtIndex:i];
358 if ([arg isEqualToString:@"-a"] && ((i + 1) < count)) {
360 NSString *adaptorName = nil;
361 NSMutableDictionary *arguments = nil;
362 WOAdaptor *adaptor = nil;
364 i++; // skip '-a' option
365 adaptorName = [args objectAtIndex:i];
366 i++; // skip adaptor name
368 if (i < count) { // search for arguments
371 arguments = [NSMutableDictionary dictionaryWithCapacity:10];
372 for (; i < count; i++) {
373 arg = [args objectAtIndex:i];
374 if ([arg isEqualToString:@"-a"] ||
375 [arg isEqualToString:@"-c"] ||
376 [arg isEqualToString:@"-d"]) {
383 [arguments setObject:arg forKey:key];
389 adaptor = [self adaptorWithName:adaptorName
390 arguments:[[arguments copy] autorelease]];
392 if (ads == nil) ads = [[NSMutableArray alloc] initWithCapacity:8];
393 [ads addObject:adaptor];
398 self->adaptors = [ads copy];
399 [ads release]; ads = nil;
401 if ([self->adaptors count] == 0) {
403 NSArray *moreAdaptors;
405 defaultAdaptor = [[self class] adaptor];
406 defaultAdaptor = [self adaptorWithName:defaultAdaptor arguments:nil];
407 if (defaultAdaptor) {
408 self->adaptors = [[NSArray alloc] initWithObjects:defaultAdaptor, nil];
411 moreAdaptors = [[self class] additionalAdaptors];
412 if ([moreAdaptors count] > 0) {
414 NSMutableArray *newArray;
418 for (i = 0, count = [moreAdaptors count]; i < count; i++) {
421 adaptor = [self adaptorWithName:[moreAdaptors objectAtIndex:i]
423 if (adaptor == nil) {
424 [self logWithFormat:@"could not find WOAdaptor '%@' !",
425 [moreAdaptors objectAtIndex:i]];
429 if (newArray == nil) {
430 newArray = [self->adaptors mutableCopy];
431 [newArray addObject:adaptor];
434 [self->adaptors release];
435 self->adaptors = [newArray shallowCopy];
436 [newArray release]; newArray = nil;
441 - (void)_setupAdaptors {
446 if ([self->adaptors count] == 0)
447 [self _loadAdaptors];
449 ads = [self->adaptors objectEnumerator];
450 while ((adaptor = [ads nextObject]))
451 [adaptor registerForEvents];
453 - (void)_tearDownAdaptors {
454 // unregister adaptors
458 ads = [self->adaptors objectEnumerator];
459 while ((adaptor = [ads nextObject]))
460 [adaptor unregisterForEvents];
462 [self->adaptors release]; self->adaptors = nil;
465 - (NSRunLoop *)runLoop { // deprecated in WO4
467 return [self mainThreadRunLoop];
469 - (NSRunLoop *)mainThreadRunLoop {
470 // wrong, should remove main thread runloop
471 return [WORunLoop currentRunLoop];
474 - (BOOL)shouldTerminate {
478 [self activateApplication];
480 [self _setupAdaptors];
482 [[NSNotificationCenter defaultCenter]
483 postNotificationName:
484 WOApplicationDidFinishLaunchingNotification
487 [self deactivateApplication];
489 while (![self isTerminating]) {
490 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
492 if ([self shouldTerminate]) {
493 /* check whether we should still process requests */
498 NSDate *limitDate = nil;
500 loop = [self mainThreadRunLoop];
502 limitDate = [loop limitDateForMode:NSDefaultRunLoopMode];
504 if ([self isTerminating])
507 [self activateApplication];
508 [loop runMode:NSDefaultRunLoopMode beforeDate:limitDate];
509 [self deactivateApplication];
515 [self debugWithFormat:@"application finished runloop."];
517 [self activateApplication];
519 [[NSNotificationCenter defaultCenter]
520 postNotificationName:
521 WOApplicationWillTerminateNotification
524 [self _tearDownAdaptors];
526 self->cappFlags.isTerminating = 1;
528 [[NSNotificationCenter defaultCenter]
529 postNotificationName:
530 WOApplicationDidTerminateNotification
533 [self deactivateApplication];
537 self->cappFlags.isTerminating = 1;
539 - (BOOL)isTerminating {
540 return self->cappFlags.isTerminating ? YES : NO;
543 - (void)_terminateNow:(id)_dummy {
546 - (void)terminateAfterTimeInterval:(NSTimeInterval)_interval {
547 [[NSRunLoop currentRunLoop] cancelPerformSelector:@selector(_terminateNow:)
548 target:self argument:nil];
549 [self performSelector:@selector(_terminateNow:) withObject:nil
550 afterDelay:_interval];
553 /* output validation */
555 - (void)setPrintsHTMLParserDiagnostics:(BOOL)_flag {
556 [[NSUserDefaults standardUserDefaults] setBool:_flag
557 forKey:@"WOOutputValidationEnabled"];
558 outputValidateOn = _flag;
560 - (BOOL)printsHTMLParserDiagnostics {
561 return outputValidateOn;
564 - (void)_logWarningOnOutputValidation {
565 static BOOL didWarn = NO;
569 @"WARNING: output validation is enabled, this will "
570 @"slow down request processing!"];
575 - (BOOL)hideValidationIssue:(NSException *)_issue {
576 /* to deal with some non-standard HTML ... */
580 - (void)validateOutputOfResponse:(WOResponse *)_response {
585 [self _logWarningOnOutputValidation];
587 if (_response == nil) {
588 [self logWithFormat:@"validate-output: no response returned by handler."];
592 if (![_response respondsToSelector:@selector(validateContent)]) {
593 [self logWithFormat:@"response does not support content validation!"];
596 if ((issues = [_response validateContent]) == nil)
599 e = [issues objectEnumerator];
600 while ((issue = [e nextObject])) {
601 if ([issue isKindOfClass:[NSException class]]) {
602 if ([self hideValidationIssue:issue])
604 [self logWithFormat:@"validate-output[%@]: %@",
605 [(NSException *)issue name], [issue reason]];
609 [self logWithFormat:@"validate-output: %@", [issue stringValue]];
613 /* request handling */
615 - (WORequestHandler *)handlerForRequest:(WORequest *)_request {
619 - (WOResponse *)dispatchRequest:(WORequest *)_request
620 usingHandler:(WORequestHandler *)handler
622 WOResponse *response = nil;
623 NSTimeInterval startDispatch = 0.0;
626 startDispatch = [[NSDateClass date] timeIntervalSince1970];
628 /* let request handler process the request */
630 NSTimeInterval startDispatch = 0.0;
633 startDispatch = [[NSDateClass date] timeIntervalSince1970];
636 response = [handler handleRequest:_request];
640 rt = [[NSDateClass date] timeIntervalSince1970] - startDispatch;
641 NSLog(@" [woapp-rq]: request handler took %4.3fs.",
642 rt < 0.0 ? -1.0 : rt);
646 response = [[response retain] autorelease];
648 if (outputValidateOn)
649 [self validateOutputOfResponse:response];
653 rt = [[NSDateClass date] timeIntervalSince1970] - startDispatch;
654 NSLog(@"[woapp]: dispatchRequest took %4.3fs.",
655 rt < 0.0 ? -1.0 : rt);
660 - (WOResponse *)dispatchRequest:(WORequest *)_request {
661 WORequestHandler *handler;
663 if ([self respondsToSelector:@selector(handleRequest:)]) {
665 @"WARNING: calling deprecated -handleRequest: method .."];
666 return [self handleRequest:_request];
669 /* find request handler for request */
670 if ((handler = [self handlerForRequest:_request]) == nil) {
671 [self logWithFormat:@"ERROR: got no request handler for request: %@ !",
676 return [self dispatchRequest:_request usingHandler:handler];
681 - (NSString *)description {
682 return [NSString stringWithFormat:@"<%@[0x%08X]: %@>",
683 NSStringFromClass([self class]), self,
684 [self isTerminating] ? @" terminating" : @""
690 + (NSArray *)resourcesSearchPathes {
693 NSString *relPath, *apath;
696 ma = [NSMutableArray arrayWithCapacity:8];
697 env = [[NSProcessInfo processInfo] environment];
700 TODO: the following is a dirty hack because GNUstep people decided
701 to change the directory structure. SIGH!
703 #if APPLE_FOUNDATION_LIBRARY || NeXT_Foundation_LIBRARY || GNUSTEP_BASE_LIBRARY
704 relPath = @"Library/Libraries";
706 relPath = @"Libraries";
708 relPath = [relPath stringByAppendingPathComponent:@"Resources"];
709 relPath = [relPath stringByAppendingPathComponent:@"NGObjWeb"];
711 if ((apath = [env objectForKey:@"GNUSTEP_PATHPREFIX_LIST"]) == nil)
712 apath = [env objectForKey:@"GNUSTEP_PATHLIST"];
714 e = [[apath componentsSeparatedByString:@":"] objectEnumerator];
715 while ((apath = [e nextObject])) {
716 apath = [apath stringByAppendingPathComponent:relPath];
717 if ([ma containsObject:apath]) continue;
719 [ma addObject:apath];
721 [ma addObject:relPath];
722 [ma addObject:@"/usr/local/share/sope-4.3/ngobjweb/"];
723 [ma addObject:@"/usr/share/sope-4.3/ngobjweb/"];
727 + (NSString *)findNGObjWebResource:(NSString *)_name ofType:(NSString *)_ext {
728 #if !COMPILE_AS_FRAMEWORK
737 #if COMPILE_AS_FRAMEWORK
738 bundle = [NSBundle bundleForClass:[WOCoreApplication class]];
739 return [bundle pathForResource:_name ofType:_ext];
741 filename = [_name stringByAppendingPathExtension:_ext];
742 fm = [NSFileManager defaultManager];
743 e = [[self resourcesSearchPathes] objectEnumerator];
744 while ((apath = [e nextObject]) != nil) {
745 apath = [apath stringByAppendingPathComponent:filename];
747 if ([fm fileExistsAtPath:apath])
754 + (void)_initDefaults {
755 static BOOL didInit = NO;
756 NSDictionary *owDefaults = nil;
762 apath = [self findNGObjWebResource:@"Defaults" ofType:@"plist"];
764 NSLog(@"ERROR: cannot find Defaults.plist resource of NGObjWeb library !");
766 owDefaults = [NSDictionary dictionaryWithContentsOfFile:apath];
768 [[NSUserDefaults standardUserDefaults] registerDefaults:owDefaults];
770 [self logWithFormat:@"did register NGObjWeb defaults: %@\n%@",
775 [self logWithFormat:@"ERROR: could not load NGObjWeb defaults: '%@'",
780 + (NSUserDefaults *)userDefaults {
781 return [NSUserDefaults standardUserDefaults];
786 + (void)setAdaptor:(NSString *)_key {
787 [[self userDefaults] setObject:_key forKey:@"WOAdaptor"];
789 + (NSString *)adaptor {
790 return [[self userDefaults] stringForKey:@"WOAdaptor"];
793 + (void)setAdditionalAdaptors:(NSArray *)_names {
794 [[self userDefaults] setObject:_names forKey:@"WOAdditionalAdaptors"];
796 + (NSArray *)additionalAdaptors {
797 return [[self userDefaults] arrayForKey:@"WOAdditionalAdaptors"];
802 + (void)setPort:(NSNumber *)_port {
803 [[self userDefaults] setObject:_port forKey:@"WOPort"];
809 woport = [[self userDefaults] objectForKey:@"WOPort"];
810 if ([woport isKindOfClass:[NSNumber class]])
813 woport = [woport stringValue];
814 addr = NGSocketAddressFromString(woport);
816 if ([addr isKindOfClass:[NGInternetSocketAddress class]])
817 return [NSNumber numberWithInt:[(NGInternetSocketAddress *)addr port]];
822 /* WOWorkerThreadCount */
824 + (NSNumber *)workerThreadCount {
825 static NSNumber *s = nil;
828 i = [[self userDefaults] integerForKey:@"WOWorkerThreadCount"];
829 s = [[NSNumber numberWithInt:i] retain];
834 /* WOListenQueueSize */
836 + (NSNumber *)listenQueueSize {
837 static NSNumber *s = nil;
840 i = [[self userDefaults] integerForKey:@"WOListenQueueSize"];
841 s = [[NSNumber numberWithInt:i] retain];
846 @end /* WOCoreApplication */