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