]> err.no Git - sope/blob - sope-appserver/WEExtensions/WEResourceManager.m
fixed resource/module lookup on 64bit systems
[sope] / sope-appserver / WEExtensions / WEResourceManager.m
1 /*
2   Copyright (C) 2005-2006 SKYRIX Software AG
3   Copyright (C) 2006      Helge Hess
4
5   This file is part of SOPE.
6
7   SOPE is free software; you can redistribute it and/or modify it under
8   the terms of the GNU Lesser General Public License as published by the
9   Free Software Foundation; either version 2, or (at your option) any
10   later version.
11
12   SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
13   WARRANTY; without even the implied warranty of MERCHANTABILITY or
14   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
15   License for more details.
16
17   You should have received a copy of the GNU Lesser General Public
18   License along with SOPE; see the file COPYING.  If not, write to the
19   Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
20   02111-1307, USA.
21 */
22
23 #include "WEResourceManager.h"
24 #include "WEStringTableManager.h"
25 #include "WEResourceKey.h"
26 #include "common.h"
27
28 @implementation WEResourceManager
29
30 static BOOL          debugOn         = NO;
31 static BOOL          debugComponents = NO;
32 static NSArray       *wsPathes       = nil;
33 static NSArray       *templatePathes = nil;
34 static NSString      *suffix   = nil;
35 static NSString      *prefix   = nil;
36 static NSFileManager *fm   = nil;
37 static NSNull        *null = nil;
38 static NSString *themesDirName = @"Themes";
39
40 + (NSString *)shareSubpath {
41   static NSString *shareSubPath = nil;
42   NSString *p;
43   
44   if (shareSubPath != nil)
45     return shareSubPath;
46   
47   p = [[WOApplication application] shareDirectoryName];
48   p = [@"share/" stringByAppendingString:p];
49   p = [p stringByAppendingString:@"/"];
50   shareSubPath = [p copy];
51   return shareSubPath;
52 }
53
54 + (NSString *)gsTemplatesSubpath {
55   NSString *p;
56   
57   p = [[WOApplication application] gsTemplatesDirectoryName];
58   p = [@"Library/" stringByAppendingString:p];
59   return p;
60 }
61 + (NSString *)gsWebSubpath {
62   NSString *p;
63   
64   p = [[WOApplication application] gsWebDirectoryName];
65   p = [@"Library/" stringByAppendingString:p];
66   return p;
67 }
68
69 /* locate resource directories */
70
71 + (NSArray *)rootPathesInGNUstep {
72   NSDictionary *env;
73   id tmp;
74   
75   env = [[NSProcessInfo processInfo] environment];
76   if ((tmp = [env objectForKey:@"GNUSTEP_PATHPREFIX_LIST"]) == nil)
77     tmp = [env objectForKey:@"GNUSTEP_PATHLIST"];
78   
79   return [tmp componentsSeparatedByString:@":"];
80 }
81 + (NSArray *)rootPathesInFHS {
82   return [NSArray arrayWithObjects:
83 #ifdef FHS_INSTALL_ROOT
84                     FHS_INSTALL_ROOT,
85 #endif
86                     @"/usr/local/", @"/usr/", nil];
87 }
88
89 + (NSArray *)findResourceDirectoryPathesWithName:(NSString *)_name
90   fhsName:(NSString *)_fhs
91 {
92   /* find directories which might contain resources */
93   NSEnumerator   *e;
94   NSFileManager  *fm;
95   NSMutableArray *ma;
96   BOOL           isDir;
97   id tmp;
98
99   fm  = [NSFileManager defaultManager];
100   ma  = [NSMutableArray arrayWithCapacity:8];
101   
102   e = [[self rootPathesInGNUstep] objectEnumerator];
103   while ((tmp = [e nextObject]) != nil) {
104     if (![tmp hasSuffix:@"/"])
105       tmp = [tmp stringByAppendingString:@"/"];
106     
107     tmp = [tmp stringByAppendingString:_name];
108     if ([ma containsObject:tmp]) continue;
109     
110     if (debugOn) [self logWithFormat:@"CHECK: %@", tmp];
111     if (![fm fileExistsAtPath:tmp isDirectory:&isDir])
112       continue;
113       
114     if (!isDir) continue;
115     
116     [ma addObject:tmp];
117   }
118
119   /* hack in FHS pathes */
120   
121   e = [[self rootPathesInFHS] objectEnumerator];
122   while ((tmp = [e nextObject]) != nil) {
123     tmp = [tmp stringByAppendingString:[[self class] shareSubpath]];
124     tmp = [tmp stringByAppendingString:_fhs];
125     if ([ma containsObject:tmp]) continue;
126     if (debugOn) [self logWithFormat:@"CHECK: %@", tmp];
127     
128     if (![fm fileExistsAtPath:tmp isDirectory:&isDir])
129       continue;
130     if (!isDir) {
131       [self logWithFormat:@"path is not a directory: %@", tmp];
132       continue;
133     }
134     
135     [ma addObject:tmp];
136   }
137   
138   return ma;
139 }
140
141 + (int)version {
142   return [super version] + 0 /* v4 */;
143 }
144 + (void)initialize {
145   static BOOL isInitialized = NO;
146   NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
147   if (isInitialized) return;
148   isInitialized = YES;
149   
150   NSAssert2([super version] == 4,
151             @"invalid superclass (%@) version %i !",
152             NSStringFromClass([self superclass]), [super version]);
153
154   null = [[NSNull null] retain];
155   
156   if ((debugOn = [ud boolForKey:@"WEResourceManagerDebugEnabled"]))
157     NSLog(@"Note: WEResourceManager debugging is enabled.");
158   debugComponents = [ud boolForKey:@"WEResourceManagerComponentDebugEnabled"];
159   if (debugComponents)
160     NSLog(@"Note: WEResourceManager component debugging is enabled.");
161   
162   fm = [[NSFileManager defaultManager] retain];
163   
164   suffix = [[ud stringForKey:@"WOApplicationSuffix"] copy];
165   prefix = [[ud stringForKey:@"WOResourcePrefix"]    copy];
166   
167   wsPathes = [[self findResourceDirectoryPathesWithName:
168                       [self gsWebSubpath] fhsName:@"www/"] copy];
169   if (debugOn)
170     NSLog(@"WebServerResources pathes: %@", wsPathes);
171   
172   // TODO: use appname to enable different setups, maybe some var too
173   templatePathes = [[self findResourceDirectoryPathesWithName:
174                             [[self class] gsTemplatesSubpath]
175                           fhsName:@"templates/"] copy];
176   if (debugOn)
177     NSLog(@"template pathes: %@", templatePathes);
178   if (![templatePathes isNotEmpty]) {
179     NSLog(@"Note: found no directories containing flat templates (subpath=%@)",
180           [[self class] gsTemplatesSubpath]);
181   }
182 }
183
184 + (NSArray *)availableThemes {
185   static NSArray *lthemes = nil;
186   NSMutableSet  *themes;
187   NSEnumerator  *e;
188   NSFileManager *fm;
189   NSString      *path;
190
191   if (lthemes != nil)
192     return lthemes;
193   
194   themes = [NSMutableSet setWithCapacity:16];
195   fm     = [NSFileManager defaultManager];
196   
197   e = [templatePathes objectEnumerator];
198   while ((path = [e nextObject]) != nil) {
199     NSArray *dl;
200     
201     path = [path stringByAppendingPathComponent:themesDirName];
202     dl   = [fm directoryContentsAtPath:path];
203     
204     [themes addObjectsFromArray:dl];
205   }
206   
207   /* remove directories to be ignored */
208   [themes removeObject:@".svn"];
209   [themes removeObject:@"CVS"];
210   
211   lthemes = [[[themes allObjects] 
212                sortedArrayUsingSelector:@selector(compare:)] copy];
213   if ([lthemes isNotEmpty]) {
214     NSLog(@"Note: located themes: %@", 
215           [lthemes componentsJoinedByString:@", "]);
216   }
217   else
218     NSLog(@"Note: located no additional themes.");
219   return lthemes;
220 }
221
222 - (id)initWithPath:(NSString *)_path {
223   if ((self = [super initWithPath:_path]) != nil) {
224     if ([WOApplication isCachingEnabled]) {
225       self->keyToComponentPath =
226         [[NSMutableDictionary alloc] initWithCapacity:128];
227       
228       self->keyToPath = [[NSMutableDictionary alloc] initWithCapacity:1024];
229       self->keyToURL  = [[NSMutableDictionary alloc] initWithCapacity:1024];
230     }
231     else
232       [self logWithFormat:@"Note: component path caching is disabled!"];
233     
234     self->labelManager = [[WEStringTableManager alloc] init];
235     self->cachedKey    = [[WEResourceKey alloc] initCachedKey];
236   }
237   return self;
238 }
239 - (id)init {
240   // TODO: maybe search and set some 'share/ogo' path?
241   return [self initWithPath:nil];
242 }
243
244 - (void)dealloc {
245   [self->cachedKey          release];
246   [self->labelManager       release];
247   [self->keyToURL           release];
248   [self->keyToPath          release];
249   [self->keyToComponentPath release];
250   [super dealloc];
251 }
252
253 /* accessors */
254
255 - (NGBundleManager *)bundleManager {
256   static NGBundleManager *bm = nil; // THREAD
257   if (bm == nil) bm = [[NGBundleManager defaultBundleManager] retain];
258   return bm;
259 }
260
261 /* resource cache */
262
263 static id
264 checkCache(NSDictionary *_cache, WEResourceKey *_key,
265            NSString *_n, NSString *_fw, NSString *_l)
266 {
267   if (_cache == nil) {
268     if (debugOn) NSLog(@"cache disabled.");
269     return nil; /* caching disabled */
270   }
271   
272   /* setup cache key (THREAD) */
273   _key->hashValue     = 0; /* reset, calculate on next access */
274   _key->name          = _n;
275   _key->frameworkName = _fw;
276   _key->language      = _l;
277   
278   return [_cache objectForKey:_key];
279 }
280
281 - (void)cacheValue:(id)_value inCache:(NSMutableDictionary *)_cache {
282   WEResourceKey *k;
283   
284   if (_cache == nil) return; /* caching disabled */
285   
286   /* we need to dup, because the cachedKey does not retain! */
287   k = [self->cachedKey duplicate];
288   
289   if (debugOn) {
290     [self debugWithFormat:@"cache key %@(#%d): %@", k, [self->keyToPath count],
291             _value];
292   }
293   
294   [_cache setObject:(_value ? _value : (id)null) forKey:k];
295   [k release]; k = nil;
296 }
297
298 /* locate Resources */
299
300 - (NSString *)_weCheckPath:(NSString *)_p forResourceNamed:(NSString *)_name
301   inFramework:(NSString *)_frameworkName
302   language:(NSString *)_language
303 {
304   NSString *path;
305   
306   path = [_frameworkName isNotEmpty]
307     ? [_p stringByAppendingPathComponent:_frameworkName]
308     : _p;
309       
310   /* check language */
311   if (_language != nil) {
312     path = [path stringByAppendingPathComponent:_language];
313     path = [path stringByAppendingPathExtension:@"lproj"];
314   }
315   
316   path = [path stringByAppendingPathComponent:_name];
317   if (debugOn) [self debugWithFormat:@"  check path: '%@'", path];
318       
319   if (![fm fileExistsAtPath:path])
320     return nil;
321   
322   return path;
323 }
324
325 - (NSString *)_weCheckPathes:(NSArray *)_p forResourceNamed:(NSString *)_name
326   inFramework:(NSString *)_frameworkName
327   language:(NSString *)_language
328 {
329   NSEnumerator *e;
330   NSString     *path;
331   
332   e = [_p objectEnumerator];
333   while ((path = [e nextObject]) != nil) {
334     path = [self _weCheckPath:path forResourceNamed:_name
335                  inFramework:_frameworkName language:_language];
336     if (path != nil) {
337       if (debugOn) [self debugWithFormat:@"FOUND: '%@'", path];
338       return path;
339     }
340   }
341   return nil;
342 }
343
344 - (NSString *)_wePathForResourceNamed:(NSString *)_name
345   inFramework:(NSString *)_fwName
346   language:(NSString *)_lang
347   searchPathes:(NSArray *)_pathes
348 {
349   // TODO: a lot of DUP code with _urlForResourceNamed, needs some refacturing
350   NSString *path;
351   
352   if (debugOn) [self debugWithFormat:@"lookup resource '%@'", _name];
353   
354   /* check cache */
355   
356   path = checkCache(self->keyToPath, self->cachedKey, _name, _fwName, _lang);
357   if (path != nil) {
358     if (debugOn) [self debugWithFormat:@"  found in cache: %@", path];
359     return [path isNotNull] ? path : (NSString *)nil;
360   }
361   
362   /* check for framework resources (webserver resources + framework) */
363
364   if (debugOn) 
365     [self debugWithFormat:@"check framework resources ..."];
366   path = [self _weCheckPathes:_pathes forResourceNamed:_name
367                inFramework:_fwName language:_lang];
368   if (path != nil) {
369     [self cacheValue:path inCache:self->keyToPath];
370     return path;
371   }
372   
373   /* check in basepath of webserver resources */
374   
375   // TODO: where is the difference, same call like above?
376   if (debugOn) [self debugWithFormat:@"check global resources ..."];
377   path = [self _weCheckPathes:_pathes forResourceNamed:_name
378                inFramework:_fwName language:_lang];
379   if (path != nil) {
380     [self cacheValue:path inCache:self->keyToPath];
381     return path;
382   }
383   
384   /* finished processing */
385   if (debugOn) 
386     [self debugWithFormat:@"NOT FOUND: %@ (%@)", _name, self->cachedKey];
387   return nil;
388 }
389
390 - (BOOL)shouldLookupResourceInWebServerResources:(NSString *)_name {
391   if ([_name hasSuffix:@".wox"]) return NO;
392   if ([_name hasSuffix:@".wo"])  return NO;
393   return YES;
394 }
395
396 - (NSString *)_wePathForResourceNamed:(NSString *)_name
397   inFramework:(NSString *)_fwName
398   language:(NSString *)_lang
399 {
400   NSString *p;
401   
402   /* check in webserver resources */
403   
404   if ([self shouldLookupResourceInWebServerResources:_name]) {
405     p = [self _wePathForResourceNamed:_name inFramework:_fwName
406               language:_lang searchPathes:wsPathes];
407     if (p != nil) return p;
408   }
409   
410   return nil;
411 }
412
413 - (BOOL)isTemplateResourceName:(NSString *)_name {
414   // TODO: non-extensible
415   return [_name hasSuffix:@".wox"];
416 }
417
418 - (NSString *)pathForResourceNamed:(NSString *)_name
419   inFramework:(NSString *)_fwName
420   languages:(NSArray *)_langs
421 {
422   /* 
423      Note: this is also called by the superclass method 
424            -pathToComponentNamed:inFramework: for each registered component
425            extension.
426   */
427   NSEnumerator *e;
428   NSString     *language;
429   NSString     *rpath;
430   
431   if (![_name isNotEmpty]) {
432     [self debugWithFormat:@"got no name for resource lookup?!"];
433     return nil;
434   }
435   
436   if (debugOn) {
437     [self debugWithFormat:@"pathForResourceNamed: %@/%@ (languages: %@)",
438             _name, _fwName, [_langs componentsJoinedByString:@","]];
439   }
440
441   if ([self isTemplateResourceName:_name]) {
442     if (debugOn) [self debugWithFormat:@"  is template resource .."];
443     return [self pathToComponentNamed:[_name stringByDeletingPathExtension]
444                  inFramework:_fwName
445                  languages:_langs];
446   }
447   
448   /* check languages */
449   
450   e = [_langs objectEnumerator];
451   while ((language = [e nextObject]) != nil) {
452       NSString *rpath;
453       
454       if (debugOn) 
455         [self logWithFormat:@"  check language (%@): '%@'", _name, language];
456       rpath = [self _wePathForResourceNamed:_name inFramework:_fwName
457                     language:language];
458       if (rpath != nil) {
459         if (debugOn) [self debugWithFormat:@"  FOUND: %@", rpath];
460         return rpath;
461       }
462   }
463   
464   /* check without language */
465   
466   rpath = [self _wePathForResourceNamed:_name inFramework:_fwName
467                 language:nil];
468   if (rpath != nil)
469     return rpath;
470   
471   if (debugOn) {
472     [self debugWithFormat:
473               @"did not find resource, try super lookup: '%@'", _name];
474   }
475     
476   /* look using WOResourceManager */
477   
478   rpath = [super pathForResourceNamed:_name inFramework:_fwName
479                  languages:_langs];
480   return rpath;
481 }
482
483 /* locate WebServerResources */
484
485 - (NSString *)_urlForResourceNamed:(NSString *)_name
486   inFramework:(NSString *)_fwName
487   language:(NSString *)_lang
488   applicationName:(NSString *)_appName
489 {
490   NSString     *url;
491   NSEnumerator *e;
492   NSString     *path;
493   
494   if (debugOn) {
495     [self logWithFormat:@"lookup URL of resource: '%@'/%@/%@", 
496             _name, _fwName, _lang];
497   }
498   
499   /* check cache */
500   
501   url = checkCache(self->keyToURL, self->cachedKey, _name, _fwName, _lang);
502   if (url != nil) {
503     if (debugOn) {
504       [self debugWithFormat:@"  found in cache: %@ (#%d)", url, 
505               [self->keyToURL count]];
506     }
507     return [url isNotNull] ? url : (NSString *)nil;
508   }
509   
510   if (debugOn) {
511     [self debugWithFormat:@"  not found in cache: %@ (%@,#%d)", 
512             url, self->cachedKey, [self->keyToURL count]];
513   }
514   
515   /* check for framework resources */
516   
517   if ([_fwName isNotEmpty]) {
518     if (debugOn) 
519       [self debugWithFormat:@"check framework: '%@'", _fwName];
520     e = [wsPathes objectEnumerator];
521     while ((path = [e nextObject])) {
522       NSMutableString *ms;
523       
524       path = [path stringByAppendingPathComponent:_fwName];
525
526       /* check language */
527       if (_lang) {
528         path = [path stringByAppendingString:_lang];
529         path = [path stringByAppendingPathExtension:@"lproj"];
530       }
531       
532       path = [path stringByAppendingPathComponent:_name];
533       if (debugOn) [self debugWithFormat:@"  check path: '%@'", path];
534       
535       if (![fm fileExistsAtPath:path])
536         continue;
537         
538       ms = [[NSMutableString alloc] initWithCapacity:256];
539         
540       if (prefix) [ms appendString:prefix];
541       if (![ms hasSuffix:@"/"]) [ms appendString:@"/"];
542       [ms appendString:_appName];
543       if (suffix) [ms appendString:suffix];
544       [ms appendString:[ms hasSuffix:@"/"] 
545             ? @"WebServerResources/" : @"/WebServerResources/"];
546       [ms appendString:_fwName];
547       [ms appendString:@"/"];
548       if (_lang) {
549           [ms appendString:_lang];
550           [ms appendString:@".lproj/"];
551       }
552       [ms appendString:_name];
553       
554       url = [ms copy];
555       [ms release]; ms = nil;
556       if (debugOn) [self debugWithFormat:@"FOUND: '%@'", url];
557       goto done;
558     }
559   }
560   
561   /* check for global resources */
562   
563   if (debugOn) [self debugWithFormat:@"check global WebServerResources ..."];
564   e = [wsPathes objectEnumerator];
565   while ((path = [e nextObject])) {
566     NSMutableString *ms;
567     NSString *fpath, *basepath;
568     
569     /* check language */
570     if (_lang) {
571       basepath = [path stringByAppendingString:_lang];
572       basepath = [basepath stringByAppendingPathExtension:@"lproj"];
573     }
574     else
575       basepath = path;
576     
577     fpath = [basepath stringByAppendingPathComponent:_name];
578     if (debugOn) {
579       [self debugWithFormat:
580               @"  check path: '%@'\n base: %@\n name: %@\n "
581               @" path: %@\n lang: %@", 
582               fpath, basepath, _name, path, _lang];
583     }
584     
585     if (![fm fileExistsAtPath:fpath])
586       continue;
587       
588     ms = [[NSMutableString alloc] initWithCapacity:256];
589       
590     if (prefix) [ms appendString:prefix];
591     if (![ms hasSuffix:@"/"]) [ms appendString:@"/"];
592     [ms appendString:_appName];
593     if (suffix) [ms appendString:suffix];
594     [ms appendString:[ms hasSuffix:@"/"] 
595           ? @"WebServerResources/" : @"/WebServerResources/"];
596     if (_lang) {
597       [ms appendString:_lang];
598       [ms appendString:@".lproj/"];
599     }
600     [ms appendString:_name];
601       
602     url = [ms copy];
603     [ms release]; ms = nil;
604     if (debugOn) [self debugWithFormat:@"FOUND: '%@'", url];
605     goto done;
606   }
607   
608   /* finished processing */
609   if (debugOn) {
610     [self debugWithFormat:@"NOT FOUND: %@ (%@,#%d)", _name, self->cachedKey,
611             [self->keyToURL count]];
612   }
613   
614  done:
615   [self cacheValue:url inCache:self->keyToURL];
616   return url;
617 }
618
619 - (NSString *)urlForResourceNamed:(NSString *)_name
620   inFramework:(NSString *)_fwName
621   languages:(NSArray *)_langs
622   request:(WORequest *)_request
623 {
624   NSEnumerator *e;
625   NSString     *language;
626   NSString     *url;
627   NSString     *appName;
628   
629   if (![_name isNotEmpty]) {
630     if (debugOn) [self logWithFormat:@"got no name for resource URL lookup?!"];
631     return nil;
632   }
633   
634   if (debugOn) [self debugWithFormat:@"urlForResourceNamed: %@", _name];
635   
636   if (_langs == nil) {
637     _langs = [_request browserLanguages];
638     if (debugOn) {
639       [self debugWithFormat:@"using browser languages: %@", 
640               [_langs componentsJoinedByString:@", "]];
641     }
642   }
643   else if (debugOn) {
644     [self debugWithFormat:@"using given languages: %@", 
645             [_langs componentsJoinedByString:@","]];
646   }
647   
648   appName = [_request applicationName];
649   if (appName == nil)
650     appName = [(WOApplication *)[WOApplication application] name];
651   
652   /* check languages */
653   
654   e = [_langs objectEnumerator];
655   while ((language = [e nextObject])) {
656     NSString *url;
657     
658     if (debugOn) [self logWithFormat:@"  check language: '%@'", language];
659     url = [self _urlForResourceNamed:_name
660                 inFramework:_fwName
661                 language:language
662                 applicationName:appName];
663     if (url != nil) {
664       if (debugOn) [self logWithFormat:@"  FOUND: %@", url];
665       return url;
666     }
667   }
668   
669   /* check without language */
670   
671   url = [self _urlForResourceNamed:_name
672               inFramework:_fwName
673               language:nil
674               applicationName:appName];
675   if (url != nil)
676     return url;
677
678   if (debugOn) {
679     [self debugWithFormat:
680             @"did not find resource in try super lookup: '%@'", _name];
681   }
682   
683   url = [super urlForResourceNamed:_name
684                inFramework:_fwName
685                languages:_langs
686                request:_request];
687
688   return url;
689 }
690
691 /* locate components */
692
693 - (NSString *)lookupComponentInStandardPathes:(NSString *)_name
694   inFramework:(NSString *)_framework theme:(NSString *)_theme
695 {
696   // TODO: what about languages/themes?!
697   NSEnumerator *e;
698   NSString *path;
699   
700   if (debugComponents) {
701     [self logWithFormat:@"lookup component in std pathes: %@|%@|%@",
702           _name, _framework, _theme];
703   }
704   
705   if ([_theme isNotNull] && [_theme length] == 0) 
706     _theme = nil;
707   if ([_framework isNotNull] && [_framework length] == 0) 
708     _framework = nil;
709   
710   e = [templatePathes objectEnumerator];
711   while ((path = [e nextObject]) != nil) {
712     NSString *pe;
713     
714     if (_theme != nil) {
715       // TODO: should be lower case for FHS? or use a different path?
716       path = [path stringByAppendingPathComponent:themesDirName];
717       path = [path stringByAppendingPathComponent:_theme];
718     }
719     
720     if (_framework != nil) {
721       NSString *pureName;
722       
723       pureName = [_framework lastPathComponent];
724       pureName = [pureName stringByDeletingPathExtension];
725       path = [path stringByAppendingPathComponent:pureName];
726     }
727     
728     path = [path stringByAppendingPathComponent:_name];
729     
730     pe = [path stringByAppendingPathExtension:@"wox"];
731     if (debugComponents) [self logWithFormat:@"CHECK %@", pe];
732     
733     if ([fm fileExistsAtPath:pe])
734       return pe;
735
736     pe = [path stringByAppendingPathExtension:@"html"];
737     if ([fm fileExistsAtPath:pe]) {
738       /* 
739          Note: we are passing in the path of the HTML template, this is some
740                kind of hack to make the wrapper template builder look for the
741                $name.html/$name.wod in there.
742       */
743       return pe;
744     }
745   }
746   
747   return nil;
748 }
749 - (NSString *)lookupComponentInStandardPathes:(NSString *)_name
750   inFramework:(NSString *)_framework languages:(NSArray *)_langs
751 {
752   NSString *path;
753   NSString *theme;
754   
755   if (debugComponents) {
756     [self logWithFormat:@"lookup component in std pathes(langs): %@|%@",
757           _name, _framework];
758   }
759   
760   /* extract theme from language array (we do not support nested themes ATM) */
761   
762   theme = nil;
763   if ([_langs count] > 1) {
764     NSRange r;
765     
766     theme = [_langs objectAtIndex:0];
767     r = [theme rangeOfString:@"_"];
768     theme = (r.length > 0)
769       ? [theme substringFromIndex:(r.location + r.length)]
770       : (NSString *)nil;
771   }
772   else
773     theme = nil;
774
775   /* check theme dirs */
776   
777   if (theme != nil) {
778     path = [self lookupComponentInStandardPathes:_name inFramework:_framework
779                  theme:theme];
780     if (path != nil)
781       return path;
782   }
783
784   /* check base dirs */
785   
786   path = [self lookupComponentInStandardPathes:_name inFramework:_framework
787                theme:nil];
788   if (path != nil)
789     return path;
790   
791   /* check without framework subdir */
792
793   if ([_framework isNotEmpty]) {
794     path = [self lookupComponentInStandardPathes:_name inFramework:nil
795                  theme:nil];
796     if (path != nil)
797       return path;
798   }
799
800   return nil;
801 }
802
803 - (NSString *)lookupComponentPathUsingBundleManager:(NSString *)_name {
804   // TODO: is this ever invoked?
805   NSFileManager *fm = nil;
806   NSString *wrapper = nil;
807   NSBundle *bundle  = nil;
808   NSString *path;
809
810   if (debugComponents)
811     [self logWithFormat:@"lookup component using bundle manager: %@", _name];
812   
813   bundle = [[self bundleManager]
814                   bundleProvidingResource:_name
815                   ofType:@"WOComponents"];
816   if (bundle == nil) {
817     [self debugWithFormat:@"did not find a bundle providing component: %@", 
818             _name];
819     return nil;
820   }
821   
822   if (debugOn) {
823     [self debugWithFormat:@"bundle %@ for component %@",
824             [[bundle bundlePath] lastPathComponent], _name];
825   }
826   
827   [bundle load];
828   
829   fm      = [NSFileManager defaultManager];
830   wrapper = [_name stringByAppendingPathExtension:@"wo"];
831   
832   path = [[bundle bundlePath] stringByAppendingPathComponent:@"Resources"];
833   path = [path stringByAppendingPathComponent:wrapper];
834   if ([fm fileExistsAtPath:path])
835     return path;
836   
837   path = [[bundle bundlePath] stringByAppendingPathComponent:wrapper];
838   if ([fm fileExistsAtPath:path])
839     return path;
840   
841   return nil;
842 }
843
844 - (NSString *)pathToComponentNamed:(NSString *)_name
845   inFramework:(NSString *)_fw languages:(NSArray *)_langs
846 {
847   // TODO: what about languages in lookup?
848   NSString *path;
849
850   if (debugComponents) {
851     [self logWithFormat:@"%s: lookup component: %@|%@",
852           __PRETTY_FUNCTION__, _name, _fw];
853   }
854   
855   /* first check cache */
856   
857   path = checkCache(self->keyToComponentPath, self->cachedKey, _name, _fw,
858                     [_langs isNotEmpty] 
859                     ? [_langs objectAtIndex:0] : (id)@"English");
860   if (path != nil) {
861     if (debugComponents)
862       [self logWithFormat:@"  use cached location: %@", path];
863     return [path isNotNull] ? path : (NSString *)nil;
864   }
865   
866   /* look in FHS locations */
867
868   path = [self lookupComponentInStandardPathes:_name inFramework:_fw
869                languages:_langs];
870   if (path != nil) {
871     if (debugComponents)
872       [self logWithFormat:@"  found in standard pathes: %@", path];
873     goto done;
874   }
875   
876   /* try to find component by standard NGObjWeb method */
877   
878   if (debugComponents)
879     [self logWithFormat:@"lookup component using WOResourceManager ..."];
880   path = [super pathToComponentNamed:_name inFramework:_fw languages:_langs];
881   if (path != nil) {
882     if (debugComponents)
883       [self logWithFormat:@"  found using WOResourceManager: %@", path];
884     goto done;
885   }
886   
887   /* find component using NGBundleManager */
888   
889   if ((path = [self lookupComponentPathUsingBundleManager:_name]) != nil) {
890     if (debugComponents)
891       [self logWithFormat:@"  found using bundle manager: %@", path];
892     goto done;
893   }
894   
895   /* did not find component */
896  done:
897   [self cacheValue:path inCache:self->keyToComponentPath];
898   return path;
899 }
900
901 /* string tables */
902
903 - (NSString *)labelForKey:(NSString *)_key component:(WOComponent *)_component{
904   return [self->labelManager labelForKey:_key component:_component];
905 }
906
907 - (id)stringTableWithName:(NSString *)_tableName
908   inFramework:(NSString *)_framework
909   languages:(NSArray *)_languages
910 {
911   id table;
912   
913   table = [self->labelManager
914                stringTableWithName:_tableName inFramework:_framework 
915                languages:_languages];
916   if (table != nil) return table;
917   
918   return [super stringTableWithName:_tableName inFramework:_framework
919                 languages:_languages];
920 }
921
922 - (NSString *)stringForKey:(NSString *)_key
923   inTableNamed:(NSString *)_tableName
924   withDefaultValue:(NSString *)_defaultValue
925   inFramework:(NSString *)_framework
926   languages:(NSArray *)_languages
927 {
928   NSString *s;
929   
930   s = [self->labelManager 
931            stringForKey:_key inTableNamed:_tableName
932            withDefaultValue:_defaultValue languages:_languages];
933   if (s != nil) return s;
934   
935   s = [super stringForKey:_key inTableNamed:_tableName
936              withDefaultValue:_defaultValue
937              inFramework:_framework
938              languages:_languages];
939   if (s != nil) return s;
940   
941   return s != nil ? s : _defaultValue;
942 }
943
944 /* debugging */
945
946 - (BOOL)isDebuggingEnabled {
947   return debugOn;
948 }
949 - (NSString *)loggingPrefix {
950   return @"[we-rm]";
951 }
952
953 @end /* WEResourceManager */
954
955 @implementation WOApplication(WEResourceManager)
956
957 - (NSString *)shareDirectoryName {
958   return [[self name] lowercaseString];
959 }
960 - (NSString *)gsTemplatesDirectoryName {
961   return [[self name] stringByAppendingString:@"/Templates/"];
962 }
963 - (NSString *)gsWebDirectoryName {
964   return [[self name] stringByAppendingString:@"/WebServerResources/"];
965 }
966
967 @end /* WOApplication(WEResourceManager) */