2 Copyright (C) 2002-2004 SKYRIX Software AG
4 This file is part of OpenGroupware.org.
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
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.
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
21 // $Id: SoObjectRequestHandler.m 24 2004-08-20 22:54:26Z helge $
23 #include "SoObjectRequestHandler.h"
25 #include "SoObjectMethodDispatcher.h"
26 #include "SoSecurityManager.h"
27 #include "SoDefaultRenderer.h"
28 #include "WOContext+SoObjects.h"
29 #include "WORequest+So.h"
30 #include <NGObjWeb/WOApplication.h>
31 #include <NGObjWeb/WORequest.h>
32 #include <NGObjWeb/WOResponse.h>
33 #include <NGObjWeb/WOElement.h>
34 #include <NGObjWeb/WOTemplate.h>
35 #include <NGObjWeb/WEClientCapabilities.h>
36 #include <NGExtensions/NGRuleContext.h>
37 #include <NGExtensions/NSString+Ext.h>
38 #include "WOComponent+private.h"
41 @interface NSObject(ObjectDispatcher)
42 - (id)dispatcherForContext:(WOContext *)_ctx;
43 - (WOResponse *)preprocessCredentialsInContext:(WOContext *)_ctx;
44 - (void)_sleepWithContext:(WOContext *)_ctx;
47 @interface NSObject(SoOFS)
48 - (NSString *)storagePath;
51 @implementation SoObjectRequestHandler
53 static BOOL debugOn = NO;
54 static BOOL debugRulesOn = NO;
55 static BOOL disableZLHack = NO;
57 static Class WOTemplateClass = Nil;
58 static NSString *rapidTurnAroundPath = nil;
61 return [super version] + 0 /* 2 */;
64 static BOOL didInit = NO;
66 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
68 NSAssert2([super version] == 2,
69 @"invalid superclass (%@) version %i !",
70 NSStringFromClass([self superclass]), [super version]);
71 debugOn = [ud boolForKey:@"SoObjectRequestHandlerDebugEnabled"];
72 debugRulesOn = [ud boolForKey:@"SoObjectRequestHandlerRulesDebugEnabled"];
73 disableZLHack = [ud boolForKey:@"DisableZideLookHack"];
75 WOTemplateClass = [WOTemplate class];
76 rapidTurnAroundPath = [[ud stringForKey:@"WOProjectDirectory"] copy];
81 if ((self = [super init])) {
82 self->dispatcherRules =
83 [[NGRuleContext ruleContextWithModelInUserDefault:
84 @"SoRequestDispatcherRules"] retain];
85 if (debugRulesOn) [self->dispatcherRules setDebugEnabled:YES];
90 [self->dispatcherRules release];
91 [self->rootObject release];
95 /* type the request */
97 - (BOOL)isObjectPublishingContext:(WOContext *)_ctx {
99 Find out, whether we should do acquisition and dynamic method publishing.
100 This is only appropriate for HEAD/GET/POST requests from non-WebDAV
105 value = [self->dispatcherRules valueForKey:@"useAcquisition"];
106 if (debugRulesOn) [self debugWithFormat:@"acquision: %@", value];
107 return [value boolValue];
110 /* request path acquisition */
112 - (BOOL)enableZideLookHack {
113 /* Temporary Hack for ZideLook */
114 return disableZLHack ? NO : YES;
117 - (BOOL)skipApplicationName {
118 /* is the application name path of a URI part of the traversal path ? */
122 - (NSString *)hackZideLookURI:(NSString *)m {
123 if ([m hasPrefix:@"H_chste_Ebene_der_Pers_nlichen_Ordner"]) {
124 m = [m stringByReplacingString:@"H_chste_Ebene_der_Pers_nlichen_Ordner"
125 withString:@"helge"];
127 else if ([m hasPrefix:@"Suchpfad"]) {
128 m = [m stringByReplacingString:@"Suchpfad"
129 withString:@"helge"];
131 else if ([m hasPrefix:@"public"]) {
132 /* Evolution query on "/public" */
133 ; // keep it completly
135 else if ([self skipApplicationName]) {
138 r = [m rangeOfString:@"/"];
139 m = [m substringFromIndex:(r.location + r.length)];
143 - (NSString *)hackZideLookName:(NSString *)_p {
144 if ([_p isEqualToString:@"Gel_schte_Objekte"])
149 - (NSMutableArray *)addSpecialFormValuesInRequest:(WORequest *)_rq
150 toTraversalPath:(NSMutableArray *)_traversalPath
155 keys = [_rq formValueKeys];
156 if ((count = [keys count]) == 0)
157 return _traversalPath;
159 for (i = 0; i < count; i++) {
164 key = [keys objectAtIndex:i];
166 if (klen != 3 && klen < 7)
169 /* calculate method name */
172 if (klen == 3 && [key isEqualToString:@"Cmd"]) {
174 check for ASP style ?Cmd query parameter (required in ZideStore),
175 the value is the additional path we need to add
177 m = [_rq formValueForKey:key]; // need to unescape somehow ?
179 else if (klen == 7 && [key isEqualToString:@":method"]) {
181 check for ":method" form value, the value is the additional path we
184 m = [_rq formValueForKey:key]; // need to unescape somehow ?
186 else if ([key hasSuffix:@":method"]) {
188 Check for XXX:method form-keys, the value is ignored and the
189 XXX is added to the path. This is useful for binding actions
190 to submit buttons, since the value of a submit button is
191 displayed as it's label in the browser
194 m = [key substringToIndex:klen];
197 /* check calculated method */
201 else if ([m length] == 0) {
202 [self debugWithFormat:@"empty '%@' query parameter !", key];
207 [_traversalPath addObject:m];
209 return _traversalPath;
212 - (NSArray *)traversalPathFromRequest:(WORequest *)_rq {
213 static NSArray *rqKeys = nil;
214 NSMutableArray *traversalPath;
220 /* cache set of registered request handlers */
221 rqKeys = [[[WOApplication application] registeredRequestHandlerKeys] copy];
223 m = [_rq requestHandlerKey];
224 if ([rqKeys containsObject:m]) {
226 If the request-handler-key parsed by WORequest is valid, we'll consider
227 it a "usual" NGObjWeb query. Note that the appname is *not* processed !
229 /MyApp/wo/... => match
230 /MyApp/bla/... => fail
231 /blah/wa/... => match
233 a = [_rq requestHandlerPathArray];
236 /* TODO: more options, eg allow the appname to be part of the path */
239 /* get URI, cut of leading slash */
241 m = [m substringFromIndex:1];
243 if ([self enableZideLookHack])
244 m = [self hackZideLookURI:m];
245 else if ([self skipApplicationName]) {
246 /* cut of application name */
247 r = [m rangeOfString:@"/"];
248 m = [m substringFromIndex:(r.location + r.length)];
251 /* cut of query parameters */
252 r = [m rangeOfString:@"?"];
254 m = [m substringToIndex:r.location];
256 /* split into path components */
257 a = [m componentsSeparatedByString:@"/"];
261 traversalPath = [NSMutableArray arrayWithCapacity:(count + 1)];
262 for (i = 0; i < count; i++) {
265 p = [a objectAtIndex:i];
267 if ([p hasPrefix:@"_range"])
268 /* a ZideLook range query, further handled by WebDAV dispatcher */
271 p = [p stringByUnescapingURL];
273 if ([self enableZideLookHack])
274 p = [self hackZideLookName:p];
277 [traversalPath addObject:p];
280 traversalPath = [self addSpecialFormValuesInRequest:_rq
281 toTraversalPath:traversalPath];
283 return traversalPath;
286 - (id)rootObjectForRequest:(WORequest *)_rq inContext:(WOContext *)_ctx {
289 if (self->rootObject)
290 return self->rootObject;
292 if ((object = [_ctx application]) == nil)
293 object = [WOApplication application];
297 If we resolve in this location, we won't be able to resolve
298 application names like "Control_Panel".
300 TODO: explain better!
302 if ([object respondsToSelector:@selector(rootObjectInContext:)])
303 object = [object rootObjectInContext:_ctx];
309 - (id)lookupObjectForRequest:(WORequest *)_rq inContext:(WOContext *)_ctx {
310 NSAutoreleasePool *pool;
311 NSArray *traversalPath;
314 pool = [[NSAutoreleasePool alloc] init];
316 NSException *error = nil;
320 /* build traversal path */
322 traversalPath = [self traversalPathFromRequest:_rq];
324 [_ctx setSoRequestTraversalPath:traversalPath];
326 /* setup root object */
328 root = [self rootObjectForRequest:_rq inContext:_ctx];
330 doAcquire = self->doesNoRequestPathAcquisition
332 : [self isObjectPublishingContext:_ctx];
334 [self debugWithFormat:@"traverse (%@): %@ %@",
336 [traversalPath componentsJoinedByString:@" => "],
337 doAcquire ? @"[acquires]" : @"(no acquisition)"];
339 currentObject = [root traversePathArray:traversalPath
344 currentObject = error;
347 currentObject = [currentObject retain];
350 return [currentObject autorelease];
353 /* object invocation */
355 - (id)dispatcherForObject:(id)_object inContext:(WOContext *)_ctx {
356 NSString *dpClass, *rqType;
359 /* ask object for dispatcher */
361 if ([_object respondsToSelector:@selector(dispatcherForContext:)]) {
362 if ((dispatcher = [_object dispatcherForContext:_ctx]))
367 [self debugWithFormat:@"select dispatcher using rules: %@",
368 self->dispatcherRules];
372 dpClass = [self->dispatcherRules valueForKey:@"dispatcher"];
373 rqType = [self->dispatcherRules valueForKey:@"requestType"];
375 [self debugWithFormat:@"selected dispatcher: %@", dpClass];
376 [self debugWithFormat:@"selected rq-type: %@", rqType];
379 /* create dispatcher */
381 if (rqType != nil) [_ctx setSoRequestType:rqType];
382 if ((dispatcher = NSClassFromString(dpClass)) == nil) {
383 [self errorWithFormat:@"did not find dispatcher class '%@'", dpClass];
387 if ((dispatcher = [[dispatcher alloc] initWithObject:_object]))
388 [_ctx setObjectDispatcher:dispatcher];
390 return [dispatcher autorelease];
393 /* object rendering */
395 - (WOResponse *)renderObject:(id)_object inContext:(WOContext *)_ctx {
396 SoDefaultRenderer *renderer;
401 [self debugWithFormat:@"render in ctx: %@", _ctx];
403 if ([_object isKindOfClass:[WOResponse class]])
404 /* already rendered ... */
407 /* check whether a container on the traversal stack provides a renderer */
410 e = [[_ctx objectTraversalStack] reverseObjectEnumerator];
411 while ((container = [e nextObject])) {
412 if (![container respondsToSelector:
413 @selector(rendererForObject:inContext:)]) {
414 /* does not provide a renderer factory ... */
418 if ((renderer = [container rendererForObject:_object inContext:_ctx])) {
419 /* the container provided an own renderer for the object */
420 [self debugWithFormat:@"use container renderer: %@", renderer];
425 /* if we didn't find a renderer, determine using rules */
427 if (renderer == nil) {
428 NSString *rendererClass;
430 rendererClass = [self->dispatcherRules valueForKey:@"renderer"];
434 if ((clazz = NSClassFromString(rendererClass)) == Nil) {
435 [self errorWithFormat:@"did not find class of selected renderer %@",
438 else if ((renderer = [clazz sharedRenderer]) == nil) {
439 [self errorWithFormat:@"could not get renderer of class %@",
442 else if (![renderer canRenderObject:_object inContext:_ctx]) {
443 [self debugWithFormat:@"renderer %@ rejected rendering of object %@",
445 renderer = [SoDefaultRenderer sharedRenderer];
450 [self debugWithFormat:@"use rule-selected renderer: %@", renderer];
454 [self debugWithFormat:@"found no renderer for object: %@", _object];
456 if ((error = [renderer renderObject:_object inContext:_ctx])) {
457 if (renderer != [SoDefaultRenderer sharedRenderer]) {
460 e2 = [(SoDefaultRenderer *)[SoDefaultRenderer sharedRenderer]
461 renderObject:error inContext:_ctx];
463 [self errorWithFormat:
464 @"default renderer could not render error %@: %@", error, e2];
469 [self errorWithFormat:@"default renderer returned error: %@", error];
473 return [_ctx response];
476 - (BOOL)doesRejectFavicon {
480 - (WOResponse *)handleRequest:(WORequest *)_rq
481 inContext:(WOContext *)_ctx
482 session:(WOSession *)_sn
483 application:(WOApplication *)app
485 /* split up this big method */
492 [self debugWithFormat:@"request 0x%08X: %@ %@ (ctx=0x%08X)", _rq,
493 [_rq method], [_rq uri], _ctx];
494 if (_sn) [self debugWithFormat:@"session 0x%08X: %@", _sn, _sn];
497 /* setup rule context */
499 [self->dispatcherRules reset];
500 [self->dispatcherRules takeValue:_rq forKey:@"request"];
501 [self->dispatcherRules takeValue:[_rq headers] forKey:@"headers"];
502 [self->dispatcherRules takeValue:[_rq method] forKey:@"method"];
503 [self->dispatcherRules takeValue:_ctx forKey:@"context"];
505 /* preprocess authentication credentials with global auth handler */
507 if ((authenticator = [app authenticatorInContext:_ctx])) {
508 [_ctx setObject:authenticator forKey:@"SoAuthenticator"];
510 /* give authenticator the chance to reject invalid credentials */
512 if ((r = [authenticator preprocessCredentialsInContext:_ctx])) {
513 [self->dispatcherRules reset];
517 [self debugWithFormat:@"authenticator allowed request."];
520 [self warnWithFormat:@"no authenticator available."];
526 object = [self lookupObjectForRequest:_rq inContext:_ctx];
529 [self->dispatcherRules
530 takeValue:[_ctx clientObject] forKey:@"clientObject"];
531 [self->dispatcherRules takeValue:object forKey:@"object"];
536 [r setHeader:@"text/html" forKey:@"content-type"];
537 [r appendContentString:@"object not found: "];
538 [r appendContentHTMLString:
539 [[_ctx soRequestTraversalPath] componentsJoinedByString:@" => "]];
544 /* dispatch object */
546 if ([object isKindOfClass:[NSException class]]) {
547 /* exceptions are not called ... */
548 [self debugWithFormat:@"not calling exception: %@", object];
555 dispatcher = [self dispatcherForObject:object inContext:_ctx];
556 [self debugWithFormat:@"dispatcher: %@", dispatcher];
558 [self debugWithFormat:@"dispatch object: %@", object];
559 object = [dispatcher dispatchInContext:_ctx];
561 if (object) [self->dispatcherRules takeValue:object forKey:@"result"];
567 [self debugWithFormat:@"got an empty result !"];
570 [r appendContentString:@"the called object returned no result"];
572 else if ([object isKindOfClass:[WOResponse class]]) {
574 [self debugWithFormat:
575 @"got response: 0x%08X (status=%i,len=%@,type=%@)",
577 [r headerForKey:@"content-length"],
578 [r headerForKey:@"content-type"]];
582 if ([object isKindOfClass:[NSData class]]) {
583 [self debugWithFormat:@"render data 0x%08X[len=%i]",
584 object, [object length]];
587 [self debugWithFormat:@"render object: %@", object];
590 [self->dispatcherRules takeValue:object forKey:@"result"];
591 r = [self renderObject:object inContext:_ctx];
594 [self debugWithFormat:
595 @"made response: 0x%08X (status=%i,len=%@,type=%@)",
597 [r headerForKey:@"content-length"],
598 [r headerForKey:@"content-type"]];
602 /* add header with primary key of new objects (for ZideLook) */
606 if ((key = [_ctx objectForKey:@"SxNewObjectID"])) {
607 key = [NSString stringWithFormat:@"%@", key];
608 [r setHeader:key forKey:@"x-skyrix-newname"];
609 [self logWithFormat:@"added new key header to response: '%@'", key];
613 /* rapid turnaround */
614 if (rapidTurnAroundPath != nil) {
616 NSString *_path = nil;
618 if ((page = [_ctx page])) {
621 template = [page _woComponentTemplate];
622 if ([template isKindOfClass:WOTemplateClass])
623 _path = [[(WOTemplate *)template url] path];
626 // TODO: ZNeK: explain!
627 // I guess we need some generic method to retrieve a template path?
628 if ([object isKindOfClass:NSClassFromString(@"OFSBaseObject")])
629 _path = [object storagePath];
632 [r setHeader:_path forKey:@"x-sope-template-path"];
635 /* sleep traversal stack */
640 e = [[_ctx objectTraversalStack] reverseObjectEnumerator];
641 while ((obj = [e nextObject])) {
642 if (![obj isNotNull])
645 if ([obj respondsToSelector:@selector(_sleepWithContext:)])
646 [obj _sleepWithContext:_ctx];
647 else if ([obj respondsToSelector:@selector(sleep)])
652 [self->dispatcherRules reset];
657 @end /* SoObjectRequestHandler */
659 @implementation WOCoreApplication(RendererSelection)
661 - (id)rendererForObject:(id)_object inContext:(WOContext *)_ctx {
665 @end /* WOCoreApplication(RendererSelection) */
667 @implementation SoObjectRequestHandler(Logging)
669 - (NSString *)loggingPrefix {
670 return @"[object-handler]";
672 - (BOOL)isDebuggingEnabled {
673 return debugOn ? YES : NO;
676 @end /* SoObjectRequestHandler(Logging) */