]> err.no Git - sope/blob - sope-appserver/NGObjWeb/SoObjects/SoActionInvocation.m
fixed copyrights for 2005
[sope] / sope-appserver / NGObjWeb / SoObjects / SoActionInvocation.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 "SoActionInvocation.h"
23 #include "SoClassSecurityInfo.h"
24 #include "SoProduct.h"
25 #include "WOContext+SoObjects.h"
26 #include <NGObjWeb/WOApplication.h>
27 #include <NGObjWeb/WODirectAction.h>
28 #include <NGObjWeb/WOResponse.h>
29 #include <DOM/EDOM.h>
30 #include "common.h"
31
32 @implementation SoActionInvocation
33
34 static int debugOn = 0;
35
36 + (void)initialize {
37   static BOOL didInit = NO;
38   if (!didInit) {
39     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
40     didInit = YES;
41     
42     debugOn = [ud boolForKey:@"SoPageInvocationDebugEnabled"] ? 1 : 0;
43   }
44 }
45
46 - (id)initWithActionClassName:(NSString *)_cn actionName:(NSString *)_action {
47   if ((self = [super init])) {
48     self->actionClassName = [_cn     copy];
49     self->actionName      = [_action copy];
50   }
51   return self;
52 }
53 - (id)initWithActionClassName:(NSString *)_cn {
54   return [self initWithActionClassName:_cn actionName:nil];
55 }
56 - (id)init {
57   return [self initWithActionClassName:nil actionName:nil];
58 }
59
60 - (void)dealloc {
61   [self->argumentSpecifications release];
62   [self->methodObject           release];
63   [self->object          release];
64   [self->actionClassName release];
65   [self->actionName      release];
66   [super dealloc];
67 }
68
69 /* accessors */
70
71 - (NSString *)defaultActionClassName {
72   return @"DirectAction";
73 }
74 - (NSString *)actionClassName {
75   return [self->actionClassName isNotNull] 
76     ? self->actionClassName 
77     : [self defaultActionClassName];
78 }
79 - (NSString *)actionName {
80   return self->actionName;
81 }
82
83 - (void)setArgumentSpecifications:(NSDictionary *)_specs {
84   ASSIGNCOPY(self->argumentSpecifications, _specs);
85 }
86 - (NSDictionary *)argumentSpecifications {
87   return self->argumentSpecifications;
88 }
89
90 /* argument processing */
91
92 - (NSDictionary *)extractSOAPArgumentsFromContext:(id)_ctx 
93   specification:(id)_spec 
94 {
95   /* 
96      spec is supposed to be a dictionary with the KVC keys as the 
97      keys and DOM query pathes as the values.
98   */
99   NSMutableDictionary *args;
100   NSEnumerator *keys;
101   NSString     *key;
102   id           soapEnvelope;
103   
104   // TODO: I guess that should be improved a bit in the dispatcher
105   if ((soapEnvelope = [_ctx valueForKey:@"SOAPEnvelope"]) == nil) {
106     // TODO: generate some kind of fault? (NSException?)
107     [self errorWithFormat:@"no SOAP envelope available in context!"];
108     return nil;
109   }
110   
111   /* for each key argument we have a query path */
112   
113   args = [NSMutableDictionary dictionaryWithCapacity:8];
114   keys = [_spec keyEnumerator];
115   while ((key = [keys nextObject])) {
116     NSString *qppath;
117     id value;
118     
119     qppath = [_spec valueForKey:key];
120     value  = [qppath isNotNull] ? [soapEnvelope lookupQueryPath:qppath] : nil;
121     
122     [args setObject:(value != nil ? value : [NSNull null]) forKey:key];
123   }
124   return args;
125 }
126
127 - (NSDictionary *)extractArgumentsFromContext:(id)_ctx
128   forRequestType:(NSString *)_type
129   specification:(id)_spec 
130 {
131   if ([_type isEqualToString:@"SOAP"])
132     return [self extractSOAPArgumentsFromContext:_ctx specification:_spec];
133   
134   [self errorWithFormat:
135           @"cannot extract parameters for request type: '%@'", _type];
136   return nil;
137 }
138
139 /* page construction */
140
141 - (id)instantiateMethodInContext:(id)_ctx {
142   Class clazz;
143   id lMethod;
144   
145   if (debugOn)
146     [self debugWithFormat:@"instantiate method: %@", self->methodObject];
147   
148   if (_ctx == nil) {
149     [self debugWithFormat:
150             @"Note: got no explicit context for method instantiation, using "
151             @"application context."];
152     _ctx = [[WOApplication application] context];
153   }
154   
155   /* find class */
156   
157   if ((clazz = NSClassFromString([self actionClassName])) == Nil) {
158     [self errorWithFormat:@"did not find action class: %@",
159             [self actionClassName]];
160     return  nil;
161   }
162   
163   /* instantiate */
164
165   if ([clazz instancesRespondToSelector:@selector(initWithContext:)])
166     lMethod = [[clazz alloc] initWithContext:_ctx];
167   else if ([clazz instancesRespondToSelector:@selector(initWithRequest:)]) {
168     lMethod = [[clazz alloc] initWithRequest:
169                                [(id<WOPageGenerationContext>)_ctx request]];
170   }
171   else
172     lMethod = [[clazz alloc] init];
173   
174   if (debugOn) [self debugWithFormat:@"   page: %@", lMethod];
175   
176   return lMethod;
177 }
178
179 /* invocation */
180
181 - (BOOL)isCallable {
182   return YES;
183 }
184 - (id)clientObject {
185   return self->object;
186 }
187
188 - (void)_prepareContext:(id)_ctx withMethodObject:(id)_method {
189   /* for subclasses (set page in context in page invocation) */
190 }
191 - (void)_prepareMethod:(id)_method inContext:(id)_ctx {
192   /* for subclasses (triggers takeValues phase in page invocation) */
193 }
194
195 - (void)_applyArgumentsOnMethod:(id)_method inContext:(id)_ctx {
196   NSDictionary *argspec;
197   NSDictionary *args;
198   
199   argspec = [self->argumentSpecifications objectForKey:[_ctx soRequestType]];
200   if (argspec == nil)
201     return;
202   
203   args = [self extractArgumentsFromContext:_ctx
204                forRequestType:[_ctx soRequestType]
205                specification:argspec];
206   if (debugOn) [self debugWithFormat:@"extracted args %@", args];
207   
208   if (args != nil) [_method takeValuesFromDictionary:args];
209 }
210
211 - (void)_applyPositionalArguments:(NSArray *)_args onMethod:(id)_method
212   inContext:(id)_ctx
213 {
214   NSArray *info;
215   unsigned i, argCount, infoCount;
216   
217   info      = [self->argumentSpecifications objectForKey:@"positionalKeys"];
218   infoCount = [info  count];
219   argCount  = [_args count];
220   if ((info == nil) && (argCount > 0)) {
221     [self warnWithFormat:
222             @"found no argument specification for positional keys!"];
223     return;
224   }
225   
226   /* copy available arguments to key */
227   
228   for (i = 0; i < argCount; i++) {
229     if (i >= infoCount) {
230       [self warnWithFormat:
231               @"could not apply argument %d (no key info)", (i + 1)];
232       continue;
233     }
234     
235     [_method takeValue:[_args objectAtIndex:i] forKey:[info objectAtIndex:i]];
236   }
237   
238   /* fill up missing arguments */
239   
240   for (i = argCount; i < infoCount; i++)
241     [_method takeValue:nil forKey:[info objectAtIndex:i]];
242 }
243
244 /* calling the method */
245
246 - (id)callOnObject:(id)_client 
247   withPositionalParametersWhenNotNil:(NSArray *)_positionalArgs
248   inContext:(id)_ctx
249 {
250   /* method used for both, positional and key arguments */
251   id method;
252   
253   if (self->object != _client) {
254     /* rebind */
255     return [[self bindToObject:_client inContext:_ctx]
256                   callOnObject:_client inContext:_ctx];
257   }
258   
259   if ((method = self->methodObject) == nil)
260     method = [self instantiateMethodInContext:_ctx];
261   
262   if (method == nil) {
263     [self logWithFormat:@"found no method named '%@' for call !", 
264             [self actionClassName]];
265     return nil;
266   }
267   
268   /* make page the "request" page */
269   
270   [self _prepareContext:_ctx withMethodObject:method];
271   
272   /* set client object in page */
273   
274   if ([method respondsToSelector:@selector(setClientObject:)])
275     [method setClientObject:_client];
276   
277   if ([_positionalArgs isNotNull]) {
278     [self _applyPositionalArguments:_positionalArgs onMethod:method
279           inContext:_ctx];
280   }
281   else {
282     /* TODO: what should be done first?, take values or args? */
283     [self _prepareMethod:method          inContext:_ctx];
284     [self _applyArgumentsOnMethod:method inContext:_ctx];
285   }
286   
287   /* call action */
288   
289   if (self->actionName) {
290     if (debugOn) {
291       [self debugWithFormat:@"  performing action %@ on page: %@", 
292               self->actionName, method];
293     }
294     return [method performActionNamed:self->actionName];
295   }
296   else {
297     if (debugOn) {
298       [self debugWithFormat:@"  performing default action on page: %@", 
299               method];
300     }
301     return [method defaultAction];
302   }
303 }
304
305 - (id)callOnObject:(id)_client inContext:(id)_ctx {
306   return [self callOnObject:_client
307                withPositionalParametersWhenNotNil:nil /* not positional */
308                inContext:_ctx];
309 }
310 - (id)callOnObject:(id)_client 
311   withPositionalParameters:(NSArray *)_args
312   inContext:(id)_ctx
313 {
314   if (_args == nil) _args = [NSArray array];
315   return [self callOnObject:_client
316                withPositionalParametersWhenNotNil:_args
317                inContext:_ctx];
318 }
319
320 /* bindings */
321
322 - (BOOL)isBound {
323   return self->object != nil ? YES : NO;
324 }
325
326 - (id)bindToObject:(id)_object inContext:(id)_ctx {
327   SoActionInvocation *inv;
328   
329   if (_object == nil) return nil;
330   
331   // TODO: clean up this section, a bit hackish
332   inv = [[[self class] alloc] initWithActionClassName:self->actionClassName];
333   inv = [inv autorelease];
334   
335   inv->object       = [_object retain];
336   inv->actionName   = [self->actionName copy];
337   inv->argumentSpecifications = [self->argumentSpecifications copy];
338   
339   inv->methodObject = [[inv instantiateMethodInContext:_ctx] retain];
340   if (inv->methodObject == nil) {
341     [self errorWithFormat:@"did not find method '%@'", [self actionClassName]];
342     return nil;
343   }
344   
345   return inv;
346 }
347
348 /* delivering as content (can happen in DAV !) */
349
350 - (void)appendToResponse:(WOResponse *)_r inContext:(WOContext *)_ctx {
351   [_r appendContentString:@"native action method: "];
352   [_r appendContentHTMLString:[self description]];
353 }
354
355 /* key/value coding */
356
357 - (id)valueForUndefinedKey:(NSString *)_key {
358   if (debugOn) [self debugWithFormat:@"return nil for KVC key: '%@'", _key];
359   return nil;
360 }
361
362 /* description */
363
364 - (void)appendAttributesToDescription:(NSMutableString *)ms {
365   NSString *tmp;
366   
367   if ((tmp = [self actionClassName])) [ms appendFormat:@" class=%@", tmp];
368   if (self->actionName) [ms appendFormat:@" action=%@", self->actionName];
369   
370   if (self->object) [ms appendString:@" bound"];
371   if (self->methodObject) [ms appendString:@" instantiated"];
372   
373   if ([self->argumentSpecifications count] > 0) {
374     id tmp;
375     
376     tmp = [self->argumentSpecifications allKeys];
377     tmp = [tmp componentsJoinedByString:@","];
378     [ms appendFormat:@" arg-handlers=%@",tmp];
379   }
380 }
381 - (NSString *)description {
382   NSMutableString *ms;
383
384   ms = [NSMutableString stringWithCapacity:64];
385   [ms appendFormat:@"<0x%08X[%@]:", self, NSStringFromClass([self class])];
386   [self appendAttributesToDescription:ms];
387   [ms appendString:@">"];
388   return ms;
389 }
390
391 /* Logging */
392
393 - (NSString *)loggingPrefix {
394   return [NSString stringWithFormat:@"[so-action 0x%08X %@]", 
395                      self, self->actionClassName];
396 }
397 - (BOOL)isDebuggingEnabled {
398   return debugOn ? YES : NO;
399 }
400
401 @end /* SoActionInvocation */