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