]> err.no Git - sope/blob - sope-appserver/NGObjWeb/SoObjects/SoObjectMethodDispatcher.m
use %p for pointer formats, fixed gcc 4.1 warnings, minor code improvs
[sope] / sope-appserver / NGObjWeb / SoObjects / SoObjectMethodDispatcher.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 "SoObjectMethodDispatcher.h"
23 #include "SoObject.h"
24 #include "SoClass.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>
31 #include "common.h"
32
33 @implementation SoObjectMethodDispatcher
34
35 static BOOL debugOn = NO;
36 static BOOL useRedirectsForDefaultMethods = NO;
37
38 + (void)initialize {
39   NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
40   static BOOL didInit = NO;
41   if (didInit) return;
42   didInit = YES;
43   
44   debugOn = [ud boolForKey:@"SoObjectMethodDispatcherDebugEnabled"];
45   useRedirectsForDefaultMethods = 
46     [ud boolForKey:@"SoRedirectToDefaultMethods"];
47 }
48
49 - (id)initWithObject:(id)_object {
50   if ((self = [super init])) {
51     self->object = [_object retain];
52   }
53   return self;
54 }
55 - (void)dealloc {
56   [self->object release];
57   [super dealloc];
58 }
59
60 /* perform dispatch */
61
62 - (id)dispatchInContext:(WOContext *)_ctx {
63   NSAutoreleasePool *pool;
64   WORequest *rq;
65   NSString  *httpMethod;
66   id clientObject;
67   id methodObject;
68   id resultObject;
69   
70   pool = [[NSAutoreleasePool alloc] init];
71   rq   = [_ctx request];
72   
73   /* find client object */
74   
75   if ((clientObject = [_ctx clientObject]) != nil) {
76     if (debugOn)
77       [self debugWithFormat:@"client object set in ctx: %@", clientObject];
78   }
79   else if ((clientObject = [self->object clientObject]) != nil) {
80     if (debugOn)
81       [self debugWithFormat:@"setting client object: %@", clientObject];
82     [_ctx setClientObject:clientObject];
83   }
84
85   /* check whether client object is a response ... */
86   
87   if ([clientObject isKindOfClass:[WOResponse class]]) {
88     [self debugWithFormat:@"clientObject is a WOResponse, returning that: %@",
89             clientObject];
90     resultObject = [clientObject retain];
91     [pool release];
92     return [resultObject autorelease];
93   }
94   
95   // TODO: should check XML-RPC !!! 
96   //       (hm, why? XML-RPC is handled by other dispatcher?)
97   
98   /* 
99      This X- field is used by Google which uses POST to trigger REST methods,
100      don't ask me why ... ;-/
101   */
102   if (![(httpMethod = [rq headerForKey:@"x-http-method-override"]) isNotEmpty])
103     httpMethod = [rq method];
104   
105   /* find callable (method) object */
106   
107   if ([self->object isCallable]) {
108     if (debugOn)
109       [self debugWithFormat:@"traversed object is callable: %@", self->object];
110     methodObject = self->object;
111   }
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] 
116                                  inContext:_ctx
117                                  acquire:NO];
118   }
119   else if (useRedirectsForDefaultMethods) {
120     /*
121       Redirect to a default method if available.
122     */
123     NSString *defaultName;
124     
125     defaultName = [self->object defaultMethodNameInContext:_ctx];
126     if ([defaultName isNotEmpty]) {
127       WOResponse *r;
128       NSString   *url;
129         
130       url = [self->object baseURLInContext:_ctx];
131       if (![url hasSuffix:@"/"]) url = [url stringByAppendingString:@"/"];
132       url = [url stringByAppendingString:defaultName];
133         
134       [self debugWithFormat:@"redirect to default method %@ of %@: %@", 
135               defaultName, self->object, url];
136         
137       r = [[_ctx response] retain];
138       [r setStatus:302 /* moved */];
139       [r setHeader:url forKey:@"location"];
140       [pool release];
141       return [r autorelease];
142     }
143   }
144   else {
145     /* 
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).
148     */
149     methodObject = [self->object lookupDefaultMethod];
150     if (debugOn)
151       [self debugWithFormat:@"using default method: %@", methodObject];
152   }
153   
154   /* apply arguments */
155     
156   if ([methodObject respondsToSelector:
157                       @selector(takeValuesFromRequest:inContext:)]) {
158     if (debugOn)
159       [self debugWithFormat:@"applying values from request ..."];
160     [methodObject takeValuesFromRequest:rq inContext:_ctx];
161   }
162
163   /* perform call */
164   
165   if (methodObject == nil || ![methodObject isCallable]) {
166     /*
167       The object is neither callable nor does it have a default method,
168       so we just pass it through.
169       
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
174             callable).
175     */
176     resultObject = self->object;
177     if (debugOn) {
178       if (methodObject == nil) {
179         [self debugWithFormat:@"got no method, using object as result: %@", 
180                 resultObject];
181       }
182       else {
183         [self debugWithFormat:
184                 @"method is not callable %@, using object as result: %@", 
185                 methodObject, resultObject];
186       }
187     }
188   }
189   else {
190     resultObject = [methodObject callOnObject:[_ctx clientObject]
191                                  inContext:_ctx];
192     if (debugOn) {
193       if ([resultObject isKindOfClass:[WOResponse class]]) {
194         [self debugWithFormat:@"call produced response: 0x%p (code=%i)", 
195                 resultObject, [(WOResponse *)resultObject status]];
196       }
197       else
198         [self debugWithFormat:@"call produced result: %@", resultObject];
199     }
200   }
201   
202   resultObject = [resultObject retain];
203   [pool release];
204   
205   /* deliver result */
206   return [resultObject autorelease];
207 }
208
209 /* logging */
210
211 - (NSString *)loggingPrefix {
212   return @"[obj-mth-dispatch]";
213 }
214 - (BOOL)isDebuggingEnabled {
215   return debugOn ? YES : NO;
216 }
217
218 /* description */
219
220 - (NSString *)description {
221   NSMutableString *ms;
222   
223   ms = [NSMutableString stringWithCapacity:64];
224   [ms appendFormat:@"<0x%p[%@]:", self,
225         NSStringFromClass((Class)*(void**)self)];
226   
227   if (self->object)
228     [ms appendFormat:@" object=%@", self->object];
229   else
230     [ms appendString:@" <no object>"];
231   
232   [ms appendString:@">"];
233   return ms;
234 }
235
236 @end /* SoObjectMethodDispatcher */