]> err.no Git - sope/blob - sope-appserver/NGObjWeb/WOResourceManager.m
export WOComponentDefinition
[sope] / sope-appserver / NGObjWeb / WOResourceManager.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/WOResourceManager.h>
23 #include <NGObjWeb/WOComponentDefinition.h>
24 #include "WOComponent+private.h"
25 #include <NGObjWeb/WORequest.h>
26 #include <NGObjWeb/WOApplication.h>
27 #include "common.h"
28 #import <Foundation/NSNull.h>
29 #include "_WOStringTable.h"
30
31 /*
32   Component Discovery and Page Creation
33
34     All WO code uses either directly or indirectly the WOResourceManager's
35     -pageWithName:languages: method to instantiate WO components.
36
37     This methods works in three steps:
38       
39       1. discovery of files associated with the component
40       
41       2. creation of a proper WOComponentDefinition, which is some kind
42          of 'blueprint' or 'class' for components
43       
44       3. component instantiation using the definition
45     
46     All the instantiation/setup work is done by a component definition, the
47     resource manager is only responsible for managing those 'blueprint'
48     resources.
49
50     If you want to customize component creation, you can supply your
51     own WOComponentDefinition in a subclass of WOResourceManager by
52     overriding:
53       - (WOComponentDefinition *)definitionForComponent:(id)_name
54         inFramework:(NSString *)_frameworkName
55         languages:(NSArray *)_languages
56 */
57
58 /* 
59    Note: this was #if !COMPILE_FOR_GSTEP_MAKE - but there is no difference
60          between Xcode and gstep-make?!
61          The only possible difference might be that .wo wrappers are directly
62          in the bundle/framework root - but this doesn't relate to Resources.
63          
64          OK, this breaks gstep-make based template lookup which places .wo
65          wrappers in .woa/Resources/xxx.wo.
66          This is an issue because .wox are looked up in Contents/Resources
67          but .wo ones in just Resources.
68          
69          This issue should be fixed in recent woapp-gs.make ...
70 */
71 #if COCOA_Foundation_LIBRARY || NeXT_Foundation_LIBRARY
72 #  define RSRCDIR_CONTENTS 1
73 #endif
74
75 @implementation WOResourceManager
76
77 + (int)version {
78   return 4;
79 }
80
81 static Class    UrlClass             = Nil;
82 static NSString *resourcePrefix      = @"";
83 static NSString *rapidTurnAroundPath = nil;
84 static NSNull   *null                = nil;
85 static BOOL     debugOn                 = NO;
86 static BOOL     debugComponentLookup    = NO;
87 static BOOL     debugResourceLookup     = NO;
88 static BOOL     genMissingResourceLinks = NO;
89
90 + (void)initialize {
91   NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
92   static BOOL isInitialized = NO;
93   NSDictionary *defs;
94   if (isInitialized) return;
95   isInitialized = YES;
96     
97   null = [[NSNull null] retain];
98   UrlClass = [NSURL class];
99   
100   defs = [NSDictionary dictionaryWithObjectsAndKeys:
101                          [NSArray arrayWithObject:@"wo"],
102                          @"WOComponentExtensions",
103                        nil];
104   [ud registerDefaults:defs];
105   debugOn                 = [WOApplication isDebuggingEnabled];
106   debugComponentLookup    = [ud boolForKey:@"WODebugComponentLookup"];
107   debugResourceLookup     = [ud boolForKey:@"WODebugResourceLookup"];
108   genMissingResourceLinks = [ud boolForKey:@"WOGenerateMissingResourceLinks"];
109   rapidTurnAroundPath     = [[ud stringForKey:@"WOProjectDirectory"] copy];
110 }
111
112 static inline BOOL
113 _pathExists(WOResourceManager *self, NSFileManager *fm, NSString *path)
114 {
115   BOOL doesExist;
116   
117   if (self->existingPathes && (path != nil)) {
118     int i;
119     
120     i = (int)NSMapGet(self->existingPathes, path);
121     if (i == 0) {
122       doesExist = [fm fileExistsAtPath:path];
123       NSMapInsert(self->existingPathes, path, (void*)(doesExist ? 1 : 0xFF));
124     }
125     else
126       doesExist = i == 1 ? YES : NO;
127   }
128   else
129     doesExist = [fm fileExistsAtPath:path];
130   return doesExist;
131 }
132
133 + (void)setResourcePrefix:(NSString *)_prefix {
134   [resourcePrefix autorelease];
135   resourcePrefix = [_prefix copy];
136 }
137   
138 - (id)initWithPath:(NSString *)_path {
139 #if __APPLE__
140   if ([_path length] == 0) {
141     NSLog(@"ERROR(%s): missing path!", __PRETTY_FUNCTION__);
142     /* this doesn't work with subclasses which do not require a path ... */
143 #if 0
144     [self release];
145     return nil;
146 #endif
147   }
148 #endif
149   
150   if ((self = [super init])) {
151     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
152     NSString *rprefix = nil;
153     NSString *tmp;
154     
155     self->componentDefinitions =
156       NSCreateMapTable(NSObjectMapKeyCallBacks,
157                        NSObjectMapValueCallBacks,
158                        128);
159     self->stringTables = 
160       NSCreateMapTable(NSObjectMapKeyCallBacks,
161                        NSObjectMapValueCallBacks,
162                        16);
163     
164     tmp = [_path stringByStandardizingPath];
165     if (tmp) _path = tmp;
166     
167     self->base = [_path copy];
168     
169     if ([WOApplication isCachingEnabled]) {
170       self->existingPathes = NSCreateMapTable(NSObjectMapKeyCallBacks,
171                                               NSIntMapValueCallBacks,
172                                               256);
173     }
174     
175     rprefix = [ud stringForKey:@"WOResourcePrefix"];
176     if (rprefix) [[self class] setResourcePrefix:rprefix];
177   }
178   return self;
179 }
180 - (id)init {
181   return [self initWithPath:[[NGBundle mainBundle] bundlePath]];
182 }
183
184 - (void)dealloc {
185   if (self->existingPathes)       NSFreeMapTable(self->existingPathes);
186   if (self->stringTables)         NSFreeMapTable(self->stringTables);
187   if (self->componentDefinitions) NSFreeMapTable(self->componentDefinitions);
188   if (self->keyedResources)       NSFreeMapTable(self->keyedResources);
189   [self->w3resources release];
190   [self->resources   release];
191   [self->base        release];
192   [super dealloc];
193 }
194
195 /* debugging */
196
197 - (BOOL)isDebuggingEnabled {
198   return debugOn;
199 }
200
201 /* path methods */
202
203 - (NSFileManager *)fileManager {
204   static NSFileManager *fm = nil;
205   if (fm == nil)
206     fm = [[NSFileManager defaultManager] retain];
207   return fm;
208 }
209
210 - (NSString *)basePath {
211   return self->base;
212 }
213
214 - (NSString *)resourcesPath {
215   NSFileManager *fm;
216   
217   if (self->resources)
218     return self->resources;
219   
220   fm = [self fileManager];
221   if ([self->base length] > 0) {
222     if (![fm fileExistsAtPath:self->base]) {
223       NSLog(@"WARNING(%s): Resources base path '%@' does not exist !",
224             __PRETTY_FUNCTION__, self->base);
225       return nil;
226     }
227   }
228   
229 #if RSRCDIR_CONTENTS
230   if ([rapidTurnAroundPath length] > 0) {
231     /* 
232       In rapid turnaround mode, first check for a Resources subdir in the
233       project directory, then directly in the project dir.
234       Note: you cannot have both! Either put stuff in a Resources subdir *or*
235             in the project dir.
236     */
237     NSString *tmp;
238     BOOL isDir;
239     
240     tmp = [rapidTurnAroundPath stringByAppendingPathComponent:@"Resources"];
241     if (![fm fileExistsAtPath:tmp isDirectory:&isDir])
242       isDir = NO;
243     if (!isDir)
244       tmp = rapidTurnAroundPath;
245     
246     self->resources = [tmp copy];
247   }
248   else {
249     self->resources =
250       [[[self->base stringByAppendingPathComponent:@"Contents"]
251                     stringByAppendingPathComponent:@"Resources"] 
252                     copy];
253   }
254 #else
255   self->resources =
256     [[self->base stringByAppendingPathComponent:@"Resources"] copy];
257 #endif
258   
259   if ([self->resources length] > 0) {
260     if (![fm fileExistsAtPath:self->resources]) {
261       [self debugWithFormat:
262               @"WARNING(%s): Resources path %@ does not exist !",
263               __PRETTY_FUNCTION__, self->resources];
264       [self->resources release]; self->resources = nil;
265     }
266     else if (self->existingPathes && (self->resources != nil))
267       NSMapInsert(self->existingPathes, self->resources, (void*)1);
268   }
269   return self->resources;
270 }
271
272 - (NSString *)resourcesPathForFramework:(NSString *)_fw {
273   if (_fw == nil) 
274     return [self resourcesPath];
275   
276 #if RSRCDIR_CONTENTS
277   return [[_fw stringByAppendingPathComponent:@"Contents"]
278                stringByAppendingPathComponent:@"Resources"];
279 #else
280   return [_fw stringByAppendingPathComponent:@"Resources"];
281 #endif
282 }
283
284 - (NSString *)webServerResourcesPath {
285   NSFileManager *fm;
286   
287   if (self->w3resources)
288     return self->w3resources;
289
290 #if GNUSTEP_BASE_LIBRARY && 0
291   self->w3resources =
292     [[self->base stringByAppendingPathComponent:@"Resources/WebServer"] copy];
293 #else  
294   self->w3resources =
295     [[self->base stringByAppendingPathComponent:@"WebServerResources"] copy];
296 #endif
297   
298   fm = [self fileManager];
299   if ([self->w3resources length] == 0)
300     return nil;
301   
302   if (![fm fileExistsAtPath:self->w3resources]) {
303     static BOOL didLog = NO;
304     if (!didLog) {
305       didLog = YES;
306       [self debugWithFormat:
307               @"WARNING(%s): WebServerResources path '%@' does not exist !",
308               __PRETTY_FUNCTION__, self->w3resources];
309     }
310     [self->w3resources release]; self->w3resources = nil;
311   }
312   else if (self->existingPathes && (self->w3resources != nil))
313     NSMapInsert(self->existingPathes, self->w3resources, (void*)1);
314   
315   if (debugResourceLookup)
316     [self logWithFormat:@"WebServerResources: '%@'", self->w3resources];
317   return self->w3resources;
318 }
319
320 - (NSString *)pathForResourceNamed:(NSString *)_name
321   inFramework:(NSString *)_frameworkName
322   languages:(NSArray *)_languages
323 {
324   NSFileManager *fm;
325   NSString      *resource = nil;
326   unsigned      langCount;
327   NSString      *w3rp, *rp;
328   
329   if (debugResourceLookup) {
330     [self logWithFormat:@"lookup '%@' bundle=%@ languages=%@", 
331           _name, _frameworkName, [_languages componentsJoinedByString:@","]];
332   }
333   
334   fm        = [self fileManager];
335   langCount = [_languages count];
336   
337   if ((w3rp = [self webServerResourcesPath])) {
338     NSString *langPath = nil;
339     unsigned i;
340     
341     // first check Language.lproj in WebServerResources
342     for (i = 0; i < langCount; i++) {
343       langPath = [_languages objectAtIndex:i];
344       langPath = [langPath stringByAppendingPathExtension:@"lproj"];
345       langPath = [w3rp stringByAppendingPathComponent:langPath];
346       
347       if (!_pathExists(self, fm, langPath)) {
348         if (debugResourceLookup) {
349           [self logWithFormat:
350                   @"  no language project for '%@' in WebServerResources: %@",
351                   [_languages objectAtIndex:i],resource];
352         }
353         continue;
354       }
355       
356       resource = [langPath stringByAppendingPathComponent:_name];
357       
358       if (debugResourceLookup) 
359         [self logWithFormat:@"  check in WebServerResources: %@", resource];
360       if (_pathExists(self, fm, resource))
361         return resource;
362     }
363     
364     /* next check in WebServerResources itself */
365     resource = [w3rp stringByAppendingPathComponent:_name];
366     if (debugResourceLookup) 
367       [self logWithFormat:@"  check in WebServerResources-flat: %@", resource];
368     if (_pathExists(self, fm, resource))
369       return resource;
370   }
371   
372   if ((rp = [self resourcesPathForFramework:_frameworkName])) {
373     NSString *langPath = nil;
374     unsigned i;
375     
376     if (debugResourceLookup) [self logWithFormat:@"  path %@", rp];
377     
378     // first check Language.lproj in Resources
379     for (i = 0; i < langCount; i++) {
380       langPath = [_languages objectAtIndex:i];
381       langPath = [langPath stringByAppendingPathExtension:@"lproj"];
382       langPath = [rp stringByAppendingPathComponent:langPath];
383       
384       if (_pathExists(self, fm, langPath)) {
385         resource = [langPath stringByAppendingPathComponent:_name];
386
387         if (debugResourceLookup) 
388           [self logWithFormat:@"  check in Resources: %@", resource];
389         if (_pathExists(self, fm, resource))
390           return resource;
391       }
392     }
393     
394     // next check in Resources itself
395     resource = [rp stringByAppendingPathComponent:_name];
396     if (debugResourceLookup) 
397       [self logWithFormat:@"  check in Resources-flat: %@", resource];
398     if (_pathExists(self, fm, resource))
399       return resource;
400   }
401   
402   /* and last check in the application directory */
403   if (_pathExists(self, fm, self->base)) {
404     resource = [self->base stringByAppendingPathComponent:_name];
405     if (_pathExists(self, fm, resource))
406       return resource;
407   }
408   return nil;
409 }
410
411 - (NSString *)pathForResourceNamed:(NSString *)_name {
412   IS_DEPRECATED;
413   return [self pathForResourceNamed:_name inFramework:nil languages:nil];
414 }
415
416 - (NSString *)pathForResourceNamed:(NSString *)_name ofType:(NSString *)_type {
417   _name = [_name stringByAppendingPathExtension:_type];
418   return [self pathForResourceNamed:_name];
419 }
420
421 /* URL methods */
422
423 - (NSString *)urlForResourceNamed:(NSString *)_name
424   inFramework:(NSString *)_frameworkName
425   languages:(NSArray *)_languages
426   request:(WORequest *)_request
427 {
428   WOApplication *app;
429   NSString *resource = nil, *tmp;
430   
431   app = [WOApplication application];
432   
433   if (_languages == nil)
434     _languages = [_request browserLanguages];
435   
436   resource = [self pathForResourceNamed:_name
437                    inFramework:_frameworkName
438                    languages:_languages];
439 #if RSRCDIR_CONTENTS
440   if ([resource rangeOfString:@"/Contents/"].length > 0) {
441     resource = [resource stringByReplacingString:@"/Contents"
442                          withString:@""];
443   }
444 #endif
445 #if 0
446   tmp = [resource stringByStandardizingPath];
447   if (tmp) resource = tmp;
448 #endif
449   
450   if (resource) {
451     NSString *path = nil, *sbase;
452     unsigned len;
453     
454     sbase = self->base;
455     tmp  = [sbase commonPrefixWithString:resource options:0];
456     
457     len  = [tmp length];
458     path = [sbase    substringFromIndex:len];
459     tmp  = [resource substringFromIndex:len];
460     if (([path length] > 0) && ![tmp hasPrefix:@"/"] && ![tmp hasPrefix:@"\\"])
461       path = [path stringByAppendingString:@"/"];
462     path = [path stringByAppendingString:tmp];
463
464 #ifdef __WIN32__
465     {
466       NSArray *cs;
467       cs   = [path componentsSeparatedByString:@"\\"];
468       path = [cs componentsJoinedByString:@"/"];
469     }
470 #endif
471     
472     if (path) {
473       static NSString *suffix = nil;
474       NSMutableString *url = nil;
475
476       if (suffix == nil) {
477         NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
478         suffix = [ud stringForKey:@"WOApplicationSuffix"];
479       }
480       
481       url = [[NSMutableString alloc] initWithCapacity:256];
482 #if 0
483       [url appendString:[_request adaptorPrefix]];
484 #endif
485       if (resourcePrefix)
486         [url appendString:resourcePrefix];
487       if (![url hasSuffix:@"/"]) [url appendString:@"/"];
488       [url appendString:app ? [app name] : [_request applicationName]];
489       [url appendString:suffix];
490       if (![path hasPrefix:@"/"]) [url appendString:@"/"];
491       [url appendString:path];
492       
493       path = [url copy];
494       [url release];
495       
496       return [path autorelease];
497     }
498   }
499   
500   if (genMissingResourceLinks) {
501     return [NSString stringWithFormat:
502                        @"/missingresource?name=%@&application=%@",
503                        _name, app ? [app name] : [_request applicationName]];
504   }
505   return nil;
506 }
507
508 - (NSString *)urlForResourceNamed:(NSString *)_name {
509   IS_DEPRECATED;
510   return [self urlForResourceNamed:_name
511                inFramework:nil
512                languages:nil
513                request:nil];
514 }
515 - (NSString *)urlForResourceNamed:(NSString *)_name ofType:(NSString *)_type {
516   return [self urlForResourceNamed:
517                  [_name stringByAppendingPathExtension:_type]];
518 }
519
520 /* string tables */
521
522 - (NSString *)stringForKey:(NSString *)_key
523   inTableNamed:(NSString *)_tableName
524   withDefaultValue:(NSString *)_defaultValue
525   inFramework:(NSString *)_framework
526   languages:(NSArray *)_languages;
527 {
528   NSFileManager  *fm;
529   _WOStringTable *table     = nil;
530   NSString       *path      = nil;
531   
532   fm = [self fileManager];
533   
534   if (_tableName == nil)
535     _tableName = @"default";
536
537   /* take a look whether a matching table is already loaded */
538   
539   path = [_tableName stringByAppendingPathExtension:@"strings"];
540   path = [self pathForResourceNamed:path inFramework:_framework 
541                languages:_languages];
542   
543   if (path != nil) {
544     if ((table = NSMapGet(self->stringTables, path)) == NULL) {
545       if ([fm fileExistsAtPath:path]) {
546         table = [_WOStringTable allocWithZone:[self zone]]; /* for gcc */
547         table = [table initWithPath:path];
548         NSMapInsert(self->stringTables, path, table);
549         [table release];
550       }
551     }
552     if (table != nil)
553       return [table stringForKey:_key withDefaultValue:_defaultValue];
554   }
555   /* didn't found table in cache */
556   
557   return _defaultValue;
558 }
559
560 - (NSString *)stringForKey:(NSString *)_key
561   inTableNamed:(NSString *)_tableName
562   withDefaultValue:(NSString *)_default
563   languages:(NSArray *)_languages
564 {
565   return [self stringForKey:_key inTableNamed:_tableName
566                withDefaultValue:_default
567                inFramework:nil
568                languages:_languages];
569 }
570
571
572 /* NSLocking */
573
574 - (void)lock {
575 }
576 - (void)unlock {
577 }
578
579 /* component definitions */
580
581 - (NSString *)pathToComponentNamed:(NSString *)_name
582   inFramework:(NSString *)_framework
583 {
584   /* search for component wrapper .. */
585   NSEnumerator *e;
586   NSString     *ext;
587   
588   if (_name == nil) {
589 #if DEBUG
590     NSLog(@"WARNING(%s): tried to get path to component with <nil> name !",
591           __PRETTY_FUNCTION__);
592 #endif
593     return nil;
594   }
595   
596   /* scan for name.$ext resource ... */
597   e = [[[NSUserDefaults standardUserDefaults]
598                         arrayForKey:@"WOComponentExtensions"]
599                         objectEnumerator];
600     
601   while ((ext = [e nextObject])) {
602     NSString *specName;
603     NSString *path;
604       
605     specName = [_name stringByAppendingPathExtension:ext];
606       
607     path = [self pathForResourceNamed:specName
608                  inFramework:_framework
609                  languages:nil];
610     if (path) return path;
611   }
612   return nil;
613 }
614
615 - (NSString *)pathToComponentNamed:(NSString *)_name
616   inFramework:(NSString *)_framework
617   languages:(NSArray *)_langs
618 {
619   return [self pathToComponentNamed:_name inFramework:_framework];
620 }
621
622 - (WOComponentDefinition *)_definitionForPathlessComponent:(NSString *)_name
623   languages:(NSArray *)_languages
624 {
625   /* definition factory */
626   WOComponentDefinition *cdef;
627   
628   cdef = [[WOComponentDefinition allocWithZone:[self zone]]
629                                  initWithName:_name
630                                  path:nil
631                                  baseURL:nil
632                                  frameworkName:nil];
633   
634   return [cdef autorelease];
635 }
636
637 - (WOComponentDefinition *)_definitionWithName:(NSString *)_name
638   url:(NSURL *)_url
639   baseURL:(NSURL *)_baseURL
640   frameworkName:(NSString *)_fwname
641 {
642   /* definition factory */
643   static Class DefClass;
644   id cdef;
645   
646   if (DefClass == Nil)
647     DefClass = [WOComponentDefinition class];
648
649   // TODO: is retained response intended?
650   cdef = [[DefClass alloc] initWithName:_name
651                            path:[_url path]
652                            baseURL:_baseURL frameworkName:_fwname];
653   return cdef;
654 }
655 - (WOComponentDefinition *)_definitionWithName:(NSString *)_name
656   path:(NSString *)_path
657   baseURL:(NSURL *)_baseURL
658   frameworkName:(NSString *)_fwname
659 {
660   NSURL *url;
661   
662   url = ([_path length] > 0)
663     ? [[[NSURL alloc] initFileURLWithPath:_path] autorelease]
664     : nil;
665   
666   return [self _definitionWithName:_name url:url
667                baseURL:_baseURL frameworkName:_fwname];
668 }
669
670 - (WOComponentDefinition *)_cachedDefinitionForComponent:(id)_name
671   languages:(NSArray *)_languages
672 {
673   NSArray *cacheKey;
674   id      cdef;
675
676   if (self->componentDefinitions == NULL)
677     return nil;
678   if (![[WOApplication application] isCachingEnabled])
679     return nil;
680   
681   cacheKey = [NSArray arrayWithObjects:_name, _languages, nil];
682   cdef     = NSMapGet(self->componentDefinitions, cacheKey);
683   
684   return cdef;
685 }
686 - (WOComponentDefinition *)_cacheDefinition:(id)_cdef
687   forComponent:(id)_name
688   languages:(NSArray *)_languages
689 {
690   NSArray *cacheKey;
691
692   if (self->componentDefinitions == NULL)
693     return _cdef;
694   if (![[WOApplication application] isCachingEnabled])
695     return _cdef;
696   
697   cacheKey = [NSArray arrayWithObjects:_name, _languages, nil];
698   NSMapInsert(self->componentDefinitions, cacheKey, _cdef ? _cdef : null);
699
700   return _cdef;
701 }
702
703 - (NSString *)resourceNameForComponentNamed:(NSString *)_name {
704   return [_name stringByAppendingPathExtension:@"wox"];
705 }
706
707 - (WOComponentDefinition *)definitionForComponent:(id)_name
708   inFramework:(NSString *)_framework
709   languages:(NSArray *)_languages
710 {
711   WOApplication         *app;
712   NSFileManager         *fm            = nil;
713   NSEnumerator          *languages     = nil;
714   NSString              *language      = nil;
715   WOComponentDefinition *cdef          = nil;
716   NSString              *sname         = nil;
717   NSURL                 *componentURL;
718   NSURL                 *appUrl;
719   BOOL                  doesCache, isDir;
720   
721   app       = [WOApplication application];
722   doesCache = [app isCachingEnabled];
723   
724   /* lookup component path */
725   
726   if ([_name isKindOfClass:UrlClass]) {
727     componentURL = _name;
728     _name = [componentURL path];
729     if (debugComponentLookup) {
730       [self debugWithFormat:@"using URL %@ for component %@",
731               componentURL, _name];
732     }
733   }
734   else {
735     NSString *path;
736     
737     if (_framework == nil && _name != nil) {
738       Class clazz;
739       
740       /* 
741          Note: this is a bit of a hack ..., actually this method should never
742          be called without a framework and pages shouldn't be instantiated
743          without specifying their framework.
744          But for legacy reasons this needs to be done and seems to work without
745          problems. It is required for loading components from bundles.
746       */
747       if ((_framework = rapidTurnAroundPath) == nil) {
748         if ((clazz = NSClassFromString(_name)))
749           _framework = [[NSBundle bundleForClass:clazz] bundlePath];
750       }
751     }
752     
753     if (debugComponentLookup) {
754       [self logWithFormat:@"component '%@' in framework '%@'", 
755               _name, _framework];
756     }
757     
758     /* look for .wox component */
759     
760     path = [self pathForResourceNamed:
761                    [self resourceNameForComponentNamed:_name]
762                  inFramework:_framework
763                  languages:_languages];
764     
765     if (debugComponentLookup)
766       [self logWithFormat:@"  path: '%@'", path];
767     
768     /* look for .wo component */
769     
770     if ([path length] == 0) {
771       path = [self pathToComponentNamed:_name
772                    inFramework:_framework
773                    languages:_languages];
774       if (debugComponentLookup)
775         [self logWithFormat:@"  path: '%@'", path];
776     }
777     
778     /* make URL from path */
779     
780     componentURL = ([path length] > 0)
781       ? [[[UrlClass alloc] initFileURLWithPath:path] autorelease]
782       : nil;
783   }
784   
785   if (debugComponentLookup) {
786     [self logWithFormat:@"  component='%@' in framework='%@': url='%@'", 
787             _name, _framework, componentURL];
788   }
789   
790   appUrl = [app baseURL];
791   
792   /* check whether it's a 'template-less' component ... */
793   
794   if (componentURL == nil) {
795     /* did not find component wrapper ! */
796     [app debugWithFormat:@"  component '%@' has no template !", _name];
797     
798     cdef = [self _definitionForPathlessComponent:_name languages:_languages];
799     return cdef;
800   }
801   
802   fm = [self fileManager];
803   
804   /* ensure that the component exists */
805
806   if ([componentURL isFileURL]) {
807     NSString *componentPath;
808     
809     componentPath = [componentURL path];
810     
811     if (![fm fileExistsAtPath:componentPath isDirectory:&isDir]) {
812       [[WOApplication application]
813                       debugWithFormat:
814                         @"%s: did not find component '%@' at path '%@' !",
815                         __PRETTY_FUNCTION__,
816                         _name, componentPath];
817       return cdef;
818     }
819   
820     /* if the component spec is a directory (eg a .wo), scan inside for stuff */
821     
822     if (isDir) {
823       languages = [_languages objectEnumerator];
824       
825       while ((language = [languages nextObject])) {
826         NSString *compoundKey  = nil;
827         NSString *languagePath = nil;
828         BOOL     isDirectory   = NO;
829         
830         if (sname == nil) sname = [_name stringByAppendingString:@"\t"];
831         compoundKey = [sname stringByAppendingString:language];
832         
833         if (doesCache) {
834           cdef = NSMapGet(self->componentDefinitions, compoundKey);
835           
836           if (cdef == (id)null)
837             /* resource does not exist */
838             continue;
839           
840           [cdef touch];
841           if (cdef) return cdef; // found definition in cache
842         }
843         
844         /* take a look into the file system */
845         languagePath = [language stringByAppendingPathExtension:@"lproj"];
846         languagePath = 
847           [componentPath stringByAppendingPathComponent:languagePath];
848         
849         if ([fm fileExistsAtPath:languagePath isDirectory:&isDirectory]) {
850           NSString *baseUrl = nil;
851           
852           if (!isDirectory) {
853             NSLog(@"WARNING(%s): language entry %@ is not a directory !",
854                   __PRETTY_FUNCTION__, languagePath);
855             if (doesCache && (compoundKey != nil)) {
856               // register null in cache, so that we know it's non-existent
857               NSMapInsert(self->componentDefinitions, compoundKey, null);
858             }
859             continue;
860           }
861           
862           baseUrl = [NSString stringWithFormat:@"%@/%@.lproj/%@.wo",
863                                 [appUrl absoluteString], language, _name];
864           
865           /* found appropriate language project */
866           cdef = [self _definitionWithName:_name
867                        path:languagePath
868                        baseURL:[NSURL URLWithString:baseUrl]
869                        frameworkName:nil];
870           if (cdef == nil) {
871             NSLog(@"WARNING(%s): could not load component definition of "
872                   @"'%@' from language project: %@", 
873                   __PRETTY_FUNCTION__, _name, languagePath);
874             if (doesCache && (compoundKey != nil)) {
875               // register null in cache, so that we know it's non-existent
876               NSMapInsert(self->componentDefinitions, compoundKey, null);
877             }
878             continue;
879           }
880     
881           if (doesCache && (compoundKey != nil)) {
882             // register in cache
883             NSMapInsert(self->componentDefinitions, compoundKey, cdef);
884             [cdef release];
885           }
886           else {
887             // don't register in cache
888             cdef = [cdef autorelease];
889           }
890
891           return cdef;
892         }
893         else {
894           if (doesCache) {
895             // register null in cache, so that we know it's non-existent
896             NSMapInsert(self->componentDefinitions, compoundKey, null);
897           }
898         }
899       }
900     }
901   }
902   
903   /* look flat */
904     
905   if (doesCache) {
906     cdef = NSMapGet(self->componentDefinitions, componentURL);
907       
908     if (cdef == (id)null)
909       /* resource does not exist */
910       return nil;
911     [cdef touch];
912       
913     if (cdef) return cdef; // found definition in cache
914   }
915   
916   /* take a look into the file system */
917   {
918     NSString *baseUrl = nil;
919     
920     baseUrl = [NSString stringWithFormat:@"%@/%@",
921                           [appUrl absoluteString], [_name lastPathComponent]];
922     
923     cdef = [self _definitionWithName:_name
924                  url:componentURL
925                  baseURL:[NSURL URLWithString:baseUrl]
926                  frameworkName:nil];
927     if (cdef == nil) {
928       NSLog(@"WARNING(%s): could not load component definition of '%@' from "
929             @"component wrapper: '%@'", 
930             __PRETTY_FUNCTION__, _name, componentURL);
931       if (doesCache) {
932         /* register null in cache, so that we know it's non-existent */
933         NSMapInsert(self->componentDefinitions, componentURL, null);
934       }
935       return nil;
936     }
937     
938     if (doesCache) {
939       /* register in cache */
940       NSMapInsert(self->componentDefinitions, componentURL, cdef);
941       [cdef release];
942     }
943     else
944       /* don't register in cache, does not cache */
945       cdef = [cdef autorelease];
946
947     return cdef;
948   }
949   
950   /* did not find component */
951   return nil;
952 }
953 - (WOComponentDefinition *)definitionForComponent:(id)_name
954   languages:(NSArray *)_langs
955 {
956   return [self definitionForComponent:_name inFramework:nil languages:_langs];
957 }
958
959 - (WOComponentDefinition *)__definitionForComponent:(id)_name
960   languages:(NSArray *)_languages
961 {
962   WOComponentDefinition *cdef;
963   
964   /* look into cache */
965   
966   cdef = [self _cachedDefinitionForComponent:_name languages:_languages];
967   if (cdef) {
968     if (cdef == (id)null)
969       /* component does not exist */
970       return nil;
971
972     if ([cdef respondsToSelector:@selector(touch)])
973       [cdef touch];
974     return cdef;
975   }
976   
977   /* not cached, create a definition */
978   
979   cdef = [self definitionForComponent:_name languages:_languages];
980
981   /* cache created definition */
982   
983   return [self _cacheDefinition:cdef forComponent:_name languages:_languages];
984 }
985
986 - (WOElement *)templateWithName:(NSString *)_name
987   languages:(NSArray *)_languages
988 {
989   WOComponentDefinition *cdef;
990   
991   cdef = [self __definitionForComponent:_name languages:_languages];
992   if (cdef == nil) return nil;
993   
994   return (WOElement *)[cdef template];
995 }
996
997 - (WOComponent *)pageWithName:(NSString *)_name
998   languages:(NSArray *)_languages
999 {
1000   /* 
1001      TODO: this appears to be deprecated since the WOComponent initializer
1002            is now -initWithContext: and we have no context here ...
1003   */
1004   NSAutoreleasePool     *pool      = nil;
1005   WOComponentDefinition *cdef      = nil;
1006   WOComponent           *component = nil;
1007   
1008   pool = [[NSAutoreleasePool alloc] init];
1009   {
1010     cdef = [self __definitionForComponent:_name languages:_languages];
1011     if (cdef) {
1012       component =
1013         [cdef instantiateWithResourceManager:self languages:_languages];
1014       component = [component retain];
1015     }
1016   }
1017   [pool release];
1018   
1019   return [component autorelease];
1020 }
1021
1022 /* description */
1023
1024 - (NSString *)description {
1025   return [NSString stringWithFormat:@"<%@[0x%08X]: path=%@>",
1026                      [self class], self, self->base];
1027                    
1028 }
1029
1030 @end /* WOResourceManager */
1031
1032 @implementation WOResourceManager(KeyedData)
1033
1034 - (void)setData:(NSData *)_data
1035   forKey:(NSString *)_key
1036   mimeType:(NSString *)_type
1037   session:(WOSession *)_session
1038 {
1039   if ((_key == nil) || (_data == nil))
1040     return;
1041   if (_type == nil)
1042     _type = @"application/octet-stream";
1043   
1044   [self lock];
1045   
1046   if (self->keyedResources == NULL) {
1047     self->keyedResources = NSCreateMapTable(NSObjectMapKeyCallBacks,
1048                                             NSObjectMapValueCallBacks,
1049                                             128);
1050   }
1051
1052   NSMapInsert(self->keyedResources,
1053               _key,
1054               [NSDictionary dictionaryWithObjectsAndKeys:
1055                               _type, @"mimeType",
1056                               _key,  @"key",
1057                               _data, @"data",
1058                             nil]);
1059   
1060   [self unlock];
1061 }
1062
1063 - (id)_dataForKey:(NSString *)_key sessionID:(NSString *)_sid {
1064   id tmp;
1065
1066   [self lock];
1067   
1068   if (self->keyedResources)
1069     tmp = NSMapGet(self->keyedResources, _key);
1070   else
1071     tmp = nil;
1072   
1073   tmp = [[tmp retain] autorelease];
1074   
1075   [self unlock];
1076
1077   return tmp;
1078 }
1079
1080 - (void)removeDataForKey:(NSString *)_key session:(WOSession *)_session {
1081   [self lock];
1082   
1083   if (self->keyedResources)
1084     NSMapRemove(self->keyedResources, _key);
1085   
1086   [self unlock];
1087 }
1088
1089 - (void)flushDataCache {
1090   [self lock];
1091
1092   if (self->keyedResources) {
1093     NSFreeMapTable(self->keyedResources);
1094     self->keyedResources = NULL;
1095   }
1096   
1097   [self unlock];
1098 }
1099
1100 @end /* WOResourceManager(KeyedData) */
1101
1102 @implementation WOResourceManager(JavaScript)
1103
1104 - (id)_jsfunc_pathForResourceNamed:(NSArray *)_args {
1105   unsigned argc = [_args count];
1106   
1107   return [self pathForResourceNamed:
1108                  argc > 0 ? [_args objectAtIndex:0] : nil
1109                inFramework:argc > 1 ? [_args objectAtIndex:1] : nil
1110                languages:argc > 2 ? [_args objectAtIndex:2] : nil];
1111 }
1112
1113 - (id)_jsfunc_loadPropertyListNamed:(NSArray *)_args {
1114   NSString *s;
1115   
1116   if ((s = [self _jsfunc_pathForResourceNamed:_args]) == nil)
1117     return nil;
1118   
1119   if ((s = [NSString stringWithContentsOfFile:s]) == nil)
1120     return nil;
1121
1122   return [s propertyList];
1123 }
1124
1125 @end /* WOResourceManager(JavaScript) */