2 Copyright (C) 2002-2005 SKYRIX Software AG
4 This file is part of SOPE.
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
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.
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
22 #include "SoObjectMethodDispatcher.h"
25 #include "SoObjectRequestHandler.h"
26 #include "WOContext+SoObjects.h"
27 #include <NGObjWeb/WORequest.h>
28 #include <NGObjWeb/WOResponse.h>
29 #include <NGObjWeb/WOContext.h>
30 #include <NGObjWeb/WOElement.h>
33 @implementation SoObjectMethodDispatcher
35 static BOOL debugOn = NO;
36 static BOOL useRedirectsForDefaultMethods = NO;
39 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
40 static BOOL didInit = NO;
44 debugOn = [ud boolForKey:@"SoObjectMethodDispatcherDebugEnabled"];
45 useRedirectsForDefaultMethods =
46 [ud boolForKey:@"SoRedirectToDefaultMethods"];
49 - (id)initWithObject:(id)_object {
50 if ((self = [super init])) {
51 self->object = [_object retain];
56 [self->object release];
60 /* perform dispatch */
62 - (id)dispatchInContext:(WOContext *)_ctx {
63 NSAutoreleasePool *pool;
70 pool = [[NSAutoreleasePool alloc] init];
73 /* find client object */
75 if ((clientObject = [_ctx clientObject]) != nil) {
77 [self debugWithFormat:@"client object set in ctx: %@", clientObject];
79 else if ((clientObject = [self->object clientObject]) != nil) {
81 [self debugWithFormat:@"setting client object: %@", clientObject];
82 [_ctx setClientObject:clientObject];
85 /* check whether client object is a response ... */
87 if ([clientObject isKindOfClass:[WOResponse class]]) {
88 [self debugWithFormat:@"clientObject is a WOResponse, returning that: %@",
90 resultObject = [clientObject retain];
92 return [resultObject autorelease];
95 // TODO: should check XML-RPC !!!
96 // (hm, why? XML-RPC is handled by other dispatcher?)
99 This X- field is used by Google which uses POST to trigger REST methods,
100 don't ask me why ... ;-/
102 if (![(httpMethod = [rq headerForKey:@"x-http-method-override"]) isNotEmpty])
103 httpMethod = [rq method];
105 /* find callable (method) object */
107 if ([self->object isCallable]) {
109 [self debugWithFormat:@"traversed object is callable: %@", self->object];
110 methodObject = self->object;
112 else if ([[self->object soClass] hasKey:httpMethod inContext:_ctx]) {
113 // TODO: I'm not sure whether this step is correct
114 /* the class has a GET/PUT/xxx method */
115 methodObject = [self->object lookupName:[rq method]
119 else if (useRedirectsForDefaultMethods) {
121 Redirect to a default method if available.
123 NSString *defaultName;
125 defaultName = [self->object defaultMethodNameInContext:_ctx];
126 if ([defaultName isNotEmpty]) {
130 url = [self->object baseURLInContext:_ctx];
131 if (![url hasSuffix:@"/"]) url = [url stringByAppendingString:@"/"];
132 url = [url stringByAppendingString:defaultName];
134 [self debugWithFormat:@"redirect to default method %@ of %@: %@",
135 defaultName, self->object, url];
137 r = [[_ctx response] retain];
138 [r setStatus:302 /* moved */];
139 [r setHeader:url forKey:@"location"];
141 return [r autorelease];
146 Note: this can lead to incorrect URLs if the base URL of the method is
147 not set (the method will run in the client URL).
149 methodObject = [self->object lookupDefaultMethod];
151 [self debugWithFormat:@"using default method: %@", methodObject];
154 /* apply arguments */
156 if ([methodObject respondsToSelector:
157 @selector(takeValuesFromRequest:inContext:)]) {
159 [self debugWithFormat:@"applying values from request ..."];
160 [methodObject takeValuesFromRequest:rq inContext:_ctx];
165 if (methodObject == nil || ![methodObject isCallable]) {
167 The object is neither callable nor does it have a default method,
168 so we just pass it through.
170 Note: you can run into situations where there is a methodObject, but
171 it isn't callable. Eg this situation can occur if the default
172 method name leads to an object which isn't a method (occured in
173 the OFS context if 'index' maps to an OFSFile which itself isn't
176 resultObject = self->object;
178 if (methodObject == nil) {
179 [self debugWithFormat:@"got no method, using object as result: %@",
183 [self debugWithFormat:
184 @"method is not callable %@, using object as result: %@",
185 methodObject, resultObject];
190 resultObject = [methodObject callOnObject:[_ctx clientObject]
193 if ([resultObject isKindOfClass:[WOResponse class]]) {
194 [self debugWithFormat:@"call produced response: 0x%p (code=%i)",
195 resultObject, [(WOResponse *)resultObject status]];
198 [self debugWithFormat:@"call produced result: %@", resultObject];
202 resultObject = [resultObject retain];
206 return [resultObject autorelease];
211 - (NSString *)loggingPrefix {
212 return @"[obj-mth-dispatch]";
214 - (BOOL)isDebuggingEnabled {
215 return debugOn ? YES : NO;
220 - (NSString *)description {
223 ms = [NSMutableString stringWithCapacity:64];
224 [ms appendFormat:@"<0x%p[%@]:", self,
225 NSStringFromClass((Class)*(void**)self)];
228 [ms appendFormat:@" object=%@", self->object];
230 [ms appendString:@" <no object>"];
232 [ms appendString:@">"];
236 @end /* SoObjectMethodDispatcher */