]> err.no Git - sope/blob - sope-appserver/NGObjWeb/WOComponentDefinition.m
ivar extensions to NGObjWeb core classes, moved SoOFS to separate project
[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 // $Id$
22
23 #include "WOComponentDefinition.h"
24 #include "WOComponent+private.h"
25 #include "WOComponentFault.h"
26 #include "WOScriptedComponent.h"
27 #include <NGObjWeb/WOAssociation.h>
28 #include <NGObjWeb/WOApplication.h>
29 #include <NGObjWeb/WOElement.h>
30 #include <NGObjWeb/WOResponse.h>
31 #include <NGObjWeb/WOResourceManager.h>
32 #include "common.h"
33 #include <EOControl/EOControl.h>
34
35 #include <NGObjWeb/WOTemplateBuilder.h>
36
37 static Class    StrClass    = Nil;
38 static Class    DictClass   = Nil;
39 static Class    AssocClass  = Nil;
40 static Class    NumberClass = Nil;
41 static Class    DateClass   = Nil;
42 static NSNumber *yesNum     = nil;
43 static NSNumber *noNum      = nil;
44
45 @interface WOComponent(UsedPrivates)
46 - (void)setBaseURL:(id)_url;
47 @end
48
49 @interface WONoContentElement : WOElement
50 {
51   WOComponentDefinition *cdef;
52   NSString *element;
53 }
54 - (id)initWithElementName:(NSString *)_elementName
55   attributes:(NSDictionary *)_attributes
56   contentElements:(NSArray *)_subElements
57   componentDefinition:(WOComponentDefinition *)_cdef;
58 @end
59
60 @interface _WOStaticHTMLElement : WOElement
61 {
62   NSString *text;
63 }
64 - (id)initWithBuffer:(const char *)_buffer length:(unsigned)_len;
65 @end
66
67 @interface WOComponentDefinition(PrivateMethods)
68
69 - (BOOL)load;
70
71 @end
72
73 #include <NGObjWeb/WOContext.h>
74 #include <NGObjWeb/WORequest.h>
75 #include <NGObjWeb/WOSession.h>
76
77 /*
78   TODO:
79   
80   WO's instantiation method is 
81     - componentInstanceInContext:forComponentReference:
82   with the primary being
83     - _componentInstanceInContext:forComponentReference:
84   
85   Maybe we should change to that. Currently this flow is a bit broken because
86   the resourcemanager sits in the middle, though I'm pretty sure that some
87   older WO used WOResourceManager just like we do in the moment.
88 */
89
90 @implementation WOComponent(InfoSetup)
91
92 - (Class)componentFaultClass {
93   return [WOComponentFault class];
94 }
95
96 - (NSMutableDictionary *)instantiateChildComponentsInTemplate:(WOTemplate *)_t
97   languages:(NSArray *)_languages
98 {
99   NSMutableDictionary *childComponents = nil;
100   WOResourceManager *_rm;
101   WOTemplate *tmpl;
102   NSEnumerator *keys;
103   NSString     *key;
104   
105   if ((tmpl = _t) == nil)
106     return nil;
107   
108   _rm = [[WOApplication application] resourceManager];
109   
110   if ([tmpl hasSubcomponentInfos] == 0)
111     return nil;
112   
113   keys = [tmpl infoKeyEnumerator];
114   while ((key = [keys nextObject])) {
115     WOSubcomponentInfo *childInfo = nil;
116     WOComponentFault   *child     = nil;
117       
118     childInfo = [tmpl subcomponentInfoForKey:key];
119     
120     child = [[WOComponentFault alloc]
121               initWithResourceManager:nil //_rm
122               pageName:[childInfo componentName]
123               languages:_languages
124               bindings:[childInfo bindings]];
125
126     if (child) {
127       if (childComponents == nil)
128         childComponents = [NSMutableDictionary dictionaryWithCapacity:16];
129         
130       [childComponents setObject:child forKey:key];
131       [child release];
132     }
133     else {
134       [self logWithFormat:
135               @"ERROR(%s): "
136               @"Could not instantiate child fault %@, component: '%@'",
137               __PRETTY_FUNCTION__, key, [childInfo componentName]];
138     }
139   }
140   return childComponents;
141 }
142
143 - (id)initWithName:(NSString *)_cname
144   template:(WOTemplate *)_template
145   inContext:(WOContext *)_ctx
146 {
147   // Note: the _template can be nil and will then get looked up dynamically!
148   [self setName:_cname];
149   if ((self = [self initWithContext:_ctx])) {
150     NSMutableDictionary *childComponents;
151     NSArray *langs;
152     
153     langs = [self hasSession]
154       ? [[self session] languages]
155       : [[_ctx request] browserLanguages];
156     
157     childComponents = [self instantiateChildComponentsInTemplate:_template
158                             languages:langs];
159     [self setSubComponents:childComponents];
160     [self setTemplate:_template];
161   }
162   return self;
163 }
164
165 - (id)initWithComponentDefinition:(WOComponentDefinition *)_cdef 
166   inContext:(WOContext *)_ctx
167 {
168   /* 
169      HACK HACK HACK CD: 
170      We reuse the wocVariables ivar to pass over the component definition to 
171      the component which will then call -_finishInitializingComponent: on the
172      definition for applying the .woo.
173      
174      Sideeffects: if a component subclass uses extra vars prior calling
175      WOComponent -init, it will run into "issues".
176   */
177   NSAssert(self->wocVariables == nil,
178            @"extra variables dict is already set! cannot transfer component "
179            @"definition in that variable (use the HACK)");
180   self->wocVariables = (id)[_cdef retain];
181   
182   return [self initWithName:[_cdef componentName]
183                template:[_cdef template]
184                inContext:_ctx];
185 }
186
187 @end /* WOComponent(InfoSetup) */
188
189 @implementation WOComponentDefinition
190
191 static BOOL debugOn     = NO;
192 static BOOL profLoading = NO;
193 static BOOL enableClassLessComponents = NO;
194 static BOOL enableWOOFiles            = NO;
195
196 + (int)version {
197   return 4;
198 }
199
200 + (void)initialize {
201   static BOOL isInitialized = NO;
202   NSUserDefaults *ud;
203   if (isInitialized) return;
204   isInitialized = YES;
205   ud = [NSUserDefaults standardUserDefaults];
206     
207   StrClass    = [NSString      class];
208   DictClass   = [NSMutableDictionary class];
209   AssocClass  = [WOAssociation class];
210   NumberClass = [NSNumber      class];
211   DateClass   = [NSDate        class];
212     
213   yesNum = [[NumberClass numberWithBool:YES] retain];
214   noNum  = [[NumberClass numberWithBool:NO]  retain];
215   
216   profLoading = [[ud objectForKey:@"WOProfileLoading"] boolValue];
217   enableClassLessComponents = 
218     [ud boolForKey:@"WOEnableComponentsWithoutClasses"];
219   enableWOOFiles = [ud boolForKey:@"WOComponentLoadWOOFiles"];
220   debugOn        = [ud boolForKey:@"WODebugComponentDefinition"];
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 = [WOScriptedComponent class];
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 - (BOOL)load {
502   WOTemplateBuilder *builder;
503   NSURL *url;
504
505   if (self->path == nil)
506     /* a pathless component (a component without a template file) */
507     return YES;
508   
509   /*
510     Note: the URL can either point directly to the .wo or .wox file entry or
511           it can point to a .lproj inside a .wo (eg Main.wo/English.lproj)
512     Note: actually the WOTemplateBuilder only supports file URLs in the moment,
513           it just checks the path extension to select the proper builder.
514   */
515   url = [self->path isKindOfClass:[NSURL class]]
516     ? (NSURL *)self->path
517     : [[[NSURL alloc] initFileURLWithPath:self->path] autorelease];
518   
519   if (debugOn) [self debugWithFormat:@"url: %@", [url absoluteString]];
520   
521   // TODO: maybe we should move the builder selection to the resource-manager
522   builder = [WOTemplateBuilder templateBuilderForURL:url];
523   if (debugOn) [self debugWithFormat:@"builder: %@", builder];
524   
525   self->template = [[builder buildTemplateAtURL:url] retain];
526   if (debugOn) [self debugWithFormat:@"template: %@", self->template];
527   
528   return self->template ? YES : NO;
529 }
530
531 /* debugging */
532
533 - (BOOL)isDebuggingEnabled {
534   return debugOn;
535 }
536
537 /* description */
538
539 - (NSString *)description {
540   NSMutableString *ms = [NSMutableString stringWithCapacity:64];
541
542   [ms appendFormat:@"<0x%08X[%@]:", self, NSStringFromClass([self class])];
543   
544   if (self->name)    [ms appendFormat:@" name=%@", self->name];
545   if (self->path)    [ms appendFormat:@" path=%@", self->path];
546   if (self->baseUrl) [ms appendFormat:@" base=%@", self->baseUrl];
547   if (self->frameworkName) 
548     [ms appendFormat:@" framework=%@", self->frameworkName];
549   
550   if (!self->template) [ms appendString:@" no-template"];
551   [ms appendString:@">"];
552   return ms;
553 }
554
555 @end /* WOComponentDefinition */
556
557 @implementation WONoContentElement
558
559 - (id)initWithElementName:(NSString *)_elementName
560   attributes:(NSDictionary *)_attributes
561   contentElements:(NSArray *)_subElements
562   componentDefinition:(WOComponentDefinition *)_cdef
563 {
564   self->cdef    = [_cdef retain];
565   self->element = [_elementName copy];
566   return self;
567 }
568
569 - (void)dealloc {
570   [self->cdef    release];
571   [self->element release];
572   [super dealloc];
573 }
574
575 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
576   [_response appendContentHTMLString:@"<<missing element '"];
577   [_response appendContentHTMLString:self->element];
578   [_response appendContentHTMLString:@"' in component '"];
579   [_response appendContentHTMLString:[self->cdef componentName]];
580   [_response appendContentHTMLString:@"'>>"];
581 }
582
583 @end /* WONoContentElement */
584
585 @implementation _WOStaticHTMLElement
586
587 - (id)initWithBuffer:(const char *)_buffer length:(unsigned)_len {
588   if (StrClass == Nil)
589     StrClass = [NSString class];
590   
591   self->text = [[StrClass alloc] initWithCString:_buffer length:_len];
592   return self;
593 }
594
595 - (void)dealloc {
596   [self->text release];
597   [super dealloc];
598 }
599
600 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
601   if (self->text)
602     [_response appendContentString:self->text];
603 }
604
605 @end /* _WOStaticHTMLElement */