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