/* Copyright (C) 2002-2004 SKYRIX Software AG This file is part of OpenGroupware.org. OGo is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. OGo is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with OGo; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // $Id$ #include "SoObjectXmlRpcDispatcher.h" #include "SoObject.h" #include "NSException+HTTP.h" #include #include #include #include #include #include "common.h" @interface NSObject(XmlRpcCall) - (id)callOnObject:(id)_client withPositionalParameters:(NSArray *)_args inContext:(id)_ctx; @end @implementation SoObjectXmlRpcDispatcher static BOOL debugOn = NO; + (void)initialize { NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; debugOn = [ud boolForKey:@"SoObjectXmlRpcDispatcherDebugEnabled"]; if (debugOn) NSLog(@"Note: SOPE XML-RPC Dispatcher Debugging turned on."); } /* error handling */ - (NSException *)missingMethodFault:(NSString *)_method inContext:(WOContext *)_ctx { NSString *r; r = [@"Could not locate requested XML-RPC method: " stringByAppendingString:_method]; return [NSException exceptionWithHTTPStatus:404 /* not found */ reason:r]; } /* perform call on object */ - (id)performActionNamed:(NSString *)_name parameters:(NSArray *)_params inContext:(WOContext *)_ctx { NSString *methodName; NSRange r; id clientObject; id method; // TODO: check whether _name is set [self debugWithFormat:@"should perform: %@", _name]; methodName = nil; r = [_name rangeOfString:@"." options:(NSLiteralSearch|NSBackwardsSearch)]; if (r.length > 0) { /* has a prefix, eg "folder.folder.a()" => traverse */ NSArray *nsParts; NSException *error = nil; unsigned count; nsParts = [_name componentsSeparatedByString:@"."]; count = [nsParts count]; methodName = [[[nsParts objectAtIndex:(count - 1)] copy] autorelease]; nsParts = [nsParts subarrayWithRange:NSMakeRange(0, count - 1)]; [self debugWithFormat:@"XML-RPC traversal: %@", [nsParts componentsJoinedByString:@" => "]]; /* TODO: we might not want to use -traverse.. so that the clientObject stays the same (the one bound to the URL)? */ clientObject = [self->object traversePathArray:nsParts inContext:_ctx error:&error acquire:YES]; if (error) { [self debugWithFormat:@" XML-RPC traversal error: %@", error]; return error; } } else { clientObject = self->object; methodName = _name; } method = [clientObject lookupName:methodName inContext:_ctx acquire:YES]; if (method == nil) { // TODO: return proper fault! [self logWithFormat:@"did not find requested XML-RPC method: '%@'", methodName]; return [self missingMethodFault:methodName inContext:_ctx]; } if (![method isCallable]) { // TODO: return proper fault! [self logWithFormat: @"located object (%@) is not callable (class=%@):\n %@", methodName, NSStringFromClass([method class]), method]; return nil; } /* TODO: do we need to bind or is this automatic? */ if ([method respondsToSelector: @selector(callOnObject:withPositionalParameters:inContext:)]) { [self debugWithFormat: @"calling XML-RPC method with %i positional parameters.", [_params count]]; return [method callOnObject:clientObject withPositionalParameters:_params inContext:_ctx]; } if ([_params count] > 0) { [self logWithFormat: @"WARNING: invoking SOPE method via XML-RPC without " @"positional paramters (%i parameters defined)", [_params count]]; } return [method callOnObject:clientObject inContext:_ctx]; } - (id)faultFromException:(NSException *)_exception methodCall:(XmlRpcMethodCall *)_call { /* add some more information to generic exceptions ... */ if (_call) { NSMutableDictionary *ui; ui = [[_exception userInfo] mutableCopy]; if (ui == nil) ui = [[NSMutableDictionary alloc] init]; [ui setObject:[_call methodName] forKey:@"methodName"]; [ui setObject:[_call parameters] forKey:@"methodParameters"]; [_exception setUserInfo:ui]; [ui release]; } [self logWithFormat:@"%s: turning exception into fault %@\n", __PRETTY_FUNCTION__, [_exception description]]; return _exception; } - (id)actionResponseForResult:(id)resValue { if ([resValue conformsToProtocol:@protocol(WOActionResults)]) { /* a "HTTP" result ... */ return resValue; } else { /* an XML-RPC result ... */ XmlRpcMethodResponse *mResponse; mResponse = [[XmlRpcMethodResponse alloc] initWithResult:resValue]; return [mResponse autorelease]; } } - (id)performMethodCall:(XmlRpcMethodCall *)_call inContext:(WOContext *)_ctx{ id resValue; NS_DURING { resValue = [self performActionNamed:[_call methodName] parameters:[_call parameters] inContext:_ctx]; resValue = [resValue retain]; } NS_HANDLER { resValue = [self faultFromException:localException methodCall:_call]; resValue = [resValue retain]; } NS_ENDHANDLER; resValue = [resValue autorelease]; return [self actionResponseForResult:resValue]; } - (id)couldNotDecodeXmlRpcRequestInContext:(WOContext *)_ctx { WOResponse *r = [_ctx response]; [r setStatus:400 /* bad request */]; [r appendContentString:@"malformed XML-RPC request !"]; return r; } - (id)handleXmlRpcEncodingException:(NSException *)_exception object:(id)_obj { [self logWithFormat:@"could not encode object: %@ (%@): %@", _obj, NSStringFromClass([_obj class]), _exception]; return nil; } - (id)dispatchInContext:(WOContext *)_ctx { XmlRpcMethodResponse *r; XmlRpcMethodCall *call; id result; call = [XmlRpcMethodCall alloc]; call = [call initWithRequest:[_ctx request]]; call = [call autorelease]; if (call == nil) return [self couldNotDecodeXmlRpcRequestInContext:_ctx]; if ((result = [self performMethodCall:call inContext:_ctx]) == nil) /* TODO: should we return a fault instead? */ return nil; if ([result isKindOfClass:[WOResponse class]]) /* pass WOResponse objects through ... */ return result; NS_DURING { if ([result isKindOfClass:[XmlRpcMethodResponse class]]) r = result; else r = [[[XmlRpcMethodResponse alloc] initWithResult:result] autorelease]; result = [[[r generateResponse] retain] autorelease]; } NS_HANDLER { result = [self handleXmlRpcEncodingException:localException object:result]; } NS_ENDHANDLER; return result; } /* debugging */ - (NSString *)loggingPrefix { return @"[obj-xmlrpc-dispatch]"; } - (BOOL)isDebuggingEnabled { return debugOn; } @end /* SoObjectXmlRpcDispatcher */