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