]> err.no Git - sope/blob - sope-appserver/NGObjWeb/WOComponentDefinition.m
fixed duplicate -awake
[sope] / sope-appserver / NGObjWeb / WOComponentDefinition.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/WOComponentDefinition.h>
23 #include "WOComponent+private.h"
24 #include "WOComponentFault.h"
25 #include <NGObjWeb/WOAssociation.h>
26 #include <NGObjWeb/WOApplication.h>
27 #include <NGObjWeb/WOElement.h>
28 #include <NGObjWeb/WOResponse.h>
29 #include <NGObjWeb/WOResourceManager.h>
30 #include "common.h"
31 #include <EOControl/EOControl.h>
32
33 #include <NGObjWeb/WOTemplateBuilder.h>
34
35 static Class    StrClass    = Nil;
36 static Class    DictClass   = Nil;
37 static Class    AssocClass  = Nil;
38 static Class    NumberClass = Nil;
39 static Class    DateClass   = Nil;
40 static NSNumber *yesNum     = nil;
41 static NSNumber *noNum      = nil;
42
43 @interface WOComponent(UsedPrivates)
44 - (void)setBaseURL:(id)_url;
45 @end
46
47 @interface WONoContentElement : WOElement
48 {
49   WOComponentDefinition *cdef;
50   NSString *element;
51 }
52 - (id)initWithElementName:(NSString *)_elementName
53   attributes:(NSDictionary *)_attributes
54   contentElements:(NSArray *)_subElements
55   componentDefinition:(WOComponentDefinition *)_cdef;
56 @end
57
58 @interface _WOStaticHTMLElement : WOElement
59 {
60   NSString *text;
61 }
62 - (id)initWithBuffer:(const char *)_buffer length:(unsigned)_len;
63 @end
64
65 @interface WOComponentDefinition(PrivateMethods)
66
67 - (BOOL)load;
68
69 @end
70
71 #include <NGObjWeb/WOContext.h>
72 #include <NGObjWeb/WORequest.h>
73 #include <NGObjWeb/WOSession.h>
74
75 /*
76   TODO:
77   
78   WO's instantiation method is 
79     - componentInstanceInContext:forComponentReference:
80   with the primary being
81     - _componentInstanceInContext:forComponentReference:
82   
83   Maybe we should change to that. Currently this flow is a bit broken because
84   the resourcemanager sits in the middle, though I'm pretty sure that some
85   older WO used WOResourceManager just like we do in the moment.
86 */
87
88 @implementation WOComponent(InfoSetup)
89
90 - (Class)componentFaultClass {
91   return [WOComponentFault class];
92 }
93
94 - (NSMutableDictionary *)instantiateChildComponentsInTemplate:(WOTemplate *)_t
95   languages:(NSArray *)_languages
96 {
97   NSMutableDictionary *childComponents = nil;
98   WOResourceManager *_rm;
99   WOTemplate *tmpl;
100   NSEnumerator *keys;
101   NSString     *key;
102   
103   if ((tmpl = _t) == nil)
104     return nil;
105   
106   _rm = [[WOApplication application] resourceManager];
107   
108   if ([tmpl hasSubcomponentInfos] == 0)
109     return nil;
110   
111   keys = [tmpl infoKeyEnumerator];
112   while ((key = [keys nextObject])) {
113     WOSubcomponentInfo *childInfo = nil;
114     WOComponentFault   *child     = nil;
115       
116     childInfo = [tmpl subcomponentInfoForKey:key];
117     
118     child = [[WOComponentFault alloc]
119               initWithResourceManager:nil //_rm
120               pageName:[childInfo componentName]
121               languages:_languages
122               bindings:[childInfo bindings]];
123
124     if (child != nil) {
125       if (childComponents == nil)
126         childComponents = [NSMutableDictionary dictionaryWithCapacity:16];
127         
128       [childComponents setObject:child forKey:key];
129       [child release];
130     }
131     else {
132       [self errorWithFormat:
133               @"(%s): Could not instantiate child fault %@, component: '%@'",
134               __PRETTY_FUNCTION__, key, [childInfo componentName]];
135     }
136   }
137   return childComponents;
138 }
139
140 - (id)initWithName:(NSString *)_cname
141   template:(WOTemplate *)_template
142   inContext:(WOContext *)_ctx
143 {
144   // Note: the _template can be nil and will then get looked up dynamically!
145   [self setName:_cname];
146   if ((self = [self initWithContext:_ctx])) {
147     NSMutableDictionary *childComponents;
148     NSArray             *langs;
149
150     langs = [[self context] resourceLookupLanguages];
151     
152     childComponents = [self instantiateChildComponentsInTemplate:_template
153                                               languages:langs];
154     [self setSubComponents:childComponents];
155     [self setTemplate:_template];
156   }
157   return self;
158 }
159
160 - (id)initWithComponentDefinition:(WOComponentDefinition *)_cdef 
161   inContext:(WOContext *)_ctx
162 {
163   /* 
164      HACK HACK HACK CD: 
165      We reuse the wocVariables ivar to pass over the component definition to 
166      the component which will then call -_finishInitializingComponent: on the
167      definition for applying the .woo.
168      
169      Sideeffects: if a component subclass uses extra vars prior calling
170      WOComponent -init, it will run into "issues".
171   */
172   NSAssert(self->wocVariables == nil,
173            @"extra variables dict is already set! cannot transfer component "
174            @"definition in that variable (use the HACK)");
175   self->wocVariables = (id)[_cdef retain];
176   
177   return [self initWithName:[_cdef componentName]
178                template:[_cdef template]
179                inContext:_ctx];
180 }
181
182 @end /* WOComponent(InfoSetup) */
183
184 @implementation WOComponentDefinition
185
186 static BOOL debugOn     = NO;
187 static BOOL profLoading = NO;
188 static BOOL enableClassLessComponents = NO;
189 static BOOL enableWOOFiles            = NO;
190 static NSArray *woxExtensions = nil;
191
192 + (int)version {
193   return 4;
194 }
195
196 + (void)initialize {
197   static BOOL isInitialized = NO;
198   NSUserDefaults *ud;
199   if (isInitialized) return;
200   isInitialized = YES;
201   ud = [NSUserDefaults standardUserDefaults];
202     
203   StrClass    = [NSString      class];
204   DictClass   = [NSMutableDictionary class];
205   AssocClass  = [WOAssociation class];
206   NumberClass = [NSNumber      class];
207   DateClass   = [NSDate        class];
208     
209   yesNum = [[NumberClass numberWithBool:YES] retain];
210   noNum  = [[NumberClass numberWithBool:NO]  retain];
211   
212   profLoading = [[ud objectForKey:@"WOProfileLoading"] boolValue];
213   enableClassLessComponents = 
214     [ud boolForKey:@"WOEnableComponentsWithoutClasses"];
215   enableWOOFiles = [ud boolForKey:@"WOComponentLoadWOOFiles"];
216   debugOn        = [ud boolForKey:@"WODebugComponentDefinition"];
217   woxExtensions  = [[ud arrayForKey:@"WOxFileExtensions"] copy];
218 }
219
220 - (id)initWithName:(NSString *)_name
221   path:(NSString *)_path
222   baseURL:(NSURL *)_baseUrl
223   frameworkName:(NSString *)_frameworkName
224 {
225   /* 
226      this method is usually called by WOResourceManager
227      (_definitionWithName:...) 
228   */
229   if ((self = [super init])) {
230     /*
231       'name'    is the name of the component
232       'path'    contains a string or a NSURL with the location of the directory
233                 containing the component - TODO: explain who calculates that!
234       'baseURL' contains a URL like /AppName/FrameworkName/Component/Eng.lProj
235                 (the external URL of the component, not sure whether this is
236                  actually used somewhere)
237     */
238     NSZone *z = [self zone];
239     
240     self->name          = [_name          copyWithZone:z];
241     self->path          = [_path          copyWithZone:z];
242     self->baseUrl       = [_baseUrl       copyWithZone:z];
243     self->frameworkName = [_frameworkName copyWithZone:z];
244   
245     if (debugOn) {
246       [self debugWithFormat:
247               @"init: '%@' path='%@'\n  URL='%@'\n  framework='%@'",
248               self->name, self->path, [self->baseUrl absoluteString], 
249               self->frameworkName];
250     }
251     
252     if (![self load]) { /* TODO: is this really required? */
253       [self release];
254       return nil;
255     }
256   }
257   return self;
258 }
259
260 - (id)init {
261   [self errorWithFormat:@"called -init on WOComponentDefinition!"];
262   [self release];
263   return nil;
264 }
265
266 - (void)dealloc {
267   [self->template      release];
268   [self->name          release];
269   [self->path          release];
270   [self->baseUrl       release];
271   [self->frameworkName release];
272   [super dealloc];
273 }
274
275 /* accessors */
276
277 - (Class)componentClassForScript:(WOComponentScript *)_script {
278   return Nil;
279 }
280
281 - (void)setComponentClass:(Class)_class {
282   self->componentClass = _class;
283 }
284 - (Class)componentClass {
285   if (self->componentClass == Nil)
286     self->componentClass = NSClassFromString(self->name);
287   
288   if (self->componentClass != Nil)
289     return self->componentClass;
290
291   if (self->name == nil)
292     return Nil;
293   
294   if ([self->name isAbsolutePath])
295         ;
296   else if ([self->name rangeOfString:@"."].length > 0)
297         ;
298   else if (enableClassLessComponents)
299         ;
300   else {
301     [self logWithFormat:@"Note: did not find component class with name '%@'",
302             self->name];
303   }
304   return Nil;
305 }
306 - (NSString *)componentName {
307   return self->name;
308 }
309
310 - (WOTemplate *)template {
311   return self->template;
312 }
313
314 - (NSString *)path {
315   return self->path;
316 }
317
318 - (NSURL *)baseURL {
319   return self->baseUrl;
320 }
321 - (NSString *)frameworkName {
322   return self->frameworkName;
323 }
324
325 /* caching */
326
327 - (void)touch {
328   self->lastTouch = [DateClass timeIntervalSinceReferenceDate];
329 }
330
331 - (NSTimeInterval)lastTouch {
332   return self->lastTouch;
333 }
334
335 /* instantiation */
336
337 - (BOOL)_checkComponentClassValidity:(Class)cClass {
338 #if 0
339   /* this make no sense, need -isSubclassOfClass: ..,
340      class instances are never isKindOfClass:WOElement ... 
341   */
342   {
343     static Class WOElementClass = Nil;
344     if (WOElementClass == Nil) WOElementClass = [WOElement class];
345     if (![cClass isKindOfClass:WOElementClass] && cClass != nil) {
346       [self warnWithFormat:@"(%s:%i): "
347               @"component class %@ is not a subclass of WOElement !",
348               __PRETTY_FUNCTION__, __LINE__,
349               NSStringFromClass(cClass)];
350       return NO;
351     }
352   }
353 #endif
354   return YES;
355 }
356 - (BOOL)_checkComponentValidity:(id)component class:(Class)cClass {
357   if (![component isKindOfClass:cClass] && component != nil) {
358     [self warnWithFormat:@"(%s:%i): component %@ is not a subclass of "
359             @"component class %@ !",
360             __PRETTY_FUNCTION__, __LINE__,
361             component, NSStringFromClass(cClass)];
362     return NO;
363   }
364   return YES;
365 }
366
367 - (void)_applyWOOVariables:(NSDictionary *)_vars
368   onComponent:(WOComponent *)_component 
369 {
370   EOKeyValueUnarchiver *unarchiver;
371   NSAutoreleasePool    *pool;
372   NSEnumerator *keys;
373   NSString     *key;
374   
375   pool = [[NSAutoreleasePool alloc] init];
376   
377   unarchiver = 
378     [[[EOKeyValueUnarchiver alloc] initWithDictionary:_vars] autorelease];
379   [unarchiver setDelegate:_component];
380   
381   keys = [_vars keyEnumerator];
382   while ((key = [keys nextObject])) {
383     id object;
384     
385     object = [unarchiver decodeObjectForKey:key];
386     [_component takeValue:object forKey:key];
387   }
388   [unarchiver finishInitializationOfObjects];
389   [unarchiver awakeObjects];
390
391   [pool release];
392 }
393
394 - (void)_applyWOOVariablesOnComponent:(WOComponent *)_component {
395   /* 
396      Note: we still need this, as components are not required to load the
397            template at all!
398   */
399   NSString     *wooPath;
400   NSDictionary *woo;
401
402   wooPath = [[_component path] stringByAppendingPathExtension:@"woo"];
403   if (![[NSFileManager defaultManager] fileExistsAtPath:wooPath])
404     return;
405   
406   if ((woo = [NSDictionary dictionaryWithContentsOfFile:wooPath]) == nil) {
407     [self errorWithFormat:@"could not load .woo-file: '%@'", wooPath];
408     return;
409   }
410   
411   [self _applyWOOVariables:[woo objectForKey:@"variables"]
412         onComponent:_component];
413 }
414
415 - (void)_finishInitializingComponent:(WOComponent *)_component {
416   if (self->baseUrl)
417     [_component setBaseURL:self->baseUrl];
418
419   if (enableWOOFiles) {
420     if (self->template) {
421       [self _applyWOOVariables:
422               [self->template keyValueArchivedTemplateVariables]
423             onComponent:_component];
424     }
425     else
426       [self _applyWOOVariablesOnComponent:_component];
427   }
428 }
429
430 - (WOComponent *)instantiateWithResourceManager:(WOResourceManager *)_rm
431   languages:(NSArray *)_languages
432 {
433   WOComponent       *component = nil;
434   Class             cClass;
435   WOComponentScript *script;
436   
437   if ((script = [self->template componentScript])) 
438     cClass = NSClassFromString(@"WOScriptedComponent");
439   else
440     cClass = [self componentClass];
441   
442   if (cClass == nil) {
443     NSString *tmpPath;
444
445     if (enableClassLessComponents) {
446       [self debugWithFormat:@"Note: missing class for component: '%@'",
447               [self componentName]];
448     }
449     else {
450       [self logWithFormat:@"Note: missing class for component: '%@'",
451               [self componentName]];
452     }
453     
454     tmpPath = [self->name stringByAppendingPathExtension:@"html"];
455     tmpPath = [self->path stringByAppendingPathComponent:tmpPath];
456     
457     if ([[NSFileManager defaultManager] fileExistsAtPath:tmpPath]) {
458       cClass = [WOComponent class];
459     }
460     else {
461       [self debugWithFormat:@"Note: did not find .html template at path: '%@'",
462               tmpPath];
463     }
464   }
465   
466   if (![self _checkComponentClassValidity:cClass]) {
467     [self logWithFormat:@"Component Class '%@' is not valid.", cClass];
468     return nil;
469   }
470   
471   /* instantiate object (this will call _finishInitializingComponent) */
472   
473   component = [[cClass alloc] initWithComponentDefinition:self
474                               inContext:[[WOApplication application] context]];
475   component = [component autorelease];
476   if (component == nil)
477     return nil;
478   
479   /* check validity */
480   
481   if (debugOn)
482     [self _checkComponentValidity:component class:cClass];
483
484   if (debugOn) {
485     if (![component isKindOfClass:cClass]) {
486       [self warnWithFormat:
487               @"(%s:%i): component '%@' is not a subclass of "
488               @"component class '%@' !",
489               __PRETTY_FUNCTION__, __LINE__,
490               component, NSStringFromClass(cClass)];
491     }
492   }
493   return component;
494 }
495
496 /* templates */
497
498 - (WOTemplateBuilder *)templateBuilderForPath:(NSString *)_path {
499   NSString *ext;
500   
501   if ([_path length] == 0)
502     return nil;
503   
504   ext = [_path pathExtension];
505   if ([woxExtensions containsObject:ext]) {
506     static WOTemplateBuilder *woxBuilder = nil;
507     if (woxBuilder == nil)
508       woxBuilder = [[NSClassFromString(@"WOxTemplateBuilder") alloc] init];
509     return woxBuilder;
510   }
511   
512   {
513     static WOTemplateBuilder *woBuilder = nil;
514     if (woBuilder == nil) {
515       woBuilder =
516         [[NSClassFromString(@"WOWrapperTemplateBuilder") alloc] init];
517     }
518     return woBuilder;
519   }
520 }
521
522 - (WOTemplateBuilder *)templateBuilderForURL:(NSURL *)_url {
523   if ([_url isFileURL])
524     return [self templateBuilderForPath:[_url path]];
525   
526   [self logWithFormat:@"only supports file URLs: %@", _url];
527   return nil;
528 }
529
530 - (BOOL)load {
531   WOTemplateBuilder *builder;
532   NSURL *url;
533
534   if (self->path == nil)
535     /* a pathless component (a component without a template file) */
536     return YES;
537   
538   /*
539     Note: the URL can either point directly to the .wo or .wox file entry or
540           it can point to a .lproj inside a .wo (eg Main.wo/English.lproj)
541     Note: actually the WOTemplateBuilder only supports file URLs in the moment,
542           it just checks the path extension to select the proper builder.
543   */
544   url = [self->path isKindOfClass:[NSURL class]]
545     ? (NSURL *)self->path
546     : [[[NSURL alloc] initFileURLWithPath:self->path] autorelease];
547   
548   if (debugOn) [self debugWithFormat:@"url: %@", [url absoluteString]];
549   
550   // TODO: maybe we should move the builder selection to the resource-manager
551   builder = [self templateBuilderForURL:url];
552   if (debugOn) [self debugWithFormat:@"builder: %@", builder];
553   
554   self->template = [[builder buildTemplateAtURL:url] retain];
555   if (debugOn) [self debugWithFormat:@"template: %@", self->template];
556   
557   return self->template ? YES : NO;
558 }
559
560 /* debugging */
561
562 - (BOOL)isDebuggingEnabled {
563   return debugOn;
564 }
565
566 /* description */
567
568 - (NSString *)description {
569   NSMutableString *ms = [NSMutableString stringWithCapacity:64];
570
571   [ms appendFormat:@"<0x%08X[%@]:", self, NSStringFromClass([self class])];
572   
573   if (self->name)    [ms appendFormat:@" name=%@", self->name];
574   if (self->path)    [ms appendFormat:@" path=%@", self->path];
575   if (self->baseUrl) [ms appendFormat:@" base=%@", self->baseUrl];
576   if (self->frameworkName) 
577     [ms appendFormat:@" framework=%@", self->frameworkName];
578   
579   if (!self->template) [ms appendString:@" no-template"];
580   [ms appendString:@">"];
581   return ms;
582 }
583
584 @end /* WOComponentDefinition */
585
586 @implementation WONoContentElement
587
588 - (id)initWithElementName:(NSString *)_elementName
589   attributes:(NSDictionary *)_attributes
590   contentElements:(NSArray *)_subElements
591   componentDefinition:(WOComponentDefinition *)_cdef
592 {
593   self->cdef    = [_cdef retain];
594   self->element = [_elementName copy];
595   return self;
596 }
597
598 - (void)dealloc {
599   [self->cdef    release];
600   [self->element release];
601   [super dealloc];
602 }
603
604 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
605   [_response appendContentHTMLString:@"<<missing element '"];
606   [_response appendContentHTMLString:self->element];
607   [_response appendContentHTMLString:@"' in component '"];
608   [_response appendContentHTMLString:[self->cdef componentName]];
609   [_response appendContentHTMLString:@"'>>"];
610 }
611
612 @end /* WONoContentElement */
613
614 @implementation _WOStaticHTMLElement
615
616 - (id)initWithBuffer:(const char *)_buffer length:(unsigned)_len {
617   if (StrClass == Nil)
618     StrClass = [NSString class];
619   
620   self->text = [[StrClass alloc] initWithCString:_buffer length:_len];
621   return self;
622 }
623
624 - (void)dealloc {
625   [self->text release];
626   [super dealloc];
627 }
628
629 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
630   if (self->text)
631     [_response appendContentString:self->text];
632 }
633
634 @end /* _WOStaticHTMLElement */