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