]> err.no Git - sope/blob - sope-appserver/NGObjWeb/OWResourceManager.m
use %p for pointer formats, fixed gcc 4.1 warnings, minor code improvs
[sope] / sope-appserver / NGObjWeb / OWResourceManager.m
1 /*
2   Copyright (C) 2000-2005 SKYRIX Software AG
3
4   This file is part of SOPE.
5
6   SOPE is free software; you can redistribute it and/or modify it under
7   the terms of the GNU Lesser General Public License as published by the
8   Free Software Foundation; either version 2, or (at your option) any
9   later version.
10
11   SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
12   WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14   License for more details.
15
16   You should have received a copy of the GNU Lesser General Public
17   License along with SOPE; see the file COPYING.  If not, write to the
18   Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19   02111-1307, USA.
20 */
21
22 // 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       [self warnWithFormat:@"(%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 warnWithFormat:
263               @"(%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 warnWithFormat:
308               @"(%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 = @"Localizable";
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 use that for WOx as well?
597   NSEnumerator *e;
598   NSString     *ext;
599   
600   if (_name == nil) {
601 #if DEBUG
602     [self warnWithFormat:@"(%s): tried to get path to component with "
603             @"<nil> name !",
604             __PRETTY_FUNCTION__];
605 #endif
606     return nil;
607   }
608   
609   /* scan for name.$ext resource ... */
610   e = [[[NSUserDefaults standardUserDefaults]
611                         arrayForKey:@"WOComponentExtensions"]
612                         objectEnumerator];
613     
614   while ((ext = [e nextObject])) {
615     NSString *specName;
616     NSString *path;
617       
618     specName = [_name stringByAppendingPathExtension:ext];
619     
620     path = [self pathForResourceNamed:specName
621                  inFramework:_framework
622                  languages:_langs];
623     if (path != nil) return path;
624   }
625   return nil;
626 }
627
628 - (NSString *)pathToComponentNamed:(NSString *)_name
629   inFramework:(NSString *)_fw
630 {
631   // TODO: is this still used somewhere?
632   return [self pathToComponentNamed:_name inFramework:_fw languages:nil];
633 }
634
635 - (WOComponentDefinition *)_definitionForPathlessComponent:(NSString *)_name
636   languages:(NSArray *)_languages
637 {
638   /* definition factory */
639   WOComponentDefinition *cdef;
640   
641   cdef = [[WOComponentDefinition allocWithZone:[self zone]]
642                                  initWithName:_name
643                                  path:nil
644                                  baseURL:nil
645                                  frameworkName:nil];
646   
647   return [cdef autorelease];
648 }
649
650 - (WOComponentDefinition *)_definitionWithName:(NSString *)_name
651   url:(NSURL *)_url
652   baseURL:(NSURL *)_baseURL
653   frameworkName:(NSString *)_fwname
654 {
655   /* definition factory */
656   static Class DefClass;
657   id cdef;
658   
659   if (DefClass == Nil)
660     DefClass = [WOComponentDefinition class];
661   
662   cdef = [[DefClass alloc] initWithName:_name
663                            path:[_url path]
664                            baseURL:_baseURL frameworkName:_fwname];
665   return cdef;
666 }
667 - (WOComponentDefinition *)_definitionWithName:(NSString *)_name
668   path:(NSString *)_path
669   baseURL:(NSURL *)_baseURL
670   frameworkName:(NSString *)_fwname
671 {
672   NSURL *url;
673   
674   url = ([_path length] > 0)
675     ? [[[NSURL alloc] initFileURLWithPath:_path] autorelease]
676     : nil;
677   
678   return [self _definitionWithName:_name url:url
679                baseURL:_baseURL frameworkName:_fwname];
680 }
681
682 - (WOComponentDefinition *)_cachedDefinitionForComponent:(id)_name
683   languages:(NSArray *)_languages
684 {
685   NSArray *cacheKey;
686   id      cdef;
687
688   if (self->componentDefinitions == NULL)
689     return nil;
690   if (![[WOApplication application] isCachingEnabled])
691     return nil;
692   
693   cacheKey = [NSArray arrayWithObjects:_name, _languages, nil];
694   cdef     = NSMapGet(self->componentDefinitions, cacheKey);
695   
696   return cdef;
697 }
698 - (WOComponentDefinition *)_cacheDefinition:(id)_cdef
699   forComponent:(id)_name
700   languages:(NSArray *)_languages
701 {
702   NSArray *cacheKey;
703
704   if (self->componentDefinitions == NULL)
705     return _cdef;
706   if (![[WOApplication application] isCachingEnabled])
707     return _cdef;
708   
709   cacheKey = [NSArray arrayWithObjects:_name, _languages, nil];
710   NSMapInsert(self->componentDefinitions, cacheKey, _cdef ? _cdef : (id)null);
711
712   return _cdef;
713 }
714
715 - (NSString *)resourceNameForComponentNamed:(NSString *)_name {
716   return [_name stringByAppendingPathExtension:@"wox"];
717 }
718
719 - (BOOL)_isValidWrapperDirectory:(NSString *)_path 
720   containingTemplate:(NSString *)_name 
721 {
722   /* 
723      Check whether this actually does contain a template! 
724        
725      This is new and hopefully doesn't break anything, but as far as I can
726      see checking for Component.html inside should do the right thing (unless
727      there are template wrappers which are not .wo wrappers ;-)
728   */
729   NSString *htmlPath;
730   
731   htmlPath = [_name stringByAppendingPathExtension:@"html"];
732   htmlPath = [_path stringByAppendingPathComponent:htmlPath];
733   return [[self fileManager] fileExistsAtPath:htmlPath];
734 }
735
736 - (WOComponentDefinition *)_processWrapperLanguageProjects:(NSString *)_name
737   componentPath:(NSString *)componentPath
738   languages:(NSArray *)_langs
739 {
740   /* 
741      this looks for language projects contained in template wrapper 
742      directories, eg "Main.wo/English.lproj/"
743   */
744   WOComponentDefinition *cdef = nil;
745   NSFileManager         *fm   = nil;
746   NSEnumerator *languages;
747   NSString     *language;
748   NSString     *sname;
749   BOOL         doesCache;
750
751   if ([_langs count] == 0)
752     return nil;
753   
754   doesCache = [[WOApplication application] isCachingEnabled];
755   fm        = [self fileManager];
756   sname     = [_name stringByAppendingString:@"\t"];
757   
758   languages = [_langs objectEnumerator];
759   while ((language = [languages nextObject])) {
760     NSString *compoundKey  = nil;
761     NSString *languagePath = nil;
762     BOOL     isDirectory   = NO;
763     NSString *baseUrl = nil;
764     
765     // [self logWithFormat:@"check %@ / %@", _name, language];
766     
767     compoundKey = [sname stringByAppendingString:language];
768     if (doesCache) {
769       cdef = NSMapGet(self->componentDefinitions, compoundKey);
770       
771       if (cdef == (id)null)
772         /* resource does not exist */
773         continue;
774           
775       [cdef touch];
776       if (cdef) return cdef; // found definition in cache
777     }
778         
779     /* take a look into the file system */
780     languagePath = [language stringByAppendingPathExtension:@"lproj"];
781     languagePath = [componentPath stringByAppendingPathComponent:languagePath];
782         
783     if (![fm fileExistsAtPath:languagePath isDirectory:&isDirectory]) {
784       if (doesCache) {
785         // register null in cache, so that we know it's non-existent
786         NSMapInsert(self->componentDefinitions, compoundKey, null);
787       }
788       continue;
789     }
790     
791     if (!isDirectory) {
792       [self warnWithFormat:@"(%s): language entry %@ is not a directory !",
793               __PRETTY_FUNCTION__, languagePath];
794       if (doesCache && (compoundKey != nil)) {
795         // register null in cache, so that we know it's non-existent
796         NSMapInsert(self->componentDefinitions, compoundKey, null);
797       }
798       continue;
799     }
800     
801     /* 
802        Now check whether this actually does contain a template! 
803        
804        This is new and hopefully doesn't break anything, but as far as I can
805        see checking for Component.html inside should do the right thing (unless
806        there are template wrappers which are not .wo wrappers ;-)
807     */
808     if (![self _isValidWrapperDirectory:languagePath
809                containingTemplate:_name]){
810       [self debugWithFormat:@"no HTML template for inside lproj '%@': '%@'",
811               _name, languagePath];
812       if (doesCache && (compoundKey != nil)) {
813         // register null in cache, so that we know it's non-existent
814         NSMapInsert(self->componentDefinitions, compoundKey, null);
815       }
816       continue;
817     }
818     
819     /* construct the base URL */
820     
821     baseUrl = [[[WOApplication application] baseURL] absoluteString];
822     baseUrl = [NSString stringWithFormat:@"%@/%@.lproj/%@.wo",
823                           baseUrl, language, _name];
824     
825     /* create WOComponentDefinition object */
826     
827     cdef = [self _definitionWithName:_name
828                  path:languagePath
829                  baseURL:[NSURL URLWithString:baseUrl]
830                  frameworkName:nil];
831     if (cdef == nil) {
832       [self warnWithFormat:@"(%s): could not load component definition of "
833               @"'%@' from language project: %@", 
834               __PRETTY_FUNCTION__, _name, languagePath];
835       if (doesCache && (compoundKey != nil)) {
836         // register null in cache, so that we know it's non-existent
837         NSMapInsert(self->componentDefinitions, compoundKey, null);
838       }
839       continue;
840     }
841     
842     if (doesCache && (compoundKey != nil)) {
843       // register in cache
844       NSMapInsert(self->componentDefinitions, compoundKey, cdef);
845       [cdef release];
846     }
847     else {
848       // don't register in cache
849       cdef = [cdef autorelease];
850     }
851     
852     return cdef;
853   }
854   
855   return nil; /* no lproj containing templates was found */
856 }
857
858 - (NSString *)defaultFrameworkForComponentNamed:(NSString *)_name {
859   Class clazz;
860   
861   if (rapidTurnAroundPath != nil)
862     return rapidTurnAroundPath;
863   
864   if ((clazz = NSClassFromString(_name)) == nil)
865     return nil;
866   
867   return [[NSBundle bundleForClass:clazz] bundlePath];
868 }
869
870 - (WOComponentDefinition *)definitionForComponent:(id)_name
871   inFramework:(NSString *)_framework
872   languages:(NSArray *)_languages
873 {
874   /* this is the primary method for finding a definition */
875   // TODO: this method is too large
876   WOApplication         *app;
877   NSFileManager         *fm            = nil;
878   WOComponentDefinition *cdef          = nil;
879   NSURL                 *componentURL;
880   BOOL                  doesCache, isDir;
881   
882   app       = [WOApplication application];
883   doesCache = [app isCachingEnabled];
884   
885   /* lookup component path */
886   
887   if ([_name isKindOfClass:UrlClass]) {
888     componentURL = _name;
889     _name = [componentURL path];
890     if (debugComponentLookup) {
891       [self debugWithFormat:@"using URL %@ for component %@",
892               componentURL, _name];
893     }
894   }
895   else {
896     NSString *path;
897     
898     /* 
899        Note: this is a bit of a hack ..., actually this method should never
900              be called without a framework and pages shouldn't be instantiated
901              without specifying their framework.
902              But for legacy reasons this needs to be done and seems to work
903              without problems. It is required for loading components from
904              bundles.
905     */
906     if (_framework == nil && _name != nil)
907       _framework = [self defaultFrameworkForComponentNamed:_name];
908     
909     if (debugComponentLookup) {
910       [self logWithFormat:@"lookup: component '%@' in framework '%@'", 
911               _name, _framework];
912     }
913     
914     /* look for .wox component */
915     
916     // TODO: why don't we use -pathForComponentNamed: here?
917     path = [self pathForResourceNamed:
918                    [self resourceNameForComponentNamed:_name]
919                  inFramework:_framework
920                  languages:_languages];
921     
922     if (debugComponentLookup)
923       [self logWithFormat:@"lookup:  path-to-resource: '%@'", path];
924     
925     /* look for .wo component */
926     
927     if ([path length] == 0) {
928       path = [self pathToComponentNamed:_name
929                    inFramework:_framework
930                    languages:_languages];
931       if (debugComponentLookup)
932         [self logWithFormat:@"lookup:  path-to-component: '%@'", path];
933     }
934     
935     /* make URL from path */
936     
937     componentURL = ([path length] > 0)
938       ? [[[UrlClass alloc] initFileURLWithPath:path] autorelease]
939       : nil;
940   }
941   
942   if (debugComponentLookup) {
943     [self logWithFormat:@"  component='%@' in framework='%@'", 
944             _name, _framework];
945     [self logWithFormat:@"  => '%@'", [componentURL absoluteString]];
946   }
947   
948   /* check whether it's a 'template-less' component ... */
949   
950   if (componentURL == nil) {
951     /* did not find component wrapper ! */
952     [app debugWithFormat:@"  component '%@' has no template !", _name];
953     
954     cdef = [self _definitionForPathlessComponent:_name languages:_languages];
955     return cdef;
956   }
957   
958   fm = [self fileManager];
959   
960   /* ensure that the component exists */
961   
962   isDir = NO;
963   if ([componentURL isFileURL]) {
964     NSString *componentPath;
965     
966     componentPath = [componentURL path];
967     
968     if (![fm fileExistsAtPath:componentPath isDirectory:&isDir]) {
969       [[WOApplication application]
970                       debugWithFormat:
971                         @"%s: did not find component '%@' at path '%@' !",
972                         __PRETTY_FUNCTION__,
973                         _name, componentPath];
974       return nil;
975     }
976     
977     /* if the component spec is a directory (eg a .wo), scan lproj's inside */
978     if (isDir && [_languages count] > 0) {
979       if (debugComponentLookup) {
980         [self logWithFormat:@"  check wrapper languages (%d)", 
981               [_languages count]];
982       }
983       cdef = [self _processWrapperLanguageProjects:_name
984                    componentPath:componentPath
985                    languages:_languages];
986       if (cdef != nil) {
987         if (debugComponentLookup)
988           [self logWithFormat:@"  => FOUND: %@", cdef];
989         return cdef;
990       }
991       else if (debugComponentLookup)
992         [self logWithFormat:@"  ... no language template found ..."];
993     }
994   }
995   
996   /* look flat */
997   
998   if (doesCache) {
999     cdef = NSMapGet(self->componentDefinitions, componentURL);
1000     if (cdef == (id)null)
1001       /* resource does not exist */
1002       return nil;
1003     [cdef touch];
1004     
1005     if (cdef != nil) return cdef; // found definition in cache
1006   }
1007
1008   /* 
1009      in case the "componentURL" is a directory, check whether it contains
1010      an HTML file
1011   */
1012   if (isDir) {
1013     if (![self _isValidWrapperDirectory:[componentURL path]
1014                containingTemplate:_name]) {
1015       if (debugComponentLookup)
1016         [self logWithFormat:@"  not a valid wrapper '%@': '%@'",
1017                 _name, [componentURL absoluteString]];
1018       if (doesCache) {
1019         /* register null in cache, so that we know it's non-existent */
1020         NSMapInsert(self->componentDefinitions, componentURL, null);
1021       }
1022       return nil;
1023     }
1024   }
1025   
1026   /* take a look into the file system */
1027   {
1028     NSString *baseUrl = nil;
1029     
1030     baseUrl = [NSString stringWithFormat:@"%@/%@",
1031                           [[app baseURL] absoluteString], 
1032                           [_name lastPathComponent]];
1033     
1034     cdef = [self _definitionWithName:_name
1035                  url:componentURL
1036                  baseURL:[NSURL URLWithString:baseUrl]
1037                  frameworkName:nil];
1038     if (cdef == nil) {
1039       [self warnWithFormat:@"(%s): could not load component definition of "
1040               @"'%@' from component wrapper: '%@'", 
1041               __PRETTY_FUNCTION__, _name, componentURL];
1042       if (doesCache) {
1043         /* register null in cache, so that we know it's non-existent */
1044         NSMapInsert(self->componentDefinitions, componentURL, null);
1045       }
1046       return nil;
1047     }
1048     
1049     if (doesCache) {
1050       /* register in cache */
1051       NSMapInsert(self->componentDefinitions, componentURL, cdef);
1052       [cdef release];
1053     }
1054     else
1055       /* don't register in cache, does not cache */
1056       cdef = [cdef autorelease];
1057
1058     return cdef;
1059   }
1060   
1061   /* did not find component */
1062   return nil;
1063 }
1064 - (WOComponentDefinition *)definitionForComponent:(id)_name
1065   languages:(NSArray *)_langs
1066 {
1067   /* Note: the framework will be determined base on the class '_name' */
1068   return [self definitionForComponent:_name inFramework:nil languages:_langs];
1069 }
1070
1071 /* caching */
1072
1073 - (WOComponentDefinition *)__definitionForComponent:(id)_name
1074   languages:(NSArray *)_languages
1075 {
1076   // TODO: this should add the framework parameter and maybe a context
1077   WOComponentDefinition *cdef;
1078   
1079   /* look into cache */
1080   
1081   cdef = [self _cachedDefinitionForComponent:_name languages:_languages];
1082   if (cdef != nil) {
1083     if (cdef == (id)null)
1084       /* component does not exist */
1085       return nil;
1086
1087     if ([cdef respondsToSelector:@selector(touch)])
1088       [cdef touch];
1089     return cdef;
1090   }
1091   
1092   /* not cached, create a definition */
1093   
1094   cdef = [self definitionForComponent:_name languages:_languages];
1095
1096   /* cache created definition */
1097   
1098   return [self _cacheDefinition:cdef forComponent:_name languages:_languages];
1099 }
1100
1101 /* primary call-in's */
1102
1103 - (WOElement *)templateWithName:(NSString *)_name
1104   languages:(NSArray *)_languages
1105 {
1106   WOComponentDefinition *cdef;
1107   
1108   cdef = [self __definitionForComponent:_name languages:_languages];
1109   if (cdef == nil) return nil;
1110   
1111   return (WOElement *)[cdef template];
1112 }
1113
1114 - (id)pageWithName:(NSString *)_name languages:(NSArray *)_languages {
1115   /* 
1116      TODO: this appears to be deprecated since the WOComponent initializer
1117            is now -initWithContext: and we have no context here ...
1118            Also misses the framework?
1119   */
1120   NSAutoreleasePool     *pool      = nil;
1121   WOComponentDefinition *cdef      = nil;
1122   WOComponent           *component = nil;
1123   
1124   pool = [[NSAutoreleasePool alloc] init];
1125   {
1126     cdef = [self __definitionForComponent:_name languages:_languages];
1127     if (cdef != nil) {
1128       component = 
1129         [cdef instantiateWithResourceManager:(WOResourceManager *)self 
1130               languages:_languages];
1131       component = [component retain];
1132     }
1133   }
1134   [pool release];
1135   
1136   return [component autorelease];
1137 }
1138
1139 /* KeyedData */
1140
1141 - (void)setData:(NSData *)_data
1142   forKey:(NSString *)_key
1143   mimeType:(NSString *)_type
1144   session:(WOSession *)_session
1145 {
1146   if ((_key == nil) || (_data == nil))
1147     return;
1148   if (_type == nil)
1149     _type = @"application/octet-stream";
1150   
1151   [self lock];
1152   
1153   if (self->keyedResources == NULL) {
1154     self->keyedResources = NSCreateMapTable(NSObjectMapKeyCallBacks,
1155                                             NSObjectMapValueCallBacks,
1156                                             128);
1157   }
1158
1159   NSMapInsert(self->keyedResources,
1160               _key,
1161               [NSDictionary dictionaryWithObjectsAndKeys:
1162                               _type, @"mimeType",
1163                               _key,  @"key",
1164                               _data, @"data",
1165                             nil]);
1166   
1167   [self unlock];
1168 }
1169
1170 - (id)_dataForKey:(NSString *)_key sessionID:(NSString *)_sid {
1171   id tmp;
1172
1173   [self lock];
1174   
1175   if (self->keyedResources)
1176     tmp = NSMapGet(self->keyedResources, _key);
1177   else
1178     tmp = nil;
1179   
1180   tmp = [[tmp retain] autorelease];
1181   
1182   [self unlock];
1183
1184   return tmp;
1185 }
1186
1187 - (void)removeDataForKey:(NSString *)_key session:(WOSession *)_session {
1188   [self lock];
1189   
1190   if (self->keyedResources)
1191     NSMapRemove(self->keyedResources, _key);
1192   
1193   [self unlock];
1194 }
1195
1196 - (void)flushDataCache {
1197   [self lock];
1198
1199   if (self->keyedResources) {
1200     NSFreeMapTable(self->keyedResources);
1201     self->keyedResources = NULL;
1202   }
1203   
1204   [self unlock];
1205 }
1206
1207 /* description */
1208
1209 - (NSString *)description {
1210   return [NSString stringWithFormat:@"<%@[0x%p]: path=%@>",
1211                      [self class], self, self->base];
1212                    
1213 }
1214
1215 @end /* OWResourceManager */