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