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