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