2 Copyright (C) 2002-2005 SKYRIX Software AG
4 This file is part of SOPE.
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
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.
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
22 #include "SoProductResourceManager.h"
23 #include "SoProduct.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>
36 How is resource lookup supposed to work?
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.
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
47 @interface WOResourceManager(UsedPrivates)
48 - (NSString *)webServerResourcesPath;
49 - (NSString *)resourcesPath;
52 @implementation SoProductResourceManager
54 static NGBundleManager *bm = nil;
55 static BOOL debugOn = NO;
58 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
60 bm = [[NGBundleManager defaultBundleManager] retain];
61 debugOn = [ud boolForKey:@"SoProductResourceManagerDebugEnabled"];
64 - (id)initWithProduct:(SoProduct *)_product {
65 if ((self = [super initWithPath:[[_product bundle] bundlePath]])) {
66 self->product = _product;
73 - (void)detachFromContainer {
79 - (NSString *)nameInContainer {
83 - (WOResourceManager *)fallbackResourceManager {
84 WOResourceManager *rm;
86 rm = [[WOApplication application] resourceManager];
87 return (rm == self) ? (WOResourceManager *)nil : rm; /* avoid recursion */
90 /* lookup resources */
92 - (NSBundle *)bundleForFrameworkName:(NSString *)_fwName {
95 if ([_fwName length] == 0) {
96 if ((bundle = [self->product bundle]) == nil)
97 [self warnWithFormat:@"missing bundle for product: %@", self->product];
101 if ([_fwName hasPrefix:@"/"]) {
102 bundle = [bm bundleWithPath:_fwName];
105 bundle = [bm bundleWithName:[_fwName stringByDeletingPathExtension]
106 type:[_fwName pathExtension]];
109 [self warnWithFormat:@"missing bundle for framework: '%@'", _fwName];
114 - (WOResourceManager *)resourceManagerForResourceNamed:(NSString *)_name
115 inFramework:(NSString *)_frameworkName
120 /* determine product bundle or explicitly requested framework/bundle */
122 if ([_frameworkName length] == 0)
125 if ((bundle = [self bundleForFrameworkName:_frameworkName]) == nil)
128 bproduct = [[SoProductRegistry sharedProductRegistry]
129 productForBundle:bundle];
132 [self debugWithFormat:
133 @" fwname: '%@'\n bundle: '%@'\n product: '%@'",
134 _frameworkName, bundle, bproduct];
136 return (bproduct == self->product)
137 ? self : (SoProductResourceManager *)[bproduct resourceManager];
140 - (NSString *)primaryLookupPathForResourceNamed:(NSString *)_name
141 languages:(NSArray *)_l
145 path = [[self->product bundle]
146 pathForResource:[_name stringByDeletingPathExtension]
147 ofType:[_name pathExtension]
150 if (debugOn && path == nil) {
151 [self debugWithFormat:@" resource %@/%@ not found in bundle: %@",
152 _name, [_l componentsJoinedByString:@","], [self->product bundle]];
157 - (NSString *)pathForResourceNamed:(NSString *)_name
158 inFramework:(NSString *)_frameworkName
159 languages:(NSArray *)_languages
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;
166 if (debugOn) [self debugWithFormat:@"lookup resource: '%@'", _name];
168 /* determine resource manager to be actually used */
170 rm = [self resourceManagerForResourceNamed:_name inFramework:_frameworkName];
172 /* lookup resource */
175 path = [self primaryLookupPathForResourceNamed:_name languages:_languages];
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];
188 if (debugOn) [self debugWithFormat:@" => found: %@", path];
192 /* fall back to global resource manager */
194 return [[self fallbackResourceManager]
195 pathForResourceNamed:_name inFramework:_frameworkName
196 languages:_languages];
199 /* generate URL for resources (eg filename binding in WOImage) */
201 - (NSString *)webServerResourcesPath {
202 /* to avoid warning that WebServerResources path does not exist ... */
203 return [[self fallbackResourceManager] webServerResourcesPath];
206 - (NSString *)urlForProductRelativeResourcePath:(NSString *)resource {
208 NSString *path = nil, *sbase;
213 tmp = [sbase commonPrefixWithString:resource options:0];
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];
225 cs = [path componentsSeparatedByString:@"\\"];
226 path = [cs componentsJoinedByString:@"/"];
232 if ([path hasPrefix:@"/Resources/"])
233 path = [path substringFromIndex:11];
234 else if ([path hasPrefix:@"Resources/"])
235 path = [path substringFromIndex:10];
237 /* Note: cannot use -stringByAppendingPathComponent: on OSX! */
238 url = [self baseURLInContext:[[WOApplication application] context]];
239 if (debugOn) [self debugWithFormat:@" base: '%@'", url];
241 if (![url hasSuffix:@"/"]) url = [url stringByAppendingString:@"/"];
242 url = [url stringByAppendingString:path];
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"
254 if ((tmp = [resource stringByStandardizingPath]) != nil)
260 - (NSString *)urlForResourceNamed:(NSString *)_name
261 inFramework:(NSString *)_frameworkName
262 languages:(NSArray *)_languages
263 request:(WORequest *)_request
265 WOResourceManager *rm;
268 if (debugOn) [self debugWithFormat:@"lookup url: '%@'", _name];
270 if (_languages == nil) _languages = [_request browserLanguages];
272 /* determine resource manager to be actually used */
274 rm = [self resourceManagerForResourceNamed:_name inFramework:_frameworkName];
276 /* lookup resource */
279 if (rm == self) { /* handle it directly */
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];
288 if (resource != nil) {
289 url = [self urlForProductRelativeResourcePath:resource];
292 [self debugWithFormat:@" => not found '%@' (fw=%@,langs=%@)",
293 _name, _frameworkName,
294 [_languages componentsJoinedByString:@","]];
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];
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];
310 if (debugOn) [self debugWithFormat:@" => '%@'", url];
314 - (NSString *)pathToComponentNamed:(NSString *)_name
315 inFramework:(NSString *)_fwname
316 languages:(NSArray *)_langs
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
326 [self logWithFormat:@" PARENT (%@) SAID: %@",
327 [self fallbackResourceManager], p];
332 - (WOElement *)templateWithName:(NSString *)_name
333 languages:(NSArray *)_languages
335 WOResourceManager *arm;
339 [self logWithFormat:@"lookup template with name '%@' (languages=%@)",
340 _name, [_languages componentsJoinedByString:@","]];
342 if ((e = [super templateWithName:_name languages:_languages]) != nil) {
343 if (debugOn) [self logWithFormat:@" found: %@", e];
347 arm = [self fallbackResourceManager];
348 if (arm == self) return nil;
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];
356 if (debugOn) [self logWithFormat:@"did not find template %@", _name];
360 /* resource manager as a SoObject */
362 - (NSString *)mimeTypeForExtension:(NSString *)_ext {
363 // TODO: HACK, move to some object
364 NSString *ctype = nil;
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";
378 - (id)lookupName:(NSString *)_key inContext:(id)_ctx acquire:(BOOL)_flag {
381 NSString *p, *pe, *ctype;
383 NSArray *languages = nil;
385 /* TODO: add support for languages (eg English.lproj/ok.gif) ! */
387 /* check whether the resource is made public */
389 if (![self->product isPublicResource:_key]) {
390 [self debugWithFormat:@"key '%@' is not declared a public resource.",_key];
394 if ((b = [self->product bundle]) == nil) {
395 [self debugWithFormat:@"product has no bundle for lookup of %@", _key];
399 pe = [_key pathExtension];
401 /* ask resource-manager (self) for path */
403 languages = [_ctx resourceLookupLanguages];
405 p = [self pathForResourceNamed:_key
406 inFramework:[b bundlePath]
407 languages:languages];
409 [self errorWithFormat:@"did not find product resource: %@", _key];
415 if ((data = [NSData dataWithContentsOfMappedFile:p]) == nil) {
416 [self errorWithFormat:@"failed to load product resource: %@", _key];
420 /* and deliver as a complete response */
422 r = [(id<WOPageGenerationContext>)_ctx response];
424 [r setStatus:200 /* OK */];
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";
434 NSDate *expDate = nil;
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"]
443 [r setHeader:str forKey:@"expires"];
447 [r setHeader:ctype forKey:@"content-type"];
453 - (BOOL)isDebuggingEnabled {
456 - (NSString *)loggingPrefix {
457 return [NSString stringWithFormat:@"[RM:%@]", [self->product productName]];
462 - (NSString *)description {
463 NSMutableString *str;
465 str = [NSMutableString stringWithCapacity:64];
466 [str appendFormat:@"<%@[0x%p]:", NSStringFromClass([self class]), self];
467 [str appendFormat:@" product='%@'", [self->product productName]];
468 [str appendString:@">"];
472 @end /* SoProductResourceManager */