]> err.no Git - sope/blob - sope-appserver/NGObjWeb/SoObjects/SoObjectXmlRpcDispatcher.m
fixed OGo bug #888
[sope] / sope-appserver / NGObjWeb / SoObjects / SoObjectXmlRpcDispatcher.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$
22
23 #include "SoObjectXmlRpcDispatcher.h"
24 #include "SoObject.h"
25 #include "NSException+HTTP.h"
26 #include <NGXmlRpc/XmlRpcMethodCall+WO.h>
27 #include <NGXmlRpc/XmlRpcMethodResponse+WO.h>
28 #include <NGObjWeb/WOActionResults.h>
29 #include <NGObjWeb/WOContext.h>
30 #include <NGObjWeb/WOResponse.h>
31 #include "common.h"
32
33 @interface NSObject(XmlRpcCall)
34
35 - (id)callOnObject:(id)_client 
36   withPositionalParameters:(NSArray *)_args
37   inContext:(id)_ctx;
38
39 @end
40
41 @implementation SoObjectXmlRpcDispatcher
42
43 static BOOL debugOn = NO;
44
45 + (void)initialize {
46   NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
47   
48   debugOn = [ud boolForKey:@"SoObjectXmlRpcDispatcherDebugEnabled"];
49   if (debugOn) NSLog(@"Note: SOPE XML-RPC Dispatcher Debugging turned on.");
50 }
51
52 /* error handling */
53
54 - (NSException *)missingMethodFault:(NSString *)_method 
55   inContext:(WOContext *)_ctx
56 {
57   NSString *r;
58   
59   r = [@"Could not locate requested XML-RPC method: " 
60         stringByAppendingString:_method];
61   return [NSException exceptionWithHTTPStatus:404 /* not found */
62                       reason:r];
63 }
64
65 /* perform call on object */
66
67 - (id)performActionNamed:(NSString *)_name parameters:(NSArray *)_params 
68   inContext:(WOContext *)_ctx
69 {
70   NSString *methodName;
71   NSRange  r;
72   id       clientObject;
73   id       method;
74
75   // TODO: check whether _name is set
76   
77   [self debugWithFormat:@"should perform: %@", _name];
78   methodName = nil;
79   
80   r = [_name rangeOfString:@"." options:(NSLiteralSearch|NSBackwardsSearch)];
81   if (r.length > 0) {
82     /* has a prefix, eg "folder.folder.a()" => traverse */
83     NSArray     *nsParts;
84     NSException *error = nil;
85     unsigned    count;
86     
87     nsParts    = [_name componentsSeparatedByString:@"."];
88     count      = [nsParts count];
89     methodName = [[[nsParts objectAtIndex:(count - 1)] copy] autorelease];
90     nsParts    = [nsParts subarrayWithRange:NSMakeRange(0, count - 1)];
91     
92     [self debugWithFormat:@"XML-RPC traversal: %@",
93             [nsParts componentsJoinedByString:@" => "]];
94     
95     /*
96        TODO: we might not want to use -traverse.. so that the clientObject
97              stays the same (the one bound to the URL)?
98     */
99     clientObject = [self->object 
100                         traversePathArray:nsParts inContext:_ctx error:&error
101                         acquire:YES];
102     if (error) {
103       [self debugWithFormat:@"  XML-RPC traversal error: %@", error];
104       return error;
105     }
106   }
107   else {
108     clientObject = self->object;
109     methodName   = _name;
110   }
111   
112   method = [clientObject lookupName:methodName inContext:_ctx acquire:YES];
113   if (method == nil) {
114     // TODO: return proper fault!
115     [self logWithFormat:@"did not find requested XML-RPC method: '%@'",
116             methodName];
117     return [self missingMethodFault:methodName inContext:_ctx];
118   }
119   if (![method isCallable]) {
120     // TODO: return proper fault!
121     [self logWithFormat:
122             @"located object (%@) is not callable (class=%@):\n  %@", 
123             methodName, NSStringFromClass([method class]), method];
124     return nil;
125   }
126   
127   /* TODO: do we need to bind or is this automatic? */
128   
129   if ([method respondsToSelector:
130                 @selector(callOnObject:withPositionalParameters:inContext:)]) {
131     [self debugWithFormat:
132             @"calling XML-RPC method with %i positional parameters.",
133             [_params count]];
134     return [method callOnObject:clientObject 
135                    withPositionalParameters:_params 
136                    inContext:_ctx];
137   }
138   
139   if ([_params count] > 0) {
140     [self logWithFormat:
141             @"WARNING: invoking SOPE method via XML-RPC without "
142             @"positional paramters (%i parameters defined)",
143             [_params count]];
144   }
145   return [method callOnObject:clientObject inContext:_ctx];
146 }
147
148 - (id)faultFromException:(NSException *)_exception
149   methodCall:(XmlRpcMethodCall *)_call
150 {
151   /* add some more information to generic exceptions ... */
152   if (_call) {
153     NSMutableDictionary *ui;
154     
155     ui = [[_exception userInfo] mutableCopy];
156     if (ui == nil) ui = [[NSMutableDictionary alloc] init];
157     
158     [ui setObject:[_call methodName] forKey:@"methodName"];
159     [ui setObject:[_call parameters] forKey:@"methodParameters"];
160     
161     [_exception setUserInfo:ui];
162     [ui release];
163   }
164   
165   [self logWithFormat:@"%s: turning exception into fault %@\n",
166           __PRETTY_FUNCTION__,
167           [_exception description]];
168   
169   return _exception;
170 }
171
172 - (id<WOActionResults>)actionResponseForResult:(id)resValue {
173   if ([resValue conformsToProtocol:@protocol(WOActionResults)]) {
174     /* a "HTTP" result ... */
175     return resValue;
176   }
177   else {
178     /* an XML-RPC result ... */
179     XmlRpcMethodResponse *mResponse;
180     
181     mResponse = [[XmlRpcMethodResponse alloc] initWithResult:resValue];
182     return [mResponse autorelease];
183   }
184 }
185
186 - (id)performMethodCall:(XmlRpcMethodCall *)_call inContext:(WOContext *)_ctx{
187   id resValue;
188   
189   NS_DURING {
190     resValue = [self performActionNamed:[_call methodName]
191                      parameters:[_call parameters]
192                      inContext:_ctx];
193     resValue = [resValue retain];
194   }
195   NS_HANDLER {
196     resValue = [self faultFromException:localException
197                      methodCall:_call];
198     resValue = [resValue retain];
199   }
200   NS_ENDHANDLER;
201   
202   resValue = [resValue autorelease];
203   
204   return [self actionResponseForResult:resValue];
205 }
206
207 - (id)couldNotDecodeXmlRpcRequestInContext:(WOContext *)_ctx {
208   WOResponse *r = [_ctx response];
209   
210   [r setStatus:400 /* bad request */];
211   [r appendContentString:@"malformed XML-RPC request !"];
212   return r;
213 }
214
215 - (id)handleXmlRpcEncodingException:(NSException *)_exception object:(id)_obj {
216   [self logWithFormat:@"could not encode object: %@ (%@): %@",
217           _obj, NSStringFromClass([_obj class]), _exception];
218   return nil;
219 }
220
221 - (id)dispatchInContext:(WOContext *)_ctx {
222   XmlRpcMethodResponse *r;
223   XmlRpcMethodCall *call;
224   id result;
225   
226   call = [XmlRpcMethodCall alloc];
227   call = [call initWithRequest:[_ctx request]];
228   call = [call autorelease];
229   
230   if (call == nil)
231     return [self couldNotDecodeXmlRpcRequestInContext:_ctx];
232   
233   if ((result = [self performMethodCall:call inContext:_ctx]) == nil)
234     /* TODO: should we return a fault instead? */
235     return nil;
236   
237   if ([result isKindOfClass:[WOResponse class]])
238     /* pass WOResponse objects through ... */
239     return result;
240   
241   NS_DURING {
242     if ([result isKindOfClass:[XmlRpcMethodResponse class]])
243       r = result;
244     else
245       r = [[[XmlRpcMethodResponse alloc] initWithResult:result] autorelease];
246     
247     result = [[[r generateResponse] retain] autorelease];
248   }
249   NS_HANDLER {
250     result = [self handleXmlRpcEncodingException:localException
251                    object:result];
252   }
253   NS_ENDHANDLER;
254   
255   return result;
256 }
257
258 /* debugging */
259
260 - (NSString *)loggingPrefix {
261   return @"[obj-xmlrpc-dispatch]";
262 }
263 - (BOOL)isDebuggingEnabled {
264   return debugOn;
265 }
266
267 @end /* SoObjectXmlRpcDispatcher */