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