]> err.no Git - sope/blob - sope-appserver/NGObjWeb/SoObjects/SoProductRegistry.m
fixed various warnings
[sope] / sope-appserver / NGObjWeb / SoObjects / SoProductRegistry.m
1 /*
2   Copyright (C) 2002-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 "SoProductRegistry.h"
23 #include "SoProduct.h"
24 #include "SoObject.h"
25 #include "SoClassSecurityInfo.h"
26 #include "common.h"
27 #include <NGObjWeb/WOResponse.h>
28 #include <NGObjWeb/WOContext.h>
29
30 @implementation SoProductRegistry
31
32 static int debugOn = 0;
33
34 + (void)initialize {
35   NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
36   static BOOL didInit = NO;
37   if (didInit) return;
38   didInit = YES;
39   
40   debugOn = [ud boolForKey:@"SoProductRegistryDebugEnabled"] ? 1 : 0;
41 }
42
43 + (id)sharedProductRegistry {
44   static SoProductRegistry *reg = nil; // THREAD
45   if (reg == nil)
46     reg = [[SoProductRegistry alloc] init];
47   return reg;
48 }
49
50 - (id)init {
51   if ((self = [super init])) {
52     [self scanForAvailableProducts];
53
54     [[NSNotificationCenter defaultCenter]
55       addObserver:self selector:@selector(_bundleDidLoad:)
56       name:@"NSBundleDidLoadNotification" object:nil];
57   }
58   return self;
59 }
60
61 - (void)dealloc {
62   [[NSNotificationCenter defaultCenter] removeObserver:self];
63   [self->bundlePathToFirstName release];
64   [self->products              release];
65   [super dealloc];
66 }
67
68 /* notifications */
69
70 - (void)_bundleDidLoad:(NSNotification *)_notification {
71   /* 
72      If bundles are loaded by some other code, check whether they contain
73      SOPE products ...
74   */
75   [self registerProductBundle:[_notification object]];
76 }
77
78 /* operations */
79
80 - (NSFileManager *)fileManager {
81   return [NSFileManager defaultManager];
82 }
83
84 - (void)registerProductBundle:(NSBundle *)_bundle {
85   NSString  *productName, *firstProductName, *bundlePath;
86   SoProduct *product;
87   NSString  *manifest;
88   
89   if (![_bundle isNotNull])
90     return;
91
92   bundlePath = [_bundle bundlePath];
93   
94   if (_bundle != [NSBundle mainBundle]) {
95     productName = 
96       [[bundlePath lastPathComponent] stringByDeletingPathExtension];
97   }
98   else
99     productName = @"MAIN";
100   
101   if ((product = [self->products objectForKey:productName])) {
102     [self debugWithFormat:@"product '%@' already registered.", productName];
103     [product reloadIfPossible];
104     return;
105   }
106   
107   firstProductName = 
108     [self->bundlePathToFirstName objectForKey:bundlePath];
109   if (firstProductName != nil) {
110     [self debugWithFormat:
111             @"Note: register bundle with a different name '%@': '%@'",
112             productName, bundlePath];
113     if ((product = [self->products objectForKey:firstProductName]) != nil) {
114       [self debugWithFormat:
115               @"add additional name '%@' (first %@) for product '%@'",
116               productName, firstProductName, product];
117       [self->products setObject:product forKey:productName];
118       return;
119     }
120     else {
121       [self warnWithFormat:
122               @"no product object for first name '%@' (name=%@,bundle=%@)",
123               firstProductName, productName, bundlePath];
124     }
125   }
126   
127   manifest = [_bundle pathForResource:@"product" ofType:@"plist"];
128   if ([manifest length] == 0) {
129     if ([productName isEqualToString:@"MAIN"])
130       [self debugWithFormat:@"  main bundle has no manifest."];
131     return;
132   }
133   
134   /* setup caches */
135   
136   if (self->products == nil)
137     self->products = [[NSMutableDictionary alloc] initWithCapacity:32];
138   if (self->bundlePathToFirstName == nil) {
139     self->bundlePathToFirstName = 
140       [[NSMutableDictionary alloc] initWithCapacity:32];
141   }
142   
143   /* register */
144   
145   [self debugWithFormat:@"register product bundle: '%@' (0x%08X[%@])", 
146           bundlePath, _bundle, NSStringFromClass([_bundle class])];
147   
148   [self debugWithFormat:@"  register as product: %@", productName];
149   
150   if ((product = [[SoProduct alloc] initWithBundle:_bundle]) == nil) {
151     [self debugWithFormat:@"  could not init product from bundle: %@", 
152             _bundle];
153     return;
154   }
155   
156   [self->bundlePathToFirstName setObject:productName forKey:bundlePath];
157   [self->products              setObject:product     forKey:productName];
158   [product release];
159 }
160
161 - (void)registerProductAtPath:(NSString *)_path {
162   static NGBundleManager *bm = nil;
163   NSBundle *bundle;
164   
165   if (![_path isNotNull])
166     return;
167   
168   if (bm == nil) bm = [[NGBundleManager defaultBundleManager] retain];
169   bundle = [bm bundleWithPath:_path];
170   
171   if (bundle == nil) {
172     [self logWithFormat:@"could not init bundle object for path: %@", _path];
173     return;
174   }
175   [self registerProductBundle:bundle];
176 }
177
178 - (void)scanForProductsInDirectory:(NSString *)_path {
179   NSFileManager *fm;
180   NSEnumerator  *pathes;
181   NSString      *lPath;
182   
183   fm = [self fileManager];
184   pathes = [[fm directoryContentsAtPath:_path] objectEnumerator];
185   while ((lPath = [pathes nextObject])) {
186     BOOL isDir;
187     
188     lPath = [_path stringByAppendingPathComponent:lPath];
189     
190     if (![fm fileExistsAtPath:lPath isDirectory:&isDir])
191       continue;
192     if (!isDir)
193       continue;
194     
195     [self registerProductAtPath:lPath];
196   }
197 }
198
199 - (void)scanForAvailableProducts {
200   NSFileManager *fm;
201   NSProcessInfo *pi;
202   NSArray  *pathes;
203   NSBundle *bundle;
204   NSString *relPath;
205   unsigned i;
206
207   /* scan mail bundle & frameworks */
208   
209   if ((bundle = [NSBundle mainBundle]))
210     [self registerProductBundle:bundle];
211   else
212     NSLog(@"%s: missing main bundle ...", __PRETTY_FUNCTION__);
213   
214   pathes = [NSBundle allFrameworks];
215   for (i = 0; i < [pathes count]; i++)
216     [self registerProductBundle:[pathes objectAtIndex:i]];
217
218   pathes = [NSBundle allBundles];
219   for (i = 0; i < [pathes count]; i++)
220     [self registerProductBundle:[pathes objectAtIndex:i]];
221   
222   /* scan library pathes */
223   
224   fm = [NSFileManager defaultManager];
225   pi = [NSProcessInfo processInfo];
226   
227 #if COCOA_Foundation_LIBRARY
228   /* 
229      TODO: (like COMPILE_FOR_GNUSTEP)
230      This should actually check whether we are compiling in the
231      GNUstep environment since this modifies the location of bundles.
232   */
233   pathes = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
234                                                NSAllDomainsMask,
235                                                YES);
236 #else
237   pathes = [[pi environment] objectForKey:@"GNUSTEP_PATHPREFIX_LIST"];
238   if (pathes == nil)
239     pathes = [[pi environment] objectForKey:@"GNUSTEP_PATHLIST"];
240   
241   pathes = [[pathes stringValue] componentsSeparatedByString:@":"];
242 #endif
243   
244   [self debugWithFormat:@"scanning for products ..."];
245   
246 #if !COCOA_Foundation_LIBRARY
247   relPath = @"Library/";
248 #else
249   relPath = @"";
250 #endif
251   relPath = [NSString stringWithFormat:@"%@SoProducts-%i.%i/", relPath,
252                         SOPE_MAJOR_VERSION, SOPE_MINOR_VERSION];
253   for (i = 0; i < [pathes count]; i++) {
254     NSString *lPath;
255     BOOL     isDir;
256     
257     lPath = [[pathes objectAtIndex:i] stringByAppendingPathComponent:relPath];
258     
259     if (![fm fileExistsAtPath:lPath isDirectory:&isDir])
260       continue;
261     if (!isDir)
262       continue;
263     
264     [self debugWithFormat:@"  directory %@", lPath];
265     [self scanForProductsInDirectory:lPath];
266   }
267
268 #if COCOA_Foundation_LIBRARY
269   /* look in wrapper places */
270   bundle = [NSBundle bundleForClass:[self class]];
271   relPath = [[bundle resourcePath]
272                      stringByAppendingPathComponent:@"SoProducts"];
273   [self scanForProductsInDirectory:relPath];
274 #endif
275   /* look into FHS pathes */
276   
277   relPath = [NSString stringWithFormat:@"lib/sope-%i.%i/products/",
278                         SOPE_MAJOR_VERSION, SOPE_MINOR_VERSION];
279   pathes = [NSArray arrayWithObjects:
280                       [@"/usr/local/" stringByAppendingString:relPath],
281                       [@"/usr/"       stringByAppendingString:relPath],
282                     nil];
283   for (i = 0; i < [pathes count]; i++) {
284     NSString *lPath;
285     BOOL     isDir;
286     
287     lPath = [pathes objectAtIndex:i];
288     if (![fm fileExistsAtPath:lPath isDirectory:&isDir])
289       continue;
290     if (!isDir)
291       continue;
292     
293     [self debugWithFormat:@"  directory %@", lPath];
294     [self scanForProductsInDirectory:lPath];
295   }
296   
297   /* report result */
298   
299   [self debugWithFormat:
300           @"finished scan for products (%i products registered).",
301           [self->products count]];
302 }
303
304 /* registering products */
305
306 - (BOOL)loadProductNamed:(NSString *)_name {
307   SoProduct    *product;
308   NSEnumerator *requiredProducts;
309   NSString     *rqname;
310   
311   if ((product = [self->products objectForKey:_name]) == nil) {
312     [self debugWithFormat:@"did not find product: %@", _name];
313     return NO;
314   }
315   
316   /* load dependencies (TODO: should detect cycles) */
317   requiredProducts = [[product requiredProducts] objectEnumerator];
318   while ((rqname = [requiredProducts nextObject])) {
319     if (![self loadProductNamed:rqname]) {
320       if ([rqname isEqualToString:@"MAIN"]) continue;
321       [self errorWithFormat:@"failed to load product %@ required by %@.",
322               rqname, _name];
323       return NO;
324     }
325   }
326   
327   return [product load];
328 }
329
330 - (BOOL)loadAllProducts {
331   NSEnumerator *e;
332   NSString *p;
333   
334   e = [self->products keyEnumerator];
335   while ((p = [e nextObject])) {
336     if (![self loadProductNamed:p])
337       [self logWithFormat:@"could not load product: %@", p];
338   }
339   return YES;
340 }
341
342 /* lookup products */
343
344 - (NSArray *)registeredProductNames {
345   return [self->products allKeys];
346 }
347 - (SoProduct *)productWithName:(NSString *)_name {
348   return [self->products objectForKey:_name];
349 }
350
351 /* bundle */
352
353 - (SoProduct *)productForBundle:(NSBundle *)_bundle {
354   /* TODO: add a registry based on path ... */
355   NSString  *pname, *bpath;
356   SoProduct *product;
357   
358   bpath = [_bundle bundlePath];
359   
360   /* check whether a name is cached for the bundle .. */
361   
362   pname = [self->bundlePathToFirstName objectForKey:bpath];
363   if ((product = [self productWithName:pname]) != nil)
364     return product;
365   
366   /* 'calculate' name of bundle */
367   
368   pname = [[bpath lastPathComponent] stringByDeletingPathExtension];
369   if ((product = [self productWithName:pname]) != nil)
370     return product;
371   
372   /* load missing product */
373   
374   [self logWithFormat:
375           @"product '%@' not yet registered, attempting to load ...", pname];
376   if (![self loadProductNamed:pname])
377     return nil;
378   return [self productWithName:pname];
379 }
380
381 /* product registry as a SoObject */
382
383 - (NSArray *)allKeys {
384   return [self registeredProductNames];
385 }
386
387 - (BOOL)hasName:(NSString *)_key inContext:(id)_ctx {
388   if ([self->products objectForKey:_key])
389     return YES;
390   return [super hasName:_key inContext:_ctx];
391 }
392
393 - (id)lookupName:(NSString *)_key inContext:(id)_ctx acquire:(BOOL)_flag {
394   SoProduct *product;
395   
396   if ((product = [self productWithName:_key]))
397     return product;
398   
399   return [super lookupName:_key inContext:_ctx acquire:_flag];
400 }
401
402 - (NSArray *)toOneRelationshipKeys {
403   NSMutableSet *ma;
404   id root;
405
406   if ((root = [super toOneRelationshipKeys]) == nil)
407     return [self->products allKeys];
408   
409   ma = [[NSMutableSet alloc] initWithArray:root];
410   [ma addObjectsFromArray:[self->products allKeys]];
411   root = [ma allObjects];
412   [ma release];
413   return root;
414 }
415
416 /* debugging */
417
418 - (NSString *)loggingPrefix {
419   return @"[so-product-registry]";
420 }
421 - (BOOL)isDebuggingEnabled {
422   return debugOn ? YES : NO;
423 }
424
425 /* web representation */
426
427 - (void)appendToResponse:(WOResponse *)_r inContext:(WOContext *)_ctx {
428   NSEnumerator *e;
429   NSString *name;
430   
431   [_r appendContentString:@"<h3>SOPE Product Registry</h3>"];
432   
433   e = [[self toOneRelationshipKeys] objectEnumerator];
434   while ((name = [e nextObject])) {
435     [_r appendContentString:@"<li><a href=\""];
436     [_r appendContentHTMLAttributeValue:name];
437     [_r appendContentString:@"\">"];
438     [_r appendContentHTMLString:name];
439     [_r appendContentString:@"</a></li>"];
440   }    
441 }
442
443 @end /* SoProductRegistry */