]> err.no Git - sope/blob - sope-appserver/NGObjWeb/SoObjects/SoProductClassInfo.m
Add libxml2-dev to libsope-xml4.7-dev deps
[sope] / sope-appserver / NGObjWeb / SoObjects / SoProductClassInfo.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 "SoProductClassInfo.h"
23 #include "SoActionInvocation.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 != nil)
114     [_security setDefaultAccess:self->defaultAccess];
115   
116   if (self->roleInfo != nil) {
117     NSEnumerator *perms;
118     NSString *perm;
119     
120     perms = [self->roleInfo keyEnumerator];
121     while ((perm = [perms nextObject]) != nil) {
122       id role = [self->roleInfo objectForKey:perm];
123       
124       if ([role isKindOfClass:[NSArray class]])
125         [_security declareRoles:role asDefaultForPermission:perm];
126       else if ([role isKindOfClass:[NSString class]])
127         [_security declareRole:role asDefaultForPermission:perm];
128       else {
129         [self warnWithFormat:
130                 @"unexpected 'role' value (expect string or array): %@", role];
131       }
132     }
133   }
134 }
135
136 - (void)applySlotSecurity:(SoClassSecurityInfo *)_security {
137   NSEnumerator *names;
138   NSString *slotName;
139   
140   names = [self->slotProtections keyEnumerator];
141   while ((slotName = [names nextObject])) {
142     NSString *perm;
143     
144     if ((perm = [self->slotProtections objectForKey:slotName]))
145       [_security declareProtected:perm:slotName,nil];
146   }
147 }
148
149 - (void)applySlotValues:(SoClass *)_soClass {
150   NSEnumerator *names;
151   NSString *slotName;
152
153   if (loadDebugOn) {
154     [self debugWithFormat:@"  applying %i slots on class %@ ...", 
155             [self->slotValues count], [self className]];
156   }
157   
158   names = [self->slotValues keyEnumerator];
159   while ((slotName = [names nextObject])) {
160     id slot;
161     
162     slot = [self->slotValues objectForKey:slotName];
163     if (slot == nil)
164       continue;
165     
166     if (loadDebugOn) {
167       [self debugWithFormat:@"  register slot named %@ on class %@", 
168               slotName, [_soClass className]];
169     }
170     
171     /* if an implementation was provided, register it with the class */
172       
173     if ([_soClass valueForSlot:slotName]) {
174         [self warnWithFormat:@"redefining slot '%@' of class '%@'",
175               slotName, _soClass];
176     }
177         
178     [_soClass setValue:slot forSlot:slotName];
179     [slot release];
180   }
181 }
182
183 - (void)applyExtensionsForSoClass:(SoClass *)_soClass
184   onRegistry:(SoClassRegistry *)_registry
185 {
186   NSEnumerator *e;
187   NSString     *ext;
188   NSException *error;
189   
190   if (_soClass == nil) {
191     [self errorWithFormat:@"(%s): missing soClass parameter?!",
192             __PRETTY_FUNCTION__];
193     return;
194   }
195   if (_registry == nil) {
196     [self errorWithFormat:@"missing registry ?!"];
197     return;
198   }
199   
200   e = [self->extensions objectEnumerator];
201   while ((ext = [e nextObject])) {
202     if ((error = [_registry registerSoClass:_soClass forExtension:ext])) {
203       [self errorWithFormat:
204               @"failed to register class %@ for extension %@: %@", 
205               [_soClass className], ext, error];
206     }
207     else if (loadDebugOn) {
208       [self debugWithFormat:@"  registered class %@ for extension %@", 
209               [_soClass className], ext];
210     }
211   }
212   
213   e = [self->exactFilenames objectEnumerator];
214   while ((ext = [e nextObject])) {
215     if ((error = [_registry registerSoClass:_soClass forExactName:ext])) {
216       [self errorWithFormat:
217               @"failed to register class %@ for name %@: %@", 
218               [_soClass className], ext, error];
219     }
220     else if (loadDebugOn) {
221       [self debugWithFormat:@"  registered class %@ for name %@", 
222               [_soClass className], ext];
223     }
224   }
225 }
226
227 - (void)applyOnRegistry:(SoClassRegistry *)_registry {
228   SoClass *soClass;
229   id security;
230
231   if (_registry == nil) {
232     [self warnWithFormat:@"(%s): did not pass a registry?!",
233             __PRETTY_FUNCTION__];
234     return;
235   }
236   
237   if ((soClass = [_registry soClassWithName:[self className]]) == nil) {
238     [self errorWithFormat:
239             @"did not find exported SoClass '%@' in product %@!", 
240             [self className], self->product];
241     return;
242   }
243   
244   security = [soClass soClassSecurityInfo];
245   if (loadDebugOn) {
246     [self debugWithFormat:@"loading info for class %@: %@", 
247             [self className], soClass];
248   }
249   
250   [self applyClassSecurity:security];
251   [self applySlotSecurity:security];
252   [self applySlotValues:soClass];
253
254   /* filename extensions for OFS */
255   [self applyExtensionsForSoClass:soClass onRegistry:_registry];
256   
257   if (loadDebugOn)
258     [self debugWithFormat:@"info for class %@ loaded.", [soClass className]];
259 }
260
261 /* debugging */
262
263 - (BOOL)isDebuggingEnabled {
264   return debugOn ? YES : NO;
265 }
266
267 /* description */
268
269 - (NSString *)description {
270   NSMutableString *ms;
271   unsigned cnt;
272
273   ms = [NSMutableString stringWithCapacity:64];
274   [ms appendFormat:@"<0x%p[%@]:", self, NSStringFromClass([self class])];
275
276   [ms appendFormat:@" name=%@", self->className];
277   
278   if ((cnt = [self->extensions count]) > 0)
279     [ms appendFormat:@" #extensions=%d", cnt];
280   if ((cnt = [self->slotValues count]) > 0)
281     [ms appendFormat:@" #slotvals=%d", cnt];
282   if ((cnt = [self->slotProtections count]) > 0)
283     [ms appendFormat:@" #slotperms=%d", cnt];
284   
285   [ms appendString:@">"];
286   return ms;
287 }
288
289 @end /* SoProductSlotSetInfo */
290
291 @implementation SoProductClassInfo
292
293 /* debugging */
294
295 - (NSString *)loggingPrefix {
296   return @"[so-class-info]";
297 }
298
299 @end /* SoProductClassInfo */
300
301 @implementation SoProductCategoryInfo
302
303 /* debugging */
304
305 - (NSString *)loggingPrefix {
306   return @"[so-category-info]";
307 }
308
309 @end /* SoProductCategoryInfo */
310
311 @implementation SoProductSlotSetInfo(ManifestLoading)
312
313 - (BOOL)isPageInvocationManifest:(NSDictionary *)_m {
314   return [_m objectForKey:@"pageName"] != nil ? YES : NO;
315 }
316 - (BOOL)isActionInvocationManifest:(NSDictionary *)_m {
317   if ([_m objectForKey:@"actionClass"]      != nil) return YES;
318   if ([_m objectForKey:@"directActionName"] != nil) return YES;
319   return NO;
320 }
321
322 - (id)makeActionInvocationForMethodNamed:(NSString *)_name 
323   manifest:(NSDictionary *)_m 
324 {
325   /*
326     Page invocation:
327       {
328         actionClass      = "DirectAction";
329         directActionName = "doIt";
330         arguments = {
331           SOAP = {
332            login    = "loginRequest/auth/username/!textValue";
333            password = "loginRequest/auth/password/!textValue";
334           };
335         }
336       }
337   */
338   SoActionInvocation *method;
339   NSString     *actionClass, *actionName;
340   NSDictionary *argspecs;
341   
342   actionClass = [_m objectForKey:@"actionClass"];
343   argspecs    = [_m objectForKey:@"arguments"];
344   
345   if ((actionName = [_m objectForKey:@"directActionName"]) == nil)
346     actionName = [_m objectForKey:@"actionName"];
347   
348   method = [[SoActionInvocation alloc]
349              initWithActionClassName:actionClass actionName:actionName];
350   [method setArgumentSpecifications:argspecs];
351   return method;
352 }
353
354 - (id)makePageInvocationForMethodNamed:(NSString *)_name 
355   manifest:(NSDictionary *)_m 
356 {
357   /*
358     Page invocation:
359       {
360         pageName   = "Main";
361         actionName = "doIt";
362         arguments = {
363           SOAP = {
364            login    = "loginRequest/auth/username/!textValue";
365            password = "loginRequest/auth/password/!textValue";
366           };
367         }
368       }
369   */
370   SoPageInvocation *method;
371   NSString     *pageName;
372   NSDictionary *argspecs;
373   
374   pageName = [_m objectForKey:@"pageName"];
375   argspecs = [_m objectForKey:@"arguments"];
376   
377   method = [[SoPageInvocation alloc]
378              initWithPageName:pageName
379              actionName:[_m objectForKey:@"actionName"]
380              product:self->product];
381   [method setArgumentSpecifications:argspecs];
382   return method;
383 }
384
385 - (id)makeInvocationForMethodNamed:(NSString *)_name selector:(id)_config {
386   SoSelectorInvocation *method;
387   
388   if (_config == nil) {
389     [self errorWithFormat:
390             @"missing config for selector invocation method: '%@'",
391             _name];
392     return nil;
393   }
394   
395   if ([_config isKindOfClass:[NSString class]]) {
396     /* form: selector = "doItInContext:" */
397     method = [[SoSelectorInvocation alloc] initWithSelectorNamed:_config 
398                                            addContextParameter:YES];
399   }
400   else if ([_config isKindOfClass:[NSDictionary class]]) {
401     /*
402       Selector Invocation:
403         selector = {
404           name                = "doItInContext:";
405           addContextParameter = YES;
406           // TODO: positionalArgumentBindings = ( );
407           // TODO: names (for mapping different argcounts)
408           arguments = {
409             SOAP = (
410               "loginRequest/auth/username/!textValue",
411               "loginRequest/auth/password/!textValue"
412             );
413           }
414         };
415     */
416     NSDictionary *config;
417     NSDictionary *argspecs;
418     NSString     *selector;
419     BOOL         ctxParameter;
420
421     config = (NSDictionary *)_config;
422     
423     selector = [config objectForKey:@"name"];
424     if ([selector length] == 0) {
425       [self errorWithFormat:
426               @"missing 'name' in selector config of method '%@': %@",
427               _name, _config];
428       return nil;
429     }
430     
431     argspecs = [config objectForKey:@"arguments"];
432     
433     ctxParameter = [[config objectForKey:@"addContextParameter"]boolValue];
434     
435     method = [[SoSelectorInvocation alloc] init];
436     [method addSelectorNamed:selector];
437     [method setDoesAddContextParameter:ctxParameter];
438     [method setArgumentSpecifications:argspecs];
439   }
440   else {
441     [self errorWithFormat:@"cannot handle selector configuration: %@",
442             _config];
443     return nil;
444   }
445   return method;
446 }
447
448 - (id)cannotHandleManifest:(NSDictionary *)_m ofMethodNamed:(NSString *)_name {
449   /* no implementation provided */
450   if (loadDebugOn) {
451     /* 
452        note, a manifest does not need to contain the actual implementation
453        info, eg it can be used to just define the protections
454     */
455     [self logWithFormat:
456             @"Note: missing implemention info for method '%@' !", _name];
457   }
458   return nil;
459 }
460
461 - (BOOL)_loadManifest:(NSDictionary *)_m ofMethodNamed:(NSString *)_name {
462   NSString *mp;
463   NSString *selector;
464   id       method;
465   
466   /* security */
467   
468   if ((mp = [_m objectForKey:@"protectedBy"]))
469     [self->slotProtections setObject:mp forKey:_name];
470   
471   /* implementation */
472   
473   if ([self isPageInvocationManifest:_m])
474     method = [self makePageInvocationForMethodNamed:_name manifest:_m];
475   else if ([self isActionInvocationManifest:_m])
476     method = [self makeActionInvocationForMethodNamed:_name manifest:_m];
477   else if ((selector = [_m objectForKey:@"selector"]))
478     method = [self makeInvocationForMethodNamed:_name selector:selector];
479   else
480     method = [self cannotHandleManifest:_m ofMethodNamed:_name];
481   
482   if (method) {
483     [self->slotValues setObject:method forKey:_name];
484     [method release];
485   }
486   
487   return YES;
488 }
489
490 - (id)instantiateObjectOfClass:(Class)clazz withPlist:(id)value {
491   /* returns a retained instance */
492   
493   if ([value isKindOfClass:[NSDictionary class]]) {
494     if ([clazz instancesRespondToSelector:@selector(initWithDictionary:)])
495       return [[clazz alloc] initWithDictionary:value];
496   }
497   else if ([value isKindOfClass:[NSArray class]]) {
498     if ([clazz instancesRespondToSelector:@selector(initWithArray:)])
499       return [[clazz alloc] initWithArray:value];
500   }
501   else if ([value isKindOfClass:[NSData class]]) {
502     if ([clazz instancesRespondToSelector:@selector(initWithData:)])
503       return [[clazz alloc] initWithData:value];
504   }
505   else {
506     if ([clazz instancesRespondToSelector:@selector(initWithString:)])
507       return [[clazz alloc] initWithString:[value stringValue]];
508   }
509   
510   if ([clazz instancesRespondToSelector:
511                @selector(initWithPropertyList:owner:)])
512     return [[clazz alloc] initWithPropertyList:value owner:nil];
513   if ([clazz instancesRespondToSelector:@selector(initWithPropertyList:)])
514     return [[clazz alloc] initWithPropertyList:value];
515   
516   return nil;
517 }
518
519 - (BOOL)_loadManifest:(NSDictionary *)_m ofSlotNamed:(NSString *)_name {
520   NSString *mp;
521   NSString *valueClassName;
522   Class    valueClass;
523   id value;
524
525   if ([_m isKindOfClass:[NSString class]]) {
526     /* user used: slots = { abc = 15; } */
527     _m = [NSDictionary dictionaryWithObject:_m forKey:@"value"];
528   }
529   
530   /* security */
531   
532   if ((mp = [_m objectForKey:@"protectedBy"]))
533     [self->slotProtections setObject:mp forKey:_name];
534   
535   if ((valueClassName = [[_m objectForKey:@"valueClass"] stringValue])) {
536     // TODO: hack, we need to load the bundle of the product to have the
537     //       contained classes available as valueClasses (But: shouldn't
538     //       that be already done by NGBundleManager?)
539     [[self->product bundle] load];
540     
541     // TODO: should we allow/use SoClasses here?
542     if ((valueClass = NSClassFromString(valueClassName)) == Nil) {
543       [self errorWithFormat:
544               @"did not find value class '%@' for slot: '%@'",
545               valueClassName, _name];
546       return NO;
547     }
548   }
549   else
550     valueClass = Nil;
551   
552   if ((value = [_m objectForKey:@"value"]) != nil) {
553     if (valueClass) {
554       value = [self instantiateObjectOfClass:valueClass withPlist:value];
555       
556       if (value == nil) {
557         [self errorWithFormat:
558           @"could not initialize value of slot %@ with class %@",
559                 _name, valueClassName];
560         return NO;
561       }
562       value = [value autorelease];
563     }
564     else
565       /* pass through property list */;
566   }
567   else if (valueClass) {
568     /* 
569        Note: a manifest does not need to contain the actual value, eg it can be
570        used to just define the protections
571     */
572     if (loadDebugOn) 
573       [self logWithFormat:@"Note: slot no value: '%@'", _name];
574     
575     value = [[[valueClass alloc] init] autorelease];
576     if (value == nil) {
577       [self errorWithFormat:
578               @"could not initialize value of slot '%@' with class: %@",
579               _name, valueClassName];
580       return NO;
581     }
582   }
583   
584   if (value)
585     [self->slotValues setObject:value forKey:_name];
586   
587   return YES;
588 }
589
590 - (BOOL)_loadManifest:(NSDictionary *)_m {
591   NSDictionary *slots;
592   id tmp;
593   
594   [self reset];
595   self->protectedBy   = [[_m objectForKey:@"protectedBy"] copy];
596   self->defaultAccess = [[_m objectForKey:@"defaultAccess"] copy];
597   self->roleInfo      = [[_m objectForKey:@"defaultRoles"] copy];
598   
599   self->exactFilenames = [[_m objectForKey:@"exactFilenames"] copy];
600   
601   self->extensions = [[_m objectForKey:@"extensions"] copy];
602   if ((tmp = [_m objectForKey:@"extension"])) {
603     if (self->extensions == nil) {
604       self->extensions = [tmp isKindOfClass:[NSArray class]]
605         ? [tmp copy]
606         : [[NSArray alloc] initWithObjects:&tmp count:1];
607     }
608     else {
609       tmp = [tmp isKindOfClass:[NSArray class]]
610         ? [[self->extensions arrayByAddingObjectsFromArray:tmp] retain]
611         : [[self->extensions arrayByAddingObject:tmp] retain];
612       [self->extensions autorelease];
613       self->extensions = tmp;
614     }
615   }
616   
617   if (self->slotValues == nil)
618     self->slotValues = [[NSMutableDictionary alloc] init];
619   if (self->slotProtections == nil)
620     self->slotProtections = [[NSMutableDictionary alloc] init];
621   
622   if ((slots = [_m objectForKey:@"methods"])) {
623     NSEnumerator *names;
624     NSString *methodName;
625     
626     names = [slots keyEnumerator];
627     while ((methodName = [names nextObject])) {
628       NSDictionary *info;
629       
630       info = [slots objectForKey:methodName];
631       
632       if (![self _loadManifest:info ofMethodNamed:methodName])
633         [self logWithFormat:@"manifest of method %@ is broken.", methodName];
634     }
635   }
636   if ((slots = [_m objectForKey:@"slots"])) {
637     NSEnumerator *names;
638     NSString *slotName;
639     
640     names = [slots keyEnumerator];
641     while ((slotName = [names nextObject])) {
642       NSDictionary *info;
643       
644       info = [slots objectForKey:slotName];
645       
646       if (![self _loadManifest:info ofSlotNamed:slotName])
647         [self logWithFormat:@"manifest of slot %@ is broken.", slotName];
648     }
649   }
650   return YES;
651 }
652
653 @end /* SoProductSlotSetInfo(ManifestLoading) */