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