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