]> err.no Git - sope/blob - sope-appserver/NGObjWeb/WOComponent.m
added new auth realm method
[sope] / sope-appserver / NGObjWeb / WOComponent.m
1 /*
2   Copyright (C) 2000-2005 SKYRIX Software AG
3
4   This file is part of SOPE.
5
6   SOPE 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   SOPE 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 SOPE; 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/WOComponent.h>
23 #include "WOComponent+private.h"
24 #include "NSObject+WO.h"
25 #include <NGObjWeb/WODynamicElement.h>
26 #include "WOContext+private.h"
27 #include "WOElement+private.h"
28 #include <NGObjWeb/WOComponentDefinition.h>
29 #include <NGObjWeb/WOResourceManager.h>
30 #include <NGObjWeb/WOApplication.h>
31 #include <NGObjWeb/WOResponse.h>
32 #include "WOComponentFault.h"
33 #include "common.h"
34 #include <NGExtensions/NGBundleManager.h>
35 #include <EOControl/EOControl.h>
36 #include <NGExtensions/NSString+Ext.h>
37
38 @interface WOContext(ComponentStackCount)
39 - (unsigned)componentStackCount;
40 @end
41
42 #if APPLE_FOUNDATION_LIBRARY || NeXT_Foundation_LIBRARY
43 @interface NSObject(Miss)
44 - (id)notImplemented:(SEL)cmd;
45 @end
46 #endif
47
48 #if !LIB_FOUNDATION_LIBRARY
49 #  define NG_USE_KVC_FALLBACK 1
50 #endif
51
52 @implementation WOComponent
53
54 static Class NSDateClass      = Nil;
55 static Class WOComponentClass = Nil;
56
57 static NGLogger *perfLogger                    = nil;
58
59 static BOOL  debugOn                           = NO;
60 static BOOL  debugComponentAwake               = NO;
61 static BOOL  debugTemplates                    = NO;
62 static BOOL  debugTakeValues                   = NO;
63 static BOOL  abortOnAwakeComponentInCtxDealloc = NO;
64 static BOOL  abortOnMissingCtx                 = NO;
65 static BOOL  wakeupPageOnCreation              = NO;
66
67 + (int)version {
68   // TODO: is really v4 for baseURL/cycleContext ivar changes
69   return [super version] + 0 /* v2 */;
70 }
71 + (void)initialize {
72   NSUserDefaults  *ud;
73   NGLoggerManager *lm;
74   static BOOL didInit = NO;
75
76   if (didInit) return;
77   didInit = YES;
78   
79   NSAssert2([super version] == 2,
80             @"invalid superclass (%@) version %i !",
81             NSStringFromClass([self superclass]), [super version]);
82
83   ud = [NSUserDefaults standardUserDefaults];
84   lm = [NGLoggerManager defaultLoggerManager];
85
86   WOComponentClass    = [WOComponent class];
87   NSDateClass         = [NSDate class];
88   perfLogger          = [lm loggerForDefaultKey:@"WOProfileElements"];
89   debugOn             = [WOApplication isDebuggingEnabled];
90   debugComponentAwake = [ud boolForKey:@"WODebugComponentAwake"];
91   
92   if ((debugTakeValues = [ud boolForKey:@"WODebugTakeValues"]))
93     NSLog(@"WOComponent: WODebugTakeValues on.");
94   
95   abortOnAwakeComponentInCtxDealloc = 
96     [ud boolForKey:@"WOCoreOnAwakeComponentInCtxDealloc"];
97 }
98
99 - (id)init {
100   if ((self = [super init])) {
101     NSNotificationCenter  *nc;
102     WOComponentDefinition *cdef;
103     
104     if ((cdef = (id)self->wocVariables)) { 
105       // HACK CD, see WOComponentDefinition
106       self->wocVariables = nil;
107     }
108     
109     if (self->wocName == nil)
110       self->wocName = [NSStringFromClass([self class]) copy];
111     
112     [self setCachingEnabled:[[self application] isCachingEnabled]];
113     
114     /* finish initialization */
115     
116     if (cdef) {
117       [cdef _finishInitializingComponent:self];
118       [cdef release]; cdef = nil;
119     }
120 #if !APPLE_FOUNDATION_LIBRARY && !NeXT_Foundation_LIBRARY
121     else {
122       /* this is triggered by Publisher on MacOSX */
123       [self debugWithFormat:
124               @"Note: got no component definition according to HACK CD"];
125     }
126 #endif
127     
128     /* add to notification center */
129     
130     nc = [NSNotificationCenter defaultCenter];
131     
132     [nc addObserver:self selector:@selector(_sessionWillDealloc:)
133         name:@"WOSessionWillDeallocate" object:nil];
134     
135     [nc addObserver:self selector:@selector(_contextWillDealloc:)
136         name:@"WOContextWillDeallocate" object:nil];
137   }
138   return self;
139 }
140 - (id)initWithContext:(WOContext *)_ctx {
141   [self _setContext:_ctx];
142   if ((self = [self init])) {
143     if (self->context != nil)
144       [self ensureAwakeInContext:self->context];
145     else {
146       [self warnWithFormat:
147               @"no context given to -initWithContext: ..."];
148     }
149   }
150   return self;
151 }
152
153 - (void)dealloc {
154   [[NSNotificationCenter defaultCenter] removeObserver:self];
155   
156   [[self->subcomponents allValues]
157                         makeObjectsPerformSelector:@selector(setParent:)
158                         withObject:nil];
159   [self->subcomponents release];
160   
161   [self->wocClientObject release];
162   [self->wocBindings   release];
163   [self->wocVariables  release];
164   [self->wocName       release];
165   [self->wocBaseURL    release];
166   [super dealloc];
167 }
168
169 static inline void _setExtraVar(WOComponent *self, NSString *_key, id _obj) {
170   if (_obj) {
171     if (self->wocVariables == nil)
172       self->wocVariables = [[NSMutableDictionary alloc] initWithCapacity:16];
173     
174     [self->wocVariables setObject:_obj forKey:_key];
175   }
176   else
177     [self->wocVariables removeObjectForKey:_key];
178 }
179 static inline id _getExtraVar(WOComponent *self, NSString *_key) {
180   return [self->wocVariables objectForKey:_key];
181 }
182
183 /* observers */
184
185 - (void)_sessionWillDealloc:(NSNotification *)_notification {
186 #if DEBUG
187   NSAssert(_notification, @"missing valid session arg ...");
188 #endif
189
190   if (self->session == nil) {
191     /* component isn't interested in session anymore anyway ... */
192     return;
193   }
194   if (self->session != [_notification object])
195     /* not the component's context ... */
196     return;
197   
198 #if DEBUG && 0
199   [self debugWithFormat:@"resetting sn/ctx because session will dealloc .."];
200 #endif
201   
202   if (self->componentFlags.isAwake) {
203     [self warnWithFormat:
204             @"session will dealloc, but component 0x%08X is awake (ctx=%@) !",
205             self, self->context];
206     [self _sleepWithContext:self->context];
207   }
208   
209   self->session = nil;
210   [self _setContext:nil];
211 }
212 - (void)_contextWillDealloc:(NSNotification *)_notification {
213 #if DEBUG
214   NSAssert(_notification, @"missing valid notification arg ...");
215 #endif
216   
217   if (self->context == nil)
218     /* component isn't interested in context anyway ... */
219     return;
220   if (![[self->context contextID] isEqualToString:[_notification object]])
221     /* not the component's context ... */
222     return;
223   
224 #if DEBUG && 0
225   [self debugWithFormat:@"resetting sn/ctx because context will dealloc .."];
226 #endif
227   
228   if (self->componentFlags.isAwake) {
229     /*
230       Note: this is not necessarily a problem, no specific reason to log
231             the event?!
232     */
233     [self debugWithFormat:
234             @"context %@ will dealloc, but component is awake in ctx %@!",
235             [_notification object], [self->context contextID]];
236     if (abortOnAwakeComponentInCtxDealloc)
237       abort();
238     
239     [self _sleepWithContext:nil];
240   }
241   
242   [self _setContext:nil];
243   self->session = nil;
244 }
245
246 /* awake & sleep */
247
248 - (void)awake {
249 }
250 - (void)sleep {
251   if (debugOn) {
252     if (self->componentFlags.isAwake) {
253       [self warnWithFormat:
254               @"component should not be awake if sleep is called !"];
255     }
256     if (self->context == nil) {
257       [self warnWithFormat:
258               @"context should not be nil if sleep is called !"];
259     }
260   }
261   
262   self->componentFlags.isAwake = 0;
263   [self _setContext:nil];
264   self->application = nil;
265   self->session     = nil;
266 }
267
268 - (void)ensureAwakeInContext:(WOContext *)_ctx {
269 #if DEBUG
270   NSAssert1(_ctx, @"missing context for awake (component=%@) ...", self);
271 #endif
272   
273   if (debugComponentAwake) 
274     [self debugWithFormat:@"0x%08X ensureAwakeInContext:0x%08X", self, _ctx];
275   
276   /* sanity check */
277
278   if (self->componentFlags.isAwake) {
279     if (self->context == _ctx) {
280       if (debugComponentAwake) 
281         [self debugWithFormat:@"0x%08X already awake:0x%08X", self, _ctx];
282       return;
283     }
284   }
285   
286   /* setup globals */
287   
288   if (self->context     == nil) [self _setContext:_ctx];
289   if (self->application == nil) self->application = [_ctx application];
290   
291   if ((self->session == nil) && [_ctx hasSession])
292     self->session = [_ctx session];
293   
294   self->componentFlags.isAwake = 1;
295   [_ctx _addAwakeComponent:self]; /* ensure that sleep is called */
296   
297   /* awake subcomponents */
298   {
299     NSEnumerator *children;
300     WOComponent  *child;
301     
302     children = [self->subcomponents objectEnumerator];
303     while ((child = [children nextObject]) != nil)
304       [child _awakeWithContext:_ctx];
305   }
306   
307   [self awake];
308 }
309
310 - (void)_awakeWithContext:(WOContext *)_ctx {
311   if (self->componentFlags.isAwake)
312     return;
313   
314   [self ensureAwakeInContext:_ctx];
315 }
316 - (void)_sleepWithContext:(WOContext *)_ctx {
317   if (debugComponentAwake) 
318     [self debugWithFormat:@"0x%08X _sleepWithContext:0x%08X", self, _ctx];
319   
320   if (_ctx != self->context) {
321     if ((self->context != nil) && (_ctx != nil)) {
322       /* component is active in different context ... */
323       [self warnWithFormat:
324               @"sleep context mismatch (own=0x%08X vs given=0x%08X)",
325               self->context, _ctx];
326       return;
327     }
328   }
329   
330   if (self->componentFlags.isAwake) {
331     /* 
332        Sleep all child components, this is necessary to ensure some ordering
333        in the sleep calls. All awake components are put to sleep in any case
334        by the WOContext destructor.
335     */
336     NSEnumerator *children;
337     WOComponent *child;
338     
339     children = [self->subcomponents objectEnumerator];
340     self->componentFlags.isAwake = 0;
341     
342     while ((child = [children nextObject]))
343       [child _sleepWithContext:_ctx];
344     
345     [self sleep];
346   }
347   [self _setContext:nil];
348   self->application = nil;
349   self->session     = nil;
350 }
351
352 /* accessors */
353
354 - (NSString *)name {
355   return self->wocName;
356 }
357 - (NSString *)frameworkName {
358   NSBundle *cbundle;
359   
360   cbundle = [NGBundle bundleForClass:[self class]];
361   if (cbundle == [NSBundle mainBundle])
362     return nil;
363   
364   return [cbundle bundleName];
365 }
366 - (NSString *)path {
367   NSArray *languages = nil;
368   
369 #if 0 // the component might not yet be awake !
370   languages = [[self context] resourceLookupLanguages];
371 #endif
372   
373   return [[self resourceManager]
374                 pathToComponentNamed:[self name]
375                 inFramework:[self frameworkName]
376                 languages:languages];
377 }
378 - (void)setBaseURL:(NSURL *)_url {
379   ASSIGNCOPY(self->wocBaseURL, _url);
380 }
381 - (NSURL *)baseURL {
382   NSURL *url;
383   
384   if (self->wocBaseURL)
385     return self->wocBaseURL;
386   
387   url = [(WOApplication *)[self application] baseURL];
388   self->wocBaseURL = 
389     [[NSURL URLWithString:@"WebServerResources" relativeToURL:url] copy];
390   return self->wocBaseURL;
391 }
392
393 - (NSString *)componentActionURLForContext:(WOContext *)_ctx {
394   return [@"/" stringByAppendingString:[self name]];
395 }
396
397 - (id)application {
398   if (self->application == nil)
399     return (self->application = [WOApplication application]);
400   return self->application;
401 }
402
403 - (id)existingSession {
404   if (self->session)
405     return self->session;
406   
407   if ([[self context] hasSession])
408     return [self session];
409
410   return nil;
411 }
412 - (id)session {
413   if (self->session == nil) {
414     if ((self->session = [[self context] session]) == nil) {
415       [self debugWithFormat:@"could not get session object from context %@",
416               self->context];
417     }
418   }
419   
420   if (self->session == nil)
421     [self warnWithFormat:@"missing session for component!"];
422   
423   return self->session;
424 }
425
426 - (void)_setContext:(WOContext *)_ctx {
427   self->context = _ctx;
428 }
429 - (WOContext *)context {
430   if (self->context)
431     return self->context;
432   
433   [self debugWithFormat:
434           @"missing context in component 0x%08X (component%s)",
435           self,
436           self->componentFlags.isAwake ? " is awake" : " is not awake"];
437   if (abortOnMissingCtx) {
438     [self errorWithFormat:@"aborting, because ctx is missing !"];
439     abort();
440   }
441     
442   if (self->application == nil)
443     self->application = [WOApplication application];
444   [self _setContext:[self->application context]];
445   
446   if (self->context == nil)
447     [self warnWithFormat:@"could not determine context object!"];
448   
449   return self->context;
450 }
451
452 - (BOOL)hasSession {
453   return [[self context] hasSession];
454 }
455
456 - (void)setCachingEnabled:(BOOL)_flag {
457   self->componentFlags.reloadTemplates = _flag ? 0 : 1;
458 }
459 - (BOOL)isCachingEnabled {
460   return (!self->componentFlags.reloadTemplates) ? YES : NO;
461 }
462
463 - (id)pageWithName:(NSString *)_name {
464   NSArray           *languages;
465   WOResourceManager *rm;
466   WOComponent       *component;
467   
468   languages = [[self context] resourceLookupLanguages];
469   rm        = [self resourceManager];
470
471   /* 
472      Note: this API is somewhat broken since the component expects the
473            -initWithContext: message for initialization yet we pass no
474            context ...
475   */
476   component = [rm pageWithName:_name languages:languages];
477   
478   // Note: should we call ensureAwakeInContext or is this to early ?
479   //       probably the component should be woken up if it enters the ctx.
480   //       If we create a page but never render it, we may get a warning 
481   //       that a context will dealloc but the page is active (yet not awake)
482   // Note: awake is not the same like "has context"! A component can have a
483   //       context without being awake - maybe we need an additional method
484   //       to hook up a component but the awake list
485   if (wakeupPageOnCreation)
486     [component ensureAwakeInContext:[self context]];
487   return component;
488 }
489
490 - (NSString *)stringForKey:(NSString *)_key
491   inTableNamed:(NSString *)_tableName
492   withDefaultValue:(NSString *)_default
493 {
494   NSArray *langs;
495   IS_DEPRECATED;
496   
497   langs = [[self context] resourceLookupLanguages];
498   
499   return [[[self application]
500                  resourceManager]
501                  stringForKey:_key
502                  inTableNamed:_tableName
503                  withDefaultValue:_default
504                  languages:langs];
505 }
506
507 - (void)setName:(NSString *)_name {
508   if (![_name isNotNull])
509     [self warnWithFormat:@"setting 'nil' name on component!"];
510   
511   ASSIGNCOPY(self->wocName, _name);
512 }
513
514 - (void)setBindings:(NSDictionary *)_bindings {
515   // this is _very_ private and used by WOComponentReference
516   ASSIGNCOPY(self->wocBindings, _bindings);
517 }
518 - (NSDictionary *)_bindings {
519   // private method
520   return self->wocBindings;
521 }
522
523 - (void)setSubComponents:(NSDictionary *)_dictionary {
524   ASSIGNCOPY(self->subcomponents, _dictionary);
525 }
526 - (NSDictionary *)_subComponents {
527   // private method
528   return self->subcomponents;
529 }
530
531 - (void)setParent:(WOComponent *)_parent {
532   self->parentComponent = _parent;
533 }
534 - (id)parent {
535   return self->parentComponent;
536 }
537
538 /* language change */
539
540 - (void)languageArrayDidChange {
541 }
542
543 /* element name */
544
545 - (NSString *)elementID {
546   return [self name];
547 }
548
549 /* resources */
550
551 - (id<WOActionResults>)redirectToLocation:(id)_loc {
552   WOContext  *ctx = [self context];
553   WOResponse *r;
554   NSString   *target;
555
556   if (_loc == nil)
557     return nil;
558   
559   if ((r = [ctx response]) == nil)
560     r = [[[WOResponse alloc] init] autorelease];
561   
562   if ([_loc isKindOfClass:[NSURL class]])
563     target = [_loc absoluteString];
564   else {
565     _loc = [_loc stringValue];
566     if ([_loc isAbsoluteURL])
567       target = _loc;
568     else if ([_loc isAbsolutePath])
569       target = _loc;
570     else {
571       target = [[ctx request] uri];
572       
573       // TODO: check whether the algorithm is correct
574       if (![target hasSuffix:@"/"])
575         target = [target stringByDeletingLastPathComponent];
576       target = [target stringByAppendingPathComponent:_loc];
577     }
578   }
579   
580   if (target == nil)
581     return nil;
582   [r setStatus:302 /* moved */];
583   [r setHeader:target forKey:@"location"];
584   return r;
585 }
586
587 - (void)setResourceManager:(WOResourceManager *)_rm {
588   _setExtraVar(self, @"__worm", _rm);
589 }
590 - (WOResourceManager *)resourceManager {
591   WOResourceManager *rm;
592   WOComponent *p;
593   
594   if ((rm = _getExtraVar(self, @"__worm")))
595     return rm;
596   
597   /* ask parent component ... */
598   if ((p = [self parent])) {
599     NSAssert2(p != self, @"parent component == component !!! (%@ vs %@)",
600               p, self);
601     if ((rm = [p resourceManager]))
602       return rm;
603   }
604   
605   /* ask application ... */
606   return [[self application] resourceManager];
607 }
608
609 - (NSString *)pathForResourceNamed:(NSString *)_name ofType:(NSString *)_ext {
610   NSFileManager *fm;
611   NSEnumerator  *languages;
612   NSString      *language;
613   BOOL          isDirectory = NO;
614   NSString      *cpath;
615   
616   if (_ext) _name = [_name stringByAppendingPathExtension:_ext];
617
618   if ((cpath = [self path]) == nil) {
619     [self warnWithFormat:@"no path set in component %@", [self name]];
620     return nil;
621   }
622   
623   fm = [NSFileManager defaultManager];
624   if (![fm fileExistsAtPath:cpath isDirectory:&isDirectory]) {
625     [self warnWithFormat:@"component directory %@ does not exist !", cpath];
626     return nil;
627   }
628   if (!isDirectory) {
629     [self warnWithFormat:@"component path %@ is not a directory !", cpath];
630     return nil;
631   }
632   
633   /* check in language projects */
634
635   languages = [self hasSession]
636     ? [[(WOSession *)[self session] languages] objectEnumerator]
637     : [[[[self context] request] browserLanguages] objectEnumerator];
638   
639   while ((language = [languages nextObject]) != nil) {
640     language = [language stringByAppendingPathExtension:@"lproj"];
641     language = [cpath stringByAppendingPathComponent:language];
642     language = [language stringByAppendingPathExtension:_name];
643     
644     if ([fm fileExistsAtPath:language])
645       return language;
646   }
647   
648   /* check in component */
649   cpath = [cpath stringByAppendingPathComponent:_name];
650   if ([fm fileExistsAtPath:cpath])
651     return cpath;
652
653   return nil;
654 }
655
656 /* template */
657
658 + (WOElement *)templateWithHTMLString:(NSString *)_html
659   declarationString:(NSString *)_wod
660   languages:(NSArray *)_languages
661 {
662   return [self notImplemented:_cmd];
663 }
664 - (WOElement *)templateWithHTMLString:(NSString *)_html
665   declarationString:(NSString *)_wod
666 {
667   IS_DEPRECATED;
668   return [[self class] templateWithHTMLString:_html
669                        declarationString:_wod
670                        languages:[(WOSession *)[self session] languages]];
671 }
672
673 - (WOElement *)templateWithName:(NSString *)_name {
674   WOResourceManager *resourceManager;
675   NSArray           *languages;
676   WOElement         *tmpl;
677   
678   if ((resourceManager = [self resourceManager]) == nil) {
679     [self errorWithFormat:@"%s: could not determine resource manager !",
680             __PRETTY_FUNCTION__];
681     return nil;
682   }
683   
684   languages = [[self context] resourceLookupLanguages];
685   tmpl      = [resourceManager templateWithName:_name languages:languages];
686
687   if (debugTemplates) [self debugWithFormat:@"found template: %@", tmpl];
688   return tmpl;
689 }
690
691 - (void)setTemplate:(id)_template {
692   /*
693     WO has private API for this:
694       - (void)setTemplate:(WOElement *)template;
695     As mentioned in the OmniGroup WO mailing list ...
696   */
697   _setExtraVar(self, @"__wotemplate", _template);
698 }
699 - (WOElement *)_woComponentTemplate {
700   WOElement *element;
701   
702   // TODO: move to ivar?
703   if ((element = _getExtraVar(self, @"__wotemplate")) != nil)
704     return element;
705   
706   return [self templateWithName:[self name]];
707 }
708
709 /* child components */
710
711 - (WOComponent *)childComponentWithName:(NSString *)_name {
712   id child;
713   
714   child = [self->subcomponents objectForKey:_name];
715   if ([child isComponentFault]) {
716     NSMutableDictionary *tmp;
717     
718     child = [child resolveWithParent:self];
719     if (child == nil) {
720       [self warnWithFormat:@"Could not resolve component fault: %@", _name];
721       return nil;
722     }
723     
724     tmp = [self->subcomponents mutableCopy];
725     [tmp setObject:child forKey:_name];
726     [self->subcomponents release]; self->subcomponents = nil;
727     self->subcomponents = [tmp copy];
728     [tmp release]; tmp = nil;
729   }
730   return child;
731 }
732
733 - (BOOL)synchronizesVariablesWithBindings {
734   return [self isStateless] ? NO : YES;
735 }
736
737 - (void)setValue:(id)_value forBinding:(NSString *)_name {
738   WOComponent      *parent;
739   WOContext        *ctx;
740   WODynamicElement *content;
741   
742   ctx     = [self context];
743   parent  = [ctx parentComponent];
744   content = [ctx componentContent];
745   
746   if (parent == nil) {
747     parent = [self parent];
748     [self warnWithFormat:@"tried to set value of binding '%@' in component "
749             @"'%@' without parent component (parent is '%@') !",
750             _name, [self name], [parent name]];
751   }
752   
753   [[self    retain] autorelease];
754   [[content retain] autorelease];
755   
756   WOContext_leaveComponent(ctx, self);
757   [[self->wocBindings objectForKey:_name] setValue:_value inComponent:parent];
758   WOContext_enterComponent(ctx, self, content);
759 }
760 - (id)valueForBinding:(NSString *)_name {
761   WOComponent      *parent;
762   WOContext        *ctx;
763   WODynamicElement *content;
764   id value;
765   
766   ctx     = [self context];
767   parent  = [ctx parentComponent];
768   content = [ctx componentContent];
769   
770   if (parent == nil) {
771     parent = [self parent];
772     [self warnWithFormat:@"tried to retrieve value of binding '%@' in"
773             @" component '%@' without parent component (parent is '%@') !",
774             _name, [self name], [parent name]];
775   }
776   
777   [[self    retain] autorelease];
778   [[content retain] autorelease];
779   
780   WOContext_leaveComponent(ctx, self);
781   value = [[self->wocBindings objectForKey:_name] valueInComponent:parent];
782   WOContext_enterComponent(ctx, self, content);
783   
784   return value;
785 }
786
787 - (BOOL)hasBinding:(NSString *)_name {
788   return ([self->wocBindings objectForKey:_name] != nil) ? YES : NO;
789 }
790
791 - (BOOL)canSetValueForBinding:(NSString *)_name {
792   WOAssociation *binding;
793
794   if ((binding = [self->wocBindings objectForKey:_name]) == nil)
795     return NO;
796   
797   return [binding isValueSettable];
798 }
799 - (BOOL)canGetValueForBinding:(NSString *)_name {
800   WOAssociation *binding;
801
802   if ((binding = [self->wocBindings objectForKey:_name]) == nil)
803     return NO;
804
805   return YES;
806 }
807
808 - (id)performParentAction:(NSString *)_name {
809   WOContext        *ctx;
810   WOComponent      *parent;
811   WODynamicElement *content;
812   SEL              action;
813   id               result   = nil;
814
815   ctx     = [self context];
816   parent  = [ctx parentComponent];
817   content = [ctx componentContent];
818   action  = NSSelectorFromString(_name);
819   
820   if (parent == nil)  return nil;
821   if (action == NULL) return nil;
822   
823   NSAssert(parent != self, @"parent component equals current component");
824
825   if (![parent respondsToSelector:action]) {
826     [self debugWithFormat:@"parent %@ doesn't respond to %@",
827             [parent name], _name];
828     return nil;
829   }
830
831   self = [self retain];
832   NS_DURING {
833     WOContext_leaveComponent(ctx, self);
834     *(&result) = [parent performSelector:action];
835     WOContext_enterComponent(ctx, self, content);
836   }
837   NS_HANDLER {
838     [self release];
839     [localException raise];
840   }
841   NS_ENDHANDLER;
842   
843   [self release];
844   return result;
845 }
846
847 /* OWResponder */
848
849 - (BOOL)shouldTakeValuesFromRequest:(WORequest *)_rq inContext:(WOContext*)_c {
850   if (debugTakeValues)
851     [self debugWithFormat:@"%s: default says no.", __PRETTY_FUNCTION__];
852   return NO;
853 }
854
855 - (void)takeValuesFromRequest:(WORequest *)_req inContext:(WOContext *)_ctx {
856   WOElement *template = nil;
857   
858   if (debugTakeValues) 
859     [self debugWithFormat:@"take values from rq 0x%08X", _req];
860   
861   NSAssert1(self->componentFlags.isAwake,
862             @"component %@ is not awake !", self);
863   
864   [self _setContext:_ctx];
865   template = [self _woComponentTemplate];
866   
867   if (template == nil) {
868     if (debugTakeValues) 
869       [self debugWithFormat:@"cannot take values, component has no template!"];
870     return;
871   }
872   
873   if (template->takeValues) {
874     template->takeValues(template,
875                          @selector(takeValuesFromRequest:inContext:),
876                          _req, _ctx);
877   }
878   else
879     [template takeValuesFromRequest:_req inContext:_ctx];
880 }
881
882 - (id)invokeActionForRequest:(WORequest *)_req inContext:(WOContext *)_ctx {
883   WOElement *template = nil;
884   id result = nil;
885   
886   NSAssert1(self->componentFlags.isAwake, @"component %@ is not awake!", self);
887
888   [self _setContext:_ctx];
889   template = [self _woComponentTemplate];
890   result = [template invokeActionForRequest:_req inContext:_ctx];
891   return result;
892 }
893
894 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
895   WOElement *template = nil;
896   NSTimeInterval st = 0.0;
897   
898   NSAssert1(self->componentFlags.isAwake,
899             @"component %@ is not awake !", self);
900   if (debugOn) {
901     if (self->context != _ctx) {
902       [self debugWithFormat:@"%s: component ctx != ctx (%@ vs %@)",
903               __PRETTY_FUNCTION__, self->context, _ctx];
904     }
905   }
906   
907   [self _setContext:_ctx];
908   
909   if ((template = [self _woComponentTemplate]) == nil) {
910     if (debugOn) {
911       [self debugWithFormat:@"component has no template (rm=%@).",
912               [self resourceManager]];
913     }
914     return;
915   }
916   
917   if (perfLogger)
918     st = [[NSDateClass date] timeIntervalSince1970];
919     
920   if (template->appendResponse) {
921     template->appendResponse(template,
922                              @selector(appendToResponse:inContext:),
923                              _response, _ctx);
924   }
925   else
926     [template appendToResponse:_response inContext:_ctx];
927
928   if (perfLogger) {
929     NSTimeInterval diff;
930     int i;
931     diff = [[NSDateClass date] timeIntervalSince1970] - st;
932 #if 1
933     for (i = [_ctx componentStackCount]; i >= 0; i--)
934       printf("  ");
935 #endif
936     [perfLogger logWithFormat:@"Template %@ (comp %@): %0.3fs\n",
937                   [_ctx elementID],
938                   [self name],
939                   diff];
940   }
941 }
942   
943 /* WOActionResults */
944
945 - (WOResponse *)generateResponse {
946   WOResponse *response = nil;
947   WOContext  *ctx = nil;
948   NSString   *ctxID;
949   
950   ctx      = [self context];
951   ctxID    = [ctx  contextID];
952   response = [WOResponse responseWithRequest:[ctx request]];
953   
954   if (ctxID == nil) {
955     [self debugWithFormat:@"missing ctx-id for context %@", ctx];
956     ctxID = @"noctx";
957   }
958   
959   [ctx deleteAllElementIDComponents];
960   [ctx appendElementIDComponent:ctxID];
961   
962   WOContext_enterComponent(ctx, self, nil);
963   [self appendToResponse:response inContext:ctx];
964   WOContext_leaveComponent(ctx, self);
965   
966   [ctx deleteLastElementIDComponent];
967
968   ctx = nil;
969
970 #if 0
971   if ([[[ctx request] method] isEqualToString:@"HEAD"])
972     [response setContent:[NSData data]];
973 #endif
974   
975   /* HTTP/1.1 caching directive, prevents browser from caching dynamic pages */
976   if ([[ctx application] isPageRefreshOnBacktrackEnabled])
977     [response disableClientCaching];
978   
979   return response;
980 }
981
982 /* coding */
983
984 - (void)encodeWithCoder:(NSCoder *)_coder {
985   BOOL doesReloadTemplates = self->componentFlags.reloadTemplates;
986
987   [_coder encodeObject:self->wocBindings];
988   [_coder encodeObject:self->wocName];
989   [_coder encodeConditionalObject:self->parentComponent];
990   [_coder encodeObject:self->subcomponents];
991   [_coder encodeObject:self->wocVariables];
992   [_coder encodeConditionalObject:self->session];
993   [_coder encodeValueOfObjCType:@encode(BOOL) at:&doesReloadTemplates];
994 }
995 - (id)initWithCoder:(NSCoder *)_decoder {
996   if ((self = [super init])) {
997     BOOL doesReloadTemplates = YES;
998
999     self->wocBindings     = [[_decoder decodeObject] retain];
1000     self->wocName         = [[_decoder decodeObject] retain];
1001     self->parentComponent = [_decoder decodeObject]; // non-retained
1002     self->subcomponents   = [[_decoder decodeObject] retain];
1003     self->wocVariables    = [[_decoder decodeObject] retain];
1004     self->session         = [_decoder decodeObject]; // non-retained
1005     
1006     [_decoder decodeValueOfObjCType:@encode(BOOL) at:&doesReloadTemplates];
1007     [self setCachingEnabled:!doesReloadTemplates];
1008   }
1009   return self;
1010 }
1011
1012 /* component variables */
1013
1014 - (BOOL)isStateless {
1015   return NO;
1016 }
1017 - (void)reset {
1018   [self->wocVariables removeAllObjects];
1019 }
1020
1021 - (void)setObject:(id)_obj forKey:(NSString *)_key {
1022   _setExtraVar(self, _key, _obj);
1023 }
1024 - (id)objectForKey:(NSString *)_key {
1025   return _getExtraVar(self, _key);
1026 }
1027 - (NSDictionary *)variableDictionary {
1028   return self->wocVariables;
1029 }
1030
1031 - (BOOL)logComponentVariableCreations {
1032   /* only if we have a subclass, we can store values in ivars ... */
1033   return (self->isa != WOComponentClass) ? YES : NO;
1034 }
1035
1036 #if !NG_USE_KVC_FALLBACK /* only override on libFoundation */
1037
1038 - (void)takeValue:(id)_value forKey:(NSString *)_key {
1039   if (WOSetKVCValueUsingMethod(self, _key, _value)) {
1040     // method is used
1041     return;
1042   }
1043   if (WOGetKVCGetMethod(self, _key) == NULL) {
1044     if (_value == nil) {
1045 #if 0
1046       [self debugWithFormat:
1047               @"storing <nil> value in component variable %@", _key];
1048 #endif
1049       
1050       if ([self->wocVariables objectForKey:_key])
1051         [self setObject:nil forKey:_key];
1052       
1053       return;
1054     }
1055 #if DEBUG
1056     if ([self logComponentVariableCreations]) {
1057       /* only if we have a subclass, we can store values in ivars ... */
1058       if (![[self->wocVariables objectForKey:_key] isNotNull]) {
1059         [self debugWithFormat:
1060                 @"Created component variable (class=%@): '%@'.",
1061                 NSStringFromClass(self->isa), _key];
1062       }
1063     }
1064 #endif
1065     
1066     [self setObject:_value forKey:_key];
1067     return;
1068   }
1069
1070   [self debugWithFormat:
1071           @"value %@ could not set via method or KVC "
1072           @"(self responds to %@: %s).",
1073                 _key, _key,
1074           [self respondsToSelector:NSSelectorFromString(_key)] ? "yes" : "no"];
1075 #if 0
1076   return NO;
1077 #endif
1078 }
1079 - (id)valueForKey:(NSString *)_key {
1080   id value;
1081   
1082   if ((value = WOGetKVCValueUsingMethod(self, _key)))
1083     return value;
1084
1085 #if DEBUG && 0
1086   [self debugWithFormat:@"KVC: accessed the component variable %@", _key];
1087 #endif
1088   if ((value = [self objectForKey:_key]))
1089     return value;
1090   
1091   return nil;
1092 }
1093
1094 #else /* use fallback methods on other Foundation libraries */
1095
1096 - (void)setValue:(id)_value forUndefinedKey:(NSString *)_key {
1097   // Note: this is not used on libFoundation, insufficient KVC implementation
1098   
1099   if (_value == nil) {
1100 #if 0
1101     [self debugWithFormat:
1102             @"storing <nil> value in component variable %@", _key];
1103 #endif
1104     
1105     if ([self->wocVariables objectForKey:_key])
1106       [self setObject:nil forKey:_key];
1107       
1108     return;
1109   }
1110   
1111 #if DEBUG
1112   if ([self logComponentVariableCreations]) {
1113     /* only if we have a subclass, we can store values in ivars ... */
1114     if (![[self->wocVariables objectForKey:_key] isNotNull]) {
1115       [self debugWithFormat:@"Created component variable (class=%@): '%@'.", 
1116                     NSStringFromClass(self->isa), _key];
1117     }
1118   }
1119 #endif
1120   
1121   [self setObject:_value forKey:_key];
1122 }
1123 - (id)valueForUndefinedKey:(NSString *)_key {
1124   // Note: this is not used on libFoundation, insufficient KVC implementation
1125 #if DEBUG && 0
1126   [self debugWithFormat:@"KVC: accessed the component variable %@", _key];
1127 #endif
1128   return [self objectForKey:_key];
1129 }
1130
1131 - (void)handleTakeValue:(id)_value forUnboundKey:(NSString *)_key {
1132   // deprecated: pre-Panther method
1133   [self setValue:_value forUndefinedKey:_key];
1134 }
1135 - (id)handleQueryWithUnboundKey:(NSString *)_key {
1136   // deprecated: pre-Panther method
1137   return [self valueForUndefinedKey:_key];
1138 }
1139
1140 - (void)unableToSetNilForKey:(NSString *)_key {
1141   // TODO: should we call setValue:NSNull forKey?
1142   [self errorWithFormat:@"unable to set 'nil' for key: '%@'", _key];
1143 }
1144
1145 #endif /* KVC */
1146
1147 - (void)validationFailedWithException:(NSException *)_exception
1148   value:(id)_value keyPath:(NSString *)_keyPath
1149 {
1150   [self warnWithFormat:
1151           @"formatter failed for value %@ (keyPath=%@): %@",
1152           _value, _keyPath, [_exception reason]];
1153 }
1154
1155 /* logging */
1156
1157 - (BOOL)isEventLoggingEnabled {
1158   return YES;
1159 }
1160
1161 - (BOOL)isDebuggingEnabled {
1162   return debugOn;
1163 }
1164 - (NSString *)loggingPrefix {
1165   NSString *n;
1166   
1167   n = [self name];
1168   if ([n length] == 0)
1169     return @"<component without name>";
1170   
1171   return n;
1172 }
1173
1174 /* woo/plist unarchiving */
1175
1176 - (id)unarchiver:(EOKeyValueUnarchiver *)_archiver 
1177   objectForReference:(id)_keyPath
1178 {
1179   return [self valueForKeyPath:_keyPath];
1180 }
1181
1182 /* NSCopying */
1183
1184 - (id)copyWithZone:(NSZone *)_zone {
1185   // TODO: find out who triggers this
1186   return [self retain];
1187 }
1188
1189 /* description */
1190
1191 - (NSString *)description {
1192   NSMutableString *str;
1193   id tmp;
1194   
1195   str = [NSMutableString stringWithCapacity:128];
1196   [str appendFormat:@"<0x%08X[%@]: name=%@", self,
1197          NSStringFromClass([self class]), [self name]];
1198
1199   if (self->parentComponent)
1200     [str appendFormat:@" parent=%@", [self->parentComponent name]];
1201   if (self->subcomponents)
1202     [str appendFormat:@" #subs=%i", [self->subcomponents count]];
1203   
1204   if (self->componentFlags.isAwake)
1205     [str appendFormat:@" awake=0x%08X", self->context];
1206   else if (self->context == nil)
1207     [str appendString:@" no-ctx"];
1208   
1209   if ((tmp = _getExtraVar(self, @"__worm")))
1210     [str appendFormat:@" rm=%@", tmp];
1211   
1212   [str appendString:@">"];
1213   return str;
1214 }
1215
1216 /* Statistics */
1217
1218 - (NSString *)descriptionForResponse:(WOResponse *)_response
1219   inContext:(WOContext *)_context
1220 {
1221   return [self name];
1222 }
1223
1224 /* AdvancedBindingAccessors */
1225
1226 - (void)setUnsignedIntValue:(unsigned)_value forBinding:(NSString *)_name {
1227   [self setValue:[NSNumber numberWithUnsignedInt:_value] forBinding:_name];
1228 }
1229 - (unsigned)unsignedIntValueForBinding:(NSString *)_name {
1230   return [[self valueForBinding:_name] unsignedIntValue];
1231 }
1232
1233 - (void)setIntValue:(int)_value forBinding:(NSString *)_name {
1234   [self setValue:[NSNumber numberWithInt:_value] forBinding:_name];
1235 }
1236 - (int)intValueForBinding:(NSString *)_name {
1237   return [[self valueForBinding:_name] intValue];
1238 }
1239
1240 - (void)setBoolValue:(BOOL)_value forBinding:(NSString *)_name {
1241   [self setValue:[NSNumber numberWithBool:_value] forBinding:_name];
1242 }
1243 - (BOOL)boolValueForBinding:(NSString *)_name {
1244   return [[self valueForBinding:_name] boolValue];
1245 }
1246
1247 #if !NG_USE_KVC_FALLBACK
1248 - (id)handleQueryWithUnboundKey:(NSString *)_key {
1249   [self logWithFormat:@"query for unbound key: %@", _key];
1250   return [super handleQueryWithUnboundKey:_key];
1251 }
1252 #endif
1253
1254 @end /* WOComponent */