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
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 <SoOFS/OFSBaseObject.h>
37 #include <NGExtensions/NGRuleContext.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 @implementation SoObjectRequestHandler
49 static BOOL debugOn = NO;
50 static BOOL debugRulesOn = NO;
51 static BOOL disableZLHack = NO;
53 static Class WOTemplateClass = Nil;
54 static NSString *rapidTurnAroundPath = nil;
57 return [super version] + 0 /* 2 */;
60 static BOOL didInit = NO;
62 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
64 NSAssert2([super version] == 2,
65 @"invalid superclass (%@) version %i !",
66 NSStringFromClass([self superclass]), [super version]);
67 debugOn = [ud boolForKey:@"SoObjectRequestHandlerDebugEnabled"];
68 debugRulesOn = [ud boolForKey:@"SoObjectRequestHandlerRulesDebugEnabled"];
69 disableZLHack = [ud boolForKey:@"DisableZideLookHack"];
71 WOTemplateClass = [WOTemplate class];
72 rapidTurnAroundPath = [[ud stringForKey:@"WOProjectDirectory"] copy];
77 if ((self = [super init])) {
78 self->dispatcherRules =
79 [[NGRuleContext ruleContextWithModelInUserDefault:
80 @"SoRequestDispatcherRules"] retain];
81 if (debugRulesOn) [self->dispatcherRules setDebugEnabled:YES];
86 [self->dispatcherRules release];
87 [self->rootObject release];
91 /* type the request */
93 - (BOOL)isObjectPublishingContext:(WOContext *)_ctx {
95 Find out, whether we should do acquisition and dynamic method publishing.
96 This is only appropriate for HEAD/GET/POST requests from non-WebDAV
101 value = [self->dispatcherRules valueForKey:@"useAcquisition"];
102 if (debugRulesOn) [self debugWithFormat:@"acquision: %@", value];
103 return [value boolValue];
106 /* request path acquisition */
108 - (BOOL)enableZideLookHack {
109 /* Temporary Hack for ZideLook */
110 return disableZLHack ? NO : YES;
113 - (BOOL)skipApplicationName {
114 /* is the application name path of a URI part of the traversal path ? */
118 - (NSString *)hackZideLookURI:(NSString *)m {
119 if ([m hasPrefix:@"H_chste_Ebene_der_Pers_nlichen_Ordner"]) {
120 m = [m stringByReplacingString:@"H_chste_Ebene_der_Pers_nlichen_Ordner"
121 withString:@"helge"];
123 else if ([m hasPrefix:@"Suchpfad"]) {
124 m = [m stringByReplacingString:@"Suchpfad"
125 withString:@"helge"];
127 else if ([m hasPrefix:@"public"]) {
128 /* Evolution query on "/public" */
129 ; // keep it completly
131 else if ([self skipApplicationName]) {
134 r = [m rangeOfString:@"/"];
135 m = [m substringFromIndex:(r.location + r.length)];
139 - (NSString *)hackZideLookName:(NSString *)_p {
140 if ([_p isEqualToString:@"Gel_schte_Objekte"])
145 - (NSMutableArray *)addSpecialFormValuesInRequest:(WORequest *)_rq
146 toTraversalPath:(NSMutableArray *)_traversalPath
151 keys = [_rq formValueKeys];
152 if ((count = [keys count]) == 0)
153 return _traversalPath;
155 for (i = 0; i < count; i++) {
160 key = [keys objectAtIndex:i];
162 if (klen != 3 && klen < 7)
165 /* calculate method name */
168 if (klen == 3 && [key isEqualToString:@"Cmd"]) {
170 check for ASP style ?Cmd query parameter (required in ZideStore),
171 the value is the additional path we need to add
173 m = [_rq formValueForKey:key]; // need to unescape somehow ?
175 else if (klen == 7 && [key isEqualToString:@":method"]) {
177 check for ":method" form value, the value is the additional path we
180 m = [_rq formValueForKey:key]; // need to unescape somehow ?
182 else if ([key hasSuffix:@":method"]) {
184 Check for XXX:method form-keys, the value is ignored and the
185 XXX is added to the path. This is useful for binding actions
186 to submit buttons, since the value of a submit button is
187 displayed as it's label in the browser
190 m = [key substringToIndex:klen];
193 /* check calculated method */
197 else if ([m length] == 0) {
198 [self debugWithFormat:@"empty '%@' query parameter !", key];
203 [_traversalPath addObject:m];
205 return _traversalPath;
208 - (NSArray *)traversalPathFromRequest:(WORequest *)_rq {
209 static NSArray *rqKeys = nil;
210 NSMutableArray *traversalPath;
216 /* cache set of registered request handlers */
217 rqKeys = [[[WOApplication application] registeredRequestHandlerKeys] copy];
219 m = [_rq requestHandlerKey];
220 if ([rqKeys containsObject:m]) {
222 If the request-handler-key parsed by WORequest is valid, we'll consider
223 it a "usual" NGObjWeb query. Note that the appname is *not* processed !
225 /MyApp/wo/... => match
226 /MyApp/bla/... => fail
227 /blah/wa/... => match
229 a = [_rq requestHandlerPathArray];
232 /* TODO: more options, eg allow the appname to be part of the path */
235 /* get URI, cut of leading slash */
237 m = [m substringFromIndex:1];
239 if ([self enableZideLookHack])
240 m = [self hackZideLookURI:m];
241 else if ([self skipApplicationName]) {
242 /* cut of application name */
243 r = [m rangeOfString:@"/"];
244 m = [m substringFromIndex:(r.location + r.length)];
247 /* cut of query parameters */
248 r = [m rangeOfString:@"?"];
250 m = [m substringToIndex:r.location];
252 /* split into path components */
253 a = [m componentsSeparatedByString:@"/"];
257 traversalPath = [NSMutableArray arrayWithCapacity:(count + 1)];
258 for (i = 0; i < count; i++) {
261 p = [a objectAtIndex:i];
263 if ([p hasPrefix:@"_range"])
264 /* a ZideLook range query, further handled by WebDAV dispatcher */
267 p = [p stringByUnescapingURL];
269 if ([self enableZideLookHack])
270 p = [self hackZideLookName:p];
273 [traversalPath addObject:p];
276 traversalPath = [self addSpecialFormValuesInRequest:_rq
277 toTraversalPath:traversalPath];
279 return traversalPath;
282 - (id)rootObjectForRequest:(WORequest *)_rq inContext:(WOContext *)_ctx {
285 if (self->rootObject)
286 return self->rootObject;
288 if ((object = [_ctx application]) == nil)
289 object = [WOApplication application];
293 if we resolve in this location, we won't be able to resolve
294 application names like "Control_Panel".
296 if ([object respondsToSelector:@selector(rootObjectInContext:)])
297 object = [object rootObjectInContext:_ctx];
303 - (id)lookupObjectForRequest:(WORequest *)_rq inContext:(WOContext *)_ctx {
304 NSAutoreleasePool *pool;
305 NSArray *traversalPath;
308 pool = [[NSAutoreleasePool alloc] init];
310 NSException *error = nil;
314 /* build traversal path */
316 traversalPath = [self traversalPathFromRequest:_rq];
318 [_ctx setSoRequestTraversalPath:traversalPath];
320 /* setup root object */
322 root = [self rootObjectForRequest:_rq inContext:_ctx];
324 doAcquire = self->doesNoRequestPathAcquisition
326 : [self isObjectPublishingContext:_ctx];
328 [self debugWithFormat:@"traverse (%@): %@ %@",
330 [traversalPath componentsJoinedByString:@" => "],
331 doAcquire ? @"[acquires]" : @"(no acquisition)"];
333 currentObject = [root traversePathArray:traversalPath
338 currentObject = error;
341 currentObject = [currentObject retain];
344 return [currentObject autorelease];
347 /* object invocation */
349 - (id)dispatcherForObject:(id)_object inContext:(WOContext *)_ctx {
350 NSString *dpClass, *rqType;
353 /* ask object for dispatcher */
355 if ([_object respondsToSelector:@selector(dispatcherForContext:)]) {
356 if ((dispatcher = [_object dispatcherForContext:_ctx]))
361 [self debugWithFormat:@"select dispatcher using rules: %@",
362 self->dispatcherRules];
366 dpClass = [self->dispatcherRules valueForKey:@"dispatcher"];
367 rqType = [self->dispatcherRules valueForKey:@"requestType"];
369 [self debugWithFormat:@" selected dispatcher: %@", dpClass];
370 [self debugWithFormat:@" selected rq-type: %@", rqType];
373 /* create dispatcher */
375 if (rqType != nil) [_ctx setSoRequestType:rqType];
376 if ((dispatcher = NSClassFromString(dpClass)) == nil) {
377 [self logWithFormat:@"ERROR: did not find dispatcher class '%@'", dpClass];
381 if ((dispatcher = [[dispatcher alloc] initWithObject:_object]))
382 [_ctx setObjectDispatcher:dispatcher];
384 return [dispatcher autorelease];
387 /* object rendering */
389 - (WOResponse *)renderObject:(id)_object inContext:(WOContext *)_ctx {
390 SoDefaultRenderer *renderer;
395 [self debugWithFormat:@" render in ctx: %@", _ctx];
397 if ([_object isKindOfClass:[WOResponse class]])
398 /* already rendered ... */
401 /* check whether a container on the traversal stack provides a renderer */
404 e = [[_ctx objectTraversalStack] reverseObjectEnumerator];
405 while ((container = [e nextObject])) {
406 if (![container respondsToSelector:
407 @selector(rendererForObject:inContext:)]) {
408 /* does not provide a renderer factory ... */
412 if ((renderer = [container rendererForObject:_object inContext:_ctx])) {
413 /* the container provided an own renderer for the object */
414 [self debugWithFormat:@" use container renderer: %@", renderer];
419 /* if we didn't find a renderer, determine using rules */
421 if (renderer == nil) {
422 NSString *rendererClass;
424 rendererClass = [self->dispatcherRules valueForKey:@"renderer"];
428 if ((clazz = NSClassFromString(rendererClass)) == Nil) {
429 [self logWithFormat:@"did not find class of selected renderer %@",
432 else if ((renderer = [clazz sharedRenderer]) == nil) {
433 [self logWithFormat:@"could not get renderer of class %@",
436 else if (![renderer canRenderObject:_object inContext:_ctx]) {
437 [self debugWithFormat:@"renderer %@ rejected rendering of object %@",
439 renderer = [SoDefaultRenderer sharedRenderer];
444 [self debugWithFormat:@" use rule-selected renderer: %@", renderer];
448 [self debugWithFormat:@" found no renderer for object: %@", _object];
450 if ((error = [renderer renderObject:_object inContext:_ctx])) {
451 if (renderer != [SoDefaultRenderer sharedRenderer]) {
454 e2 = [(SoDefaultRenderer *)[SoDefaultRenderer sharedRenderer]
455 renderObject:error inContext:_ctx];
457 [self logWithFormat:@"default renderer could not render error %@: %@",
463 [self logWithFormat:@"default renderer returned error: %@", error];
467 return [_ctx response];
470 - (BOOL)doesRejectFavicon {
474 - (WOResponse *)handleRequest:(WORequest *)_rq
475 inContext:(WOContext *)_ctx
476 session:(WOSession *)_sn
477 application:(WOApplication *)app
479 /* split up this big method */
486 [self debugWithFormat:@"request 0x%08X: %@ %@ (ctx=0x%08X)", _rq,
487 [_rq method], [_rq uri], _ctx];
488 if (_sn) [self debugWithFormat:@" session 0x%08X: %@", _sn, _sn];
491 /* setup rule context */
493 [self->dispatcherRules reset];
494 [self->dispatcherRules takeValue:_rq forKey:@"request"];
495 [self->dispatcherRules takeValue:[_rq headers] forKey:@"headers"];
496 [self->dispatcherRules takeValue:[_rq method] forKey:@"method"];
497 [self->dispatcherRules takeValue:_ctx forKey:@"context"];
499 /* preprocess authentication credentials with global auth handler */
501 if ((authenticator = [app authenticatorInContext:_ctx])) {
502 [_ctx setObject:authenticator forKey:@"SoAuthenticator"];
504 /* give authenticator the chance to reject invalid credentials */
506 if ((r = [authenticator preprocessCredentialsInContext:_ctx])) {
507 [self->dispatcherRules reset];
511 [self debugWithFormat:@"authenticator allowed request."];
514 [self debugWithFormat:@"WARNING: no authenticator available."];
520 object = [self lookupObjectForRequest:_rq inContext:_ctx];
523 [self->dispatcherRules
524 takeValue:[_ctx clientObject] forKey:@"clientObject"];
525 [self->dispatcherRules takeValue:object forKey:@"object"];
530 [r setHeader:@"text/html" forKey:@"content-type"];
531 [r appendContentString:@"object not found: "];
532 [r appendContentHTMLString:
533 [[_ctx soRequestTraversalPath] componentsJoinedByString:@" => "]];
538 /* dispatch object */
540 if ([object isKindOfClass:[NSException class]]) {
541 /* exceptions are not called ... */
542 [self debugWithFormat:@" not calling exception: %@", object];
549 dispatcher = [self dispatcherForObject:object inContext:_ctx];
550 [self debugWithFormat:@" dispatcher: %@", dispatcher];
552 [self debugWithFormat:@" dispatch object: %@", object];
553 object = [dispatcher dispatchInContext:_ctx];
555 if (object) [self->dispatcherRules takeValue:object forKey:@"result"];
561 [self debugWithFormat:@" got an empty result !"];
564 [r appendContentString:@"the called object returned no result"];
566 else if ([object isKindOfClass:[WOResponse class]]) {
568 [self debugWithFormat:
569 @" got response: 0x%08X (status=%i,len=%@,type=%@)",
571 [r headerForKey:@"content-length"],
572 [r headerForKey:@"content-type"]];
576 if ([object isKindOfClass:[NSData class]]) {
577 [self debugWithFormat:@" render data 0x%08X[len=%i]",
578 object, [object length]];
581 [self debugWithFormat:@" render object: %@", object];
584 [self->dispatcherRules takeValue:object forKey:@"result"];
585 r = [self renderObject:object inContext:_ctx];
588 [self debugWithFormat:
589 @" made response: 0x%08X (status=%i,len=%@,type=%@)",
591 [r headerForKey:@"content-length"],
592 [r headerForKey:@"content-type"]];
596 /* add header with primary key of new objects (for ZideLook) */
600 if ((key = [_ctx objectForKey:@"SxNewObjectID"])) {
601 key = [NSString stringWithFormat:@"%@", key];
602 [r setHeader:key forKey:@"x-skyrix-newname"];
603 [self logWithFormat:@"added new key header to response: '%@'", key];
607 /* rapid turnaround */
608 if(rapidTurnAroundPath != nil) {
610 NSString *_path = nil;
612 if((page = [_ctx page])) {
615 template = [page _woComponentTemplate];
616 if([template isKindOfClass:WOTemplateClass])
617 _path = [[(WOTemplate *)template url] path];
620 if([object isKindOfClass:[OFSBaseObject class]])
621 _path = [object storagePath];
624 [r setHeader:_path forKey:@"x-sope-template-path"];
627 /* sleep traversal stack */
632 e = [[_ctx objectTraversalStack] reverseObjectEnumerator];
633 while ((obj = [e nextObject])) {
634 if (![obj isNotNull])
637 if ([obj respondsToSelector:@selector(_sleepWithContext:)])
638 [obj _sleepWithContext:_ctx];
639 else if ([obj respondsToSelector:@selector(sleep)])
644 [self->dispatcherRules reset];
649 @end /* SoObjectRequestHandler */
651 @implementation WOCoreApplication(RendererSelection)
653 - (id)rendererForObject:(id)_object inContext:(WOContext *)_ctx {
657 @end /* WOCoreApplication(RendererSelection) */
659 @implementation SoObjectRequestHandler(Logging)
661 - (NSString *)loggingPrefix {
662 return @"[object-handler]";
664 - (BOOL)isDebuggingEnabled {
665 return debugOn ? YES : NO;
668 @end /* SoObjectRequestHandler(Logging) */