]> err.no Git - sope/blob - sope-appserver/NGObjWeb/WOComponentDefinition.m
enhanced wrapper template build to allow "wrapperless" wod templates
[sope] / sope-appserver / NGObjWeb / WOComponentDefinition.m
1 /*
2   Copyright (C) 2000-2004 SKYRIX Software AG
3
4   This file is part of OpenGroupware.org.
5
6   OGo is free software; you can redistribute it and/or modify it under
7   the terms of the GNU Lesser General Public License as published by the
8   Free Software Foundation; either version 2, or (at your option) any
9   later version.
10
11   OGo is distributed in the hope that it will be useful, but WITHOUT ANY
12   WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14   License for more details.
15
16   You should have received a copy of the GNU Lesser General Public
17   License along with OGo; see the file COPYING.  If not, write to the
18   Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19   02111-1307, USA.
20 */
21
22 #include <NGObjWeb/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 logWithFormat:
133               @"ERROR(%s): "
134               @"Could not instantiate child fault %@, component: '%@'",
135               __PRETTY_FUNCTION__, key, [childInfo componentName]];
136     }
137   }
138   return childComponents;
139 }
140
141 - (id)initWithName:(NSString *)_cname
142   template:(WOTemplate *)_template
143   inContext:(WOContext *)_ctx
144 {
145   // Note: the _template can be nil and will then get looked up dynamically!
146   [self setName:_cname];
147   if ((self = [self initWithContext:_ctx])) {
148     NSMutableDictionary *childComponents;
149     NSArray *langs;
150     
151     langs = [self hasSession]
152       ? [[self session] languages]
153       : [[_ctx request] browserLanguages];
154     
155     childComponents = [self instantiateChildComponentsInTemplate:_template
156                             languages:langs];
157     [self setSubComponents:childComponents];
158     [self setTemplate:_template];
159   }
160   return self;
161 }
162
163 - (id)initWithComponentDefinition:(WOComponentDefinition *)_cdef 
164   inContext:(WOContext *)_ctx
165 {
166   /* 
167      HACK HACK HACK CD: 
168      We reuse the wocVariables ivar to pass over the component definition to 
169      the component which will then call -_finishInitializingComponent: on the
170      definition for applying the .woo.
171      
172      Sideeffects: if a component subclass uses extra vars prior calling
173      WOComponent -init, it will run into "issues".
174   */
175   NSAssert(self->wocVariables == nil,
176            @"extra variables dict is already set! cannot transfer component "
177            @"definition in that variable (use the HACK)");
178   self->wocVariables = (id)[_cdef retain];
179   
180   return [self initWithName:[_cdef componentName]
181                template:[_cdef template]
182                inContext:_ctx];
183 }
184
185 @end /* WOComponent(InfoSetup) */
186
187 @implementation WOComponentDefinition
188
189 static BOOL debugOn     = NO;
190 static BOOL profLoading = NO;
191 static BOOL enableClassLessComponents = NO;
192 static BOOL enableWOOFiles            = NO;
193 static NSArray *woxExtensions = nil;
194
195 + (int)version {
196   return 4;
197 }
198
199 + (void)initialize {
200   static BOOL isInitialized = NO;
201   NSUserDefaults *ud;
202   if (isInitialized) return;
203   isInitialized = YES;
204   ud = [NSUserDefaults standardUserDefaults];
205     
206   StrClass    = [NSString      class];
207   DictClass   = [NSMutableDictionary class];
208   AssocClass  = [WOAssociation class];
209   NumberClass = [NSNumber      class];
210   DateClass   = [NSDate        class];
211     
212   yesNum = [[NumberClass numberWithBool:YES] retain];
213   noNum  = [[NumberClass numberWithBool:NO]  retain];
214   
215   profLoading = [[ud objectForKey:@"WOProfileLoading"] boolValue];
216   enableClassLessComponents = 
217     [ud boolForKey:@"WOEnableComponentsWithoutClasses"];
218   enableWOOFiles = [ud boolForKey:@"WOComponentLoadWOOFiles"];
219   debugOn        = [ud boolForKey:@"WODebugComponentDefinition"];
220   woxExtensions  = [[ud arrayForKey:@"WOxFileExtensions"] copy];
221 }
222
223 - (id)initWithName:(NSString *)_name
224   path:(NSString *)_path
225   baseURL:(NSURL *)_baseUrl
226   frameworkName:(NSString *)_frameworkName
227 {
228   /* 
229      this method is usually called by WOResourceManager
230      (_definitionWithName:...) 
231   */
232   if ((self = [super init])) {
233     /*
234       'name'    is the name of the component
235       'path'    contains a string or a NSURL with the location of the directory
236                 containing the component - TODO: explain who calculates that!
237       'baseURL' contains a URL like /AppName/FrameworkName/Component/Eng.lProj
238                 (the external URL of the component, not sure whether this is
239                  actually used somewhere)
240     */
241     NSZone *z = [self zone];
242     
243     self->name          = [_name          copyWithZone:z];
244     self->path          = [_path          copyWithZone:z];
245     self->baseUrl       = [_baseUrl       copyWithZone:z];
246     self->frameworkName = [_frameworkName copyWithZone:z];
247   
248     if (debugOn) {
249       [self debugWithFormat:
250               @"init: '%@' path='%@'\n  URL='%@'\n  framework='%@'",
251               self->name, self->path, [self->baseUrl absoluteString], 
252               self->frameworkName];
253     }
254     
255     if (![self load]) { /* TODO: is this really required? */
256       [self release];
257       return nil;
258     }
259   }
260   return self;
261 }
262
263 - (id)init {
264   [self debugWithFormat:@"ERROR: called -init on WOComponentDefinition!"];
265   [self release];
266   return nil;
267 }
268
269 - (void)dealloc {
270   [self->template      release];
271   [self->name          release];
272   [self->path          release];
273   [self->baseUrl       release];
274   [self->frameworkName release];
275   [super dealloc];
276 }
277
278 /* accessors */
279
280 - (Class)componentClassForScript:(WOComponentScript *)_script {
281   return Nil;
282 }
283
284 - (void)setComponentClass:(Class)_class {
285   self->componentClass = _class;
286 }
287 - (Class)componentClass {
288   if (self->componentClass == Nil)
289     self->componentClass = NSClassFromString(self->name);
290   
291   if (self->componentClass != Nil)
292     return self->componentClass;
293
294   if (self->name == nil)
295     return Nil;
296   
297   if ([self->name isAbsolutePath])
298         ;
299   else if ([self->name rangeOfString:@"."].length > 0)
300         ;
301   else if (enableClassLessComponents)
302         ;
303   else {
304     [self logWithFormat:@"Note: did not find component class with name '%@'",
305             self->name];
306   }
307   return Nil;
308 }
309 - (NSString *)componentName {
310   return self->name;
311 }
312
313 - (WOTemplate *)template {
314   return self->template;
315 }
316
317 - (NSString *)path {
318   return self->path;
319 }
320
321 - (NSURL *)baseURL {
322   return self->baseUrl;
323 }
324 - (NSString *)frameworkName {
325   return self->frameworkName;
326 }
327
328 /* caching */
329
330 - (void)touch {
331   self->lastTouch = [DateClass timeIntervalSinceReferenceDate];
332 }
333
334 - (NSTimeInterval)lastTouch {
335   return self->lastTouch;
336 }
337
338 /* instantiation */
339
340 - (BOOL)_checkComponentClassValidity:(Class)cClass {
341 #if 0
342   /* this make no sense, need -isSubclassOfClass: ..,
343      class instances are never isKindOfClass:WOElement ... 
344   */
345   {
346     static Class WOElementClass = Nil;
347     if (WOElementClass == Nil) WOElementClass = [WOElement class];
348     if (![cClass isKindOfClass:WOElementClass] && cClass != nil) {
349       [self logWithFormat:@"WARNING(%s:%i): "
350             @"component class %@ is not a subclass of WOElement !",
351             __PRETTY_FUNCTION__, __LINE__,
352             NSStringFromClass(cClass)];
353       return NO;
354     }
355   }
356 #endif
357   return YES;
358 }
359 - (BOOL)_checkComponentValidity:(id)component class:(Class)cClass {
360   if (![component isKindOfClass:cClass] && component != nil) {
361     NSLog(@"WARNING2(%s:%i): component %@ is not a subclass of "
362           @"component class %@ !",
363           __PRETTY_FUNCTION__, __LINE__,
364           component, NSStringFromClass(cClass));
365     return NO;
366   }
367   return YES;
368 }
369
370 - (void)_applyWOOVariables:(NSDictionary *)_vars
371   onComponent:(WOComponent *)_component 
372 {
373   EOKeyValueUnarchiver *unarchiver;
374   NSAutoreleasePool    *pool;
375   NSEnumerator *keys;
376   NSString     *key;
377   
378   pool = [[NSAutoreleasePool alloc] init];
379   
380   unarchiver = 
381     [[[EOKeyValueUnarchiver alloc] initWithDictionary:_vars] autorelease];
382   [unarchiver setDelegate:_component];
383   
384   keys = [_vars keyEnumerator];
385   while ((key = [keys nextObject])) {
386     id object;
387     
388     object = [unarchiver decodeObjectForKey:key];
389     [_component takeValue:object forKey:key];
390   }
391   [unarchiver finishInitializationOfObjects];
392   [unarchiver awakeObjects];
393
394   [pool release];
395 }
396
397 - (void)_applyWOOVariablesOnComponent:(WOComponent *)_component {
398   /* 
399      Note: we still need this, as components are not required to load the
400            template at all!
401   */
402   NSString     *wooPath;
403   NSDictionary *woo;
404
405   wooPath = [[_component path] stringByAppendingPathExtension:@"woo"];
406   if (![[NSFileManager defaultManager] fileExistsAtPath:wooPath])
407     return;
408   
409   if ((woo = [NSDictionary dictionaryWithContentsOfFile:wooPath]) == nil) {
410     [self logWithFormat:@"ERROR: could not load .woo-file: '%@'", wooPath];
411     return;
412   }
413   
414   [self _applyWOOVariables:[woo objectForKey:@"variables"]
415         onComponent:_component];
416 }
417
418 - (void)_finishInitializingComponent:(WOComponent *)_component {
419   if (self->baseUrl)
420     [_component setBaseURL:self->baseUrl];
421
422   if (enableWOOFiles) {
423     if (self->template) {
424       [self _applyWOOVariables:
425               [self->template keyValueArchivedTemplateVariables]
426             onComponent:_component];
427     }
428     else
429       [self _applyWOOVariablesOnComponent:_component];
430   }
431 }
432
433 - (WOComponent *)instantiateWithResourceManager:(WOResourceManager *)_rm
434   languages:(NSArray *)_languages
435 {
436   WOComponent       *component = nil;
437   Class             cClass;
438   WOComponentScript *script;
439   
440   if ((script = [self->template componentScript])) 
441     cClass = NSClassFromString(@"WOScriptedComponent");
442   else
443     cClass = [self componentClass];
444   
445   if (cClass == nil) {
446     NSString *tmpPath;
447
448     if (enableClassLessComponents) {
449       [self debugWithFormat:@"Note: missing class for component: '%@'",
450               [self componentName]];
451     }
452     else {
453       [self logWithFormat:@"Note: missing class for component: '%@'",
454               [self componentName]];
455     }
456     
457     tmpPath = [self->name stringByAppendingPathExtension:@"html"];
458     tmpPath = [self->path stringByAppendingPathComponent:tmpPath];
459     
460     if ([[NSFileManager defaultManager] fileExistsAtPath:tmpPath]) {
461       cClass = [WOComponent class];
462     }
463     else {
464       [self debugWithFormat:@"Note: did not find .html template at path: '%@'",
465               tmpPath];
466     }
467   }
468   
469   if (![self _checkComponentClassValidity:cClass]) {
470     [self logWithFormat:@"Component Class '%@' is not valid.", cClass];
471     return nil;
472   }
473   
474   /* instantiate object (this will call _finishInitializingComponent) */
475   
476   component = [[cClass alloc] initWithComponentDefinition:self
477                               inContext:[[WOApplication application] context]];
478   component = [component autorelease];
479   if (component == nil)
480     return nil;
481   
482   /* check validity */
483   
484   if (debugOn)
485     [self _checkComponentValidity:component class:cClass];
486
487   if (debugOn) {
488     if (![component isKindOfClass:cClass]) {
489       [self debugWithFormat:
490               @"WARNING(%s:%i): component '%@' is not a subclass of "
491           @"component class '%@' !",
492           __PRETTY_FUNCTION__, __LINE__,
493           component, NSStringFromClass(cClass)];
494     }
495   }
496   return component;
497 }
498
499 /* templates */
500
501 - (WOTemplateBuilder *)templateBuilderForPath:(NSString *)_path {
502   NSString *ext;
503   
504   if ([_path length] == 0)
505     return nil;
506   
507   ext = [_path pathExtension];
508   if ([woxExtensions containsObject:ext]) {
509     static WOTemplateBuilder *woxBuilder = nil;
510     if (woxBuilder == nil)
511       woxBuilder = [[NSClassFromString(@"WOxTemplateBuilder") alloc] init];
512     return woxBuilder;
513   }
514   
515   {
516     static WOTemplateBuilder *woBuilder = nil;
517     if (woBuilder == nil) {
518       woBuilder =
519         [[NSClassFromString(@"WOWrapperTemplateBuilder") alloc] init];
520     }
521     return woBuilder;
522   }
523 }
524
525 - (WOTemplateBuilder *)templateBuilderForURL:(NSURL *)_url {
526   if ([_url isFileURL])
527     return [self templateBuilderForPath:[_url path]];
528   
529   [self logWithFormat:@"only supports file URLs: %@", _url];
530   return nil;
531 }
532
533 - (BOOL)load {
534   WOTemplateBuilder *builder;
535   NSURL *url;
536
537   if (self->path == nil)
538     /* a pathless component (a component without a template file) */
539     return YES;
540   
541   /*
542     Note: the URL can either point directly to the .wo or .wox file entry or
543           it can point to a .lproj inside a .wo (eg Main.wo/English.lproj)
544     Note: actually the WOTemplateBuilder only supports file URLs in the moment,
545           it just checks the path extension to select the proper builder.
546   */
547   url = [self->path isKindOfClass:[NSURL class]]
548     ? (NSURL *)self->path
549     : [[[NSURL alloc] initFileURLWithPath:self->path] autorelease];
550   
551   if (debugOn) [self debugWithFormat:@"url: %@", [url absoluteString]];
552   
553   // TODO: maybe we should move the builder selection to the resource-manager
554   builder = [self templateBuilderForURL:url];
555   if (debugOn) [self debugWithFormat:@"builder: %@", builder];
556   
557   self->template = [[builder buildTemplateAtURL:url] retain];
558   if (debugOn) [self debugWithFormat:@"template: %@", self->template];
559   
560   return self->template ? YES : NO;
561 }
562
563 /* debugging */
564
565 - (BOOL)isDebuggingEnabled {
566   return debugOn;
567 }
568
569 /* description */
570
571 - (NSString *)description {
572   NSMutableString *ms = [NSMutableString stringWithCapacity:64];
573
574   [ms appendFormat:@"<0x%08X[%@]:", self, NSStringFromClass([self class])];
575   
576   if (self->name)    [ms appendFormat:@" name=%@", self->name];
577   if (self->path)    [ms appendFormat:@" path=%@", self->path];
578   if (self->baseUrl) [ms appendFormat:@" base=%@", self->baseUrl];
579   if (self->frameworkName) 
580     [ms appendFormat:@" framework=%@", self->frameworkName];
581   
582   if (!self->template) [ms appendString:@" no-template"];
583   [ms appendString:@">"];
584   return ms;
585 }
586
587 @end /* WOComponentDefinition */
588
589 @implementation WONoContentElement
590
591 - (id)initWithElementName:(NSString *)_elementName
592   attributes:(NSDictionary *)_attributes
593   contentElements:(NSArray *)_subElements
594   componentDefinition:(WOComponentDefinition *)_cdef
595 {
596   self->cdef    = [_cdef retain];
597   self->element = [_elementName copy];
598   return self;
599 }
600
601 - (void)dealloc {
602   [self->cdef    release];
603   [self->element release];
604   [super dealloc];
605 }
606
607 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
608   [_response appendContentHTMLString:@"<<missing element '"];
609   [_response appendContentHTMLString:self->element];
610   [_response appendContentHTMLString:@"' in component '"];
611   [_response appendContentHTMLString:[self->cdef componentName]];
612   [_response appendContentHTMLString:@"'>>"];
613 }
614
615 @end /* WONoContentElement */
616
617 @implementation _WOStaticHTMLElement
618
619 - (id)initWithBuffer:(const char *)_buffer length:(unsigned)_len {
620   if (StrClass == Nil)
621     StrClass = [NSString class];
622   
623   self->text = [[StrClass alloc] initWithCString:_buffer length:_len];
624   return self;
625 }
626
627 - (void)dealloc {
628   [self->text release];
629   [super dealloc];
630 }
631
632 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
633   if (self->text)
634     [_response appendContentString:self->text];
635 }
636
637 @end /* _WOStaticHTMLElement */