]> err.no Git - sope/blob - sope-appserver/NGObjWeb/SoObjects/SoProductResourceManager.m
use %p for pointer formats, fixed gcc 4.1 warnings, minor code improvs
[sope] / sope-appserver / NGObjWeb / SoObjects / SoProductResourceManager.m
1 /*
2   Copyright (C) 2002-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 "SoProductResourceManager.h"
23 #include "SoProduct.h"
24 #include "SoObject.h"
25 #include "SoClassSecurityInfo.h"
26 #include "SoProductRegistry.h"
27 #include <NGObjWeb/WOApplication.h>
28 #include <NGObjWeb/WOContext.h>
29 #include <NGObjWeb/WOResponse.h>
30 #include <NGObjWeb/WOSession.h>
31 #include <NGObjWeb/WORequest.h>
32 #include <NGExtensions/NSString+Ext.h>
33 #include "common.h"
34
35 /*
36   How is resource lookup supposed to work?
37
38   First, we need to determine whether the resource being asked for is a 
39   resource contained in the product bundle. If thats the case, this resource
40   manager will take of it.
41   
42   If not, there are two options:
43   a) the resource is from a different product, so we direct lookup to that
44   b) the resource is a "global" resource, so we ask the WOApplication manager
45 */
46
47 @interface WOResourceManager(UsedPrivates)
48 - (NSString *)webServerResourcesPath;
49 - (NSString *)resourcesPath;
50 @end
51
52 @implementation SoProductResourceManager
53
54 static NGBundleManager *bm = nil;
55 static BOOL debugOn = NO;
56
57 + (void)initialize {
58   NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
59   
60   bm = [[NGBundleManager defaultBundleManager] retain];
61   debugOn = [ud boolForKey:@"SoProductResourceManagerDebugEnabled"];
62 }
63
64 - (id)initWithProduct:(SoProduct *)_product {
65   if ((self = [super initWithPath:[[_product bundle] bundlePath]])) {
66     self->product = _product;
67   }
68   return self;
69 }
70
71 /* containment */
72
73 - (void)detachFromContainer {
74   self->product = nil;
75 }
76 - (id)container {
77   return self->product;
78 }
79 - (NSString *)nameInContainer {
80   return @"Resources";
81 }
82
83 - (WOResourceManager *)fallbackResourceManager {
84   WOResourceManager *rm;
85   
86   rm = [[WOApplication application] resourceManager];
87   return (rm == self) ? (WOResourceManager *)nil : rm; /* avoid recursion */
88 }
89
90 /* lookup resources */
91
92 - (NSBundle *)bundleForFrameworkName:(NSString *)_fwName {
93   NSBundle *bundle;
94   
95   if ([_fwName length] == 0) {
96     if ((bundle = [self->product bundle]) == nil)
97       [self warnWithFormat:@"missing bundle for product: %@", self->product];
98     return bundle;
99   }
100   
101   if ([_fwName hasPrefix:@"/"]) {
102     bundle = [bm bundleWithPath:_fwName];
103   }
104   else {
105     bundle = [bm bundleWithName:[_fwName stringByDeletingPathExtension]
106                  type:[_fwName pathExtension]];
107   }
108   if (bundle == nil)
109       [self warnWithFormat:@"missing bundle for framework: '%@'", _fwName];
110   
111   return bundle;
112 }
113
114 - (WOResourceManager *)resourceManagerForResourceNamed:(NSString *)_name
115   inFramework:(NSString *)_frameworkName
116 {
117   SoProduct *bproduct;
118   NSBundle  *bundle;
119   
120   /* determine product bundle or explicitly requested framework/bundle */
121
122   if ([_frameworkName length] == 0)
123     return self;
124   
125   if ((bundle = [self bundleForFrameworkName:_frameworkName]) == nil)
126     return nil;
127   
128   bproduct = [[SoProductRegistry sharedProductRegistry] 
129                productForBundle:bundle];
130   
131   if (debugOn) {
132     [self debugWithFormat:
133             @"  fwname: '%@'\n  bundle: '%@'\n  product: '%@'", 
134             _frameworkName, bundle, bproduct];
135   }
136   return (bproduct == self->product)
137     ? self : (SoProductResourceManager *)[bproduct resourceManager];
138 }
139
140 - (NSString *)primaryLookupPathForResourceNamed:(NSString *)_name
141   languages:(NSArray *)_l
142 {
143   NSString *path;
144   
145   path = [[self->product bundle]
146              pathForResource:[_name stringByDeletingPathExtension]
147              ofType:[_name pathExtension]
148              inDirectory:nil
149              languages:_l];
150   if (debugOn && path == nil) {
151     [self debugWithFormat:@"  resource %@/%@ not found in bundle: %@",
152           _name, [_l componentsJoinedByString:@","], [self->product bundle]];
153   }
154   return path;
155 }
156
157 - (NSString *)pathForResourceNamed:(NSString *)_name
158   inFramework:(NSString *)_frameworkName
159   languages:(NSArray *)_languages
160 {
161   // TODO: should we do acquisition? (hm, don't think so!, done in lookup)
162   //       but maybe we should not fall back to WOApplication resources
163   WOResourceManager *rm;
164   NSString *path;
165   
166   if (debugOn) [self debugWithFormat:@"lookup resource: '%@'", _name];
167   
168   /* determine resource manager to be actually used */
169   
170   rm = [self resourceManagerForResourceNamed:_name inFramework:_frameworkName];
171   
172   /* lookup resource */
173   
174   if (rm == self) {
175     path = [self primaryLookupPathForResourceNamed:_name languages:_languages];
176   }
177   else if (rm != nil) {
178     /* delegate lookup to resource manager of other products */
179     path = [rm pathForResourceNamed:_name inFramework:_frameworkName
180                languages:_languages];
181     if (debugOn && path == nil)
182       [self debugWithFormat:@"  resource %@ not found in rm: %@", _name, rm];
183   }
184   else
185     path = nil;
186   
187   if (path != nil) {
188     if (debugOn) [self debugWithFormat:@"  => found: %@", path];
189     return path;
190   }
191   
192   /* fall back to global resource manager */
193   
194   return [[self fallbackResourceManager]
195            pathForResourceNamed:_name inFramework:_frameworkName
196            languages:_languages];
197 }
198
199 /* generate URL for resources (eg filename binding in WOImage) */
200
201 - (NSString *)webServerResourcesPath {
202   /* to avoid warning that WebServerResources path does not exist ... */
203   return [[self fallbackResourceManager] webServerResourcesPath];
204 }
205
206 - (NSString *)urlForProductRelativeResourcePath:(NSString *)resource {
207   NSString *tmp;
208   NSString *path = nil, *sbase;
209   unsigned len;
210   NSString *url;
211   
212   sbase = self->base;
213   tmp  = [sbase commonPrefixWithString:resource options:0];
214   
215   len  = [tmp length];
216   path = [sbase    substringFromIndex:len];
217   tmp  = [resource substringFromIndex:len];
218   if (([path length] > 0) && ![tmp hasPrefix:@"/"] && ![tmp hasPrefix:@"\\"])
219     path = [path stringByAppendingString:@"/"];
220   path = [path stringByAppendingString:tmp];
221   
222 #ifdef __WIN32__
223   {
224     NSArray *cs;
225     cs   = [path componentsSeparatedByString:@"\\"];
226     path = [cs componentsJoinedByString:@"/"];
227   }
228 #endif
229   if (path == nil)
230     return nil;
231   
232   if ([path hasPrefix:@"/Resources/"])
233     path = [path substringFromIndex:11];
234   else if ([path hasPrefix:@"Resources/"])
235     path = [path substringFromIndex:10];
236   
237   /* Note: cannot use -stringByAppendingPathComponent: on OSX! */
238   url = [self baseURLInContext:[[WOApplication application] context]];
239   if (debugOn) [self debugWithFormat:@" base: '%@'", url];
240   
241   if (![url hasSuffix:@"/"]) url = [url stringByAppendingString:@"/"];
242   url = [url stringByAppendingString:path];
243   return url;
244 }
245
246 - (NSString *)fixupResourcePath:(NSString *)resource {
247 #if APPLE_Foundation_LIBRARY || NeXT_Foundation_LIBRARY
248   if ([resource rangeOfString:@"/Contents/"].length > 0) {
249     resource = [resource stringByReplacingString:@"/Contents"
250                          withString:@""];
251   }
252 #endif
253 #if 0
254   if ((tmp = [resource stringByStandardizingPath]) != nil)
255     resource = tmp;
256 #endif
257   return resource;
258 }
259
260 - (NSString *)urlForResourceNamed:(NSString *)_name
261   inFramework:(NSString *)_frameworkName
262   languages:(NSArray *)_languages
263   request:(WORequest *)_request
264 {
265   WOResourceManager *rm;
266   NSString *url;
267   
268   if (debugOn) [self debugWithFormat:@"lookup url: '%@'", _name];
269   
270   if (_languages == nil) _languages = [_request browserLanguages];
271   
272   /* determine resource manager to be actually used */
273   
274   rm = [self resourceManagerForResourceNamed:_name inFramework:_frameworkName];
275
276   /* lookup resource */
277   
278   url = nil;
279   if (rm == self) { /* handle it directly */
280     NSString *resource;
281     
282     resource = [self primaryLookupPathForResourceNamed:_name
283                      languages:_languages];
284     if (debugOn) [self debugWithFormat:@"  resource: %@", resource];
285     resource = [self fixupResourcePath:resource];
286     if (debugOn) [self debugWithFormat:@"  resource to URL: %@", resource];
287     
288     if (resource != nil) {
289       url = [self urlForProductRelativeResourcePath:resource];
290     }
291     else if (debugOn) {
292       [self debugWithFormat:@"  => not found '%@' (fw=%@,langs=%@)", 
293               _name, _frameworkName, 
294               [_languages componentsJoinedByString:@","]];
295     }
296   }
297   else if (rm != nil) { /* delegate to other product */
298     if (debugOn) [self debugWithFormat:@"  lookup with rm: %@", rm];
299     url = [rm urlForResourceNamed:_name inFramework:_frameworkName
300               languages:_languages request:_request];
301   }
302   
303   if (url == nil) { /* fallback */
304     rm = [self fallbackResourceManager];
305     if (debugOn) [self debugWithFormat:@"  fallback: %@", rm];
306     url = [rm urlForResourceNamed:_name inFramework:_frameworkName
307               languages:_languages request:_request];
308   }
309   
310   if (debugOn) [self debugWithFormat:@"  => '%@'", url];
311   return url;
312 }
313
314 - (NSString *)pathToComponentNamed:(NSString *)_name
315   inFramework:(NSString *)_fwname
316   languages:(NSArray *)_langs
317 {
318   NSString *p;
319   
320   p = [super pathToComponentNamed:_name inFramework:_fwname languages:_langs];
321   if (![p isNotNull] || [p length] == 0 ) {
322     [self logWithFormat:@"LOOKUP FAILED: %@", _name];
323     p = [[self fallbackResourceManager] pathToComponentNamed:_name
324                                         inFramework:_fwname
325                                         languages:_langs];
326     [self logWithFormat:@"  PARENT (%@) SAID: %@", 
327             [self fallbackResourceManager], p];
328   }
329   return p;
330 }
331
332 - (WOElement *)templateWithName:(NSString *)_name
333   languages:(NSArray *)_languages
334 {
335   WOResourceManager *arm;
336   WOElement *e;
337   
338   if (debugOn) {
339     [self logWithFormat:@"lookup template with name '%@' (languages=%@)",
340           _name, [_languages componentsJoinedByString:@","]];
341   }
342   if ((e = [super templateWithName:_name languages:_languages]) != nil) {
343     if (debugOn) [self logWithFormat:@"  found: %@", e];
344     return e;
345   }
346   
347   arm = [self fallbackResourceManager];
348   if (arm == self) return nil;
349   
350   if (debugOn) [self logWithFormat:@"  lookup in parent RM: %@", arm];
351   if ((e = [arm templateWithName:_name languages:_languages]) != nil) {
352     if (debugOn) [self logWithFormat:@"  found: %@", e];
353     return e;
354   }
355   
356   if (debugOn) [self logWithFormat:@"did not find template %@", _name];
357   return nil;
358 }
359
360 /* resource manager as a SoObject */
361
362 - (NSString *)mimeTypeForExtension:(NSString *)_ext {
363   // TODO: HACK, move to some object
364   NSString *ctype = nil;
365   
366   if ([_ext isEqualToString:@"css"])       ctype = @"text/css";
367   else if ([_ext isEqualToString:@"gif"])  ctype = @"image/gif";
368   else if ([_ext isEqualToString:@"jpg"])  ctype = @"image/jpeg";
369   else if ([_ext isEqualToString:@"png"])  ctype = @"image/png";
370   else if ([_ext isEqualToString:@"html"]) ctype = @"text/html";
371   else if ([_ext isEqualToString:@"xml"])  ctype = @"text/xml";
372   else if ([_ext isEqualToString:@"txt"])  ctype = @"text/plain";
373   else if ([_ext isEqualToString:@"js"])   ctype = @"application/x-javascript";
374   else if ([_ext isEqualToString:@"xhtml"]) ctype = @"application/xhtml+xml";
375   return ctype;
376 }
377
378 - (id)lookupName:(NSString *)_key inContext:(id)_ctx acquire:(BOOL)_flag {
379   WOResponse *r;
380   NSBundle   *b;
381   NSString   *p, *pe, *ctype;
382   NSData     *data;
383   NSArray    *languages = nil;
384   
385   /* TODO: add support for languages (eg English.lproj/ok.gif) ! */
386   
387   /* check whether the resource is made public */
388   
389   if (![self->product isPublicResource:_key]) {
390     [self debugWithFormat:@"key '%@' is not declared a public resource.",_key];
391     return nil;
392   }
393   
394   if ((b = [self->product bundle]) == nil) {
395     [self debugWithFormat:@"product has no bundle for lookup of %@", _key];
396     return nil;
397   }
398   
399   pe = [_key pathExtension];
400
401   /* ask resource-manager (self) for path */
402   
403   languages = [_ctx resourceLookupLanguages];
404   
405   p = [self pathForResourceNamed:_key 
406             inFramework:[b bundlePath]
407             languages:languages];
408   if (p == nil) {
409     [self errorWithFormat:@"did not find product resource: %@", _key];
410     return nil;
411   }
412
413   /* load data */
414
415   if ((data = [NSData dataWithContentsOfMappedFile:p]) == nil) {
416     [self errorWithFormat:@"failed to load product resource: %@", _key];
417     return nil;
418   }
419   
420   /* and deliver as a complete response */
421   
422   r = [(id<WOPageGenerationContext>)_ctx response];
423   
424   [r setStatus:200 /* OK */];
425   [r setContent:data];
426   
427   if ((ctype = [self mimeTypeForExtension:pe]) == nil) {
428     [self warnWithFormat:@"did not recognize extension '%@', "
429             @"delivering as application/octet-stream.", pe];
430     ctype = @"application/octet-stream";
431   }
432
433   {
434     NSDate *expDate = nil;
435     NSString *str = nil;
436     
437     expDate = [[NSDate alloc] initWithTimeInterval:(60 * 60 * 1) /* 1 hour */
438                               sinceDate:[NSDate date]];
439     str = [expDate descriptionWithCalendarFormat:
440                      @"%a, %d %b %Y %H:%M:%S GMT"
441                    timeZone:[NSTimeZone timeZoneWithAbbreviation:@"GMT"]
442                    locale:nil];
443     [r setHeader:str forKey:@"expires"];
444     [expDate release];
445   }
446   
447   [r setHeader:ctype forKey:@"content-type"];
448   return r;
449 }
450
451 /* debugging */
452
453 - (BOOL)isDebuggingEnabled {
454   return debugOn;
455 }
456 - (NSString *)loggingPrefix {
457   return [NSString stringWithFormat:@"[RM:%@]", [self->product productName]];
458 }
459
460 /* description */
461
462 - (NSString *)description {
463   NSMutableString *str;
464
465   str = [NSMutableString stringWithCapacity:64];
466   [str appendFormat:@"<%@[0x%p]:", NSStringFromClass([self class]), self];
467   [str appendFormat:@" product='%@'", [self->product productName]];
468   [str appendString:@">"];
469   return str;
470 }
471
472 @end /* SoProductResourceManager */