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