]> err.no Git - sope/blob - sope-appserver/NGObjWeb/SoObjects/SoProductClassInfo.m
fixed OGo bug #888
[sope] / sope-appserver / NGObjWeb / SoObjects / SoProductClassInfo.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 // $Id: SoProductClassInfo.m 1 2004-08-20 10:08:27Z znek $
22
23 #include "SoProductClassInfo.h"
24 #include "SoPageInvocation.h"
25 #include "SoSelectorInvocation.h"
26 #include "SoClassSecurityInfo.h"
27 #include "SoClass.h"
28 #include "SoClassRegistry.h"
29 #include "SoProduct.h"
30 #include "common.h"
31
32 static int debugOn     = 1;
33 static int loadDebugOn = 0;
34
35 @interface SoProductSlotSetInfo(ManifestLoading)
36 - (BOOL)_loadManifest:(NSDictionary *)_m;
37 @end
38
39 @interface SoProductSlotSetInfo(Privates)
40 - (void)reset;
41 @end
42
43 @interface NSObject(PListInit)
44 - (id)initWithPropertyList:(id)_plist;
45 - (id)initWithPropertyList:(id)_plist owner:(id)_owner;
46 @end
47
48 @implementation SoProductSlotSetInfo
49
50 + (void)initialize {
51   NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
52   static BOOL didInit = NO;
53   if (didInit) return;
54   didInit = YES;
55   
56   loadDebugOn = [ud boolForKey:@"SoDebugProductLoading"] ? 1 : 0;
57 }
58
59 - (void)reset {
60   [self->protectedBy   release]; self->protectedBy   = nil;
61   [self->defaultAccess release]; self->defaultAccess = nil;
62   [self->roleInfo      release]; self->roleInfo      = nil;
63   [self->slotValues      removeAllObjects];
64   [self->slotProtections removeAllObjects];
65 }
66
67 - (id)initWithName:(NSString *)_name manifest:(NSDictionary *)_dict
68   product:(SoProduct *)_product
69 {
70   if ((self = [super init])) {
71     self->product   = _product; // non-retained
72     self->className = [_name copy];
73     [self _loadManifest:_dict];
74   }
75   return self;
76 }
77
78 - (void)dealloc {
79   [[self->slotValues allValues]
80     makeObjectsPerformSelector:@selector(detachFromContainer)];
81   
82   [self->roleInfo        release];
83   [self->protectedBy     release];
84   [self->extensions      release];
85   [self->exactFilenames  release];
86   [self->className       release];
87   [self->slotValues      release];
88   [self->slotProtections release];
89   [super dealloc];
90 }
91
92 /* accessors */
93
94 - (NSString *)className {
95   return self->className;
96 }
97 - (Class)objcClass {
98   return NSClassFromString([self className]);
99 }
100
101 /* apply */
102
103 - (void)applyClassSecurity:(SoClassSecurityInfo *)_security {
104   if (self->protectedBy) {
105     if ([self->protectedBy isEqualToString:@"<public>"])
106       [_security declareObjectPublic];
107     else if ([self->protectedBy isEqualToString:@"<private>"])
108       [_security declareObjectPrivate];
109     else
110       [_security declareObjectProtected:self->protectedBy];
111   }
112   
113   if (self->defaultAccess)
114     [_security setDefaultAccess:self->defaultAccess];
115   
116   if (self->roleInfo) {
117     NSEnumerator *perms;
118     NSString *perm;
119     
120     perms = [self->roleInfo keyEnumerator];
121     while ((perm = [perms nextObject])) {
122       NSString *role = [self->roleInfo objectForKey:perm];
123       
124       [_security declareRole:role asDefaultForPermission:perm];
125     }
126   }
127 }
128
129 - (void)applySlotSecurity:(SoClassSecurityInfo *)_security {
130   NSEnumerator *names;
131   NSString *slotName;
132   
133   names = [self->slotProtections keyEnumerator];
134   while ((slotName = [names nextObject])) {
135     NSString *perm;
136     
137     if ((perm = [self->slotProtections objectForKey:slotName]))
138       [_security declareProtected:perm:slotName,nil];
139   }
140 }
141
142 - (void)applySlotValues:(SoClass *)_soClass {
143   NSEnumerator *names;
144   NSString *slotName;
145
146   if (loadDebugOn) {
147     [self debugWithFormat:@"  applying %i slots on class %@ ...", 
148             [self->slotValues count], [self className]];
149   }
150   
151   names = [self->slotValues keyEnumerator];
152   while ((slotName = [names nextObject])) {
153     id slot;
154     
155     slot = [self->slotValues objectForKey:slotName];
156     if (slot == nil)
157       continue;
158     
159     if (loadDebugOn) {
160       [self debugWithFormat:@"  register slot named %@ on class %@", 
161               slotName, [_soClass className]];
162     }
163     
164     /* if an implementation was provided, register it with the class */
165       
166     if ([_soClass valueForSlot:slotName]) {
167         [self logWithFormat:@"WARNING: redefining slot '%@' of class '%@'",
168               slotName, _soClass];
169     }
170         
171     [_soClass setValue:slot forSlot:slotName];
172     [slot release];
173   }
174 }
175
176 - (void)applyExtensionsForSoClass:(SoClass *)_soClass
177   onRegistry:(SoClassRegistry *)_registry
178 {
179   NSEnumerator *e;
180   NSString     *ext;
181   NSException *error;
182   
183   if (_soClass == nil) {
184     [self logWithFormat:@"ERROR(%s): missing soClass parameter?!",
185             __PRETTY_FUNCTION__];
186     return;
187   }
188   if (_registry == nil) {
189     [self logWithFormat:@"ERROR: missing registry ?!"];
190     return;
191   }
192   
193   e = [self->extensions objectEnumerator];
194   while ((ext = [e nextObject])) {
195     if ((error = [_registry registerSoClass:_soClass forExtension:ext])) {
196       [self logWithFormat:
197               @"ERROR: failed to register class %@ for extension %@: %@", 
198               [_soClass className], ext, error];
199     }
200     else if (loadDebugOn) {
201       [self debugWithFormat:@"  registered class %@ for extension %@", 
202               [_soClass className], ext];
203     }
204   }
205   
206   e = [self->exactFilenames objectEnumerator];
207   while ((ext = [e nextObject])) {
208     if ((error = [_registry registerSoClass:_soClass forExactName:ext])) {
209       [self logWithFormat:
210               @"ERROR: failed to register class %@ for name %@: %@", 
211               [_soClass className], ext, error];
212     }
213     else if (loadDebugOn) {
214       [self debugWithFormat:@"  registered class %@ for name %@", 
215               [_soClass className], ext];
216     }
217   }
218 }
219
220 - (void)applyOnRegistry:(SoClassRegistry *)_registry {
221   SoClass *soClass;
222   id security;
223
224   if (_registry == nil) {
225     [self logWithFormat:@"WARNING(%s): did not pass a registry?!",
226             __PRETTY_FUNCTION__];
227     return;
228   }
229   
230   if ((soClass = [_registry soClassWithName:[self className]]) == nil) {
231     [self logWithFormat:
232             @"ERROR: did not find exported SoClass '%@' in product %@!", 
233             [self className], self->product];
234     return;
235   }
236   
237   security = [soClass soClassSecurityInfo];
238   if (loadDebugOn) {
239     [self debugWithFormat:@"loading info for class %@: %@", 
240             [self className], soClass];
241   }
242   
243   [self applyClassSecurity:security];
244   [self applySlotSecurity:security];
245   [self applySlotValues:soClass];
246
247   /* filename extensions for OFS */
248   [self applyExtensionsForSoClass:soClass onRegistry:_registry];
249   
250   if (loadDebugOn)
251     [self debugWithFormat:@"info for class %@ loaded.", [soClass className]];
252 }
253
254 /* debugging */
255
256 - (BOOL)isDebuggingEnabled {
257   return debugOn ? YES : NO;
258 }
259
260 /* description */
261
262 - (NSString *)description {
263   NSMutableString *ms;
264   unsigned cnt;
265
266   ms = [NSMutableString stringWithCapacity:64];
267   [ms appendFormat:@"<0x%08X[%@]:", self, NSStringFromClass([self class])];
268
269   [ms appendFormat:@" name=%@", self->className];
270   
271   if ((cnt = [self->extensions count]) > 0)
272     [ms appendFormat:@" #extensions=%d", cnt];
273   if ((cnt = [self->slotValues count]) > 0)
274     [ms appendFormat:@" #slotvals=%d", cnt];
275   if ((cnt = [self->slotProtections count]) > 0)
276     [ms appendFormat:@" #slotperms=%d", cnt];
277   
278   [ms appendString:@">"];
279   return ms;
280 }
281
282 @end /* SoProductSlotSetInfo */
283
284 @implementation SoProductClassInfo
285
286 /* debugging */
287
288 - (NSString *)loggingPrefix {
289   return @"[so-class-info]";
290 }
291
292 @end /* SoProductClassInfo */
293
294 @implementation SoProductCategoryInfo
295
296 /* debugging */
297
298 - (NSString *)loggingPrefix {
299   return @"[so-category-info]";
300 }
301
302 @end /* SoProductCategoryInfo */
303
304 @implementation SoProductSlotSetInfo(ManifestLoading)
305
306 - (BOOL)isPageInvocationManifest:(NSDictionary *)_m {
307   return [_m objectForKey:@"pageName"] ? YES : NO;
308 }
309
310 - (id)makePageInvocationForMethodNamed:(NSString *)_name 
311   manifest:(NSDictionary *)_m 
312 {
313   /*
314     Page invocation:
315       {
316         pageName   = "Main";
317         actionName = "doIt";
318         arguments = {
319           SOAP = {
320            login    = "loginRequest/auth/username/!textValue";
321            password = "loginRequest/auth/password/!textValue";
322           };
323         }
324       }
325   */
326   SoPageInvocation *method;
327   NSString     *pageName;
328   NSDictionary *argspecs;
329   
330   pageName = [_m objectForKey:@"pageName"];
331   argspecs = [_m objectForKey:@"arguments"];
332   
333   method = [[SoPageInvocation alloc]
334              initWithPageName:pageName
335              actionName:[_m objectForKey:@"actionName"]
336              product:self->product];
337   [method setArgumentSpecifications:argspecs];
338   return method;
339 }
340
341 - (id)makeInvocationForMethodNamed:(NSString *)_name selector:(id)_config {
342   SoSelectorInvocation *method;
343   
344   if (_config == nil) {
345     [self logWithFormat:
346             @"ERROR: missing config for selector invocation method: '%@'",
347             _name];
348     return nil;
349   }
350   
351   if ([_config isKindOfClass:[NSString class]]) {
352     /* form: selector = "doItInContext:" */
353     method = [[SoSelectorInvocation alloc] initWithSelectorNamed:_config 
354                                            addContextParameter:YES];
355   }
356   else if ([_config isKindOfClass:[NSDictionary class]]) {
357     /*
358       Selector Invocation:
359         selector = {
360           name                = "doItInContext:";
361           addContextParameter = YES;
362           // TODO: positionalArgumentBindings = ( );
363           // TODO: names (for mapping different argcounts)
364           arguments = {
365             SOAP = (
366               "loginRequest/auth/username/!textValue",
367               "loginRequest/auth/password/!textValue"
368             );
369           }
370         };
371     */
372     NSDictionary *config;
373     NSDictionary *argspecs;
374     NSString     *selector;
375     BOOL         ctxParameter;
376
377     config = (NSDictionary *)_config;
378     
379     selector = [config objectForKey:@"name"];
380     if ([selector length] == 0) {
381       [self logWithFormat:
382               @"ERROR: missing 'name' in selector config of method '%@': %@",
383               _name, _config];
384       return nil;
385     }
386     
387     argspecs = [config objectForKey:@"arguments"];
388     
389     ctxParameter = [[config objectForKey:@"addContextParameter"]boolValue];
390     
391     method = [[SoSelectorInvocation alloc] init];
392     [method addSelectorNamed:selector];
393     [method setDoesAddContextParameter:ctxParameter];
394     [method setArgumentSpecifications:argspecs];
395   }
396   else {
397     [self logWithFormat:@"ERROR: cannot handle selector configuration: %@",
398             _config];
399     return nil;
400   }
401   return method;
402 }
403
404 - (id)cannotHandleManifest:(NSDictionary *)_m ofMethodNamed:(NSString *)_name {
405   /* no implementation provided */
406   if (loadDebugOn) {
407     /* 
408        note, a manifest does not need to contain the actual implementation
409        info, eg it can be used to just define the protections
410     */
411     [self logWithFormat:
412             @"Note: missing implemention info for method '%@' !", _name];
413   }
414   return nil;
415 }
416
417 - (BOOL)_loadManifest:(NSDictionary *)_m ofMethodNamed:(NSString *)_name {
418   NSString *mp;
419   NSString *selector;
420   id       method;
421   
422   /* security */
423   
424   if ((mp = [_m objectForKey:@"protectedBy"]))
425     [self->slotProtections setObject:mp forKey:_name];
426   
427   /* implementation */
428   
429   if ([self isPageInvocationManifest:_m])
430     method = [self makePageInvocationForMethodNamed:_name manifest:_m];
431   else if ((selector = [_m objectForKey:@"selector"]))
432     method = [self makeInvocationForMethodNamed:_name selector:selector];
433   else
434     method = [self cannotHandleManifest:_m ofMethodNamed:_name];
435   
436   if (method) {
437     [self->slotValues setObject:method forKey:_name];
438     [method release];
439   }
440   
441   return YES;
442 }
443
444 - (id)instantiateObjectOfClass:(Class)clazz withPlist:(id)value {
445   /* returns a retained instance */
446   
447   if ([value isKindOfClass:[NSDictionary class]]) {
448     if ([clazz instancesRespondToSelector:@selector(initWithDictionary:)])
449       return [[clazz alloc] initWithDictionary:value];
450   }
451   else if ([value isKindOfClass:[NSArray class]]) {
452     if ([clazz instancesRespondToSelector:@selector(initWithArray:)])
453       return [[clazz alloc] initWithArray:value];
454   }
455   else if ([value isKindOfClass:[NSData class]]) {
456     if ([clazz instancesRespondToSelector:@selector(initWithData:)])
457       return [[clazz alloc] initWithData:value];
458   }
459   else {
460     if ([clazz instancesRespondToSelector:@selector(initWithString:)])
461       return [[clazz alloc] initWithString:[value stringValue]];
462   }
463   
464   if ([clazz instancesRespondToSelector:
465                @selector(initWithPropertyList:owner:)])
466     return [[clazz alloc] initWithPropertyList:value owner:nil];
467   if ([clazz instancesRespondToSelector:@selector(initWithPropertyList:)])
468     return [[clazz alloc] initWithPropertyList:value];
469   
470   return nil;
471 }
472
473 - (BOOL)_loadManifest:(NSDictionary *)_m ofSlotNamed:(NSString *)_name {
474   NSString *mp;
475   NSString *valueClassName;
476   Class    valueClass;
477   id value;
478   
479   /* security */
480   
481   if ((mp = [_m objectForKey:@"protectedBy"]))
482     [self->slotProtections setObject:mp forKey:_name];
483   
484   if ((valueClassName = [[_m objectForKey:@"valueClass"] stringValue])) {
485     // TODO: hack, we need to load the bundle of the product to have the
486     //       contained classes available as valueClasses (But: shouldn't
487     //       that be already done by NGBundleManager?)
488     [[self->product bundle] load];
489     
490     // TODO: should we allow/use SoClasses here?
491     if ((valueClass = NSClassFromString(valueClassName)) == Nil) {
492       [self logWithFormat:
493               @"ERROR: did not find value class '%@' for slot: '%@'",
494               valueClassName, _name];
495       return NO;
496     }
497   }
498   else
499     valueClass = Nil;
500   
501   if ((value = [_m objectForKey:@"value"])) {
502     if (valueClass) {
503       value = [self instantiateObjectOfClass:valueClass withPlist:value];
504       
505       if (value == nil) {
506         [self logWithFormat:
507                 @"ERROR: could not initialize value of slot %@ with class %@",
508                 _name, valueClassName];
509         return NO;
510       }
511       value = [value autorelease];
512     }
513     else
514       /* pass through property list */;
515   }
516   else if (valueClass) {
517     /* 
518        Note: a manifest does not need to contain the actual value, eg it can be
519        used to just define the protections
520     */
521     if (loadDebugOn) 
522       [self logWithFormat:@"Note: slot no value: '%@'", _name];
523     
524     value = [[[valueClass alloc] init] autorelease];
525     if (value == nil) {
526       [self logWithFormat:
527               @"ERROR: could not initialize value of slot '%@' with class: %@",
528               _name, valueClassName];
529       return NO;
530     }
531   }
532   
533   if (value)
534     [self->slotValues setObject:value forKey:_name];
535   
536   return YES;
537 }
538
539 - (BOOL)_loadManifest:(NSDictionary *)_m {
540   NSDictionary *slots;
541   id tmp;
542   
543   [self reset];
544   self->protectedBy   = [[_m objectForKey:@"protectedBy"] copy];
545   self->defaultAccess = [[_m objectForKey:@"defaultAccess"] copy];
546   self->roleInfo      = [[_m objectForKey:@"defaultRoles"] copy];
547   
548   self->exactFilenames = [[_m objectForKey:@"exactFilenames"] copy];
549   
550   self->extensions = [[_m objectForKey:@"extensions"] copy];
551   if ((tmp = [_m objectForKey:@"extension"])) {
552     if (self->extensions == nil) {
553       self->extensions = [tmp isKindOfClass:[NSArray class]]
554         ? [tmp copy]
555         : [[NSArray alloc] initWithObjects:&tmp count:1];
556     }
557     else {
558       tmp = [tmp isKindOfClass:[NSArray class]]
559         ? [[self->extensions arrayByAddingObjectsFromArray:tmp] retain]
560         : [[self->extensions arrayByAddingObject:tmp] retain];
561       [self->extensions autorelease];
562       self->extensions = tmp;
563     }
564   }
565   
566   if (self->slotValues == nil)
567     self->slotValues = [[NSMutableDictionary alloc] init];
568   if (self->slotProtections == nil)
569     self->slotProtections = [[NSMutableDictionary alloc] init];
570   
571   if ((slots = [_m objectForKey:@"methods"])) {
572     NSEnumerator *names;
573     NSString *methodName;
574     
575     names = [slots keyEnumerator];
576     while ((methodName = [names nextObject])) {
577       NSDictionary *info;
578       
579       info = [slots objectForKey:methodName];
580       
581       if (![self _loadManifest:info ofMethodNamed:methodName])
582         [self logWithFormat:@"manifest of method %@ is broken.", methodName];
583     }
584   }
585   if ((slots = [_m objectForKey:@"slots"])) {
586     NSEnumerator *names;
587     NSString *slotName;
588     
589     names = [slots keyEnumerator];
590     while ((slotName = [names nextObject])) {
591       NSDictionary *info;
592       
593       info = [slots objectForKey:slotName];
594       
595       if (![self _loadManifest:info ofSlotNamed:slotName])
596         [self logWithFormat:@"manifest of slot %@ is broken.", slotName];
597     }
598   }
599   return YES;
600 }
601
602 @end /* SoProductSlotSetInfo(ManifestLoading) */