]> err.no Git - sope/blob - sope-appserver/NGObjWeb/SoObjects/SoObjectSOAPDispatcher.m
fixed OGo bug #888
[sope] / sope-appserver / NGObjWeb / SoObjects / SoObjectSOAPDispatcher.m
1 /*
2   Copyright (C) 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 "SoObjectSOAPDispatcher.h"
24 #include "SoObject.h"
25 #include "NSException+HTTP.h"
26 #include "WOContext+SoObjects.h"
27 #include "SoDefaultRenderer.h"
28 #include <NGObjWeb/WOActionResults.h>
29 #include <NGObjWeb/WOContext.h>
30 #include <NGObjWeb/WOResponse.h>
31 #include <NGObjWeb/WORequest.h>
32 #include "common.h"
33 #include <DOM/DOM.h>
34 #include <SaxObjC/XMLNamespaces.h>
35
36 /*
37   TODO: is it required by SOAP that the HTTP method is POST?
38
39   SOAP sample:
40     <?xml version="1.0" encoding="UTF-8" standalone="no"?>
41     <SOAP-ENV:Envelope 
42       xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" 
43       xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" 
44       xmlns:xsd="http://www.w3.org/1999/XMLSchema" 
45       xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
46     >
47       <SOAP-ENV:Body 
48         xmlns:types="http://schemas.novell.com/2003/10/NCSP/types.xsd" 
49         SOAP-ENV:encodingStyle=""
50       >
51         <loginRequest>
52           <types:auth xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
53                       xsi:type="types:PlainText"
54           >
55             <types:username>dummy</types:username>
56             <types:password>user</types:password>
57           </types:auth>
58         </loginRequest>
59       </SOAP-ENV:Body>
60     </SOAP-ENV:Envelope>
61 */
62
63 @interface SoSOAPRenderer : SoDefaultRenderer
64 @end
65
66 @implementation SoObjectSOAPDispatcher
67
68 static BOOL debugOn      = NO;
69 static BOOL debugParsing = NO;
70
71 + (void)initialize {
72   NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
73   
74   debugOn = [ud boolForKey:@"SoObjectSOAPDispatcherDebugEnabled"];
75   if (debugOn) NSLog(@"Note: SOPE SOAP dispatcher debugging turned on.");
76 }
77
78 /* XML actions */
79
80 - (id)performSOAPAction:(NSString *)_actionName
81   header:(id<DOMElement>)_header body:(id<DOMElement>)_body
82   inContext:(WOContext *)_ctx
83 {
84   id clientObject;
85   id methodObject;
86   id resultObject;
87
88   if (debugOn) 
89     [self debugWithFormat:@"calling SOAP method: '%@'", _actionName];
90   
91   /* find client object */
92   
93   if ((clientObject = [_ctx clientObject]) != nil) {
94     if (debugOn)
95       [self debugWithFormat:@"  client object from ctx: %@", clientObject];
96   }
97   else if ((clientObject = [self->object clientObject])) {
98     if (debugOn)
99       [self debugWithFormat:@"  setting client object: %@", clientObject];
100     [_ctx setClientObject:clientObject];
101   }
102
103   /* find callable (method) object */
104   
105   // TODO: should we allow acquisition?
106   methodObject = [clientObject lookupName:_actionName inContext:_ctx
107                                acquire:NO];
108   if (methodObject == nil) {
109     if ([_actionName hasSuffix:@"Request"])
110       _actionName = [_actionName substringToIndex:[_actionName length] - 7];
111     methodObject = [clientObject lookupName:_actionName inContext:_ctx
112                                  acquire:NO];
113   }
114   
115   if (methodObject == nil) {
116     [self debugWithFormat:@"WARNING: could not locate SOAP method: %@", 
117             _actionName];
118     return [NSException exceptionWithHTTPStatus:501 /* not implemented */
119                         reason:@"did not find the specified SOAP method"];
120   }
121   else if (![methodObject isCallable]) {
122     [self debugWithFormat:
123             @"WARNING: object found for SOAP method '%@' is not callable: "
124             @"%@", _actionName, methodObject];
125     return [NSException exceptionWithHTTPStatus:501 /* not implemented */
126                         reason:@"did not find the specified SOAP method"];
127   }
128   if (debugOn) [self debugWithFormat:@"  method: %@", methodObject];
129   
130   /* apply arguments */
131   
132   // TODO: use some query syntax in product.plist to retrieve parameters
133   //       from SOAP
134   
135   // TODO: somehow apply SOPE header/body?
136   if (_header) [_ctx setObject:_header forKey:@"SOAPHeader"];
137   if (_body)   [_ctx setObject:_body   forKey:@"SOAPBody"];
138   
139   if ([methodObject respondsToSelector:
140                       @selector(takeValuesFromRequest:inContext:)]) {
141     if (debugOn)
142       [self debugWithFormat:@"  applying values from request ..."];
143     [methodObject takeValuesFromRequest:[_ctx request] inContext:_ctx];
144   }
145   
146   /* perform call */
147   
148   resultObject = [methodObject callOnObject:[_ctx clientObject] 
149                                inContext:_ctx];
150   if (debugOn) [self debugWithFormat:@"got SOAP result: %@", resultObject];
151   return resultObject;
152 }
153
154 - (id)performSOAPAction:(NSString *)_actionName document:(id)_dom
155   inContext:(WOContext *)_ctx
156 {
157   id<DOMElement>  envelope;
158   id<DOMElement>  header;
159   id<DOMElement>  body;
160   id<DOMNodeList> list;
161
162   /* envelope */
163
164   envelope = [_dom documentElement];
165   if (![[envelope tagName] isEqualToString:@"Envelope"] ||
166       ![[envelope namespaceURI] isEqualToString:XMLNS_SOAP_ENVELOPE]) {
167     [self debugWithFormat:@"Note: missing SOAP envelope at document root."];
168     return [NSException exceptionWithHTTPStatus:400 /* bad request */
169                         reason:@"could not parse SOAP content of request"];
170   }
171   if (debugParsing) [self debugWithFormat:@"envelope: %@", envelope];
172   
173   [_ctx setObject:envelope forKey:@"SOAPEnvelope"];
174   
175   /* header */
176
177   list = [envelope getElementsByTagName:@"Header"];
178   // TODO: not yet supported by DOMElement: namespaceURI:XMLNS_SOAP_ENVELOPE];
179   if ([list length] > 1) {
180     [self logWithFormat:
181             @"WARNING: multiple SOAP headers in request?! (using first)"];
182   }
183   header = [list length] > 0 ? [list objectAtIndex:0] : nil;
184   if (debugParsing) [self debugWithFormat:@"header: %@", header];
185
186   /* body */
187   
188   list = [envelope getElementsByTagName:@"Body"];
189   // TODO: not yet supported by DOMElement: namespaceURI:XMLNS_SOAP_ENVELOPE];
190   if ([list length] == 0) {
191     [self debugWithFormat:@"Note: missing SOAP body."];
192     return [NSException exceptionWithHTTPStatus:400 /* bad request */
193                         reason:@"could not parse SOAP body of request"];
194   }
195   else if ([list length] > 1) {
196     [self logWithFormat:
197             @"WARNING: multiple SOAP bodies in request?! (using first)"];
198   }
199   body = [list objectAtIndex:0];
200   if (debugParsing) [self debugWithFormat:@"body: %@", body];
201   
202   /* process */
203   
204   return [self performSOAPAction:_actionName 
205                header:header body:body inContext:_ctx];
206 }
207
208 /* main dispatcher */
209
210 - (id)dispatchInContext:(WOContext *)_ctx {
211   NSAutoreleasePool *pool;
212   WORequest         *rq;
213   NSString          *SOAPAction;
214   id<DOMDocument>   dom;
215   id resultObject;
216   
217   pool = [[NSAutoreleasePool alloc] init];
218   
219   if ((rq = [_ctx request]) == nil) {
220     [self logWithFormat:@"ERROR: missing request in context!"];
221     return nil;
222   }
223   
224   /* 
225      Note: the SOAPAction is also contained in the body which is probably
226            considered the authority.
227   */
228   
229   SOAPAction = [rq headerForKey:@"soapaction"];
230   if ([SOAPAction length] == 0) {
231     [self logWithFormat:@"ERROR: missing SOAPAction HTTP header!"];
232     return nil;
233   }
234
235   /* parse XML */
236   
237   if ((dom = [rq contentAsDOMDocument]) == nil) {
238     [self debugWithFormat:@"Note: could not parse XML content of request"];
239     return [NSException exceptionWithHTTPStatus:400 /* bad request */
240                         reason:@"could not parse XML content of request"];
241   }
242   
243   resultObject = 
244     [[self performSOAPAction:SOAPAction document:dom inContext:_ctx]retain];
245   [pool release];
246   
247   return [resultObject autorelease];
248 }
249
250 /* debugging */
251
252 - (NSString *)loggingPrefix {
253   return @"[obj-soap-dispatch]";
254 }
255 - (BOOL)isDebuggingEnabled {
256   return debugOn;
257 }
258
259 @end /* SoObjectSOAPDispatcher */
260
261 @implementation SoSOAPRenderer
262
263 // TODO: render exceptions as SOAP faults
264 // TODO: maybe support rendering of DOM trees? (should be supported by default)
265 // TODO: maybe some "schema" driven rendering
266
267 @end /* SoSOAPRenderer */