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