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