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