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 if ([object respondsToSelector:@selector(rootObjectInContext:)])
301 object = [object rootObjectInContext:_ctx];
307 - (id)lookupObjectForRequest:(WORequest *)_rq inContext:(WOContext *)_ctx {
308 NSAutoreleasePool *pool;
309 NSArray *traversalPath;
312 pool = [[NSAutoreleasePool alloc] init];
314 NSException *error = nil;
318 /* build traversal path */
320 traversalPath = [self traversalPathFromRequest:_rq];
322 [_ctx setSoRequestTraversalPath:traversalPath];
324 /* setup root object */
326 root = [self rootObjectForRequest:_rq inContext:_ctx];
328 doAcquire = self->doesNoRequestPathAcquisition
330 : [self isObjectPublishingContext:_ctx];
332 [self debugWithFormat:@"traverse (%@): %@ %@",
334 [traversalPath componentsJoinedByString:@" => "],
335 doAcquire ? @"[acquires]" : @"(no acquisition)"];
337 currentObject = [root traversePathArray:traversalPath
342 currentObject = error;
345 currentObject = [currentObject retain];
348 return [currentObject autorelease];
351 /* object invocation */
353 - (id)dispatcherForObject:(id)_object inContext:(WOContext *)_ctx {
354 NSString *dpClass, *rqType;
357 /* ask object for dispatcher */
359 if ([_object respondsToSelector:@selector(dispatcherForContext:)]) {
360 if ((dispatcher = [_object dispatcherForContext:_ctx]))
365 [self debugWithFormat:@"select dispatcher using rules: %@",
366 self->dispatcherRules];
370 dpClass = [self->dispatcherRules valueForKey:@"dispatcher"];
371 rqType = [self->dispatcherRules valueForKey:@"requestType"];
373 [self debugWithFormat:@" selected dispatcher: %@", dpClass];
374 [self debugWithFormat:@" selected rq-type: %@", rqType];
377 /* create dispatcher */
379 if (rqType != nil) [_ctx setSoRequestType:rqType];
380 if ((dispatcher = NSClassFromString(dpClass)) == nil) {
381 [self logWithFormat:@"ERROR: did not find dispatcher class '%@'", dpClass];
385 if ((dispatcher = [[dispatcher alloc] initWithObject:_object]))
386 [_ctx setObjectDispatcher:dispatcher];
388 return [dispatcher autorelease];
391 /* object rendering */
393 - (WOResponse *)renderObject:(id)_object inContext:(WOContext *)_ctx {
394 SoDefaultRenderer *renderer;
399 [self debugWithFormat:@" render in ctx: %@", _ctx];
401 if ([_object isKindOfClass:[WOResponse class]])
402 /* already rendered ... */
405 /* check whether a container on the traversal stack provides a renderer */
408 e = [[_ctx objectTraversalStack] reverseObjectEnumerator];
409 while ((container = [e nextObject])) {
410 if (![container respondsToSelector:
411 @selector(rendererForObject:inContext:)]) {
412 /* does not provide a renderer factory ... */
416 if ((renderer = [container rendererForObject:_object inContext:_ctx])) {
417 /* the container provided an own renderer for the object */
418 [self debugWithFormat:@" use container renderer: %@", renderer];
423 /* if we didn't find a renderer, determine using rules */
425 if (renderer == nil) {
426 NSString *rendererClass;
428 rendererClass = [self->dispatcherRules valueForKey:@"renderer"];
432 if ((clazz = NSClassFromString(rendererClass)) == Nil) {
433 [self logWithFormat:@"did not find class of selected renderer %@",
436 else if ((renderer = [clazz sharedRenderer]) == nil) {
437 [self logWithFormat:@"could not get renderer of class %@",
440 else if (![renderer canRenderObject:_object inContext:_ctx]) {
441 [self debugWithFormat:@"renderer %@ rejected rendering of object %@",
443 renderer = [SoDefaultRenderer sharedRenderer];
448 [self debugWithFormat:@" use rule-selected renderer: %@", renderer];
452 [self debugWithFormat:@" found no renderer for object: %@", _object];
454 if ((error = [renderer renderObject:_object inContext:_ctx])) {
455 if (renderer != [SoDefaultRenderer sharedRenderer]) {
458 e2 = [(SoDefaultRenderer *)[SoDefaultRenderer sharedRenderer]
459 renderObject:error inContext:_ctx];
461 [self logWithFormat:@"default renderer could not render error %@: %@",
467 [self logWithFormat:@"default renderer returned error: %@", error];
471 return [_ctx response];
474 - (BOOL)doesRejectFavicon {
478 - (WOResponse *)handleRequest:(WORequest *)_rq
479 inContext:(WOContext *)_ctx
480 session:(WOSession *)_sn
481 application:(WOApplication *)app
483 /* split up this big method */
490 [self debugWithFormat:@"request 0x%08X: %@ %@ (ctx=0x%08X)", _rq,
491 [_rq method], [_rq uri], _ctx];
492 if (_sn) [self debugWithFormat:@" session 0x%08X: %@", _sn, _sn];
495 /* setup rule context */
497 [self->dispatcherRules reset];
498 [self->dispatcherRules takeValue:_rq forKey:@"request"];
499 [self->dispatcherRules takeValue:[_rq headers] forKey:@"headers"];
500 [self->dispatcherRules takeValue:[_rq method] forKey:@"method"];
501 [self->dispatcherRules takeValue:_ctx forKey:@"context"];
503 /* preprocess authentication credentials with global auth handler */
505 if ((authenticator = [app authenticatorInContext:_ctx])) {
506 [_ctx setObject:authenticator forKey:@"SoAuthenticator"];
508 /* give authenticator the chance to reject invalid credentials */
510 if ((r = [authenticator preprocessCredentialsInContext:_ctx])) {
511 [self->dispatcherRules reset];
515 [self debugWithFormat:@"authenticator allowed request."];
518 [self debugWithFormat:@"WARNING: no authenticator available."];
524 object = [self lookupObjectForRequest:_rq inContext:_ctx];
527 [self->dispatcherRules
528 takeValue:[_ctx clientObject] forKey:@"clientObject"];
529 [self->dispatcherRules takeValue:object forKey:@"object"];
534 [r setHeader:@"text/html" forKey:@"content-type"];
535 [r appendContentString:@"object not found: "];
536 [r appendContentHTMLString:
537 [[_ctx soRequestTraversalPath] componentsJoinedByString:@" => "]];
542 /* dispatch object */
544 if ([object isKindOfClass:[NSException class]]) {
545 /* exceptions are not called ... */
546 [self debugWithFormat:@" not calling exception: %@", object];
553 dispatcher = [self dispatcherForObject:object inContext:_ctx];
554 [self debugWithFormat:@" dispatcher: %@", dispatcher];
556 [self debugWithFormat:@" dispatch object: %@", object];
557 object = [dispatcher dispatchInContext:_ctx];
559 if (object) [self->dispatcherRules takeValue:object forKey:@"result"];
565 [self debugWithFormat:@" got an empty result !"];
568 [r appendContentString:@"the called object returned no result"];
570 else if ([object isKindOfClass:[WOResponse class]]) {
572 [self debugWithFormat:
573 @" got response: 0x%08X (status=%i,len=%@,type=%@)",
575 [r headerForKey:@"content-length"],
576 [r headerForKey:@"content-type"]];
580 if ([object isKindOfClass:[NSData class]]) {
581 [self debugWithFormat:@" render data 0x%08X[len=%i]",
582 object, [object length]];
585 [self debugWithFormat:@" render object: %@", object];
588 [self->dispatcherRules takeValue:object forKey:@"result"];
589 r = [self renderObject:object inContext:_ctx];
592 [self debugWithFormat:
593 @" made response: 0x%08X (status=%i,len=%@,type=%@)",
595 [r headerForKey:@"content-length"],
596 [r headerForKey:@"content-type"]];
600 /* add header with primary key of new objects (for ZideLook) */
604 if ((key = [_ctx objectForKey:@"SxNewObjectID"])) {
605 key = [NSString stringWithFormat:@"%@", key];
606 [r setHeader:key forKey:@"x-skyrix-newname"];
607 [self logWithFormat:@"added new key header to response: '%@'", key];
611 /* rapid turnaround */
612 if (rapidTurnAroundPath != nil) {
614 NSString *_path = nil;
616 if ((page = [_ctx page])) {
619 template = [page _woComponentTemplate];
620 if ([template isKindOfClass:WOTemplateClass])
621 _path = [[(WOTemplate *)template url] path];
624 // TODO: ZNeK: explain!
625 // I guess we need some generic method to retrieve a template path?
626 if ([object isKindOfClass:NSClassFromString(@"OFSBaseObject")])
627 _path = [object storagePath];
630 [r setHeader:_path forKey:@"x-sope-template-path"];
633 /* sleep traversal stack */
638 e = [[_ctx objectTraversalStack] reverseObjectEnumerator];
639 while ((obj = [e nextObject])) {
640 if (![obj isNotNull])
643 if ([obj respondsToSelector:@selector(_sleepWithContext:)])
644 [obj _sleepWithContext:_ctx];
645 else if ([obj respondsToSelector:@selector(sleep)])
650 [self->dispatcherRules reset];
655 @end /* SoObjectRequestHandler */
657 @implementation WOCoreApplication(RendererSelection)
659 - (id)rendererForObject:(id)_object inContext:(WOContext *)_ctx {
663 @end /* WOCoreApplication(RendererSelection) */
665 @implementation SoObjectRequestHandler(Logging)
667 - (NSString *)loggingPrefix {
668 return @"[object-handler]";
670 - (BOOL)isDebuggingEnabled {
671 return debugOn ? YES : NO;
674 @end /* SoObjectRequestHandler(Logging) */