]> err.no Git - sope/blob - sope-appserver/NGObjWeb/WOContext.m
added patches from #1495 (OpenBSD)
[sope] / sope-appserver / NGObjWeb / WOContext.m
1 /*
2   Copyright (C) 2000-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 <NGObjWeb/WOContext.h>
23 #include "NSObject+WO.h"
24 #include "WOComponent+private.h"
25 #include "WOContext+private.h"
26 #include "WOApplication+private.h"
27 #include <NGObjWeb/WOApplication.h>
28 #include <NGObjWeb/WORequest.h>
29 #include <NGObjWeb/WOResponse.h>
30 #include <NGObjWeb/WOSession.h>
31 #import <EOControl/EONull.h>
32 #include "WOElementID.h"
33 #include "common.h"
34 #include <time.h>
35
36
37 @interface WOContext(Privates5)
38 - (NSArray *)_componentStack;
39 @end
40
41 @interface WOComponent(Cursors)
42 - (void)pushCursor:(id)_obj;
43 - (id)popCursor;
44 - (id)cursor;
45 @end
46
47 static Class WOAppClass = Nil;
48
49 @implementation WOContext
50
51 + (int)version {
52   return 8;
53 }
54
55 static Class    WOContextClass       = Nil;
56 static Class    MutableStrClass      = Nil;
57 static int      contextCount         = 0;
58 static int      logComponents        = -1;
59 static int      relativeURLs         = -1;
60 static BOOL     debugOn              = NO;
61 static int      debugCursor          = -1;
62 static BOOL     debugComponentAwake  = NO;
63 static BOOL     testNSURLs           = NO;
64 static BOOL     newCURLStyle         = NO;
65 static NSString *WOApplicationSuffix = nil;
66
67 + (void)initialize {
68   static BOOL    didInit = NO;
69   NSUserDefaults *ud;
70   NSString       *cn;
71
72   if (didInit) return;
73
74   didInit = YES;
75
76   ud = [NSUserDefaults standardUserDefaults];
77
78   if (WOAppClass == Nil)
79     WOAppClass = [WOApplication class];
80   if (MutableStrClass == Nil)
81     MutableStrClass = [NSMutableString class];
82   
83   cn             = [ud stringForKey:@"WOContextClass"];
84   WOContextClass = NSClassFromString(cn);
85   NSAssert1(WOContextClass != Nil,
86             @"Couldn't instantiate WOContextClass (%@)!", cn);
87
88   logComponents = [[ud objectForKey:@"WOLogComponents"] boolValue] ? 1 : 0;
89   relativeURLs  = [[ud objectForKey:@"WOUseRelativeURLs"] boolValue]? 1 : 0;
90   debugCursor         = [ud boolForKey:@"WODebugCursor"] ? 1 : 0;
91   debugComponentAwake = [ud boolForKey:@"WODebugComponentAwake"];
92   WOApplicationSuffix = [[ud stringForKey:@"WOApplicationSuffix"] copy];
93 }
94
95 + (id)contextWithRequest:(WORequest *)_r {
96   return [[(WOContext *)[WOContextClass alloc] initWithRequest:_r] autorelease];
97 }
98
99 - (id)initWithRequest:(WORequest *)_request {
100   if ((self = [super init])) {
101     char buf[24];
102     self->qpJoin = @"&amp;";
103     
104     sprintf(buf, "%03x%08x%08x", ++contextCount, (int)time(NULL), (int)self);
105     self->ctxId = [[NSString alloc] initWithCString:buf];
106     
107     /* per default close tags in XML style */
108     self->wcFlags.xmlStyleEmptyElements = 1;
109     self->wcFlags.allowEmptyAttributes  = 0;
110     
111     self->elementID = [[WOElementID alloc] init];
112     self->awakeComponents = [[NSMutableSet alloc] initWithCapacity:64];
113     
114     self->request  = [_request retain];
115     self->response = [[WOResponse responseWithRequest:_request] retain];
116   }
117   return self;
118 }
119
120 + (id)context {
121   return [[[self alloc] init] autorelease];
122 }
123 - (id)init {
124   return [self initWithRequest:nil];
125 }
126
127 /* components */
128
129 - (void)_addAwakeComponent:(WOComponent *)_component {
130   if (_component == nil)
131     return;
132
133   if ([self->awakeComponents containsObject:_component])
134     return;
135
136   /* wake up component */
137   if (debugComponentAwake)
138     [self logWithFormat:@"mark component awake: %@", _component];
139   
140   [self->awakeComponents addObject:_component];
141 }
142
143 - (void)_awakeComponent:(WOComponent *)_component {
144   if (_component == nil)
145     return;
146
147   if ([self->awakeComponents containsObject:_component])
148     return;
149
150   /* wake up component */
151   if (debugComponentAwake)
152     [self logWithFormat:@"awake component: %@", _component];
153     
154   [_component _awakeWithContext:self];
155
156   [self _addAwakeComponent:_component];
157   
158   if (debugComponentAwake)
159     [self logWithFormat:@"woke up component: %@", _component];
160 }
161
162 - (void)sleepComponents {
163   NSEnumerator *e;
164   WOComponent  *component;
165   BOOL sendSleepToPage;
166
167   if (debugComponentAwake) {
168     [self logWithFormat:@"sleep %d components ...", 
169             [self->awakeComponents count]];
170   }
171   
172   sendSleepToPage = YES;
173   e = [self->awakeComponents objectEnumerator];
174   while ((component = [e nextObject])) {
175     if (debugComponentAwake)
176       [self logWithFormat:@"  sleep component: %@", component];
177     [component _sleepWithContext:self];
178     if (component == self->page) sendSleepToPage = NO;
179   }
180   if (sendSleepToPage && (self->page != nil)) {
181     if (debugComponentAwake)
182       [self logWithFormat:@"  sleep page: %@", self->page];
183     [self->page _sleepWithContext:self];
184   }
185   
186   if (debugComponentAwake) {
187     [self logWithFormat:@"done sleep %d components.", 
188             [self->awakeComponents count]];
189   }
190   [self->awakeComponents removeAllObjects];
191 }
192
193 #if WITH_DEALLOC_OBSERVERS
194 - (void)addDeallocObserver:(id)_observer {
195   if (_observer == NULL) return;
196   
197   /* check array */
198   if (self->deallocObservers == NULL) {
199     self->deallocObservers        = calloc(8, sizeof(id));
200     self->deallocObserverCount    = 0;
201     self->deallocObserverCapacity = 8;
202   }
203
204   /* check capacity */
205   if (self->deallocObserverCapacity == self->deallocObserverCount) {
206     /* need to increase array */
207     id *newa;
208     
209     newa = calloc(self->deallocObserverCapacity * 2, sizeof(id));
210     memcpy(newa, self->deallocObservers, 
211            sizeof(id) * self->deallocObserverCount);
212     free(self->deallocObservers);
213     self->deallocObservers = newa;
214     self->deallocObserverCapacity *= 2;
215   }
216   
217   /* register */
218   self->deallocObservers[self->deallocObserverCount] = _observer;
219   self->deallocObserverCount++;
220 }
221 - (void)removeDeallocObserver:(id)_observer {
222   /* the observer currently will only grow (this should be OK for WOContext) */
223   register int i;
224   if (_observer == NULL) return;
225   
226   for (i = self->deallocObserverCount - 1; i >= 0; i++) {
227     if ((self->deallocObservers[i]) == _observer)
228       self->deallocObservers[i] = NULL;
229   }
230 }
231 #endif
232
233 - (void)dealloc {
234   [self sleepComponents];
235   
236 #if WITH_DEALLOC_OBSERVERS
237   if (self->deallocObservers) {
238     register int i;
239     
240 #if DEBUG
241     printf("%s: dealloc observer capacity: %i\n",
242            __PRETTY_FUNCTION__, self->deallocObserverCapacity);
243 #endif
244     
245     /* GC!! process in reverse order ... */
246     for (i = self->deallocObserverCount - 1; i >= 0; i++)
247       [self->deallocObservers[i] _objectWillDealloc:self];
248     
249     free(self->deallocObservers);
250     self->deallocObservers = NULL;
251   }
252 #endif
253
254   [self->activeUser            release];
255   [self->rootURL               release];
256   [self->objectPermissionCache release];
257   [self->traversalStack        release];
258   [self->clientObject          release];
259   [self->objectDispatcher      release];
260   [self->soRequestType         release];
261   [self->pathInfo              release];
262   
263   [[NSNotificationCenter defaultCenter]
264                          postNotificationName:@"WOContextWillDeallocate"
265                          object:self->ctxId];
266   
267   { /* release component stack */
268     int i;
269     for (i = (self->componentStackCount - 1); i >= 0; i--) {
270       [self->componentStack[i] release]; self->componentStack[i] = nil;
271       [self->contentStack[i]   release]; self->contentStack[i]   = nil;
272     }
273   }
274   
275   [self->urlPrefix         release];
276   [self->elementID         release];
277   [self->reqElementID      release];
278   [self->activeFormElement release];
279   [self->page              release];
280   [self->awakeComponents   release];
281   [self->appURL            release];
282   [self->baseURL           release];
283   [self->session           release];
284   [self->variables         release];
285   [self->request           release];
286   [self->response          release];
287   [self->ctxId             release];
288   [super dealloc];
289 }
290
291 - (void)setSession:(WOSession *)_session {
292   ASSIGN(self->session, _session);
293 }
294
295 - (id)session {
296   // in WO4 -session creates a new session if none is associated
297   
298   if (self->session == nil) {
299     [[self application] _initializeSessionInContext:self];
300     
301     if (self->session == nil)
302       [self logWithFormat:@"%s: missing session for context ..",
303               __PRETTY_FUNCTION__];
304   }
305   
306   return self->session;
307 }
308
309 - (NSString *)contextID {
310   NSAssert(self->ctxId, @"context without id !");
311 #if 0
312   // in WO4 -contextID returns nil if there is no associated session
313   return self->session ? self->ctxId : nil;
314 #else
315   /*
316     IMHO the above isn't true, otherwise session cannot be automagically
317     generated!
318     
319     TODO: well, we might want to generate component URLs which work without
320           a session - at least in theory the ID tree should be stable even
321           without a session (and if proper uids are used for dynamic content).
322           eg this would be quite useful for SOPE.
323   */
324   return self->ctxId;
325 #endif
326 }
327
328 - (WORequest *)request {
329   return self->request;
330 }
331 - (WOResponse *)response {
332   return self->response;
333 }
334
335 - (BOOL)hasSession {
336   return (self->session != nil) ? YES : NO;
337 }
338
339 - (BOOL)savePageRequired {
340   return self->wcFlags.savePageRequired ? YES : NO;
341 }
342
343 /* cursors */
344
345 - (void)pushCursor:(id)_obj {
346   if (debugCursor == -1) {
347     debugCursor = [[NSUserDefaults standardUserDefaults]
348                                    boolForKey:@"WODebugCursor"]
349       ? 1 : 0;
350   }
351   
352   if (debugCursor) [self logWithFormat:@"enter cursor: %@", _obj];
353   [[self component] pushCursor:_obj];
354 }
355
356 - (id)popCursor {
357   if (debugCursor) [self logWithFormat:@"leave cursor ..."];
358   return [[self component] popCursor];
359 }
360
361 - (id)cursor {
362   return [(id <WOPageGenerationContext>)[self component] cursor];
363 }
364
365 /* components */
366
367 - (id)component {
368   return (self->componentStackCount > 0)
369     ? self->componentStack[self->componentStackCount - 1]
370     : nil;
371 }
372
373 - (void)setPage:(WOComponent *)_page {
374   [_page ensureAwakeInContext:self];
375   ASSIGN(self->page, _page);
376 }
377 - (id)page {
378   return self->page;
379 }
380
381 void WOContext_enterComponent
382 (WOContext *self, WOComponent *_component, WOElement *_content)
383 {
384   WOComponent *parent = nil;
385 #if DEBUG
386   NSCAssert(_component, @"missing component to enter ...");
387 #endif
388   
389   if (logComponents) {
390     [self->application logWithFormat:@"enter component %@ (content=%@) ..",
391                          [_component name], _content];
392   }
393   
394   parent = self->componentStackCount > 0
395     ? self->componentStack[self->componentStackCount - 1]
396     : nil;
397   
398   NSCAssert2(self->componentStackCount < NGObjWeb_MAX_COMPONENT_NESTING_DEPTH,
399              @"exceeded maximum component nesting depth (%i):\n%@",
400              NGObjWeb_MAX_COMPONENT_NESTING_DEPTH,
401              [self _componentStack]);
402   self->componentStack[(int)self->componentStackCount] = [_component retain];
403   self->contentStack[(int)self->componentStackCount]   = [_content   retain];
404   self->componentStackCount++;
405   
406   [self _awakeComponent:_component];
407   
408   if (parent) {
409     if ([_component synchronizesVariablesWithBindings])
410       WOComponent_syncFromParent(_component, parent);
411   }
412 }
413 void WOContext_leaveComponent(WOContext *self, WOComponent *_component) {
414   WOComponent *parent = nil;
415
416   BEGIN_PROFILE;
417
418   parent = (self->componentStackCount > 1)
419     ? self->componentStack[self->componentStackCount - 2]
420     : nil;
421   
422   if (parent) {
423     if ([_component synchronizesVariablesWithBindings])
424       WOComponent_syncToParent(_component, parent);
425   }
426
427   PROFILE_CHECKPOINT("after sync");
428   
429   /* remove last object */
430   self->componentStackCount--;
431   NSCAssert(self->componentStackCount >= 0,
432             @"tried to pop component from empty component stack !");
433   [self->componentStack[(int)self->componentStackCount] release];
434   self->componentStack[(int)self->componentStackCount] = nil;
435   [self->contentStack[(int)self->componentStackCount] release];
436   self->contentStack[(int)self->componentStackCount] = nil;
437   
438   if (logComponents)
439     [self->application logWithFormat:@"left component %@.", [_component name]];
440
441   END_PROFILE;
442 }
443
444 - (void)enterComponent:(WOComponent *)_comp content:(WOElement *)_content {
445   WOContext_enterComponent(self, _comp, _content);
446 }
447 - (void)leaveComponent:(WOComponent *)_component {
448   BEGIN_PROFILE;
449   WOContext_leaveComponent(self, _component);
450   END_PROFILE;
451 }
452
453 - (WOComponent *)parentComponent {
454   return (self->componentStackCount > 1)
455     ? self->componentStack[(int)self->componentStackCount - 2]
456     : nil;
457 }
458
459 - (WODynamicElement *)componentContent {
460   return (self->componentStackCount > 0)
461     ? self->contentStack[(int)self->componentStackCount - 1]
462     : nil;
463 }
464
465 - (unsigned)componentStackCount {
466   return self->componentStackCount;
467 }
468 - (NSArray *)_componentStack {
469   return [NSArray arrayWithObjects:self->componentStack
470                   count:self->componentStackCount];
471 }
472
473 /* URLs */
474
475 - (NSURL *)serverURL {
476   WORequest *rq;
477   NSString  *serverURL;
478   NSURL     *url;
479   NSString  *host;
480     
481   if ((rq = [self request]) == nil) {
482     [self logWithFormat:@"missing request in -baseURL call .."];
483     return nil;
484   }
485   
486   if ((serverURL = [rq headerForKey:@"x-webobjects-server-url"]) == nil) {
487     if ((host = [rq headerForKey:@"host"]))
488       serverURL = [@"http://" stringByAppendingString:host];
489   }
490   else {
491     // TODO: fix that (host is also broken for example with SOUP)
492     /* sometimes the port is broken in the server URL ... */
493     if ([serverURL hasSuffix:@":0"]) { // bad bad bad
494       if ((host = [rq headerForKey:@"host"])) {
495         NSString *scheme;
496         scheme = [serverURL hasPrefix:@"https://"] ? @"https://" : @"http://";
497         serverURL = [scheme stringByAppendingString:host];
498       }
499     }
500   }
501   
502   if ([serverURL length] == 0) {
503     [self errorWithFormat:@"could not find x-webobjects-server-url header !"];
504     return nil;
505   }
506   
507   if ((url = [NSURL URLWithString:serverURL]) == nil) {
508     [self logWithFormat:@"could not construct NSURL from string '%@'",
509             serverURL];
510     return nil;
511   }
512   return url;
513 }
514
515 - (NSURL *)baseURL {
516   WORequest *rq;
517   NSURL     *serverURL;
518
519   if (self->baseURL) 
520     return self->baseURL;
521     
522   if ((rq = [self request]) == nil) {
523     [self logWithFormat:@"missing request in -baseURL call .."];
524     return nil;
525   }
526     
527   serverURL = [self serverURL];
528   self->baseURL =
529     [[NSURL URLWithString:[rq uri] relativeToURL:serverURL] retain];
530     
531   if (self->baseURL == nil) {
532     [self logWithFormat:
533             @"could not construct NSURL for uri '%@' and base '%@' ...",
534             [rq uri], serverURL];
535   }
536   return self->baseURL;
537 }
538
539 - (NSURL *)applicationURL {
540   NSString *s;
541   
542   if (self->appURL != nil)
543     return self->appURL;
544
545   // TODO: we should ensure that the suffix (.woa) is in the URL
546   
547   s = [self->request adaptorPrefix];
548   if ([s length] > 0) {
549     s = [[[s stringByAppendingString:@"/"]
550              stringByAppendingString:[self->request applicationName]]
551              stringByAppendingString:@"/"];
552   }
553   else
554     s = [[self->request applicationName] stringByAppendingString:@"/"];
555   
556   self->appURL =
557     [[NSURL URLWithString:s relativeToURL:[self serverURL]] retain];
558   return self->appURL;
559 }
560 - (NSURL *)urlForKey:(NSString *)_key {
561   _key = [_key stringByAppendingString:@"/"];
562   return [NSURL URLWithString:_key relativeToURL:[self applicationURL]];
563 }
564
565 /* forms */
566
567 - (void)setInForm:(BOOL)_form {
568   self->wcFlags.inForm = _form ? 1 : 0;
569 }
570 - (BOOL)isInForm {
571   return self->wcFlags.inForm ? YES : NO;
572 }
573
574 - (void)addActiveFormElement:(WOElement *)_formElement {
575   if (self->activeFormElement) {
576     [[self component] debugWithFormat:@"active form element already set !"];
577     return;
578   }
579   
580   ASSIGN(self->activeFormElement, _formElement);
581   [self setRequestSenderID:[self elementID]];
582 }
583 - (WOElement *)activeFormElement {
584   return self->activeFormElement;
585 }
586
587 /* context variables (transient) */
588
589 - (void)setObject:(id)_obj forKey:(NSString *)_key {
590   if (self->variables == nil) {
591     self->variables =
592       [[NSMutableDictionary allocWithZone:[self zone]]
593                             initWithCapacity:16];
594   }
595
596   if (_obj)
597     [self->variables setObject:_obj forKey:_key];
598   else
599     [self->variables removeObjectForKey:_key];
600 }
601 - (id)objectForKey:(NSString *)_key {
602   return [self->variables objectForKey:_key];
603 }
604 - (void)removeObjectForKey:(NSString *)_key {
605   [self->variables removeObjectForKey:_key];
606 }
607
608 - (NSDictionary *)variableDictionary {
609   return self->variables;
610 }
611
612 - (void)takeValue:(id)_value forKey:(NSString *)_key {
613   if (WOSetKVCValueUsingMethod(self, _key, _value))
614     // method is used
615     return;
616   else if (WOGetKVCGetMethod(self, _key) == NULL) {
617     if (_value == nil)
618       _value = [EONull null];
619     
620     if (self->variables == nil) {
621       self->variables =
622         [[NSMutableDictionary allocWithZone:[self zone]]
623                               initWithCapacity:16];
624     }
625     [self->variables setObject:_value forKey:_key];
626     return;
627   }
628   else {
629     // only a 'get' method is defined for _key !
630     [self handleTakeValue:_value forUnboundKey:_key];
631   }
632 }
633 - (id)valueForKey:(NSString *)_key {
634   id value;
635   
636   if ((value = WOGetKVCValueUsingMethod(self, _key)))
637     return value;
638   value = [self->variables objectForKey:_key];
639   return value;
640 }
641
642 /* NSCopying */
643
644 - (id)copyWithZone:(NSZone *)_zone {
645   return [self retain];
646 }
647
648 /* description */
649
650 - (NSString *)description {
651   NSString *sid = nil;
652   WOApplication *app = [self application];
653
654   if ([self hasSession])
655     sid = [[self session] sessionID];
656   
657   return [NSString stringWithFormat:
658                      @"<0x%08X[%@]: %@ app=%@ sn=%@ eid=%@ rqeid=%@>",
659                      (unsigned)self, NSStringFromClass([self class]),
660                      [self  contextID],
661                      [app name],
662                      sid ? sid : @"none",
663                      [self  elementID],
664                      [self  senderID]];
665 }
666
667 /* ElementIDs */
668
669 - (NSString *)elementID {
670   return [self->elementID elementID];
671 }
672 - (void)appendElementIDComponent:(NSString *)_eid {
673   [self->elementID appendElementIDComponent:_eid];
674 }
675 - (void)appendZeroElementIDComponent {
676   [self->elementID appendZeroElementIDComponent];
677 }
678 - (void)deleteAllElementIDComponents {
679   [self->elementID deleteAllElementIDComponents];
680 }
681 - (void)deleteLastElementIDComponent {
682   [self->elementID deleteLastElementIDComponent];
683 }
684 - (void)incrementLastElementIDComponent {
685   [self->elementID incrementLastElementIDComponent];
686 }
687 - (void)appendIntElementIDComponent:(int)_eid {
688   [self->elementID appendIntElementIDComponent:_eid];
689 }
690
691 /* the following can be later moved to WOElementID */
692
693 - (id)currentElementID {
694   return [self->reqElementID currentElementID];
695 }
696 - (id)consumeElementID {
697   return [self->reqElementID consumeElementID];
698 }
699
700 /* URLs */
701
702 - (void)_generateCompleteURLs {
703   /* described in Apple TIL article 70101 */
704 }
705
706 - (void)setQueryPathSeparator:(NSString *)_sp {
707   ASSIGNCOPY(self->qpJoin, _sp);
708 }
709 - (NSString *)queryPathSeparator {
710   return self->qpJoin;
711 }
712
713 - (void)setGenerateXMLStyleEmptyElements:(BOOL)_flag {
714   self->wcFlags.xmlStyleEmptyElements = _flag ? 1 : 0;
715 }
716 - (BOOL)generateXMLStyleEmptyElements {
717   return self->wcFlags.xmlStyleEmptyElements ? YES : NO;
718 }
719
720 - (void)setGenerateEmptyAttributes:(BOOL)_flag {
721   self->wcFlags.allowEmptyAttributes = _flag ? 1 : 0;
722 }
723 - (BOOL)generateEmptyAttributes {
724   return self->wcFlags.allowEmptyAttributes ? YES : NO;
725 }
726
727 - (NSString *)queryStringFromDictionary:(NSDictionary *)_queryDict {
728   NSEnumerator    *keys;
729   NSString        *key;
730   BOOL            isFirst;
731   NSMutableString *qs;
732   
733   qs   = [MutableStrClass stringWithCapacity:256];
734   keys = [_queryDict keyEnumerator];
735   for (isFirst = YES; (key = [keys nextObject]) != nil; ) {
736     id value;
737     
738     value = [_queryDict objectForKey:key];
739
740     /* check for multi-value parameter */
741
742     if ([value isKindOfClass:[NSArray class]]) {
743       NSArray  *a = value;
744       unsigned i, count;
745       
746       for (i = 0, count = [a count]; i < count; i++) {
747         value = [a objectAtIndex:i];
748         
749         if (isFirst) isFirst = NO;
750         else [qs appendString:self->qpJoin];
751
752         // TODO: code duplication ...
753         value = ![value isNotNull] ? nil : [value stringValue];
754         key   = [key   stringByEscapingURL];
755         value = [value stringByEscapingURL];
756     
757         [qs appendString:key];
758         if (value != nil) {
759           [qs appendString:@"="];
760           [qs appendString:value];
761         }
762       }
763       continue;
764     }
765
766     /* regular, single-value parameter */
767     
768     if (isFirst) isFirst = NO;
769     else [qs appendString:self->qpJoin];
770     
771     value = ![value isNotNull] ? nil : [value stringValue];
772     key   = [key   stringByEscapingURL];
773     value = [value stringByEscapingURL];
774     
775     [qs appendString:key];
776     if (value != nil) {
777       [qs appendString:@"="];
778       [qs appendString:value];
779     }
780   }
781   
782   return qs;
783 }
784
785 - (NSString *)directActionURLForActionNamed:(NSString *)_actionName
786   queryDictionary:(NSDictionary *)_queryDict
787 {
788   NSMutableString *url;
789   NSString        *qs;
790
791   url = [MutableStrClass stringWithCapacity:256];
792   
793   if (!testNSURLs)
794      [url appendString:@"/"];
795   
796   [url appendString:_actionName];
797   
798   /* add query parameters */
799   
800   qs = [self queryStringFromDictionary:_queryDict];
801     
802   return [self urlWithRequestHandlerKey:
803                  [WOAppClass directActionRequestHandlerKey]
804                path:url queryString:qs];
805 }
806
807 - (NSString *)componentActionURL {
808   // TODO: add a -cComponentActionURL 
809   //       (without NSString for use with appendContentCString:)
810   // Profiling:
811   //   26% -urlWithRequestHandler...
812   //   21% -elementID (was 40% !! :-)
813   //   ~20% mutable string ops
814   if (newCURLStyle) {
815     NSMutableString *qs;
816     NSString *p;
817   
818     self->wcFlags.savePageRequired = 1;
819     qs = [MutableStrClass stringWithCapacity:64];
820   
821     [qs appendString:WORequestValueSenderID];
822     [qs appendString:@"="];
823     [qs appendString:[self elementID]];
824     [qs appendString:self->qpJoin];
825     [qs appendString:WORequestValueSessionID];
826     [qs appendString:@"="];
827     [qs appendString:[[self session] sessionID]];
828     [qs appendString:self->qpJoin];
829     [qs appendString:WORequestValueContextID];
830     [qs appendString:@"="];
831     [qs appendString:[self contextID]];
832   
833     p = [[self page] componentActionURLForContext:self];
834
835     if (testNSURLs) {
836       if ([p hasPrefix:@"/"]) p = [p substringFromIndex:1];
837     }
838   
839     return [self urlWithRequestHandlerKey:
840                    [WOAppClass componentRequestHandlerKey]
841                  path:p
842                  queryString:qs];
843   }
844   else {
845     /* old style URLs ... */
846     static NSMutableString *url = nil; // THREAD
847     static IMP addStr = NULL;
848     NSString *s;
849     
850     self->wcFlags.savePageRequired = 1;
851     if (url == nil) {
852       url = [[MutableStrClass alloc] initWithCapacity:256];
853       addStr = [url methodForSelector:@selector(appendString:)];
854       addStr(url, @selector(appendString:), @"/");
855     }
856     else
857       [url setString:@"/"];
858   
859     /*
860       Note: component actions *always* require sessions to be able to locate
861       the request component !
862     */
863     addStr(url, @selector(appendString:), [[self session] sessionID]);
864     addStr(url, @selector(appendString:), @"/");
865     addStr(url, @selector(appendString:), [self->elementID elementID]);
866   
867     s = [self urlWithRequestHandlerKey:
868                 [WOAppClass componentRequestHandlerKey]
869               path:url queryString:nil];
870     return s;
871   }
872 }
873
874 - (NSString *)urlWithRequestHandlerKey:(NSString *)_key
875   path:(NSString *)_path
876   queryString:(NSString *)_query
877 {
878   if (testNSURLs) { /* use NSURLs for processing */
879     NSURL *rqUrl;
880     
881     if ([_path hasPrefix:@"/"]) {
882 #if DEBUG
883       [self warnWithFormat:@"got absolute path '%@'", _path];
884 #endif
885       _path = [_path substringFromIndex:1];
886     }
887     
888     if (_key == nil) _key = [WOAppClass componentRequestHandlerKey];
889     rqUrl = [self urlForKey:_key];
890   
891     if ([_query length] > 0) {
892       NSMutableString *s;
893     
894       s = [_path mutableCopy];
895       [s appendString:@"?"];
896       [s appendString:_query];
897       rqUrl = [NSURL URLWithString:s relativeToURL:rqUrl];
898       [s release];
899     }
900     else
901       rqUrl = [NSURL URLWithString:_path relativeToURL:rqUrl];
902     
903     //[self logWithFormat:@"constructed component URL: %@", rqUrl];
904     
905     return [rqUrl stringValueRelativeToURL:[self baseURL]];
906   }
907   else {
908     NSMutableString *url;
909     NSString *tmp;
910     IMP addStr;
911   
912     if (_key == nil) _key = [WOAppClass componentRequestHandlerKey];
913   
914     url = [MutableStrClass stringWithCapacity:256];
915     addStr = [url methodForSelector:@selector(appendString:)];
916
917     /* static part */
918     if (self->urlPrefix == nil) {
919       if (!relativeURLs) {
920         if ((tmp = [self->request headerForKey:@"x-webobjects-server-url"])) {
921           if ([tmp hasSuffix:@":0"] && [tmp length] > 2) // TODO: BAD BAD BAD
922             tmp = [tmp substringToIndex:([tmp length] - 2)];
923           addStr(url, @selector(appendString:), tmp);
924         }
925         else if ((tmp = [self->request headerForKey:@"host"])) {
926           addStr(url, @selector(appendString:), @"http://");
927           addStr(url, @selector(appendString:), tmp);
928         }
929       }
930
931       addStr(url, @selector(appendString:), [self->request adaptorPrefix]);
932       addStr(url, @selector(appendString:), @"/");
933       tmp = [[self request] applicationName];
934       if ([tmp length] == 0)
935         tmp = [(WOApplication *)[self application] name];
936       if ([tmp length] > 0) {
937         addStr(url, @selector(appendString:), tmp);
938         if (WOApplicationSuffix)
939           addStr(url, @selector(appendString:), WOApplicationSuffix);
940         addStr(url, @selector(appendString:), @"/");
941       }
942       
943       /* cache prefix */
944       self->urlPrefix = [url copy];
945       if (debugOn) [self debugWithFormat:@"URL prefix: '%@'", self->urlPrefix];
946     }
947     else {
948       /* prefix is cached :-) */
949       addStr(url, @selector(appendString:), self->urlPrefix);
950     }
951   
952     /* variable part */
953     addStr(url, @selector(appendString:), _key);
954     if (_path) 
955       addStr(url, @selector(appendString:), _path);
956     if ([_query length] > 0) {
957       addStr(url, @selector(appendString:), @"?");
958       addStr(url, @selector(appendString:), _query);
959     }
960     return url;
961   }
962 }
963 - (NSString *)completeURLWithRequestHandlerKey:(NSString *)_key
964   path:(NSString *)_path queryString:(NSString *)_query
965   isSecure:(BOOL)_isSecure port:(int)_port
966 {
967   NSMutableString *url = [MutableStrClass stringWithCapacity:256];
968   [url appendString:_isSecure ? @"https://" : @"http://"];
969   [url appendString:[[self request] headerForKey:@"host"]];
970   if (_port > 0) {
971     if (!(_isSecure && _port == 443) && !(!_isSecure && _port == 80))
972       [url appendFormat:@":%i", _port];
973   }
974   [url appendString:[self urlWithRequestHandlerKey:_key
975                           path:_path
976                           queryString:_query]];
977   return url;
978 }
979
980 - (void)setRequestSenderID:(NSString *)_rid {
981   WOElementID *eid;
982   
983   eid = [[WOElementID alloc] initWithString:_rid];
984   [self->reqElementID release];
985   self->reqElementID = eid;
986 }
987 - (NSString *)senderID {
988 #if 1
989   return [self->reqElementID elementID];
990 #else
991   NSMutableString *eid;
992   IMP addStr;
993   int i;
994   
995   eid = [MutableStrClass stringWithCapacity:(self->reqElementIdCount * 4) + 1];
996   addStr = [eid methodForSelector:@selector(appendString:)];
997   for (i = 0; i < self->reqElementIdCount; i++) {
998     if (i != 0) addStr(eid, @selector(appendString:), @".");
999     addStr(eid, @selector(appendString:), [self->reqElementId[i] stringValue]);
1000   }
1001   return eid;
1002 #endif
1003 }
1004
1005 /* languages for resource lookup (non-WO) */
1006
1007 - (NSArray *)resourceLookupLanguages {
1008   return [self hasSession] 
1009     ? [[self session] languages]
1010     : [[self request] browserLanguages];
1011 }
1012
1013
1014 /* DeprecatedMethodsInWO4 */
1015
1016 - (id)application {
1017   if (self->application == nil)
1018     self->application = [WOAppClass application];
1019
1020   if (self->application == nil) {
1021     [self logWithFormat:
1022             @"%s: missing application for context %@", 
1023             __PRETTY_FUNCTION__, self];
1024   }
1025   
1026   return self->application;
1027 }
1028
1029 - (void)setDistributionEnabled:(BOOL)_flag {
1030   IS_DEPRECATED;
1031   [[self session] setDistributionEnabled:_flag];
1032 }
1033 - (BOOL)isDistributionEnabled {
1034   IS_DEPRECATED;
1035   return [[self session] isDistributionEnabled];
1036 }
1037
1038 - (NSString *)url {
1039   return [self componentActionURL];
1040 }
1041
1042 - (NSString *)urlSessionPrefix {
1043   NSMutableString *url;
1044   NSString *tmp;
1045
1046   url = [MutableStrClass stringWithCapacity:128];
1047   
1048   [url appendString:[[self request] adaptorPrefix]];
1049   [url appendString:@"/"];
1050   tmp = [[self request] applicationName];
1051   [url appendString:
1052          tmp ? tmp : [(WOApplication *)[self application] name]];
1053
1054 #if DEBUG
1055   if ([url length] == 0) {
1056     [self warnWithFormat:@"(%s): could not determine session URL prefix !",
1057             __PRETTY_FUNCTION__];
1058   }
1059 #endif
1060   
1061   return url;
1062 }
1063
1064 @end /* WOContext */
1065
1066
1067 @implementation WOComponent(Cursors)
1068
1069 - (void)pushCursor:(id)_obj {
1070   if (debugCursor)
1071     [self logWithFormat:@"enter cursor: %@", _obj];
1072   
1073   if (!self->cycleContext)
1074     self->cycleContext = [[NSMutableArray alloc] initWithCapacity:8];
1075   
1076   /* add to cursor stack */
1077   [self->cycleContext addObject:(_obj ? _obj : [NSNull null])];
1078   
1079   /* set active cursor */
1080   [self setObject:_obj forKey:@"_"];
1081 }
1082
1083 - (id)popCursor {
1084   NSMutableArray *ctxStack;
1085   id old;
1086   
1087   /* retrieve last context */
1088   old  = [[self objectForKey:@"_"] retain];
1089   [self setObject:nil forKey:@"_"];
1090   
1091   /* restore old ctx */
1092   if ((ctxStack = self->cycleContext) != nil) {
1093     unsigned count;
1094     
1095     if ((count = [ctxStack count]) > 0) {
1096       [ctxStack removeObjectAtIndex:(count - 1)];
1097       count--;
1098       
1099       if (count > 0) {
1100         id obj;
1101         
1102         obj = [ctxStack objectAtIndex:(count - 1)];
1103       
1104         if (![obj isNotNull]) obj = nil;
1105         [self setObject:obj forKey:@"_"];
1106       }      
1107     }
1108   }
1109 #if DEBUG
1110   else {
1111     [self warnWithFormat:@"-popCursor called without cycle ctx !"];
1112   }
1113 #endif
1114   
1115   if (debugCursor) {
1116     [self logWithFormat:@"leave cursor: %@ (restored=%@)",
1117             old, [self cursor]];
1118   }
1119   
1120   return [old autorelease];
1121 }
1122
1123 - (id)cursor {
1124   NSMutableArray *ctxStack;
1125   
1126   // TODO: why do we check for _ODCycleCtx, if we query '_' ?
1127   
1128   if ((ctxStack = self->cycleContext) == nil)
1129     /* no cycle context setup for component ... */
1130     return self;
1131   if ([ctxStack count] == 0)
1132     /* nothing contained in cycle context ... */
1133     return self;
1134   
1135   return [self objectForKey:@"_"];
1136 }
1137
1138 @end /* WOComponent(Cursors) */