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