]> err.no Git - sope/blob - sope-appserver/NGObjWeb/WOResourceManager.m
added a flag to disable XML style generation of close tags
[sope] / sope-appserver / NGObjWeb / WOResourceManager.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 #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     [self errorWithFormat:@"(%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       [self warnWithFormat:@"(%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 warnWithFormat:
272               @"(%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 || (_fw == rapidTurnAroundPath)) 
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 warnWithFormat:
317               @"(%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     [self warnWithFormat:
610             @"(%s): tried to get path to component with <nil> name !",
611             __PRETTY_FUNCTION__];
612 #endif
613     return nil;
614   }
615   
616   /* scan for name.$ext resource ... */
617   e = [[[NSUserDefaults standardUserDefaults]
618                         arrayForKey:@"WOComponentExtensions"]
619                         objectEnumerator];
620     
621   while ((ext = [e nextObject])) {
622     NSString *specName;
623     NSString *path;
624       
625     specName = [_name stringByAppendingPathExtension:ext];
626       
627     path = [self pathForResourceNamed:specName
628                  inFramework:_framework
629                  languages:nil];
630     if (path) return path;
631   }
632   return nil;
633 }
634
635 - (NSString *)pathToComponentNamed:(NSString *)_name
636   inFramework:(NSString *)_framework
637   languages:(NSArray *)_langs
638 {
639   return [self pathToComponentNamed:_name inFramework:_framework];
640 }
641
642 - (WOComponentDefinition *)_definitionForPathlessComponent:(NSString *)_name
643   languages:(NSArray *)_languages
644 {
645   /* definition factory */
646   WOComponentDefinition *cdef;
647   
648   cdef = [[WOComponentDefinition allocWithZone:[self zone]]
649                                  initWithName:_name
650                                  path:nil
651                                  baseURL:nil
652                                  frameworkName:nil];
653   
654   return [cdef autorelease];
655 }
656
657 - (WOComponentDefinition *)_definitionWithName:(NSString *)_name
658   url:(NSURL *)_url
659   baseURL:(NSURL *)_baseURL
660   frameworkName:(NSString *)_fwname
661 {
662   /* definition factory */
663   static Class DefClass;
664   id cdef;
665   
666   if (DefClass == Nil)
667     DefClass = [WOComponentDefinition class];
668
669   // TODO: is retained response intended?
670   cdef = [[DefClass alloc] initWithName:_name
671                            path:[_url path]
672                            baseURL:_baseURL frameworkName:_fwname];
673   return cdef;
674 }
675 - (WOComponentDefinition *)_definitionWithName:(NSString *)_name
676   path:(NSString *)_path
677   baseURL:(NSURL *)_baseURL
678   frameworkName:(NSString *)_fwname
679 {
680   NSURL *url;
681   
682   url = ([_path length] > 0)
683     ? [[[NSURL alloc] initFileURLWithPath:_path] autorelease]
684     : nil;
685   
686   return [self _definitionWithName:_name url:url
687                baseURL:_baseURL frameworkName:_fwname];
688 }
689
690 - (WOComponentDefinition *)_cachedDefinitionForComponent:(id)_name
691   languages:(NSArray *)_languages
692 {
693   NSArray *cacheKey;
694   id      cdef;
695
696   if (self->componentDefinitions == NULL)
697     return nil;
698   if (![[WOApplication application] isCachingEnabled])
699     return nil;
700   
701   cacheKey = [NSArray arrayWithObjects:_name, _languages, nil];
702   cdef     = NSMapGet(self->componentDefinitions, cacheKey);
703   
704   return cdef;
705 }
706 - (WOComponentDefinition *)_cacheDefinition:(id)_cdef
707   forComponent:(id)_name
708   languages:(NSArray *)_languages
709 {
710   NSArray *cacheKey;
711
712   if (self->componentDefinitions == NULL)
713     return _cdef;
714   if (![[WOApplication application] isCachingEnabled])
715     return _cdef;
716   
717   cacheKey = [NSArray arrayWithObjects:_name, _languages, nil];
718   NSMapInsert(self->componentDefinitions, cacheKey, _cdef ? _cdef : null);
719
720   return _cdef;
721 }
722
723 - (NSString *)resourceNameForComponentNamed:(NSString *)_name {
724   return [_name stringByAppendingPathExtension:@"wox"];
725 }
726
727 /* create component definition */
728
729 - (void)_getComponentURL:(NSURL **)url_ andName:(NSString **)name_
730   forNameOrURL:(id)_nameOrURL inFramework:(NSString *)_framework
731   languages:(NSArray *)_languages
732 {
733   NSString *path;
734   
735   if ([_nameOrURL isKindOfClass:UrlClass]) {
736     // TODO: where is this used currently? It probably was required for forms,
737     //       but might not be anymore?
738     *url_  = _nameOrURL;
739     *name_ = [*url_ path];
740     if (debugComponentLookup)
741       [self debugWithFormat:@"using URL %@ for component %@", *url_, *name_];
742     return;
743   }
744   
745   /* the _nameOrURL is a string containing the component name */
746   
747   *name_ = _nameOrURL;
748
749   if (_framework == nil && _nameOrURL != nil) {
750     Class clazz;
751       
752     /* 
753        Note: this is a bit of a hack ..., actually this method should never
754        be called without a framework and pages shouldn't be instantiated
755        without specifying their framework.
756        But for legacy reasons this needs to be done and seems to work without
757        problems. It is required for loading components from bundles.
758     */
759     if ((_framework = rapidTurnAroundPath) == nil) {
760       if ((clazz = NSClassFromString(_nameOrURL)))
761         _framework = [[NSBundle bundleForClass:clazz] bundlePath];
762     }
763   }
764   
765   if (debugComponentLookup) {
766     [self logWithFormat:@"component '%@' in framework '%@'", 
767             _nameOrURL, _framework];
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:@"  .wox 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:@"  .wo  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       [self warnWithFormat:@"(%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             [self warnWithFormat:
883                     @"(%s): could not load component definition of "
884                     @"'%@' from language project: %@", 
885                     __PRETTY_FUNCTION__, _name, languagePath];
886             if (doesCache && (compoundKey != nil)) {
887               // register null in cache, so that we know it's non-existent
888               NSMapInsert(self->componentDefinitions, compoundKey, null);
889             }
890             continue;
891     }
892     
893     if (doesCache && (compoundKey != nil)) {
894             // register in cache
895             NSMapInsert(self->componentDefinitions, compoundKey, cdef);
896             [cdef release];
897     }
898     else {
899             // don't register in cache
900             cdef = [cdef autorelease];
901     }
902           
903     return cdef;
904   }
905   return nil;
906 }
907
908 - (WOComponentDefinition *)definitionForComponent:(id)_name
909   inFramework:(NSString *)_framework
910   languages:(NSArray *)_languages
911 {
912   // TODO: this method is definitely too big! => refacture
913   WOApplication         *app;
914   NSFileManager         *fm            = nil;
915   WOComponentDefinition *cdef          = nil;
916   NSURL                 *componentURL;
917   NSURL                 *appUrl;
918   BOOL                  doesCache;
919   
920   app       = [WOApplication application];
921   doesCache = [app isCachingEnabled];
922   
923   /* lookup component path */
924   
925   [self _getComponentURL:&componentURL andName:&_name 
926         forNameOrURL:_name inFramework:nil languages:nil];
927   
928   if (debugComponentLookup) {
929     [self logWithFormat:@"  component='%@' in framework='%@': url='%@'", 
930             _name, _framework, componentURL];
931   }
932   
933   appUrl = [app baseURL];
934   
935   /* check whether it's a 'template-less' component ... */
936   
937   if (componentURL == nil) {
938     /* did not find component wrapper ! */
939     [app debugWithFormat:@"  component '%@' has no template !", _name];
940     
941     cdef = [self _definitionForPathlessComponent:_name languages:_languages];
942     return cdef;
943   }
944   
945   fm = [self fileManager];
946   
947   /* ensure that the component exists */
948
949   if ([componentURL isFileURL]) {
950     WOComponentDefinition *cdef;
951
952     cdef = [self definitionForFileURL:componentURL componentName:_name
953                  inFramework:_framework languages:_languages];
954     if (cdef != nil && ![cdef isNotNull])
955       return nil;
956     else if (cdef != nil) 
957       return cdef;
958   }
959   
960   /* look flat */
961     
962   if (doesCache) {
963     cdef = NSMapGet(self->componentDefinitions, componentURL);
964       
965     if (cdef == (id)null)
966       /* resource does not exist */
967       return nil;
968     [cdef touch];
969       
970     if (cdef) return cdef; // found definition in cache
971   }
972   
973   /* take a look into the file system */
974   {
975     NSString *baseUrl = nil;
976     
977     baseUrl = [NSString stringWithFormat:@"%@/%@",
978                           [appUrl absoluteString], [_name lastPathComponent]];
979     
980     cdef = [self _definitionWithName:_name
981                  url:componentURL
982                  baseURL:[NSURL URLWithString:baseUrl]
983                  frameworkName:nil];
984     if (cdef == nil) {
985       [self warnWithFormat:
986               @"(%s): could not load component definition of '%@' from "
987               @"component wrapper: '%@'", 
988               __PRETTY_FUNCTION__, _name, componentURL];
989       if (doesCache) {
990         /* register null in cache, so that we know it's non-existent */
991         NSMapInsert(self->componentDefinitions, componentURL, null);
992       }
993       return nil;
994     }
995     
996     if (doesCache) {
997       /* register in cache */
998       NSMapInsert(self->componentDefinitions, componentURL, cdef);
999       [cdef release];
1000     }
1001     else
1002       /* don't register in cache, does not cache */
1003       cdef = [cdef autorelease];
1004
1005     return cdef;
1006   }
1007   
1008   /* did not find component */
1009   return nil;
1010 }
1011 - (WOComponentDefinition *)definitionForComponent:(id)_name
1012   languages:(NSArray *)_langs
1013 {
1014   // TODO: who uses that? Probably should be deprecated?
1015   return [self definitionForComponent:_name inFramework:nil languages:_langs];
1016 }
1017
1018 - (WOComponentDefinition *)__definitionForComponent:(id)_name
1019   languages:(NSArray *)_languages
1020 {
1021   /* 
1022      First check whether the cdef is cached, otherwise create a new one.
1023
1024      This method is used by the higher level methods and just implements the
1025      cache control.
1026      The definition itself is created by definitionForComponent:languages:.
1027   */
1028   WOComponentDefinition *cdef;
1029   
1030   /* look into cache */
1031   
1032   cdef = [self _cachedDefinitionForComponent:_name languages:_languages];
1033   if (cdef != nil) {
1034     if (cdef == (id)null)
1035       /* component does not exist */
1036       return nil;
1037     
1038     if ([cdef respondsToSelector:@selector(touch)])
1039       [cdef touch];
1040     return cdef;
1041   }
1042   
1043   /* not cached, create a definition */
1044   
1045   cdef = [self definitionForComponent:_name languages:_languages];
1046
1047   /* cache created definition */
1048   
1049   return [self _cacheDefinition:cdef forComponent:_name languages:_languages];
1050 }
1051
1052 - (WOElement *)templateWithName:(NSString *)_name
1053   languages:(NSArray *)_languages
1054 {
1055   WOComponentDefinition *cdef;
1056   
1057   cdef = [self __definitionForComponent:_name languages:_languages];
1058   if (cdef == nil)
1059     return nil;
1060   
1061   return (WOElement *)[cdef template];
1062 }
1063
1064 - (WOComponent *)pageWithName:(NSString *)_name languages:(NSArray *)_langs {
1065   /* 
1066      TODO: this appears to be deprecated since the WOComponent initializer
1067            is now -initWithContext: and we have no context over here ...
1068   */
1069   NSAutoreleasePool     *pool;
1070   WOComponentDefinition *cdef;
1071   WOComponent           *component = nil;
1072   
1073   pool = [[NSAutoreleasePool alloc] init];
1074   {
1075     cdef = [self __definitionForComponent:_name languages:_langs];
1076     if (cdef != nil) {
1077       // TODO: document what the resource manager is used for in the cdef
1078       component =
1079         [[cdef instantiateWithResourceManager:self languages:_langs] retain];
1080     }
1081   }
1082   [pool release];
1083   
1084   return [component autorelease];
1085 }
1086
1087 /* description */
1088
1089 - (NSString *)description {
1090   NSMutableString *ms;
1091
1092   ms = [NSMutableString stringWithCapacity:32];
1093   [ms appendFormat:@"<0x%08X[%@]:", self, NSStringFromClass([self class])];
1094   if ([self->base length] > 0)
1095     [ms appendFormat:@" path='%@'", self->base];
1096   [ms appendString:@">"];
1097   return ms;
1098 }
1099
1100 /* KeyedData */
1101
1102 - (void)setData:(NSData *)_data
1103   forKey:(NSString *)_key
1104   mimeType:(NSString *)_type
1105   session:(WOSession *)_session
1106 {
1107   if ((_key == nil) || (_data == nil))
1108     return;
1109   if (_type == nil)
1110     _type = @"application/octet-stream";
1111   
1112   [self lock];
1113   
1114   if (self->keyedResources == NULL) {
1115     self->keyedResources = NSCreateMapTable(NSObjectMapKeyCallBacks,
1116                                             NSObjectMapValueCallBacks,
1117                                             128);
1118   }
1119
1120   NSMapInsert(self->keyedResources,
1121               _key,
1122               [NSDictionary dictionaryWithObjectsAndKeys:
1123                               _type, @"mimeType",
1124                               _key,  @"key",
1125                               _data, @"data",
1126                             nil]);
1127   
1128   [self unlock];
1129 }
1130
1131 - (id)_dataForKey:(NSString *)_key sessionID:(NSString *)_sid {
1132   id tmp;
1133
1134   [self lock];
1135   
1136   if (self->keyedResources)
1137     tmp = NSMapGet(self->keyedResources, _key);
1138   else
1139     tmp = nil;
1140   
1141   tmp = [[tmp retain] autorelease];
1142   
1143   [self unlock];
1144
1145   return tmp;
1146 }
1147
1148 - (void)removeDataForKey:(NSString *)_key session:(WOSession *)_session {
1149   [self lock];
1150   
1151   if (self->keyedResources)
1152     NSMapRemove(self->keyedResources, _key);
1153   
1154   [self unlock];
1155 }
1156
1157 - (void)flushDataCache {
1158   [self lock];
1159
1160   if (self->keyedResources) {
1161     NSFreeMapTable(self->keyedResources);
1162     self->keyedResources = NULL;
1163   }
1164   
1165   [self unlock];
1166 }
1167
1168 @end /* WOResourceManager */