]> err.no Git - sope/blob - sope-appserver/NGObjWeb/SoObjects/SoProductRegistry.m
fixed OGo bug #888
[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   static BOOL didInit = NO;
36   if (!didInit) {
37     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
38     didInit = YES;
39     debugOn = [ud boolForKey:@"SoProductRegistryDebugEnabled"] ? 1 : 0;
40   }
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 logWithFormat:
122               @"WARNING: no product object for first name '%@' "
123               @"(name=%@,bundle=%@)",
124               firstProductName, productName, bundlePath];
125     }
126   }
127   
128   manifest = [_bundle pathForResource:@"product" ofType:@"plist"];
129   if ([manifest length] == 0) {
130     if ([productName isEqualToString:@"MAIN"])
131       [self debugWithFormat:@"  main bundle has no manifest."];
132     return;
133   }
134   
135   /* setup caches */
136   
137   if (self->products == nil)
138     self->products = [[NSMutableDictionary alloc] initWithCapacity:32];
139   if (self->bundlePathToFirstName == nil) {
140     self->bundlePathToFirstName = 
141       [[NSMutableDictionary alloc] initWithCapacity:32];
142   }
143   
144   /* register */
145   
146   [self debugWithFormat:@"register product bundle: '%@' (0x%08X[%@])", 
147           bundlePath, _bundle, NSStringFromClass([_bundle class])];
148   
149   [self debugWithFormat:@"  register as product: %@", productName];
150   
151   if ((product = [[SoProduct alloc] initWithBundle:_bundle]) == nil) {
152     [self debugWithFormat:@"  could not init product from bundle: %@", 
153             _bundle];
154     return;
155   }
156   
157   [self->bundlePathToFirstName setObject:productName forKey:bundlePath];
158   [self->products              setObject:product     forKey:productName];
159   [product release];
160 }
161
162 - (void)registerProductAtPath:(NSString *)_path {
163   static NGBundleManager *bm = nil;
164   NSBundle *bundle;
165   
166   if (![_path isNotNull])
167     return;
168   
169   if (bm == nil) bm = [[NGBundleManager defaultBundleManager] retain];
170   bundle = [bm bundleWithPath:_path];
171   
172   if (bundle == nil) {
173     [self logWithFormat:@"could not init bundle object for path: %@", _path];
174     return;
175   }
176   [self registerProductBundle:bundle];
177 }
178
179 - (void)scanForProductsInDirectory:(NSString *)_path {
180   NSFileManager *fm;
181   NSEnumerator  *pathes;
182   NSString      *lPath;
183   
184   fm = [self fileManager];
185   pathes = [[fm directoryContentsAtPath:_path] objectEnumerator];
186   while ((lPath = [pathes nextObject])) {
187     BOOL isDir;
188     
189     lPath = [_path stringByAppendingPathComponent:lPath];
190     
191     if (![fm fileExistsAtPath:lPath isDirectory:&isDir])
192       continue;
193     if (!isDir)
194       continue;
195     
196     [self registerProductAtPath:lPath];
197   }
198 }
199
200 - (void)scanForAvailableProducts {
201   NSFileManager *fm;
202   NSProcessInfo *pi;
203   NSArray  *pathes;
204   NSBundle *bundle;
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   if ([pathes count] == 0) {
245     [self debugWithFormat:@"found no product pathes."];
246     return;
247   }
248   
249   [self debugWithFormat:@"scanning for products ..."];
250   
251   for (i = 0; i < [pathes count]; i++) {
252     NSString *lPath;
253     BOOL     isDir;
254     
255     lPath = [pathes objectAtIndex:i];
256 #if !COCOA_Foundation_LIBRARY
257     lPath = [lPath stringByAppendingPathComponent:@"Library"];
258 #endif
259     lPath = [lPath stringByAppendingPathComponent:@"SoProducts-4.3"];
260     
261     if (![fm fileExistsAtPath:lPath isDirectory:&isDir])
262       continue;
263     if (!isDir)
264       continue;
265     
266     [self debugWithFormat:@"  directory %@", lPath];
267     [self scanForProductsInDirectory:lPath];
268   }
269   
270   /* look into FHS pathes */
271   
272   pathes = [NSArray arrayWithObjects:
273                       @"/usr/local/lib/sope-4.3/products/",
274                       @"/usr/lib/sope-4.3/products/",
275                     nil];
276   for (i = 0; i < [pathes count]; i++) {
277     NSString *lPath;
278     BOOL     isDir;
279     
280     lPath = [pathes objectAtIndex:i];
281     if (![fm fileExistsAtPath:lPath isDirectory:&isDir])
282       continue;
283     if (!isDir)
284       continue;
285     
286     [self debugWithFormat:@"  directory %@", lPath];
287     [self scanForProductsInDirectory:lPath];
288   }
289   
290   /* report result */
291   
292   [self debugWithFormat:
293           @"finished scan for products (%i products registered).",
294           [self->products count]];
295 }
296
297 /* registering products */
298
299 - (BOOL)loadProductNamed:(NSString *)_name {
300   SoProduct    *product;
301   NSEnumerator *requiredProducts;
302   NSString     *rqname;
303   
304   if ((product = [self->products objectForKey:_name]) == nil) {
305     [self debugWithFormat:@"did not find product: %@", _name];
306     return NO;
307   }
308   
309   /* load dependencies (TODO: should detect cycles) */
310   requiredProducts = [[product requiredProducts] objectEnumerator];
311   while ((rqname = [requiredProducts nextObject])) {
312     if (![self loadProductNamed:rqname]) {
313       if ([rqname isEqualToString:@"MAIN"]) continue;
314       [self logWithFormat:@"ERROR: failed to load product %@ required by %@.",
315               rqname, _name];
316       return NO;
317     }
318   }
319   
320   return [product load];
321 }
322
323 - (BOOL)loadAllProducts {
324   NSEnumerator *e;
325   NSString *p;
326   
327   e = [self->products keyEnumerator];
328   while ((p = [e nextObject])) {
329     if (![self loadProductNamed:p])
330       [self logWithFormat:@"could not load product: %@", p];
331   }
332   return YES;
333 }
334
335 /* lookup products */
336
337 - (NSArray *)registeredProductNames {
338   return [self->products allKeys];
339 }
340 - (SoProduct *)productWithName:(NSString *)_name {
341   return [self->products objectForKey:_name];
342 }
343
344 /* bundle */
345
346 - (SoProduct *)productForBundle:(NSBundle *)_bundle {
347   /* TODO: add a registry based on path ... */
348   NSString  *pname, *bpath;
349   SoProduct *product;
350   
351   bpath = [_bundle bundlePath];
352   
353   /* check whether a name is cached for the bundle .. */
354   
355   pname = [self->bundlePathToFirstName objectForKey:bpath];
356   if ((product = [self productWithName:pname]) != nil)
357     return product;
358   
359   /* 'calculate' name of bundle */
360   
361   pname = [[bpath lastPathComponent] stringByDeletingPathExtension];
362   if ((product = [self productWithName:pname]) != nil)
363     return product;
364   
365   /* load missing product */
366   
367   [self logWithFormat:
368           @"product '%@' not yet registered, attempting to load ...", pname];
369   if (![self loadProductNamed:pname])
370     return nil;
371   return [self productWithName:pname];
372 }
373
374 /* product registry as a SoObject */
375
376 - (NSArray *)allKeys {
377   return [self registeredProductNames];
378 }
379
380 - (BOOL)hasName:(NSString *)_key inContext:(id)_ctx {
381   if ([self->products objectForKey:_key])
382     return YES;
383   return [super hasName:_key inContext:_ctx];
384 }
385
386 - (id)lookupName:(NSString *)_key inContext:(id)_ctx acquire:(BOOL)_flag {
387   SoProduct *product;
388   
389   if ((product = [self productWithName:_key]))
390     return product;
391   
392   return [super lookupName:_key inContext:_ctx acquire:_flag];
393 }
394
395 - (NSArray *)toOneRelationshipKeys {
396   NSMutableSet *ma;
397   id root;
398
399   if ((root = [super toOneRelationshipKeys]) == nil)
400     return [self->products allKeys];
401   
402   ma = [[NSMutableSet alloc] initWithArray:root];
403   [ma addObjectsFromArray:[self->products allKeys]];
404   root = [ma allObjects];
405   [ma release];
406   return root;
407 }
408
409 /* debugging */
410
411 - (NSString *)loggingPrefix {
412   return @"[so-product-registry]";
413 }
414 - (BOOL)isDebuggingEnabled {
415   return debugOn ? YES : NO;
416 }
417
418 /* web representation */
419
420 - (void)appendToResponse:(WOResponse *)_r inContext:(WOContext *)_ctx {
421   NSEnumerator *e;
422   NSString *name;
423   
424   [_r appendContentString:@"<h3>SOPE Product Registry</h3>"];
425   
426   e = [[self toOneRelationshipKeys] objectEnumerator];
427   while ((name = [e nextObject])) {
428     [_r appendContentString:@"<li><a href=\""];
429     [_r appendContentHTMLAttributeValue:name];
430     [_r appendContentString:@"\">"];
431     [_r appendContentHTMLString:name];
432     [_r appendContentString:@"</a></li>"];
433   }    
434 }
435
436 @end /* SoProductRegistry */