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