-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathDBSoap.m
More file actions
2111 lines (1769 loc) · 60.2 KB
/
Copy pathDBSoap.m
File metadata and controls
2111 lines (1769 loc) · 60.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/* -*- mode: objc -*-
Project: DataBasin
Copyright (C) 2008-2025 Free Software Foundation
Author: Riccardo Mottola
Created: 2008-11-13 22:44:45 +0100 by Riccardo Mottola
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free
Software Foundation, Inc., 31 Milk Street #960789 Boston, MA 02196 USA.
*/
#import <AppKit/AppKit.h>
#import "DBSoap.h"
#import "DBSObject.h"
#import "DBProgressProtocol.h"
#import "DBLoggerProtocol.h"
#import "DBSFTypeWrappers.h"
#if defined(__APPLE__) && (MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_4)
@interface NSString (AdditionsReplacement)
- (NSString *)stringByReplacingOccurrencesOfString:(NSString *)target withString:(NSString *)replacement;
@end
@implementation NSString (AdditionsReplacement)
- (NSString *)stringByReplacingOccurrencesOfString:(NSString *)target withString:(NSString *)replacement
{
NSRange rT;
NSString *newS;
newS = [NSString stringWithString:self];
rT = [newS rangeOfString:target];
while (rT.location != NSNotFound)
{
NSString *s;
NSRange remainingSubrange;
s = [newS substringToIndex:rT.location];
if (replacement)
s = [s stringByAppendingString:replacement];
s = [s stringByAppendingString:[newS substringFromIndex:rT.location + [target length]]];
newS = s;
remainingSubrange = NSMakeRange(rT.location + [replacement length], [newS length] - (rT.location + [replacement length]) );
rT = [newS rangeOfString:target options:0 range:remainingSubrange];
}
return newS;
}
@end
#endif
@implementation DBSoap
/**
<p>Analyzes <em>query</em> and splits the select part into fields.<br>
These fields can be used, for example, to predict the output returned by
query and queryAll</p>
<p>Contains additional logic to work around idiosynchrasies of salesforce.com
with handling aggregate queryes. Complex objects are flattened to their names.<br>
E.g. MyObject1__r.MyObject2__r.Field__c returns Field__c.
*/
+ (NSArray *)fieldsByParsingQuery:(NSString *)query
{
NSMutableArray *fields;
NSString *selectPart;
NSArray *components;
NSRange fromPosition;
NSRange selectPosition;
NSUInteger i;
if (query == nil)
return nil;
query = [query stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
components = nil;
fields = nil;
fromPosition = [query rangeOfString:@"from" options:NSCaseInsensitiveSearch];
selectPosition = [query rangeOfString:@"select" options:NSCaseInsensitiveSearch];
/* we assume that we always have select and from in the query */
if (fromPosition.location != NSNotFound && selectPosition.location != NSNotFound)
{
BOOL hasAggregate;
NSMutableString *cleansedSelectPart;
NSUInteger exprProgressive; /* to enumerate Expr0, Expr1... */
NSRange firstParenthesisPosition;
exprProgressive = 0;
selectPart = [query substringWithRange:NSMakeRange([@"select " length], fromPosition.location - [@"select " length])];
/* check for a nested query */
if ([selectPart rangeOfString:@"select " options:NSCaseInsensitiveSearch].location != NSNotFound)
{
NSLog(@"We have a nested query, it is not supported since we have no information of the nested object");
[[NSException exceptionWithName:@"DBException" reason:@"Nested query not supported" userInfo:nil] raise];
}
/* we replace certain characters with space */
cleansedSelectPart = [NSMutableString stringWithString:selectPart];
[cleansedSelectPart replaceOccurrencesOfString:@"\r" withString:@" " options:0 range:NSMakeRange(0, [cleansedSelectPart length])];
[cleansedSelectPart replaceOccurrencesOfString:@"\n" withString:@" " options:0 range:NSMakeRange(0, [cleansedSelectPart length])];
[cleansedSelectPart replaceOccurrencesOfString:@"\t" withString:@" " options:0 range:NSMakeRange(0, [cleansedSelectPart length])];
/* now we do some white-space coalescing */
while ([cleansedSelectPart replaceOccurrencesOfString:@" " withString:@" " options:0 range:NSMakeRange(0, [cleansedSelectPart length])] > 0);
/* now we trust the string enough and get the single comma-separated components */
components = [cleansedSelectPart componentsSeparatedByString:@","];
/* if we only have one field, we fake an array to retain the same logic */
if ([components count] == 0)
{
components = [NSArray arrayWithObject:selectPart];
}
/* now we look for (, to check if it is an aggregate query */
hasAggregate = NO;
firstParenthesisPosition = [cleansedSelectPart rangeOfString:@"("];
if (firstParenthesisPosition.location != NSNotFound && firstParenthesisPosition.location < fromPosition.location)
hasAggregate = YES;
fields = [NSMutableArray arrayWithCapacity:[components count]];
for (i = 0; i < [components count]; i++)
{
NSString *field;
NSRange r;
field = [components objectAtIndex:i];
field = [field stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
/* now we safely if the field has aliases */
r = [field rangeOfCharacterFromSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if (r.location != NSNotFound)
{ /* we probably have alias */
NSArray *subComponents;
NSRange rParRange; // look for the right parenthesis
subComponents = [field componentsSeparatedByString:@" "];
rParRange = [field rangeOfString:@")"];
if (rParRange.location != NSNotFound)
{
NSString *lastComponent;
NSRange rightPar;
NSLog(@"we have an aggregate in %@", field);
lastComponent = [subComponents objectAtIndex:[subComponents count]-1];
/* now we try to understand if the last component is:
- an alias
- () of count()
- ) of function(id)
*/
rightPar = [lastComponent rangeOfString:@")"];
if (rightPar.location != NSNotFound) // we have no alias
{
if([lastComponent rangeOfString:@"()"].location != NSNotFound)
{
/* old style count */
field = @"count";
}
else
{
field = [NSString stringWithFormat:@"Expr%lu", (unsigned long)exprProgressive];
exprProgressive++;
}
}
else
{
field = lastComponent;
}
}
else
{
NSLog(@"Error, white space but no aggregate function found");
}
}
else
{ /* no alias */
/* the field is not aliased and we know we have an aggregate query, count () separated by space was handled above
salesforce returns Expr0 for count(id) but count for count()
*/
if ([field caseInsensitiveCompare:@"count()"] == NSOrderedSame)
{
NSLog(@"no spaces, but we have count, the field is: %@", field);
field = @"count";
}
else if ([field rangeOfString:@")"].location != NSNotFound)
{
field = [NSString stringWithFormat:@"Expr%lu", (unsigned long)exprProgressive];
exprProgressive++;
}
else if (hasAggregate)
{
NSRange dotRange;
dotRange = [field rangeOfString:@"." options:NSBackwardsSearch];
if (dotRange.location != NSNotFound)
{
field = [field substringWithRange:NSMakeRange(dotRange.location + 1, [field length]-dotRange.location-1)];
}
}
}
[fields addObject:field];
}
}
return [NSArray arrayWithArray:fields];
}
/** Returns the supported API version */
+ (NSString *)apiVersion
{
return @"61.0";
}
/** Returns the standard URL for login into production, https. Use this as login: parameter */
+ (NSURL *)loginURLProduction
{
NSURL *url;
url = [NSURL URLWithString:@"https://login.salesforce.com/services/Soap/u/"];
url = [url URLByAppendingPathComponent:[DBSoap apiVersion] isDirectory:NO];
return url;
}
/** Returns the standard URL for login into sandbox, https. Use this as login: parameter */
+ (NSURL *)loginURLTest
{
NSURL *url;
url = [NSURL URLWithString:@"https://test.salesforce.com/services/Soap/u/"];
url = [url URLByAppendingPathComponent:[DBSoap apiVersion] isDirectory:NO];
return url;
}
/** returns a GWService inited usefully for DBSoap */
+ (GWSService *)gwserviceForDBSoap
{
GWSService *gws;
GWSSOAPCoder *coder;
/* initialize the coder */
coder = [GWSSOAPCoder new];
/* salesforce WSDL specifies it to be literal */
[coder setUseLiteral:YES];
gws = [[GWSService alloc] init];
[gws setCoder:coder];
[coder release];
/* set the SOAP action to an empty string, salesforce likes that more */
[gws setSOAPAction:@"\"\""];
return [gws autorelease];
}
- (id)init
{
if ((self = [super init]))
{
lockBusy = [[NSRecursiveLock alloc] init];
busyCount = 0;
standardTimeoutSec = 60;
queryTimeoutSec = 180;
runAssignmentRules = NO;
upBatchSize = 1;
downBatchSize = 500;
maxSOQLLength = MAX_SOQL_LENGTH;
enableFieldTypesDescribeForQuery = NO;
returnSuccessResults = YES;
returnMultipleErrors = YES;
sObjectDetailsDict = [[NSMutableDictionary alloc] init];
}
return self;
}
- (void)dealloc
{
[lockBusy release];
[sObjectDetailsDict release];
[sessionId release];
[serverURL release];
[userInfo release];
[super dealloc];
}
- (void)setLogger: (id<DBLoggerProtocol>)l
{
if (logger)
[logger release];
logger = [l retain];
}
- (id<DBLoggerProtocol>)logger
{
return logger;
}
- (void)setRunAssignmentRules :(BOOL)flag
{
runAssignmentRules = flag;
}
- (BOOL)runAssignmentRules
{
return runAssignmentRules;
}
- (unsigned)upBatchSize
{
return upBatchSize;
}
/** Sets the size of the batches in which records are inserted, updated or deleted */
- (void)setUpBatchSize:(unsigned)size
{
upBatchSize = size;
}
- (unsigned)downBatchSize
{
return downBatchSize;
}
/** Set the maximum suggested query size (download). Maximum effective is 2000, standard is 500. */
- (void)setDownBatchSize:(unsigned)size
{
downBatchSize = size;
}
- (unsigned)maxSOQLLength
{
return maxSOQLLength;
}
/** Set the maximum used lenght for a SOQL statement. Maximum is defined by MAX_SOQL_LENGTH but
if a query returns QUERY_TOO_COMPLICATED the size must be reduced. A large value is capped to MAX_SOQL_LENGTH */
- (void)setMaxSOQLLength:(unsigned)size
{
if (size < MAX_SOQL_LENGTH)
maxSOQLLength = size;
else
maxSOQLLength = MAX_SOQL_LENGTH;
}
- (void)_login :(NSURL *)url :(NSString *)userName :(NSString *)password
{
NSUserDefaults *defs;
NSMutableArray *orderArray;
NSMutableDictionary *parmsDict;
NSMutableDictionary *loginParmDict;
NSDictionary *resultDict;
NSEnumerator *enumerator;
NSString *key;
NSDictionary *loginResult;
NSDictionary *loginResult2;
NSMutableDictionary *userInfoResult;
NSDictionary *queryFault;
GWSService *service;
NSURL *gottenURL;
defs = [NSUserDefaults standardUserDefaults];
[defs registerDefaults:
[NSDictionary dictionaryWithObjectsAndKeys:
@"80", @"Port",
nil]];
/* init our service */
service = [[DBSoap gwserviceForDBSoap] retain];
[service setURL:url];
//[service setDebug:YES];
/* prepare the parameters */
loginParmDict = [NSMutableDictionary dictionaryWithCapacity: 3];
[loginParmDict setObject: @"urn:partner.soap.sforce.com" forKey: GWSSOAPNamespaceURIKey];
[loginParmDict setObject: userName forKey: @"username"];
[loginParmDict setObject: password forKey: @"password"];
orderArray = [NSMutableArray arrayWithCapacity: 2];
[orderArray addObject: @"username"];
[orderArray addObject: @"password"];
[loginParmDict setObject: orderArray forKey: GWSOrderKey];
parmsDict = [NSMutableDictionary dictionaryWithCapacity: 1];
[parmsDict setObject: loginParmDict forKey: @"login"];
/* invoke the login */
resultDict = [service invokeMethod: @"login"
parameters : parmsDict
order : nil
timeout : standardTimeoutSec];
[service release];
[logger log: LogDebug: @"[DBSoap Login]:resultDict is %d big\n", [resultDict count]];
queryFault = [resultDict objectForKey:GWSFaultKey];
if (queryFault != nil)
{
NSString *faultCode;
NSString *faultString;
faultCode = [queryFault objectForKey:@"faultcode"];
faultString = [queryFault objectForKey:@"faultstring"];
[logger log: LogStandard: @"[DBSoap Login]: fault code: %@\n", faultCode];
[logger log: LogStandard: @"[DBSoap Login]: fault String: %@\n", faultString];
[[NSException exceptionWithName:@"DBException" reason:faultString userInfo:nil] raise];
}
loginResult = [resultDict objectForKey:GWSParametersKey];
[logger log: LogDebug: @"[DBSoap Login]: coder parameters is %@\n", loginResult];
enumerator = [loginResult keyEnumerator];
while ((key = [enumerator nextObject]))
[logger log: LogDebug: @"[DBSoap Login]:%@ - %@\n", key, [loginResult objectForKey:key]];
loginResult2 = [loginResult objectForKey:@"result"];
[logger log: LogDebug: @"[DBSoap Login]: %@\n", loginResult2];
enumerator = [loginResult2 keyEnumerator];
while ((key = [enumerator nextObject]))
[logger log: LogDebug: @"[DBSoap Login]:%@ - %@\n", key, [loginResult2 objectForKey:key]];
[sessionId release];
sessionId = [loginResult2 objectForKey:@"sessionId"];
gottenURL = [NSURL URLWithString:[loginResult2 objectForKey:@"serverUrl"]];
passwordExpired = NO;
if ([[loginResult2 objectForKey:@"passwordExpired"] isEqualToString:@"true"])
passwordExpired = YES;
userInfoResult = [NSMutableDictionary dictionaryWithDictionary:[loginResult2 objectForKey:@"userInfo"]];
[userInfoResult removeObjectForKey:@"GWSCoderOrder"];
userInfo = [[NSDictionary dictionaryWithDictionary:userInfoResult] retain];
[self setServerURL:gottenURL];
if (sessionId == nil)
{
[[NSException exceptionWithName:@"DBException" reason:@"No Session information returned." userInfo:nil] raise];
}
else
{
[logger log: LogStandard: @"[DBSoap Login]: sessionId: %@\n", sessionId];
[logger log: LogStandard: @"[DBSoap Login]: serverUrl: %@\n", serverURL];
}
[sessionId retain];
}
/**
<p>The passed <i>value</i> is transformed from a String to a specific Type
(using a DBSTypeWrapper if recognized) using information of the object <i>sObj</i>
through an object describes.<br>
Besides Strings the value could be complex being either a special type like an Address
or a nested result of a query.</p>
<p>Source strings are supposed in SalesForce format for TypeWrappers initialization.</p>
<p>Currently, queryMore is not performed for nested results</p>
*/
- (id)adjustFormatForField:(NSString *)key forValue:(id)value inObject:(DBSObject *)sObj
{
id retObj;
NSString *type;
DBSObject *objDetails;
type = [sObj type];
// NSLog(@"Object Type: %@, key: %@, value %@", type, key, value);
/* this is not an object, we cannot describe it */
if ([type isEqualToString:@"AggregateResult"])
return value;
/* check for described object cache */
objDetails = [sObjectDetailsDict objectForKey:type];
if (!objDetails)
{
NSLog(@"*** describe Start***");
// since we are already inside the queryAll lock, we call the unlocked describe version
objDetails = [self _describeSObject:type];
NSLog(@"*** describe End***");
if (objDetails)
[sObjectDetailsDict setObject:objDetails forKey:type];
}
if (objDetails)
{
NSDictionary *fieldProps;
NSString *fieldType;
fieldProps = [objDetails propertiesOfField: key];
fieldType = [fieldProps objectForKey:@"type"];
if ([fieldType isEqualToString:@"double"])
{
retObj = [[[DBSFDouble alloc] initWithSFString:value] autorelease];
}
else if ([fieldType isEqualToString:@"int"])
{
retObj = [[[DBSFInteger alloc] initWithSFString:value] autorelease];
}
else if ([fieldType isEqualToString:@"boolean"])
{
retObj = [[[DBSFBoolean alloc] initWithSFString:value] autorelease];
}
else if ([fieldType isEqualToString:@"currency"])
{
retObj = [[[DBSFCurrency alloc] initWithSFString:value] autorelease];
}
else if ([fieldType isEqualToString:@"percent"])
{
retObj = [[[DBSFPercentage alloc] initWithSFString:value] autorelease];
}
else if ([fieldType isEqualToString:@"date"])
{
retObj = [[[DBSFDate alloc] initWithSFString:value] autorelease];
}
else if ([fieldType isEqualToString:@"datetime"])
{
retObj = [[[DBSFDateTime alloc] initWithSFString:value] autorelease];
}
else if ([fieldType isEqualToString:@"address"])
{
retObj = value;
}
else if ([value isKindOfClass:[NSDictionary class]])
{
NSArray *records;
BOOL done;
/* a dictionary might be a single complex object or an array of nested objects */
records = [value objectForKey:@"records"];
if (records)
{
NSString *doneStr;
NSString *sizeStr;
NSMutableArray *objArray;
unsigned long size;
objArray = [[NSMutableArray alloc] init];
NSLog(@"adjustFormatForField : we have a nested array of objects: %lu", [records count]);
doneStr = [(NSDictionary *)value objectForKey:@"done"];
sizeStr = [(NSDictionary *)value objectForKey:@"size"];
if (doneStr != nil)
{
[logger log: LogDebug: @"[DBSoap adjustFormatForField] done: %@\n", doneStr];
done = NO;
if ([doneStr isEqualToString:@"true"])
done = YES;
else if ([doneStr isEqualToString:@"false"])
done = NO;
else
[logger log: LogStandard: @"[DBSoap adjustFormatForField] done, unexpected value: %@\n", doneStr];
if (!done)
{
// TODO we could retrieve more objects
[logger log: LogStandard: @"[DBSoap adjustFormatForField] nested query contains more results. queryMore not performed"];
}
}
size = 0;
if (sizeStr != nil)
{
NSScanner *scan;
long long ll;
scan = [NSScanner scannerWithString:sizeStr];
if ([scan scanLongLong:&ll])
{
size = (unsigned long)ll;
[logger log: LogInformative: @"[DBSoap adjustFormatForField] Declared size is: %lu\n", size];
}
else
{
[logger log: LogStandard : @"[DBSoap adjustFormatForField] Could not parse Size string: %@\n", sizeStr];
NSLog(@"Error parsing subquery size string");
size = 0;
value = nil;
}
}
/* if we have only one element, put it in an array */
if (size == 1)
{
records = [NSArray arrayWithObject:records];
}
[self extractQueryRecords:records toObjects:objArray];
retObj = [objArray autorelease];
}
else
{
NSString *type2;
DBSObject *sObj2;
NSMutableDictionary *dict2;
NSUInteger i;
NSDictionary *propDict;
NSArray *keys;
sObj2 = [[DBSObject alloc] init];
type2 = [value objectForKey:@"type"];
propDict = [NSDictionary dictionaryWithObject:type2 forKey:@"type"];
[sObj2 setObjectProperties: propDict];
// hack until DBSObjects can be handled by writers
dict2 = [[NSMutableDictionary alloc] init];
keys = [(NSDictionary *)value allKeys];
// NSLog(@"we have a complex object: %@ with keys: %@", type2, keys);
for (i = 0; i < [keys count]; i++)
{
id obj;
id value2;
NSString *key2;
key2 = [keys objectAtIndex:i];
/* special GSWS field */
if (key2 == GWSOrderKey)
continue;
obj = [value objectForKey: key2];
if ([key2 isEqualToString:@"Id"])
{
/* when queried, Id is always in an array, else empty string */
if ([obj isKindOfClass: [NSArray class]])
value2 = [(NSArray *)obj objectAtIndex: 0];
else
continue; /* skip empty Id */
}
else if ([key2 isEqualToString:@"type"])
{
continue;
}
else
value2 = obj;
if (enableFieldTypesDescribeForQuery)
{
value2 = [self adjustFormatForField:key2 forValue:value2 inObject:sObj2];
}
[sObj2 setValue: value2 forField: key2];
[dict2 setObject: value2 forKey: key2];
}
[sObj2 autorelease];
[dict2 autorelease];
retObj = dict2;
}
}
else
{
retObj = value;
}
}
else
{
[logger log: LogStandard: @"[DBSoap adjustFormatForField] Failed to get field information: %@.%@\n", type, key];
retObj = value;
}
return retObj;
}
/* A record of the result - may contain nested objects of subqueries */
- (DBSObject *)extractQueryRecord:(NSDictionary *)record
{
NSUInteger j;
DBSObject *sObj;
NSMutableArray *keys;
BOOL typePresent;
sObj = [[DBSObject alloc] init];
/* let's get the fields from the keys of the first record */
keys = [NSMutableArray arrayWithArray:[record allKeys]];
[keys removeObject:GWSOrderKey];
/* remove some fields which get added automatically by salesforce even if not asked for */
typePresent = [keys containsObject:@"type"];
if (typePresent)
[keys removeObject:@"type"];
/* remove Id only if it is null, else an array of two populated Id is returned by SF */
if (![[record objectForKey:@"Id"] isKindOfClass: [NSArray class]])
[keys removeObject:@"Id"];
/* we removed type, but if it is present, set it as a property
Further, we describe the type if desired to get field types.
*/
if (typePresent)
{
NSDictionary *propDict;
NSString *typeStr;
typeStr = [record objectForKey: @"type"];
propDict = [NSDictionary dictionaryWithObject:typeStr forKey:@"type"];
[sObj setObjectProperties: propDict];
}
// NSLog (@"single record: %@", record);
for (j = 0; j < [keys count]; j++)
{
id obj;
id value;
NSString *key;
key = [keys objectAtIndex:j];
obj = [record objectForKey: key];
// NSLog(@"analyzing %@ : %@", key, obj);
if ([key isEqualToString:@"Id"])
{
value = [(NSArray *)obj objectAtIndex: 0];
[sObj setValue: value forField: key];
}
else if ([obj isKindOfClass:[NSDictionary class]])
{
// This is recurisve, it is a sub-query result
NSDictionary *result = (NSDictionary *)obj;
id subRecords;
subRecords = [obj objectForKey:@"records"];
if (subRecords != nil)
{
NSString *sizeStr;
int size;
// We have a sub-query or otherwise a list of records
sizeStr = [result objectForKey:@"size"];
size = [sizeStr intValue];
/* if we have only one element, we just recurse */
if (size == 1)
{
DBSObject *o;
o = [self extractQueryRecord:subRecords];
[sObj setValue:o forField: key];
}
else
{
NSMutableArray *subObjects;
subObjects = [NSMutableArray new];
[self extractQueryRecords:subRecords toObjects:subObjects];
[sObj setValue:subObjects forField: key];
[subObjects release];
}
}
else
{
value = obj;
//NSLog(@"complex field: %@", value);
// we have a complex field, but not an object
if (enableFieldTypesDescribeForQuery)
{
value = [self adjustFormatForField:key forValue:value inObject:sObj];
}
[sObj setValue: value forField: key];
}
}
else
{
value = obj;
if (enableFieldTypesDescribeForQuery)
{
value = [self adjustFormatForField:key forValue:value inObject:sObj];
}
[sObj setValue: value forField: key];
}
}
return [sObj autorelease];
}
/*
Parses a result: this can be the main result but can also be nested in case of a subquery (nested query)
*/
- (void)extractQueryRecords:(NSArray *)records toObjects:(NSMutableArray *)objects
{
NSUInteger i;
NSUInteger batchSize;
NSMutableArray *keys;
if (records == nil || [records count] == 0)
return;
[records retain];
batchSize = [records count];
[logger log: LogInformative :@"[DBSoap extractQueryRecords] records size is: %d\n", batchSize];
/* now cycle all the records and read out the fields */
for (i = 0; i < batchSize; i++)
{
DBSObject *sObj;
NSDictionary *record;
record = [records objectAtIndex:i];
[logger log: LogDebug: @"[DBSoap query] record :%@\n", record];
sObj = [self extractQueryRecord:record];
[objects addObject:sObj];
}
[records release];
}
/*
<p>Parses a string from its type representation into the internal object, interpreting DBSFTypeObjects
valueStr: string value to parse, supposed in user format for TypeWrappers<br>
fieldName: field name<br>
objectName: string with the object name (used for describe)</p>
<p>If valueStr is nil or interpretation fails, returns an empty string</p>
*/
- (NSString *)interpretString:(NSString *)valueStr forField:(NSString *)fieldName forObject:(NSString *)objectName
{
DBSObject *objDetails;
NSString *resultStr;;
objDetails = nil;
if ([self enableFieldTypesDescribeForQuery])
{
/* check for described object cache */
objDetails = [[self sObjectDetailsDict] objectForKey:objectName];
if (!objDetails)
{
NSLog(@"*** describe Start***");
// since we are already inside the queryAll lock, we call the unlocked describe version
objDetails = [self _describeSObject:objectName];
NSLog(@"*** describe End***");
if (objDetails)
[[self sObjectDetailsDict] setObject:objDetails forKey:objectName];
}
}
resultStr = nil;
if (objDetails)
{
NSString *fieldType;
DBSFDataType *dbsfObj;
fieldType = [[objDetails propertiesOfField: fieldName] objectForKey:@"type"];
if ([fieldType isEqualToString:@"double"])
{
dbsfObj = [[[DBSFDouble alloc] initWithString:valueStr] autorelease];
resultStr = [dbsfObj stringValueSF];
}
else if ([fieldType isEqualToString:@"int"])
{
dbsfObj = [[[DBSFInteger alloc] initWithString:valueStr] autorelease];
resultStr = [dbsfObj stringValueSF];
}
else if ([fieldType isEqualToString:@"boolean"])
{
dbsfObj = [[[DBSFBoolean alloc] initWithString:valueStr] autorelease];
resultStr = [dbsfObj stringValueSF];
}
else if ([fieldType isEqualToString:@"currency"])
{
dbsfObj = [[[DBSFCurrency alloc] initWithString:valueStr] autorelease];
resultStr = [dbsfObj stringValueSF];
}
else if ([fieldType isEqualToString:@"percent"])
{
dbsfObj = [[[DBSFPercentage alloc] initWithString:valueStr] autorelease];
resultStr = [dbsfObj stringValueSF];
}
else if ([fieldType isEqualToString:@"date"])
{
dbsfObj = [[[DBSFDate alloc] initWithString:valueStr] autorelease];
resultStr = [dbsfObj stringValueSF];
}
else if ([fieldType isEqualToString:@"datetime"])
{
dbsfObj = [[[DBSFDateTime alloc] initWithString:valueStr] autorelease];
resultStr = [dbsfObj stringValueSF];
}
else // a type without wrapper, pass-through
{
resultStr = valueStr;
}
}
else
{ // no describe? again a string pass-through
resultStr = valueStr;
}
if (nil == resultStr)
resultStr = @"";
return resultStr;
}
- (NSArray *)_describeGlobal
{
GWSService *service;
NSMutableDictionary *headerDict;
NSMutableDictionary *sessionHeaderDict;
NSMutableDictionary *parmsDict;
NSMutableDictionary *queryParmDict;
NSDictionary *resultDict;
NSDictionary *queryResult;
NSDictionary *result;
NSDictionary *queryFault;
NSArray *sobjects;
NSMutableArray *objectList;
unsigned i;
/* prepare the header */
sessionHeaderDict = [NSMutableDictionary dictionaryWithCapacity: 2];
[sessionHeaderDict setObject: sessionId forKey: @"sessionId"];
[sessionHeaderDict setObject: @"urn:partner.soap.sforce.com" forKey: GWSSOAPNamespaceURIKey];
headerDict = [NSMutableDictionary dictionaryWithCapacity: 2];
[headerDict setObject: sessionHeaderDict forKey: @"SessionHeader"];
[headerDict setObject: GWSSOAPUseLiteral forKey: GWSSOAPUseKey];
/* prepare the parameters */
queryParmDict = [NSMutableDictionary dictionaryWithCapacity: 2];
[queryParmDict setObject: @"urn:partner.soap.sforce.com" forKey: GWSSOAPNamespaceURIKey];
parmsDict = [NSMutableDictionary dictionaryWithCapacity: 1];
[parmsDict setObject: queryParmDict forKey: @"describeGlobal"];
[parmsDict setObject: headerDict forKey:GWSSOAPMessageHeadersKey];
/* init our service */
service = [[DBSoap gwserviceForDBSoap] retain];
[service setURL:serverURL];
/* make the query */
resultDict = [service invokeMethod: @"describeGlobal"
parameters : parmsDict
order : nil
timeout : standardTimeoutSec];
[service release];
[logger log: LogDebug: @"[DBSoap describeGlobal] Describe Global dict is %lu big\n", [resultDict count]];
queryFault = [resultDict objectForKey:GWSFaultKey];
if (queryFault != nil)
{
NSDictionary *fault;
NSDictionary *faultDetail;
faultDetail = [queryFault objectForKey:@"detail"];
fault = [faultDetail objectForKey:@"fault"];
NSLog(@"fault: %@", fault);
[logger log: LogStandard :@"[DBSoap describeGlobal] exception code: %@\n", [fault objectForKey:@"exceptionCode"]];
[logger log: LogStandard :@"[DBSoap describeGlobal] exception message: %@\n", [fault objectForKey:@"exceptionMessage"]];
[[NSException exceptionWithName:@"DBException" reason:[fault objectForKey:@"exceptionMessage"] userInfo:nil] raise];
}
queryResult = [resultDict objectForKey:GWSParametersKey];
result = [queryResult objectForKey:@"result"];
// NSLog(@"result: %@", result);
objectList = [NSMutableArray arrayWithCapacity:1];
sobjects = [result objectForKey:@"sobjects"];
for (i = 0; i < [sobjects count]; i++)
{
NSMutableDictionary *sObj;
NSArray *propertiesArray;
NSMutableDictionary *propertiesDict;