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;
53 - (NSDictionary *)memoryStatistics;
54 - (WOResponse *)handleException:(NSException *)_exc;
57 #if COCOA_Foundation_LIBRARY || NeXT_Foundation_LIBRARY
58 @interface NSObject(KVCWarn)
59 + (void)suppressCapitalizedKeyWarning;
60 - (void)notImplemented:(SEL)cmd;
64 @implementation WOCoreApplication
66 static BOOL outputValidateOn = NO;
67 static Class NSDateClass = Nil;
68 static NGLogger *logger = nil;
69 static NGLogger *perfLogger = nil;
75 NGObjWeb_DECLARE id WOApp = nil;
76 static NSMutableArray *activeApps = nil; // THREAD
80 [self warnWithFormat:@"%s: some code called +application without an "
81 @"active app !", __PRETTY_FUNCTION__];
83 # warning REMOVE THAT ABORT IN PRODUCTION CODE !!!
89 - (void)activateApplication {
91 if (activeApps == nil)
92 activeApps = [[NSMutableArray alloc] init];
93 [activeApps addObject:WOApp];
97 - (void)deactivateApplication {
101 [self warnWithFormat:
102 @"tried to deactivate inactive application !\n"
111 if ((idx = [activeApps count]) > 0) {
113 WOApp = [[activeApps objectAtIndex:idx] retain];
114 [activeApps removeObjectAtIndex:idx];
118 + (void)_initializeClass {
120 this must be called in -init, since the environment is not setup
121 properly if this is called first.
123 static BOOL didInit = NO;
128 [self _initDefaults];
130 lm = [NGLoggerManager defaultLoggerManager];
131 logger = [lm loggerForClass:self];
132 perfLogger = [lm loggerForDefaultKey:@"WOProfileApplication"];
134 ud = [NSUserDefaults standardUserDefaults];
135 outputValidateOn = [ud boolForKey:@"WOOutputValidationEnabled"];
136 NSDateClass = [NSDate class];
140 [[self class] _initializeClass];
141 #if COCOA_Foundation_LIBRARY
143 NSKeyBinding Warning: <Application 0xc1f70> was accessed using a capitalized key
144 'NSFileSubject'. Keys should normally start with a lowercase character. A
145 typographical error like this could cause a crash or an infinite loop. Use
146 +[NSKeyBinding suppressCapitalizedKeyWarning] to suppress this warning.
148 [NSClassFromString(@"NSKeyBinding") suppressCapitalizedKeyWarning];
151 if ((self = [super init])) {
152 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
154 [self activateApplication];
156 if ([[ud objectForKey:@"WORunMultithreaded"] boolValue]) {
157 self->lock = [[NSRecursiveLock alloc] init];
158 self->requestLock = [[NSLock alloc] init];
162 #if !defined(__MINGW32__) && !defined(NeXT_Foundation_LIBRARY)
164 UnixSignalHandler *us = [UnixSignalHandler sharedHandler];
166 [us addObserver:self selector:@selector(terminateOnSignal:)
167 forSignal:SIGTERM immediatelyNotifyOnSignal:YES];
168 [us addObserver:self selector:@selector(terminateOnSignal:)
169 forSignal:SIGINT immediatelyNotifyOnSignal:YES];
170 [us addObserver:self selector:@selector(terminateOnSignal:)
171 forSignal:SIGQUIT immediatelyNotifyOnSignal:YES];
172 [us addObserver:self selector:@selector(terminateOnSignal:)
173 forSignal:SIGILL immediatelyNotifyOnSignal:YES];
175 [us addObserver:self selector:@selector(processHupSignal:)
176 forSignal:SIGHUP immediatelyNotifyOnSignal:NO];
184 #if !defined(__MINGW32__) && !defined(NeXT_Foundation_LIBRARY)
185 [[UnixSignalHandler sharedHandler] removeObserver:self];
187 [[NSNotificationCenter defaultCenter] removeObserver:self];
188 [self->adaptors release];
189 [self->requestLock release];
190 [self->lock release];
206 - (void)processHupSignal:(int)_signal {
207 /* this isn't called immediatly */
210 - (void)terminateOnSignal:(int)_signal {
211 /* STDIO is forbidden in signal handlers !!! no malloc !!! */
213 self->cappFlags.isTerminating = 1;
215 static int termCount = 0;
219 pid = (unsigned)GetCurrentProcessId();
221 pid = (unsigned)getpid();
224 if ([self isTerminating]) {
230 fprintf(stderr, "%d: forcing termination because of signal %i\n",
238 fprintf(stderr, "%i: terminating because of signal %i\n", pid, _signal);
247 - (NSArray *)adaptors {
248 return self->adaptors;
250 - (WOAdaptor *)adaptorWithName:(NSString *)_name
251 arguments:(NSDictionary *)_args
253 Class adaptorClass = Nil;
254 WOAdaptor *adaptor = nil;
256 adaptorClass = NSClassFromString(_name);
257 if (adaptorClass == Nil) {
258 [self errorWithFormat:@"did not find adaptor class %@", _name];
262 adaptor = [[adaptorClass allocWithZone:[self zone]]
267 return [adaptor autorelease];
270 - (BOOL)allowsConcurrentRequestHandling {
273 - (BOOL)adaptorsDispatchRequestsConcurrently {
277 /* request recording */
279 - (void)setRecordingPath:(NSString *)_path {
280 [self notImplemented:_cmd];
282 - (NSString *)recordingPath {
283 static NSString *rp = nil;
285 rp = [[[NSUserDefaults standardUserDefaults]
286 stringForKey:@"WORecordingPath"]
295 return NSStringFromClass([self class]);
298 - (WOResponse *)handleException:(NSException *)_exc
299 inContext:(WOContext *)_ctx
301 WORequest *rq = [_ctx request];
304 if ([self respondsToSelector:@selector(handleException:)]) {
305 [self warnWithFormat:@"calling deprecated -handleException method !"];
306 return [self handleException:_exc];
310 [self errorWithFormat:@"%@: caught (without context):\n %@.", self, _exc];
315 [self errorWithFormat:@"%@: caught (without context):\n %@.",
319 else if (rq == nil) {
320 [self errorWithFormat:@"%@: caught (without request):\n %@.",
325 [self logWithFormat:@"%@: caught:\n %@\nin context:\n %@.",
330 if ((r = [WOResponse responseWithRequest:rq]) == nil)
331 [self warnWithFormat:@"could not create response !"];
346 return [self->lock tryLock];
349 - (void)lockRequestHandling {
350 [self->requestLock lock];
352 - (void)unlockRequestHandling {
353 [self->requestLock unlock];
365 - (void)_loadAdaptors {
366 NSMutableArray *ads = nil;
370 args = [[NSProcessInfo processInfo] arguments];
372 for (i = 0, count = [args count]; i < count; i++) {
375 arg = [args objectAtIndex:i];
377 if ([arg isEqualToString:@"-a"] && ((i + 1) < count)) {
379 NSString *adaptorName = nil;
380 NSMutableDictionary *arguments = nil;
381 WOAdaptor *adaptor = nil;
383 i++; // skip '-a' option
384 adaptorName = [args objectAtIndex:i];
385 i++; // skip adaptor name
387 if (i < count) { // search for arguments
390 arguments = [NSMutableDictionary dictionaryWithCapacity:10];
391 for (; i < count; i++) {
392 arg = [args objectAtIndex:i];
393 if ([arg isEqualToString:@"-a"] ||
394 [arg isEqualToString:@"-c"] ||
395 [arg isEqualToString:@"-d"]) {
402 [arguments setObject:arg forKey:key];
408 adaptor = [self adaptorWithName:adaptorName
409 arguments:[[arguments copy] autorelease]];
411 if (ads == nil) ads = [[NSMutableArray alloc] initWithCapacity:8];
412 [ads addObject:adaptor];
417 self->adaptors = [ads copy];
418 [ads release]; ads = nil;
420 if ([self->adaptors count] == 0) {
422 NSArray *moreAdaptors;
424 defaultAdaptor = [[self class] adaptor];
425 defaultAdaptor = [self adaptorWithName:defaultAdaptor arguments:nil];
426 if (defaultAdaptor) {
427 self->adaptors = [[NSArray alloc] initWithObjects:defaultAdaptor, nil];
430 moreAdaptors = [[self class] additionalAdaptors];
431 if ([moreAdaptors count] > 0) {
433 NSMutableArray *newArray;
437 for (i = 0, count = [moreAdaptors count]; i < count; i++) {
440 adaptor = [self adaptorWithName:[moreAdaptors objectAtIndex:i]
442 if (adaptor == nil) {
443 [self warnWithFormat:@"could not find WOAdaptor '%@' !",
444 [moreAdaptors objectAtIndex:i]];
448 if (newArray == nil) {
449 newArray = [self->adaptors mutableCopy];
450 [newArray addObject:adaptor];
453 [self->adaptors release];
454 self->adaptors = [newArray shallowCopy];
455 [newArray release]; newArray = nil;
460 - (void)_setupAdaptors {
465 if ([self->adaptors count] == 0)
466 [self _loadAdaptors];
468 ads = [self->adaptors objectEnumerator];
469 while ((adaptor = [ads nextObject]))
470 [adaptor registerForEvents];
472 - (void)_tearDownAdaptors {
473 // unregister adaptors
477 ads = [self->adaptors objectEnumerator];
478 while ((adaptor = [ads nextObject]))
479 [adaptor unregisterForEvents];
481 [self->adaptors release]; self->adaptors = nil;
484 - (NSRunLoop *)runLoop { // deprecated in WO4
486 return [self mainThreadRunLoop];
488 - (NSRunLoop *)mainThreadRunLoop {
489 // wrong, should remove main thread runloop
490 return [WORunLoop currentRunLoop];
493 - (BOOL)shouldTerminate {
497 [self activateApplication];
499 [self _setupAdaptors];
501 [[NSNotificationCenter defaultCenter]
502 postNotificationName:
503 WOApplicationDidFinishLaunchingNotification
506 [self deactivateApplication];
508 while (![self isTerminating]) {
509 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
511 if ([self shouldTerminate]) {
512 /* check whether we should still process requests */
517 NSDate *limitDate = nil;
519 loop = [self mainThreadRunLoop];
521 limitDate = [loop limitDateForMode:NSDefaultRunLoopMode];
523 if ([self isTerminating])
526 [self activateApplication];
527 [loop runMode:NSDefaultRunLoopMode beforeDate:limitDate];
528 [self deactivateApplication];
534 [self debugWithFormat:@"application finished runloop."];
536 [self activateApplication];
538 [[NSNotificationCenter defaultCenter]
539 postNotificationName:
540 WOApplicationWillTerminateNotification
543 [self _tearDownAdaptors];
545 self->cappFlags.isTerminating = 1;
547 [[NSNotificationCenter defaultCenter]
548 postNotificationName:
549 WOApplicationDidTerminateNotification
552 [self deactivateApplication];
556 self->cappFlags.isTerminating = 1;
558 - (BOOL)isTerminating {
559 return self->cappFlags.isTerminating ? YES : NO;
562 - (void)_terminateNow:(id)_dummy {
565 - (void)terminateAfterTimeInterval:(NSTimeInterval)_interval {
566 [[NSRunLoop currentRunLoop] cancelPerformSelector:@selector(_terminateNow:)
567 target:self argument:nil];
568 [self performSelector:@selector(_terminateNow:) withObject:nil
569 afterDelay:_interval];
572 /* output validation */
574 - (void)setPrintsHTMLParserDiagnostics:(BOOL)_flag {
575 [[NSUserDefaults standardUserDefaults] setBool:_flag
576 forKey:@"WOOutputValidationEnabled"];
577 outputValidateOn = _flag;
579 - (BOOL)printsHTMLParserDiagnostics {
580 return outputValidateOn;
583 - (void)_logWarningOnOutputValidation {
584 static BOOL didWarn = NO;
587 [self warnWithFormat:
588 @"output validation is enabled, this will "
589 @"slow down request processing!"];
594 - (BOOL)hideValidationIssue:(NSException *)_issue {
595 /* to deal with some non-standard HTML ... */
599 - (void)validateOutputOfResponse:(WOResponse *)_response {
604 [self _logWarningOnOutputValidation];
606 if (_response == nil) {
607 [self logWithFormat:@"validate-output: no response returned by handler."];
611 if (![_response respondsToSelector:@selector(validateContent)]) {
612 [self logWithFormat:@"response does not support content validation!"];
615 if ((issues = [_response validateContent]) == nil)
618 e = [issues objectEnumerator];
619 while ((issue = [e nextObject])) {
620 if ([issue isKindOfClass:[NSException class]]) {
621 if ([self hideValidationIssue:issue])
623 [self logWithFormat:@"validate-output[%@]: %@",
624 [(NSException *)issue name], [issue reason]];
628 [self logWithFormat:@"validate-output: %@", [issue stringValue]];
632 /* request handling */
634 - (WORequestHandler *)handlerForRequest:(WORequest *)_request {
638 - (WOResponse *)dispatchRequest:(WORequest *)_request
639 usingHandler:(WORequestHandler *)handler
641 WOResponse *response = nil;
642 NSTimeInterval startDispatch = 0.0;
645 startDispatch = [[NSDateClass date] timeIntervalSince1970];
647 /* let request handler process the request */
649 NSTimeInterval startDispatch = 0.0;
652 startDispatch = [[NSDateClass date] timeIntervalSince1970];
655 response = [handler handleRequest:_request];
659 rt = [[NSDateClass date] timeIntervalSince1970] - startDispatch;
660 [perfLogger logWithFormat:@"[woapp-rq]: request handler took %4.3fs.",
661 rt < 0.0 ? -1.0 : rt];
665 response = [[response retain] autorelease];
667 if (outputValidateOn)
668 [self validateOutputOfResponse:response];
672 rt = [[NSDateClass date] timeIntervalSince1970] - startDispatch;
673 [perfLogger logWithFormat:@"[woapp]: dispatchRequest took %4.3fs.",
674 rt < 0.0 ? -1.0 : rt];
679 - (WOResponse *)dispatchRequest:(WORequest *)_request {
680 WORequestHandler *handler;
682 if ([self respondsToSelector:@selector(handleRequest:)]) {
683 [self warnWithFormat:
684 @"calling deprecated -handleRequest: method .."];
685 return [self handleRequest:_request];
688 /* find request handler for request */
689 if ((handler = [self handlerForRequest:_request]) == nil) {
690 [self errorWithFormat:@"got no request handler for request: %@ !",
695 return [self dispatchRequest:_request usingHandler:handler];
700 - (NSString *)description {
701 return [NSString stringWithFormat:@"<%@[0x%08X]: %@>",
702 NSStringFromClass([self class]), self,
703 [self isTerminating] ? @" terminating" : @""
709 + (int)sopeMajorVersion {
710 return SOPE_MAJOR_VERSION;
712 + (int)sopeMinorVersion {
713 return SOPE_MINOR_VERSION;
715 + (NSString *)ngobjwebShareDirectorySubPath {
716 return [NSString stringWithFormat:@"share/sope-%i.%i/ngobjweb/",
717 [self sopeMajorVersion], [self sopeMinorVersion]];
719 + (NGResourceLocator *)ngobjwebResourceLocator {
720 return [NGResourceLocator resourceLocatorForGNUstepPath:
721 @"Library/Libraries/Resources/NGObjWeb"
722 fhsPath:[self ngobjwebShareDirectorySubPath]];
725 + (NSArray *)resourcesSearchPathes {
726 // TODO: is this actually used somewhere?
727 return [[self ngobjwebResourceLocator] searchPathes];
730 + (NSString *)findNGObjWebResource:(NSString *)_name ofType:(NSString *)_ext {
731 #if COMPILE_AS_FRAMEWORK
734 bundle = [NSBundle bundleForClass:[WOCoreApplication class]];
735 return [bundle pathForResource:_name ofType:_ext];
737 return [[self ngobjwebResourceLocator] lookupFileWithName:_name
742 + (void)_initDefaults {
743 static BOOL didInit = NO;
744 NSDictionary *owDefaults = nil;
750 apath = [self findNGObjWebResource:@"Defaults" ofType:@"plist"];
752 [self errorWithFormat:@"cannot find Defaults.plist resource of NGObjWeb library!"];
755 [self debugWithFormat:@"Note: loading default defaults: %@", apath];
758 owDefaults = [NSDictionary dictionaryWithContentsOfFile:apath];
760 [[NSUserDefaults standardUserDefaults] registerDefaults:owDefaults];
762 [self debugWithFormat:@"did register NGObjWeb defaults: %@\n%@",
767 [self errorWithFormat:@"could not load NGObjWeb defaults: '%@'",
772 + (NSUserDefaults *)userDefaults {
773 return [NSUserDefaults standardUserDefaults];
778 + (void)setAdaptor:(NSString *)_key {
779 [[self userDefaults] setObject:_key forKey:@"WOAdaptor"];
781 + (NSString *)adaptor {
782 return [[self userDefaults] stringForKey:@"WOAdaptor"];
785 + (void)setAdditionalAdaptors:(NSArray *)_names {
786 [[self userDefaults] setObject:_names forKey:@"WOAdditionalAdaptors"];
788 + (NSArray *)additionalAdaptors {
789 return [[self userDefaults] arrayForKey:@"WOAdditionalAdaptors"];
794 + (void)setPort:(NSNumber *)_port {
795 [[self userDefaults] setObject:_port forKey:@"WOPort"];
801 woport = [[self userDefaults] objectForKey:@"WOPort"];
802 if ([woport isKindOfClass:[NSNumber class]])
804 woport = [woport stringValue];
805 if ([woport length] > 0 && isdigit([woport characterAtIndex:0]))
806 return [NSNumber numberWithInt:[woport intValue]];
808 addr = NGSocketAddressFromString(woport);
810 if ([addr isKindOfClass:[NGInternetSocketAddress class]])
811 return [NSNumber numberWithInt:[(NGInternetSocketAddress *)addr port]];
813 [self fatalWithFormat:@"GOT NO PORT FOR ADDR: %@ (%@)", addr, woport];
817 /* WOWorkerThreadCount */
819 + (NSNumber *)workerThreadCount {
820 static NSNumber *s = nil;
823 i = [[self userDefaults] integerForKey:@"WOWorkerThreadCount"];
824 s = [[NSNumber numberWithInt:i] retain];
829 /* WOListenQueueSize */
831 + (NSNumber *)listenQueueSize {
832 static NSNumber *s = nil;
835 i = [[self userDefaults] integerForKey:@"WOListenQueueSize"];
836 s = [[NSNumber numberWithInt:i] retain];
841 @end /* WOCoreApplication */