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 perflog = NO;
66 static BOOL outputValidateOn = NO;
67 static Class NSDateClass = Nil;
73 NGObjWeb_DECLARE id WOApp = nil;
74 static NSMutableArray *activeApps = nil; // THREAD
78 NSLog(@"WARNING(%s): some code called +application without "
79 @"an 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;
125 [self _initDefaults];
127 ud = [NSUserDefaults standardUserDefaults];
128 perflog = [ud boolForKey:@"WOProfileApplication"];
129 outputValidateOn = [ud boolForKey:@"WOOutputValidationEnabled"];
130 NSDateClass = [NSDate class];
134 [[self class] _initializeClass];
135 #if COCOA_Foundation_LIBRARY
137 NSKeyBinding Warning: <Application 0xc1f70> was accessed using a capitalized key
138 'NSFileSubject'. Keys should normally start with a lowercase character. A
139 typographical error like this could cause a crash or an infinite loop. Use
140 +[NSKeyBinding suppressCapitalizedKeyWarning] to suppress this warning.
142 [NSClassFromString(@"NSKeyBinding") suppressCapitalizedKeyWarning];
145 if ((self = [super init])) {
146 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
148 [self activateApplication];
150 if ([[ud objectForKey:@"WORunMultithreaded"] boolValue]) {
151 self->lock = [[NSRecursiveLock alloc] init];
152 self->requestLock = [[NSLock alloc] init];
156 #if !defined(__MINGW32__) && !defined(NeXT_Foundation_LIBRARY)
158 UnixSignalHandler *us = [UnixSignalHandler sharedHandler];
160 [us addObserver:self selector:@selector(terminateOnSignal:)
161 forSignal:SIGTERM immediatelyNotifyOnSignal:YES];
162 [us addObserver:self selector:@selector(terminateOnSignal:)
163 forSignal:SIGINT immediatelyNotifyOnSignal:YES];
164 [us addObserver:self selector:@selector(terminateOnSignal:)
165 forSignal:SIGQUIT immediatelyNotifyOnSignal:YES];
166 [us addObserver:self selector:@selector(terminateOnSignal:)
167 forSignal:SIGILL immediatelyNotifyOnSignal:YES];
169 [us addObserver:self selector:@selector(processHupSignal:)
170 forSignal:SIGHUP immediatelyNotifyOnSignal:NO];
178 #if !defined(__MINGW32__) && !defined(NeXT_Foundation_LIBRARY)
179 [[UnixSignalHandler sharedHandler] removeObserver:self];
181 [[NSNotificationCenter defaultCenter] removeObserver:self];
182 [self->adaptors release];
183 [self->requestLock release];
184 [self->lock release];
190 - (void)processHupSignal:(int)_signal {
191 /* this isn't called immediatly */
194 - (void)terminateOnSignal:(int)_signal {
195 /* STDIO is forbidden in signal handlers !!! no malloc !!! */
197 self->cappFlags.isTerminating = 1;
199 static int termCount = 0;
203 pid = (unsigned)GetCurrentProcessId();
205 pid = (unsigned)getpid();
208 if ([self isTerminating]) {
214 fprintf(stderr, "%d: forcing termination because of signal %i\n",
222 fprintf(stderr, "%i: terminating because of signal %i\n", pid, _signal);
231 - (NSArray *)adaptors {
232 return self->adaptors;
234 - (WOAdaptor *)adaptorWithName:(NSString *)_name
235 arguments:(NSDictionary *)_args
237 Class adaptorClass = Nil;
238 WOAdaptor *adaptor = nil;
240 adaptorClass = NSClassFromString(_name);
241 if (adaptorClass == Nil) {
242 [self logWithFormat:@"ERROR: did not find adaptor class %@", _name];
246 adaptor = [[adaptorClass allocWithZone:[self zone]]
251 return [adaptor autorelease];
254 - (BOOL)allowsConcurrentRequestHandling {
257 - (BOOL)adaptorsDispatchRequestsConcurrently {
261 /* request recording */
263 - (void)setRecordingPath:(NSString *)_path {
264 [self notImplemented:_cmd];
266 - (NSString *)recordingPath {
267 static NSString *rp = nil;
269 rp = [[[NSUserDefaults standardUserDefaults]
270 stringForKey:@"WORecordingPath"]
279 return NSStringFromClass([self class]);
282 - (WOResponse *)handleException:(NSException *)_exc
283 inContext:(WOContext *)_ctx
285 WORequest *rq = [_ctx request];
288 if ([self respondsToSelector:@selector(handleException:)]) {
289 NSLog(@"WARNING: calling deprecated -handleException method !");
290 return [self handleException:_exc];
294 [self logWithFormat:@"%@: caught (without context):\n %@.", self, _exc];
299 [self logWithFormat:@"%@: caught (without context):\n %@.", self, _exc];
302 else if (rq == nil) {
303 [self logWithFormat:@"%@: caught (without request):\n %@.", self, _exc];
307 [self logWithFormat:@"%@: caught:\n %@\nin context:\n %@.",
312 if ((r = [WOResponse responseWithRequest:rq]) == nil)
313 [self logWithFormat:@"could not create response !"];
328 return [self->lock tryLock];
331 - (void)lockRequestHandling {
332 [self->requestLock lock];
334 - (void)unlockRequestHandling {
335 [self->requestLock unlock];
347 - (void)_loadAdaptors {
348 NSMutableArray *ads = nil;
352 args = [[NSProcessInfo processInfo] arguments];
354 for (i = 0, count = [args count]; i < count; i++) {
357 arg = [args objectAtIndex:i];
359 if ([arg isEqualToString:@"-a"] && ((i + 1) < count)) {
361 NSString *adaptorName = nil;
362 NSMutableDictionary *arguments = nil;
363 WOAdaptor *adaptor = nil;
365 i++; // skip '-a' option
366 adaptorName = [args objectAtIndex:i];
367 i++; // skip adaptor name
369 if (i < count) { // search for arguments
372 arguments = [NSMutableDictionary dictionaryWithCapacity:10];
373 for (; i < count; i++) {
374 arg = [args objectAtIndex:i];
375 if ([arg isEqualToString:@"-a"] ||
376 [arg isEqualToString:@"-c"] ||
377 [arg isEqualToString:@"-d"]) {
384 [arguments setObject:arg forKey:key];
390 adaptor = [self adaptorWithName:adaptorName
391 arguments:[[arguments copy] autorelease]];
393 if (ads == nil) ads = [[NSMutableArray alloc] initWithCapacity:8];
394 [ads addObject:adaptor];
399 self->adaptors = [ads copy];
400 [ads release]; ads = nil;
402 if ([self->adaptors count] == 0) {
404 NSArray *moreAdaptors;
406 defaultAdaptor = [[self class] adaptor];
407 defaultAdaptor = [self adaptorWithName:defaultAdaptor arguments:nil];
408 if (defaultAdaptor) {
409 self->adaptors = [[NSArray alloc] initWithObjects:defaultAdaptor, nil];
412 moreAdaptors = [[self class] additionalAdaptors];
413 if ([moreAdaptors count] > 0) {
415 NSMutableArray *newArray;
419 for (i = 0, count = [moreAdaptors count]; i < count; i++) {
422 adaptor = [self adaptorWithName:[moreAdaptors objectAtIndex:i]
424 if (adaptor == nil) {
425 [self logWithFormat:@"could not find WOAdaptor '%@' !",
426 [moreAdaptors objectAtIndex:i]];
430 if (newArray == nil) {
431 newArray = [self->adaptors mutableCopy];
432 [newArray addObject:adaptor];
435 [self->adaptors release];
436 self->adaptors = [newArray shallowCopy];
437 [newArray release]; newArray = nil;
442 - (void)_setupAdaptors {
447 if ([self->adaptors count] == 0)
448 [self _loadAdaptors];
450 ads = [self->adaptors objectEnumerator];
451 while ((adaptor = [ads nextObject]))
452 [adaptor registerForEvents];
454 - (void)_tearDownAdaptors {
455 // unregister adaptors
459 ads = [self->adaptors objectEnumerator];
460 while ((adaptor = [ads nextObject]))
461 [adaptor unregisterForEvents];
463 [self->adaptors release]; self->adaptors = nil;
466 - (NSRunLoop *)runLoop { // deprecated in WO4
468 return [self mainThreadRunLoop];
470 - (NSRunLoop *)mainThreadRunLoop {
471 // wrong, should remove main thread runloop
472 return [WORunLoop currentRunLoop];
475 - (BOOL)shouldTerminate {
479 [self activateApplication];
481 [self _setupAdaptors];
483 [[NSNotificationCenter defaultCenter]
484 postNotificationName:
485 WOApplicationDidFinishLaunchingNotification
488 [self deactivateApplication];
490 while (![self isTerminating]) {
491 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
493 if ([self shouldTerminate]) {
494 /* check whether we should still process requests */
499 NSDate *limitDate = nil;
501 loop = [self mainThreadRunLoop];
503 limitDate = [loop limitDateForMode:NSDefaultRunLoopMode];
505 if ([self isTerminating])
508 [self activateApplication];
509 [loop runMode:NSDefaultRunLoopMode beforeDate:limitDate];
510 [self deactivateApplication];
516 [self debugWithFormat:@"application finished runloop."];
518 [self activateApplication];
520 [[NSNotificationCenter defaultCenter]
521 postNotificationName:
522 WOApplicationWillTerminateNotification
525 [self _tearDownAdaptors];
527 self->cappFlags.isTerminating = 1;
529 [[NSNotificationCenter defaultCenter]
530 postNotificationName:
531 WOApplicationDidTerminateNotification
534 [self deactivateApplication];
538 self->cappFlags.isTerminating = 1;
540 - (BOOL)isTerminating {
541 return self->cappFlags.isTerminating ? YES : NO;
544 - (void)_terminateNow:(id)_dummy {
547 - (void)terminateAfterTimeInterval:(NSTimeInterval)_interval {
548 [[NSRunLoop currentRunLoop] cancelPerformSelector:@selector(_terminateNow:)
549 target:self argument:nil];
550 [self performSelector:@selector(_terminateNow:) withObject:nil
551 afterDelay:_interval];
554 /* output validation */
556 - (void)setPrintsHTMLParserDiagnostics:(BOOL)_flag {
557 [[NSUserDefaults standardUserDefaults] setBool:_flag
558 forKey:@"WOOutputValidationEnabled"];
559 outputValidateOn = _flag;
561 - (BOOL)printsHTMLParserDiagnostics {
562 return outputValidateOn;
565 - (void)_logWarningOnOutputValidation {
566 static BOOL didWarn = NO;
570 @"WARNING: output validation is enabled, this will "
571 @"slow down request processing!"];
576 - (BOOL)hideValidationIssue:(NSException *)_issue {
577 /* to deal with some non-standard HTML ... */
581 - (void)validateOutputOfResponse:(WOResponse *)_response {
586 [self _logWarningOnOutputValidation];
588 if (_response == nil) {
589 [self logWithFormat:@"validate-output: no response returned by handler."];
593 if (![_response respondsToSelector:@selector(validateContent)]) {
594 [self logWithFormat:@"response does not support content validation!"];
597 if ((issues = [_response validateContent]) == nil)
600 e = [issues objectEnumerator];
601 while ((issue = [e nextObject])) {
602 if ([issue isKindOfClass:[NSException class]]) {
603 if ([self hideValidationIssue:issue])
605 [self logWithFormat:@"validate-output[%@]: %@",
606 [(NSException *)issue name], [issue reason]];
610 [self logWithFormat:@"validate-output: %@", [issue stringValue]];
614 /* request handling */
616 - (WORequestHandler *)handlerForRequest:(WORequest *)_request {
620 - (WOResponse *)dispatchRequest:(WORequest *)_request
621 usingHandler:(WORequestHandler *)handler
623 WOResponse *response = nil;
624 NSTimeInterval startDispatch = 0.0;
627 startDispatch = [[NSDateClass date] timeIntervalSince1970];
629 /* let request handler process the request */
631 NSTimeInterval startDispatch = 0.0;
634 startDispatch = [[NSDateClass date] timeIntervalSince1970];
637 response = [handler handleRequest:_request];
641 rt = [[NSDateClass date] timeIntervalSince1970] - startDispatch;
642 NSLog(@" [woapp-rq]: request handler took %4.3fs.",
643 rt < 0.0 ? -1.0 : rt);
647 response = [[response retain] autorelease];
649 if (outputValidateOn)
650 [self validateOutputOfResponse:response];
654 rt = [[NSDateClass date] timeIntervalSince1970] - startDispatch;
655 NSLog(@"[woapp]: dispatchRequest took %4.3fs.",
656 rt < 0.0 ? -1.0 : rt);
661 - (WOResponse *)dispatchRequest:(WORequest *)_request {
662 WORequestHandler *handler;
664 if ([self respondsToSelector:@selector(handleRequest:)]) {
666 @"WARNING: calling deprecated -handleRequest: method .."];
667 return [self handleRequest:_request];
670 /* find request handler for request */
671 if ((handler = [self handlerForRequest:_request]) == nil) {
672 [self logWithFormat:@"ERROR: got no request handler for request: %@ !",
677 return [self dispatchRequest:_request usingHandler:handler];
682 - (NSString *)description {
683 return [NSString stringWithFormat:@"<%@[0x%08X]: %@>",
684 NSStringFromClass([self class]), self,
685 [self isTerminating] ? @" terminating" : @""
691 + (int)sopeMajorVersion {
692 return SOPE_MAJOR_VERSION;
694 + (int)sopeMinorVersion {
695 return SOPE_MINOR_VERSION;
697 + (NSString *)ngobjwebShareDirectorySubPath {
698 return [NSString stringWithFormat:@"share/sope-%i.%i/ngobjweb/",
699 [self sopeMajorVersion], [self sopeMinorVersion]];
701 + (NGResourceLocator *)ngobjwebResourceLocator {
702 return [NGResourceLocator resourceLocatorForGNUstepPath:
703 @"Library/Libraries/Resources/NGObjWeb"
704 fhsPath:[self ngobjwebShareDirectorySubPath]];
707 + (NSArray *)resourcesSearchPathes {
708 // TODO: is this actually used somewhere?
709 return [[self ngobjwebResourceLocator] searchPathes];
712 + (NSString *)findNGObjWebResource:(NSString *)_name ofType:(NSString *)_ext {
713 #if COMPILE_AS_FRAMEWORK
716 bundle = [NSBundle bundleForClass:[WOCoreApplication class]];
717 return [bundle pathForResource:_name ofType:_ext];
719 return [[self ngobjwebResourceLocator] lookupFileWithName:_name
724 + (void)_initDefaults {
725 static BOOL didInit = NO;
726 NSDictionary *owDefaults = nil;
732 apath = [self findNGObjWebResource:@"Defaults" ofType:@"plist"];
734 NSLog(@"ERROR: cannot find Defaults.plist resource of NGObjWeb library!");
737 NSLog(@"Note: loading default defaults: %@", apath);
740 owDefaults = [NSDictionary dictionaryWithContentsOfFile:apath];
742 [[NSUserDefaults standardUserDefaults] registerDefaults:owDefaults];
744 [self logWithFormat:@"did register NGObjWeb defaults: %@\n%@",
749 [self logWithFormat:@"ERROR: could not load NGObjWeb defaults: '%@'",
754 + (NSUserDefaults *)userDefaults {
755 return [NSUserDefaults standardUserDefaults];
760 + (void)setAdaptor:(NSString *)_key {
761 [[self userDefaults] setObject:_key forKey:@"WOAdaptor"];
763 + (NSString *)adaptor {
764 return [[self userDefaults] stringForKey:@"WOAdaptor"];
767 + (void)setAdditionalAdaptors:(NSArray *)_names {
768 [[self userDefaults] setObject:_names forKey:@"WOAdditionalAdaptors"];
770 + (NSArray *)additionalAdaptors {
771 return [[self userDefaults] arrayForKey:@"WOAdditionalAdaptors"];
776 + (void)setPort:(NSNumber *)_port {
777 [[self userDefaults] setObject:_port forKey:@"WOPort"];
783 woport = [[self userDefaults] objectForKey:@"WOPort"];
784 if ([woport isKindOfClass:[NSNumber class]])
786 woport = [woport stringValue];
787 if ([woport length] > 0 && isdigit([woport characterAtIndex:0]))
788 return [NSNumber numberWithInt:[woport intValue]];
790 addr = NGSocketAddressFromString(woport);
792 if ([addr isKindOfClass:[NGInternetSocketAddress class]])
793 return [NSNumber numberWithInt:[(NGInternetSocketAddress *)addr port]];
795 NSLog(@"GOT NO PORT FOR ADDR: %@ (%@)", addr, woport);
799 /* WOWorkerThreadCount */
801 + (NSNumber *)workerThreadCount {
802 static NSNumber *s = nil;
805 i = [[self userDefaults] integerForKey:@"WOWorkerThreadCount"];
806 s = [[NSNumber numberWithInt:i] retain];
811 /* WOListenQueueSize */
813 + (NSNumber *)listenQueueSize {
814 static NSNumber *s = nil;
817 i = [[self userDefaults] integerForKey:@"WOListenQueueSize"];
818 s = [[NSNumber numberWithInt:i] retain];
823 @end /* WOCoreApplication */