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 <NGExtensions/NGResourceLocator.h>
32 #include "WORunLoop.h"
35 #if LIB_FOUNDATION_LIBRARY
36 # import <Foundation/UnixSignalHandler.h>
38 # include "UnixSignalHandler.h"
41 NGObjWeb_DECLARE NSString *WOApplicationDidFinishLaunchingNotification =
42 @"WOApplicationDidFinishLaunching";
43 NGObjWeb_DECLARE NSString *WOApplicationWillFinishLaunchingNotification =
44 @"WOApplicationWillFinishLaunching";
45 NGObjWeb_DECLARE NSString *WOApplicationWillTerminateNotification =
46 @"WOApplicationWillTerminate";
47 NGObjWeb_DECLARE NSString *WOApplicationDidTerminateNotification =
48 @"WOApplicationDidTerminate";
50 @interface WOCoreApplication(PrivateMethods)
51 + (void)_initDefaults;
52 - (NSDictionary *)memoryStatistics;
53 - (WOResponse *)handleException:(NSException *)_exc;
56 #if COCOA_Foundation_LIBRARY || NeXT_Foundation_LIBRARY
57 @interface NSObject(KVCWarn)
58 + (void)suppressCapitalizedKeyWarning;
59 - (void)notImplemented:(SEL)cmd;
63 @implementation WOCoreApplication
65 static BOOL outputValidateOn = NO;
66 static Class NSDateClass = Nil;
67 static NGLogger *perfLogger = nil;
73 NGObjWeb_DECLARE id WOApp = nil;
74 static NSMutableArray *activeApps = nil; // THREAD
78 [self logWarnWithFormat:@"(%s): some code called +application without an "
79 @"active app !", __PRETTY_FUNCTION__];
81 # warning REMOVE THAT ABORT IN PRODUCTION CODE !!!
87 - (void)activateApplication {
89 if (activeApps == nil)
90 activeApps = [[NSMutableArray alloc] init];
91 [activeApps addObject:WOApp];
95 - (void)deactivateApplication {
100 @"tried to deactivate inactive application !\n"
109 if ((idx = [activeApps count]) > 0) {
111 WOApp = [[activeApps objectAtIndex:idx] retain];
112 [activeApps removeObjectAtIndex:idx];
116 + (void)_initializeClass {
118 this must be called in -init, since the environment is not setup
119 properly if this is called first.
121 static BOOL didInit = NO;
126 [self _initDefaults];
128 lm = [NGLoggerManager defaultLoggerManager];
129 perfLogger = [lm loggerForDefaultKey:@"WOProfileApplication"];
131 ud = [NSUserDefaults standardUserDefaults];
132 outputValidateOn = [ud boolForKey:@"WOOutputValidationEnabled"];
133 NSDateClass = [NSDate class];
137 [[self class] _initializeClass];
138 #if COCOA_Foundation_LIBRARY
140 NSKeyBinding Warning: <Application 0xc1f70> was accessed using a capitalized key
141 'NSFileSubject'. Keys should normally start with a lowercase character. A
142 typographical error like this could cause a crash or an infinite loop. Use
143 +[NSKeyBinding suppressCapitalizedKeyWarning] to suppress this warning.
145 [NSClassFromString(@"NSKeyBinding") suppressCapitalizedKeyWarning];
148 if ((self = [super init])) {
149 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
151 [self activateApplication];
153 if ([[ud objectForKey:@"WORunMultithreaded"] boolValue]) {
154 self->lock = [[NSRecursiveLock alloc] init];
155 self->requestLock = [[NSLock alloc] init];
159 #if !defined(__MINGW32__) && !defined(NeXT_Foundation_LIBRARY)
161 UnixSignalHandler *us = [UnixSignalHandler sharedHandler];
163 [us addObserver:self selector:@selector(terminateOnSignal:)
164 forSignal:SIGTERM immediatelyNotifyOnSignal:YES];
165 [us addObserver:self selector:@selector(terminateOnSignal:)
166 forSignal:SIGINT immediatelyNotifyOnSignal:YES];
167 [us addObserver:self selector:@selector(terminateOnSignal:)
168 forSignal:SIGQUIT immediatelyNotifyOnSignal:YES];
169 [us addObserver:self selector:@selector(terminateOnSignal:)
170 forSignal:SIGILL immediatelyNotifyOnSignal:YES];
172 [us addObserver:self selector:@selector(processHupSignal:)
173 forSignal:SIGHUP immediatelyNotifyOnSignal:NO];
181 #if !defined(__MINGW32__) && !defined(NeXT_Foundation_LIBRARY)
182 [[UnixSignalHandler sharedHandler] removeObserver:self];
184 [[NSNotificationCenter defaultCenter] removeObserver:self];
185 [self->adaptors release];
186 [self->requestLock release];
187 [self->lock release];
194 static id sharedLogger = nil;
195 if(sharedLogger == nil) {
196 sharedLogger = [[NGLogger alloc] initWithLogLevel:NGLogLevelDebug];
203 - (void)processHupSignal:(int)_signal {
204 /* this isn't called immediatly */
207 - (void)terminateOnSignal:(int)_signal {
208 /* STDIO is forbidden in signal handlers !!! no malloc !!! */
210 self->cappFlags.isTerminating = 1;
212 static int termCount = 0;
216 pid = (unsigned)GetCurrentProcessId();
218 pid = (unsigned)getpid();
221 if ([self isTerminating]) {
227 fprintf(stderr, "%d: forcing termination because of signal %i\n",
235 fprintf(stderr, "%i: terminating because of signal %i\n", pid, _signal);
244 - (NSArray *)adaptors {
245 return self->adaptors;
247 - (WOAdaptor *)adaptorWithName:(NSString *)_name
248 arguments:(NSDictionary *)_args
250 Class adaptorClass = Nil;
251 WOAdaptor *adaptor = nil;
253 adaptorClass = NSClassFromString(_name);
254 if (adaptorClass == Nil) {
255 [self logWithFormat:@"ERROR: did not find adaptor class %@", _name];
259 adaptor = [[adaptorClass allocWithZone:[self zone]]
264 return [adaptor autorelease];
267 - (BOOL)allowsConcurrentRequestHandling {
270 - (BOOL)adaptorsDispatchRequestsConcurrently {
274 /* request recording */
276 - (void)setRecordingPath:(NSString *)_path {
277 [self notImplemented:_cmd];
279 - (NSString *)recordingPath {
280 static NSString *rp = nil;
282 rp = [[[NSUserDefaults standardUserDefaults]
283 stringForKey:@"WORecordingPath"]
292 return NSStringFromClass([self class]);
295 - (WOResponse *)handleException:(NSException *)_exc
296 inContext:(WOContext *)_ctx
298 WORequest *rq = [_ctx request];
301 if ([self respondsToSelector:@selector(handleException:)]) {
302 [self logWarnWithFormat:@"calling deprecated -handleException method !"];
303 return [self handleException:_exc];
307 [self logWithFormat:@"%@: caught (without context):\n %@.", self, _exc];
312 [self logWithFormat:@"%@: caught (without context):\n %@.", self, _exc];
315 else if (rq == nil) {
316 [self logWithFormat:@"%@: caught (without request):\n %@.", self, _exc];
320 [self logWithFormat:@"%@: caught:\n %@\nin context:\n %@.",
325 if ((r = [WOResponse responseWithRequest:rq]) == nil)
326 [self logWithFormat:@"could not create response !"];
341 return [self->lock tryLock];
344 - (void)lockRequestHandling {
345 [self->requestLock lock];
347 - (void)unlockRequestHandling {
348 [self->requestLock unlock];
360 - (void)_loadAdaptors {
361 NSMutableArray *ads = nil;
365 args = [[NSProcessInfo processInfo] arguments];
367 for (i = 0, count = [args count]; i < count; i++) {
370 arg = [args objectAtIndex:i];
372 if ([arg isEqualToString:@"-a"] && ((i + 1) < count)) {
374 NSString *adaptorName = nil;
375 NSMutableDictionary *arguments = nil;
376 WOAdaptor *adaptor = nil;
378 i++; // skip '-a' option
379 adaptorName = [args objectAtIndex:i];
380 i++; // skip adaptor name
382 if (i < count) { // search for arguments
385 arguments = [NSMutableDictionary dictionaryWithCapacity:10];
386 for (; i < count; i++) {
387 arg = [args objectAtIndex:i];
388 if ([arg isEqualToString:@"-a"] ||
389 [arg isEqualToString:@"-c"] ||
390 [arg isEqualToString:@"-d"]) {
397 [arguments setObject:arg forKey:key];
403 adaptor = [self adaptorWithName:adaptorName
404 arguments:[[arguments copy] autorelease]];
406 if (ads == nil) ads = [[NSMutableArray alloc] initWithCapacity:8];
407 [ads addObject:adaptor];
412 self->adaptors = [ads copy];
413 [ads release]; ads = nil;
415 if ([self->adaptors count] == 0) {
417 NSArray *moreAdaptors;
419 defaultAdaptor = [[self class] adaptor];
420 defaultAdaptor = [self adaptorWithName:defaultAdaptor arguments:nil];
421 if (defaultAdaptor) {
422 self->adaptors = [[NSArray alloc] initWithObjects:defaultAdaptor, nil];
425 moreAdaptors = [[self class] additionalAdaptors];
426 if ([moreAdaptors count] > 0) {
428 NSMutableArray *newArray;
432 for (i = 0, count = [moreAdaptors count]; i < count; i++) {
435 adaptor = [self adaptorWithName:[moreAdaptors objectAtIndex:i]
437 if (adaptor == nil) {
438 [self logWithFormat:@"could not find WOAdaptor '%@' !",
439 [moreAdaptors objectAtIndex:i]];
443 if (newArray == nil) {
444 newArray = [self->adaptors mutableCopy];
445 [newArray addObject:adaptor];
448 [self->adaptors release];
449 self->adaptors = [newArray shallowCopy];
450 [newArray release]; newArray = nil;
455 - (void)_setupAdaptors {
460 if ([self->adaptors count] == 0)
461 [self _loadAdaptors];
463 ads = [self->adaptors objectEnumerator];
464 while ((adaptor = [ads nextObject]))
465 [adaptor registerForEvents];
467 - (void)_tearDownAdaptors {
468 // unregister adaptors
472 ads = [self->adaptors objectEnumerator];
473 while ((adaptor = [ads nextObject]))
474 [adaptor unregisterForEvents];
476 [self->adaptors release]; self->adaptors = nil;
479 - (NSRunLoop *)runLoop { // deprecated in WO4
481 return [self mainThreadRunLoop];
483 - (NSRunLoop *)mainThreadRunLoop {
484 // wrong, should remove main thread runloop
485 return [WORunLoop currentRunLoop];
488 - (BOOL)shouldTerminate {
492 [self activateApplication];
494 [self _setupAdaptors];
496 [[NSNotificationCenter defaultCenter]
497 postNotificationName:
498 WOApplicationDidFinishLaunchingNotification
501 [self deactivateApplication];
503 while (![self isTerminating]) {
504 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
506 if ([self shouldTerminate]) {
507 /* check whether we should still process requests */
512 NSDate *limitDate = nil;
514 loop = [self mainThreadRunLoop];
516 limitDate = [loop limitDateForMode:NSDefaultRunLoopMode];
518 if ([self isTerminating])
521 [self activateApplication];
522 [loop runMode:NSDefaultRunLoopMode beforeDate:limitDate];
523 [self deactivateApplication];
529 [self debugWithFormat:@"application finished runloop."];
531 [self activateApplication];
533 [[NSNotificationCenter defaultCenter]
534 postNotificationName:
535 WOApplicationWillTerminateNotification
538 [self _tearDownAdaptors];
540 self->cappFlags.isTerminating = 1;
542 [[NSNotificationCenter defaultCenter]
543 postNotificationName:
544 WOApplicationDidTerminateNotification
547 [self deactivateApplication];
551 self->cappFlags.isTerminating = 1;
553 - (BOOL)isTerminating {
554 return self->cappFlags.isTerminating ? YES : NO;
557 - (void)_terminateNow:(id)_dummy {
560 - (void)terminateAfterTimeInterval:(NSTimeInterval)_interval {
561 [[NSRunLoop currentRunLoop] cancelPerformSelector:@selector(_terminateNow:)
562 target:self argument:nil];
563 [self performSelector:@selector(_terminateNow:) withObject:nil
564 afterDelay:_interval];
567 /* output validation */
569 - (void)setPrintsHTMLParserDiagnostics:(BOOL)_flag {
570 [[NSUserDefaults standardUserDefaults] setBool:_flag
571 forKey:@"WOOutputValidationEnabled"];
572 outputValidateOn = _flag;
574 - (BOOL)printsHTMLParserDiagnostics {
575 return outputValidateOn;
578 - (void)_logWarningOnOutputValidation {
579 static BOOL didWarn = NO;
583 @"WARNING: output validation is enabled, this will "
584 @"slow down request processing!"];
589 - (BOOL)hideValidationIssue:(NSException *)_issue {
590 /* to deal with some non-standard HTML ... */
594 - (void)validateOutputOfResponse:(WOResponse *)_response {
599 [self _logWarningOnOutputValidation];
601 if (_response == nil) {
602 [self logWithFormat:@"validate-output: no response returned by handler."];
606 if (![_response respondsToSelector:@selector(validateContent)]) {
607 [self logWithFormat:@"response does not support content validation!"];
610 if ((issues = [_response validateContent]) == nil)
613 e = [issues objectEnumerator];
614 while ((issue = [e nextObject])) {
615 if ([issue isKindOfClass:[NSException class]]) {
616 if ([self hideValidationIssue:issue])
618 [self logWithFormat:@"validate-output[%@]: %@",
619 [(NSException *)issue name], [issue reason]];
623 [self logWithFormat:@"validate-output: %@", [issue stringValue]];
627 /* request handling */
629 - (WORequestHandler *)handlerForRequest:(WORequest *)_request {
633 - (WOResponse *)dispatchRequest:(WORequest *)_request
634 usingHandler:(WORequestHandler *)handler
636 WOResponse *response = nil;
637 NSTimeInterval startDispatch = 0.0;
640 startDispatch = [[NSDateClass date] timeIntervalSince1970];
642 /* let request handler process the request */
644 NSTimeInterval startDispatch = 0.0;
647 startDispatch = [[NSDateClass date] timeIntervalSince1970];
650 response = [handler handleRequest:_request];
654 rt = [[NSDateClass date] timeIntervalSince1970] - startDispatch;
655 [perfLogger logInfoWithFormat:@"[woapp-rq]: request handler took %4.3fs.",
656 rt < 0.0 ? -1.0 : rt];
660 response = [[response retain] autorelease];
662 if (outputValidateOn)
663 [self validateOutputOfResponse:response];
667 rt = [[NSDateClass date] timeIntervalSince1970] - startDispatch;
668 [perfLogger logInfoWithFormat:@"[woapp]: dispatchRequest took %4.3fs.",
669 rt < 0.0 ? -1.0 : rt];
674 - (WOResponse *)dispatchRequest:(WORequest *)_request {
675 WORequestHandler *handler;
677 if ([self respondsToSelector:@selector(handleRequest:)]) {
678 [self logWarnWithFormat:
679 @"calling deprecated -handleRequest: method .."];
680 return [self handleRequest:_request];
683 /* find request handler for request */
684 if ((handler = [self handlerForRequest:_request]) == nil) {
685 [self logErrorWithFormat:@"got no request handler for request: %@ !",
690 return [self dispatchRequest:_request usingHandler:handler];
695 - (NSString *)description {
696 return [NSString stringWithFormat:@"<%@[0x%08X]: %@>",
697 NSStringFromClass([self class]), self,
698 [self isTerminating] ? @" terminating" : @""
704 + (int)sopeMajorVersion {
705 return SOPE_MAJOR_VERSION;
707 + (int)sopeMinorVersion {
708 return SOPE_MINOR_VERSION;
710 + (NSString *)ngobjwebShareDirectorySubPath {
711 return [NSString stringWithFormat:@"share/sope-%i.%i/ngobjweb/",
712 [self sopeMajorVersion], [self sopeMinorVersion]];
714 + (NGResourceLocator *)ngobjwebResourceLocator {
715 return [NGResourceLocator resourceLocatorForGNUstepPath:
716 @"Library/Libraries/Resources/NGObjWeb"
717 fhsPath:[self ngobjwebShareDirectorySubPath]];
720 + (NSArray *)resourcesSearchPathes {
721 // TODO: is this actually used somewhere?
722 return [[self ngobjwebResourceLocator] searchPathes];
725 + (NSString *)findNGObjWebResource:(NSString *)_name ofType:(NSString *)_ext {
726 #if COMPILE_AS_FRAMEWORK
729 bundle = [NSBundle bundleForClass:[WOCoreApplication class]];
730 return [bundle pathForResource:_name ofType:_ext];
732 return [[self ngobjwebResourceLocator] lookupFileWithName:_name
737 + (void)_initDefaults {
738 static BOOL didInit = NO;
739 NSDictionary *owDefaults = nil;
745 apath = [self findNGObjWebResource:@"Defaults" ofType:@"plist"];
747 [self logErrorWithFormat:@"cannot find Defaults.plist resource of NGObjWeb library!"];
750 [self logDebugWithFormat:@"Note: loading default defaults: %@", apath];
753 owDefaults = [NSDictionary dictionaryWithContentsOfFile:apath];
755 [[NSUserDefaults standardUserDefaults] registerDefaults:owDefaults];
757 [self logDebugWithFormat:@"did register NGObjWeb defaults: %@\n%@",
762 [self logErrorWithFormat:@"could not load NGObjWeb defaults: '%@'",
767 + (NSUserDefaults *)userDefaults {
768 return [NSUserDefaults standardUserDefaults];
773 + (void)setAdaptor:(NSString *)_key {
774 [[self userDefaults] setObject:_key forKey:@"WOAdaptor"];
776 + (NSString *)adaptor {
777 return [[self userDefaults] stringForKey:@"WOAdaptor"];
780 + (void)setAdditionalAdaptors:(NSArray *)_names {
781 [[self userDefaults] setObject:_names forKey:@"WOAdditionalAdaptors"];
783 + (NSArray *)additionalAdaptors {
784 return [[self userDefaults] arrayForKey:@"WOAdditionalAdaptors"];
789 + (void)setPort:(NSNumber *)_port {
790 [[self userDefaults] setObject:_port forKey:@"WOPort"];
796 woport = [[self userDefaults] objectForKey:@"WOPort"];
797 if ([woport isKindOfClass:[NSNumber class]])
799 woport = [woport stringValue];
800 if ([woport length] > 0 && isdigit([woport characterAtIndex:0]))
801 return [NSNumber numberWithInt:[woport intValue]];
803 addr = NGSocketAddressFromString(woport);
805 if ([addr isKindOfClass:[NGInternetSocketAddress class]])
806 return [NSNumber numberWithInt:[(NGInternetSocketAddress *)addr port]];
808 [self logFatalWithFormat:@"GOT NO PORT FOR ADDR: %@ (%@)", addr, woport];
812 /* WOWorkerThreadCount */
814 + (NSNumber *)workerThreadCount {
815 static NSNumber *s = nil;
818 i = [[self userDefaults] integerForKey:@"WOWorkerThreadCount"];
819 s = [[NSNumber numberWithInt:i] retain];
824 /* WOListenQueueSize */
826 + (NSNumber *)listenQueueSize {
827 static NSNumber *s = nil;
830 i = [[self userDefaults] integerForKey:@"WOListenQueueSize"];
831 s = [[NSNumber numberWithInt:i] retain];
836 @end /* WOCoreApplication */