]> err.no Git - sope/blob - sope-appserver/NGObjWeb/WOCoreApplication.m
minor fixes
[sope] / sope-appserver / NGObjWeb / WOCoreApplication.m
1 /*
2   Copyright (C) 2000-2004 SKYRIX Software AG
3
4   This file is part of OpenGroupware.org.
5
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
9   later version.
10
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.
15
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
19   02111-1307, USA.
20 */
21
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"
33 #include "common.h"
34
35 #if LIB_FOUNDATION_LIBRARY
36 #  import <Foundation/UnixSignalHandler.h>
37 #else
38 #  include "UnixSignalHandler.h"
39 #endif
40
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";
49
50 @interface WOCoreApplication(PrivateMethods)
51 + (void)_initDefaults;
52 - (NSDictionary *)memoryStatistics;
53 - (WOResponse *)handleException:(NSException *)_exc;
54 @end
55
56 #if COCOA_Foundation_LIBRARY || NeXT_Foundation_LIBRARY
57 @interface NSObject(KVCWarn)
58 + (void)suppressCapitalizedKeyWarning;
59 - (void)notImplemented:(SEL)cmd;
60 @end
61 #endif
62
63 @implementation WOCoreApplication
64
65 static BOOL     outputValidateOn = NO;
66 static Class    NSDateClass      = Nil;
67 static NGLogger *perfLogger      = nil;
68
69 + (int)version {
70   return 1;
71 }
72
73 NGObjWeb_DECLARE id WOApp = nil;
74 static NSMutableArray *activeApps = nil; // THREAD
75
76 + (id)application {
77   if (WOApp == nil) {
78     [self logWarnWithFormat:@"(%s): some code called +application without an "
79                             @"active app !", __PRETTY_FUNCTION__];
80 #if DEBUG && 0
81 #  warning REMOVE THAT ABORT IN PRODUCTION CODE !!!
82     abort();
83 #endif
84   }
85   return WOApp;
86 }
87 - (void)activateApplication {
88   if (WOApp) {
89     if (activeApps == nil)
90       activeApps = [[NSMutableArray alloc] init];
91     [activeApps addObject:WOApp];
92   }
93   ASSIGN(WOApp, self);
94 }
95 - (void)deactivateApplication {
96   unsigned idx;
97   
98   if (WOApp != self) {
99     [self logWithFormat:
100             @"tried to deactivate inactive application !\n"
101             @"  self:   %@\n"
102             @"  active: %@",
103             self, WOApp];
104     return;
105   }
106   [self autorelease];
107   WOApp = nil;
108   
109   if ((idx = [activeApps count]) > 0) {
110     idx--;
111     WOApp = [[activeApps objectAtIndex:idx] retain];
112     [activeApps removeObjectAtIndex:idx];
113   }
114 }
115
116 + (void)_initializeClass {
117   /*
118     this must be called in -init, since the environment is not setup
119     properly if this is called first.
120   */
121   static BOOL didInit = NO;
122   NSUserDefaults  *ud;
123   NGLoggerManager *lm;
124   if (didInit) return;
125   didInit = YES;
126   [self _initDefaults];
127
128   lm         = [NGLoggerManager defaultLoggerManager];
129   perfLogger = [lm loggerForDefaultKey:@"WOProfileApplication"];
130
131   ud               = [NSUserDefaults standardUserDefaults];
132   outputValidateOn = [ud boolForKey:@"WOOutputValidationEnabled"];
133   NSDateClass      = [NSDate class];
134 }
135
136 - (id)init {
137   [[self class] _initializeClass];
138 #if COCOA_Foundation_LIBRARY
139   /*
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.
144   */
145   [NSClassFromString(@"NSKeyBinding") suppressCapitalizedKeyWarning];
146 #endif
147   
148   if ((self = [super init])) {
149     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
150
151     [self activateApplication];
152     
153     if ([[ud objectForKey:@"WORunMultithreaded"] boolValue]) {
154       self->lock        = [[NSRecursiveLock alloc] init];
155       self->requestLock = [[NSLock alloc] init];
156     }
157     
158     /* handle signals */
159 #if !defined(__MINGW32__) && !defined(NeXT_Foundation_LIBRARY)
160     {
161       UnixSignalHandler *us = [UnixSignalHandler sharedHandler];
162       
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];
171       
172       [us addObserver:self selector:@selector(processHupSignal:)
173           forSignal:SIGHUP immediatelyNotifyOnSignal:NO];
174     }
175 #endif
176   }
177   return self;
178 }
179
180 - (void)dealloc {
181 #if !defined(__MINGW32__) && !defined(NeXT_Foundation_LIBRARY)
182   [[UnixSignalHandler sharedHandler] removeObserver:self];
183 #endif
184   [[NSNotificationCenter defaultCenter] removeObserver:self];
185   [self->adaptors    release];
186   [self->requestLock release];
187   [self->lock        release];
188   [super dealloc];
189 }
190
191 /* NGLogging */
192
193 - (id)logger {
194   static id sharedLogger = nil;
195   if(sharedLogger == nil) {
196     sharedLogger = [[NGLogger alloc] initWithLogLevel:NGLogLevelDebug];
197   }
198   return sharedLogger;
199 }
200
201 /* signals */
202
203 - (void)processHupSignal:(int)_signal {
204   /* this isn't called immediatly */
205 }
206
207 - (void)terminateOnSignal:(int)_signal {
208   /* STDIO is forbidden in signal handlers !!! no malloc !!! */
209 #if 1
210   self->cappFlags.isTerminating = 1;
211 #else
212   static int termCount = 0;
213   unsigned pid;
214   
215 #ifdef __MINGW32__
216   pid = (unsigned)GetCurrentProcessId();
217 #else
218   pid = (unsigned)getpid();
219 #endif
220   
221   if ([self isTerminating]) {
222 #if 0
223     termCount++;
224     if (termCount > 2);
225 #endif
226     fflush(stderr);
227     fprintf(stderr, "%d: forcing termination because of signal %i\n",
228             pid, _signal);
229     fflush(stderr);
230     exit(20);
231   }
232   termCount = 0;
233   
234   fflush(stderr);
235   fprintf(stderr, "%i: terminating because of signal %i\n", pid, _signal);
236   fflush(stderr);
237   
238   [self terminate];
239 #endif
240 }
241
242 /* adaptors */
243
244 - (NSArray *)adaptors {
245   return self->adaptors;
246 }
247 - (WOAdaptor *)adaptorWithName:(NSString *)_name
248   arguments:(NSDictionary *)_args
249 {
250   Class     adaptorClass = Nil;
251   WOAdaptor *adaptor     = nil;
252
253   adaptorClass = NSClassFromString(_name);
254   if (adaptorClass == Nil) {
255     [self logWithFormat:@"ERROR: did not find adaptor class %@", _name];
256     return nil;
257   }
258
259   adaptor = [[adaptorClass allocWithZone:[self zone]]
260                            initWithName:_name
261                            arguments:_args
262                            application:self];
263   
264   return [adaptor autorelease];
265 }
266
267 - (BOOL)allowsConcurrentRequestHandling {
268   return NO;
269 }
270 - (BOOL)adaptorsDispatchRequestsConcurrently {
271   return NO;
272 }
273
274 /* request recording */
275
276 - (void)setRecordingPath:(NSString *)_path {
277   [self notImplemented:_cmd];
278 }
279 - (NSString *)recordingPath {
280   static NSString *rp = nil;
281   if (rp == nil) {
282     rp = [[[NSUserDefaults standardUserDefaults]
283                            stringForKey:@"WORecordingPath"]
284                            copy];
285   }
286   return rp;
287 }
288
289 /* exceptions */
290
291 - (NSString *)name {
292   return NSStringFromClass([self class]);
293 }
294
295 - (WOResponse *)handleException:(NSException *)_exc
296   inContext:(WOContext *)_ctx
297 {
298   WORequest  *rq = [_ctx request];
299   WOResponse *r  = nil;
300   
301   if ([self respondsToSelector:@selector(handleException:)]) {
302     [self logWarnWithFormat:@"calling deprecated -handleException method !"];
303     return [self handleException:_exc];
304   }
305   
306 #if 0 && DEBUG
307   [self logWithFormat:@"%@: caught (without context):\n  %@.", self, _exc];
308   abort();
309 #endif
310   
311   if (_ctx == nil) {
312     [self logWithFormat:@"%@: caught (without context):\n  %@.", self, _exc];
313     [self terminate];
314   }
315   else if (rq == nil) {
316     [self logWithFormat:@"%@: caught (without request):\n  %@.", self, _exc];
317     [self terminate];
318   }
319   else {
320     [self logWithFormat:@"%@: caught:\n  %@\nin context:\n  %@.",
321             self, _exc, _ctx];
322     
323   }
324   
325   if ((r = [WOResponse responseWithRequest:rq]) == nil)
326     [self logWithFormat:@"could not create response !"];
327     
328   [r setStatus:500];
329   return r;
330 }
331
332 /* multithreading */
333
334 - (void)lock {
335   [self->lock lock];
336 }
337 - (void)unlock {
338   [self->lock unlock];
339 }
340 - (BOOL)tryLock {
341   return [self->lock tryLock];
342 }
343
344 - (void)lockRequestHandling {
345   [self->requestLock lock];
346 }
347 - (void)unlockRequestHandling {
348   [self->requestLock unlock];
349 }
350
351 /* notifications */
352
353 - (void)awake {
354 }
355 - (void)sleep {
356 }
357
358 /* runloop */
359
360 - (void)_loadAdaptors {
361   NSMutableArray *ads = nil;
362   NSArray *args;
363   int     i, count;
364       
365   args = [[NSProcessInfo processInfo] arguments];
366       
367   for (i = 0, count = [args count]; i < count; i++) {
368     NSString *arg;
369         
370     arg = [args objectAtIndex:i];
371         
372     if ([arg isEqualToString:@"-a"] && ((i + 1) < count)) {
373       // found adaptor
374       NSString            *adaptorName = nil;
375       NSMutableDictionary *arguments   = nil;
376       WOAdaptor           *adaptor     = nil;
377
378       i++; // skip '-a' option
379       adaptorName = [args objectAtIndex:i];
380       i++; // skip adaptor name
381
382       if (i < count) { // search for arguments
383         NSString *key = nil;
384             
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"]) {
391             i--;
392             break;
393           }
394           if (key == nil)
395             key = arg;
396           else {
397             [arguments setObject:arg forKey:key];
398             key = nil;
399           }
400         }
401       }
402
403       adaptor = [self adaptorWithName:adaptorName
404                       arguments:[[arguments copy] autorelease]];
405       if (adaptor) {
406         if (ads == nil) ads = [[NSMutableArray alloc] initWithCapacity:8];
407         [ads addObject:adaptor];
408       }
409     }
410   }
411
412   self->adaptors = [ads copy];
413   [ads release]; ads = nil;
414       
415   if ([self->adaptors count] == 0) {
416     id      defaultAdaptor;
417     NSArray *moreAdaptors;
418         
419     defaultAdaptor = [[self class] adaptor];
420     defaultAdaptor = [self adaptorWithName:defaultAdaptor arguments:nil];
421     if (defaultAdaptor) {
422       self->adaptors = [[NSArray alloc] initWithObjects:defaultAdaptor, nil];
423     }
424
425     moreAdaptors = [[self class] additionalAdaptors];
426     if ([moreAdaptors count] > 0) {
427       unsigned i, count;
428       NSMutableArray *newArray;
429
430       newArray = nil;
431       
432       for (i = 0, count = [moreAdaptors count]; i < count; i++) {
433         WOAdaptor *adaptor;
434
435         adaptor = [self adaptorWithName:[moreAdaptors objectAtIndex:i]
436                         arguments:nil];
437         if (adaptor == nil) {
438           [self logWithFormat:@"could not find WOAdaptor '%@' !",
439                 [moreAdaptors objectAtIndex:i]];
440           continue;
441         }
442
443         if (newArray == nil) {
444           newArray = [self->adaptors mutableCopy];
445           [newArray addObject:adaptor];
446         }
447       }
448       [self->adaptors release];
449       self->adaptors = [newArray shallowCopy];
450       [newArray release]; newArray = nil;
451     }
452   }
453 }
454
455 - (void)_setupAdaptors {
456   // register adaptors
457   NSEnumerator *ads;
458   WOAdaptor    *adaptor;
459
460   if ([self->adaptors count] == 0)
461     [self _loadAdaptors];
462   
463   ads = [self->adaptors objectEnumerator];
464   while ((adaptor = [ads nextObject]))
465     [adaptor registerForEvents];
466 }
467 - (void)_tearDownAdaptors {
468   // unregister adaptors
469   NSEnumerator *ads;
470   WOAdaptor    *adaptor;
471   
472   ads = [self->adaptors objectEnumerator];
473   while ((adaptor = [ads nextObject]))
474     [adaptor unregisterForEvents];
475   
476   [self->adaptors release]; self->adaptors = nil;
477 }
478
479 - (NSRunLoop *)runLoop { // deprecated in WO4
480   IS_DEPRECATED;
481   return [self mainThreadRunLoop];
482 }
483 - (NSRunLoop *)mainThreadRunLoop {
484   // wrong, should remove main thread runloop
485   return [WORunLoop currentRunLoop];
486 }
487
488 - (BOOL)shouldTerminate {
489   return NO;
490 }
491 - (void)run {
492   [self activateApplication];
493   {
494     [self _setupAdaptors];
495     
496     [[NSNotificationCenter defaultCenter]
497                            postNotificationName:
498                              WOApplicationDidFinishLaunchingNotification
499                            object:self];
500   }
501   [self deactivateApplication];
502   
503   while (![self isTerminating]) {
504     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
505     
506     if ([self shouldTerminate]) {
507       /* check whether we should still process requests */
508       [self terminate];
509     }
510     else {
511       NSRunLoop *loop;
512       NSDate *limitDate = nil;
513       
514       loop = [self mainThreadRunLoop];
515       
516       limitDate = [loop limitDateForMode:NSDefaultRunLoopMode];
517       
518       if ([self isTerminating])
519         break;
520       
521       [self activateApplication];
522       [loop runMode:NSDefaultRunLoopMode beforeDate:limitDate];
523       [self deactivateApplication];
524     }
525     
526     [pool release];
527   }
528
529   [self debugWithFormat:@"application finished runloop."];
530
531   [self activateApplication];
532   {
533     [[NSNotificationCenter defaultCenter]
534                            postNotificationName:
535                              WOApplicationWillTerminateNotification
536                            object:self];
537   
538     [self _tearDownAdaptors];
539   
540     self->cappFlags.isTerminating = 1;
541
542     [[NSNotificationCenter defaultCenter]
543                            postNotificationName:
544                              WOApplicationDidTerminateNotification
545                            object:self];
546   }
547   [self deactivateApplication];
548 }
549
550 - (void)terminate {
551   self->cappFlags.isTerminating = 1;
552 }
553 - (BOOL)isTerminating {
554   return self->cappFlags.isTerminating ? YES : NO;
555 }
556
557 - (void)_terminateNow:(id)_dummy {
558   [self terminate];
559 }
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];
565 }
566
567 /* output validation */
568
569 - (void)setPrintsHTMLParserDiagnostics:(BOOL)_flag {
570   [[NSUserDefaults standardUserDefaults] setBool:_flag 
571                                          forKey:@"WOOutputValidationEnabled"];
572   outputValidateOn = _flag;
573 }
574 - (BOOL)printsHTMLParserDiagnostics {
575   return outputValidateOn;
576 }
577
578 - (void)_logWarningOnOutputValidation {
579   static BOOL didWarn = NO;
580
581   if (!didWarn) {
582     [self logWithFormat:
583             @"WARNING: output validation is enabled, this will "
584             @"slow down request processing!"];
585     didWarn = YES;
586   }
587 }
588
589 - (BOOL)hideValidationIssue:(NSException *)_issue {
590   /* to deal with some non-standard HTML ... */
591   return NO;
592 }
593
594 - (void)validateOutputOfResponse:(WOResponse *)_response {
595   NSArray      *issues;
596   NSEnumerator *e;
597   id           issue;
598   
599   [self _logWarningOnOutputValidation];
600   
601   if (_response == nil) {
602     [self logWithFormat:@"validate-output: no response returned by handler."];
603     return;
604   }
605   
606   if (![_response respondsToSelector:@selector(validateContent)]) {
607     [self logWithFormat:@"response does not support content validation!"];
608     return;
609   }
610   if ((issues = [_response validateContent]) == nil)
611     return;
612   
613   e = [issues objectEnumerator];
614   while ((issue = [e nextObject])) {
615     if ([issue isKindOfClass:[NSException class]]) {
616       if ([self hideValidationIssue:issue])
617         continue;
618       [self logWithFormat:@"validate-output[%@]: %@",
619               [(NSException *)issue name], [issue reason]];
620       continue;
621     }
622     
623     [self logWithFormat:@"validate-output: %@", [issue stringValue]];
624   }
625 }
626
627 /* request handling */
628
629 - (WORequestHandler *)handlerForRequest:(WORequest *)_request {
630   return nil;
631 }
632
633 - (WOResponse *)dispatchRequest:(WORequest *)_request
634   usingHandler:(WORequestHandler *)handler
635 {
636   WOResponse     *response = nil;
637   NSTimeInterval startDispatch = 0.0;
638   
639   if (perfLogger)
640     startDispatch = [[NSDateClass date] timeIntervalSince1970];
641   
642   /* let request handler process the request */
643   {
644     NSTimeInterval startDispatch = 0.0;
645     
646     if (perfLogger)
647       startDispatch = [[NSDateClass date] timeIntervalSince1970];
648     
649     /* the call ;-) */
650     response = [handler handleRequest:_request];
651     
652     if (perfLogger) {
653       NSTimeInterval rt;
654       rt = [[NSDateClass date] timeIntervalSince1970] - startDispatch;
655       [perfLogger logInfoWithFormat:@"[woapp-rq]: request handler took %4.3fs.",
656                                     rt < 0.0 ? -1.0 : rt];
657     }
658   }
659   
660   response = [[response retain] autorelease];
661
662   if (outputValidateOn)
663     [self validateOutputOfResponse:response];
664   
665   if (perfLogger) {
666     NSTimeInterval rt;
667     rt = [[NSDateClass date] timeIntervalSince1970] - startDispatch;
668     [perfLogger logInfoWithFormat:@"[woapp]: dispatchRequest took %4.3fs.",
669                                   rt < 0.0 ? -1.0 : rt];
670   }
671   
672   return response;
673 }
674 - (WOResponse *)dispatchRequest:(WORequest *)_request {
675   WORequestHandler *handler;
676   
677   if ([self respondsToSelector:@selector(handleRequest:)]) {
678     [self logWarnWithFormat:
679             @"calling deprecated -handleRequest: method .."];
680     return [self handleRequest:_request];
681   }
682   
683   /* find request handler for request */
684   if ((handler = [self handlerForRequest:_request]) == nil) {
685     [self logErrorWithFormat:@"got no request handler for request: %@ !",
686             _request];
687     return nil;
688   }
689   
690   return [self dispatchRequest:_request usingHandler:handler];
691 }
692
693 /* description */
694
695 - (NSString *)description {
696   return [NSString stringWithFormat:@"<%@[0x%08X]: %@>",
697                      NSStringFromClass([self class]), self,
698                      [self isTerminating] ? @" terminating" : @""
699                    ];
700 }
701
702 /* defaults */
703
704 + (int)sopeMajorVersion {
705   return SOPE_MAJOR_VERSION;
706 }
707 + (int)sopeMinorVersion {
708   return SOPE_MINOR_VERSION;
709 }
710 + (NSString *)ngobjwebShareDirectorySubPath {
711   return [NSString stringWithFormat:@"share/sope-%i.%i/ngobjweb/",
712                      [self sopeMajorVersion], [self sopeMinorVersion]];
713 }
714 + (NGResourceLocator *)ngobjwebResourceLocator {
715   return [NGResourceLocator resourceLocatorForGNUstepPath:
716                               @"Library/Libraries/Resources/NGObjWeb"
717                             fhsPath:[self ngobjwebShareDirectorySubPath]];
718 }
719
720 + (NSArray *)resourcesSearchPathes {
721   // TODO: is this actually used somewhere?
722   return [[self ngobjwebResourceLocator] searchPathes];
723 }
724
725 + (NSString *)findNGObjWebResource:(NSString *)_name ofType:(NSString *)_ext {
726 #if COMPILE_AS_FRAMEWORK
727   NSBundle *bundle;
728   
729   bundle = [NSBundle bundleForClass:[WOCoreApplication class]];
730   return [bundle pathForResource:_name ofType:_ext];
731 #else
732   return [[self ngobjwebResourceLocator] lookupFileWithName:_name 
733                                          extension:_ext];
734 #endif
735 }
736
737 + (void)_initDefaults {
738   static BOOL didInit = NO;
739   NSDictionary *owDefaults = nil;
740   NSString     *apath;
741   
742   if (didInit) return;
743   didInit = YES;
744   
745   apath = [self findNGObjWebResource:@"Defaults" ofType:@"plist"];
746   if (apath == nil)
747     [self logErrorWithFormat:@"cannot find Defaults.plist resource of NGObjWeb library!"];
748 #if HEAVY_DEBUG
749   else
750     [self logDebugWithFormat:@"Note: loading default defaults: %@", apath];
751 #endif
752   
753   owDefaults = [NSDictionary dictionaryWithContentsOfFile:apath];
754   if (owDefaults) {
755     [[NSUserDefaults standardUserDefaults] registerDefaults:owDefaults];
756 #if HEAVY_DEBUG
757     [self logDebugWithFormat:@"did register NGObjWeb defaults: %@\n%@", 
758             apath, owDefaults];
759 #endif
760   }
761   else {
762     [self logErrorWithFormat:@"could not load NGObjWeb defaults: '%@'",
763             apath];
764   }
765 }
766
767 + (NSUserDefaults *)userDefaults {
768   return [NSUserDefaults standardUserDefaults];
769 }
770
771 /* WOAdaptor */
772
773 + (void)setAdaptor:(NSString *)_key {
774   [[self userDefaults] setObject:_key forKey:@"WOAdaptor"];
775 }
776 + (NSString *)adaptor {
777   return [[self userDefaults] stringForKey:@"WOAdaptor"];
778 }
779
780 + (void)setAdditionalAdaptors:(NSArray *)_names {
781   [[self userDefaults] setObject:_names forKey:@"WOAdditionalAdaptors"];
782 }
783 + (NSArray *)additionalAdaptors {
784   return [[self userDefaults] arrayForKey:@"WOAdditionalAdaptors"];
785 }
786
787 /* WOPort */
788
789 + (void)setPort:(NSNumber *)_port {
790   [[self userDefaults] setObject:_port forKey:@"WOPort"];
791 }
792 + (NSNumber *)port {
793   id woport;
794   id addr;
795   
796   woport = [[self userDefaults] objectForKey:@"WOPort"];
797   if ([woport isKindOfClass:[NSNumber class]])
798     return woport;
799   woport = [woport stringValue];
800   if ([woport length] > 0 && isdigit([woport characterAtIndex:0]))
801     return [NSNumber numberWithInt:[woport intValue]];
802   
803   addr   = NGSocketAddressFromString(woport);
804   
805   if ([addr isKindOfClass:[NGInternetSocketAddress class]])
806     return [NSNumber numberWithInt:[(NGInternetSocketAddress *)addr port]];
807
808   [self logFatalWithFormat:@"GOT NO PORT FOR ADDR: %@ (%@)", addr, woport];
809   return nil;
810 }
811
812 /* WOWorkerThreadCount */
813
814 + (NSNumber *)workerThreadCount {
815   static NSNumber *s = nil;
816   if (s == nil) {
817     int i;
818     i = [[self userDefaults] integerForKey:@"WOWorkerThreadCount"];
819     s = [[NSNumber numberWithInt:i] retain];
820   }
821   return s;
822 }
823
824 /* WOListenQueueSize */
825
826 + (NSNumber *)listenQueueSize {
827   static NSNumber *s = nil;
828   if (s == nil) {
829     int i;
830     i = [[self userDefaults] integerForKey:@"WOListenQueueSize"];
831     s = [[NSNumber numberWithInt:i] retain];
832   }
833   return s;
834 }
835
836 @end /* WOCoreApplication */