We have different use case.
New version of FF accounting package (c2g v. 13) breaks our current S2S connection. Package has some logic expecting created user to be a real user, however, when records are shared through s2s connection, they are created by a fake "connection user" which doesn't really exist, so Account records now cannot be shared through s2s connection.
So we are working on SOAP Web Services to replace S2S functionality for update operation (for insert we can use manual accept of S2S).
I generally like some generic solution, where I can set field names dynamically, however, both Partner and Enterprise WDSL classes look giant, do not allow to set field names dynamically and use the limit of characters allowed in Apex Classes in Organization.
So I have made my own approach inspired by finding on the web.
This is version using imported WDSL files: ( partnerSoapSforceCom and sobjectPartnerSoapSforceCom ) - actually I have slightly modify that class to contain fields:
public class sobjectPartnerSoapSforceCom {
public class sObject_x {
public String type_x;
public String[] fieldsToNull;
public String Id;
private String[] type_x_type_info = new String[]{'type','urn:sobject.partner.soap.sforce.com',null,'1','1','false'};
private String[] fieldsToNull_type_info = new String[]{'fieldsToNull','urn:sobject.partner.soap.sforce.com',null,'0','-1','true'};
private String[] Id_type_info = new String[]{'Id','urn:sobject.partner.soap.sforce.com',null,'1','1','true'};
private String[] apex_schema_type_info = new String[]{'urn:sobject.partner.soap.sforce.com','true','false'};
private String[] field_order_type_info = new String[]{'type_x','Name','Phone','Website','fieldsToNull','Id'};
public String Phone;
private String[] Phone_type_info = new String[]{'Phone','urn:partner.soap.sforce.com','string','1','1','true'};
public String Website;
private String[] Website_type_info = new String[]{'Website','urn:partner.soap.sforce.com','string','1','1','true'};
public String Name;
private String[] Name_type_info = new String[]{'Name','urn:partner.soap.sforce.com','string','1','1','true'};
}
}
and this is utility class method:
global class WebS2SServices {
public class WebS2SDMLException extends Exception {}
/**
* up means abbreviation of Update-Patch
*/
webservice static void up(SObject[] scope) {
partnerSoapSforceCom.Soap con = new partnerSoapSforceCom.Soap();
partnerSoapSforceCom.LoginResult loginResult = con.login('login@domain.com', 'PasswordAndSECURITYTOKEN');
con.SessionHeader = new partnerSoapSforceCom.SessionHeader_element();
con.endpoint_x = loginResult.ServerUrl;
con.Sessionheader.sessionid = loginResult.sessionid;
List<sobjectPartnerSoapSforceCom.sObject_x> sObjects = new List<sobjectPartnerSoapSforceCom.sObject_x>();
System.debug(LoggingLevel.ERROR, '@@@ scope.size(): ' + scope.size() );
System.debug(LoggingLevel.ERROR, '@@@ scope: ' + scope );
for(Account ac:(Account[]) scope) {
sobjectPartnerSoapSforceCom.sObject_x tmpObj = new sobjectPartnerSoapSforceCom.sObject_x();
tmpObj.type_x = 'Account';
tmpObj.Id = ac.FF_Account_Id__c;
tmpObj.Name = ac.Name;
tmpObj.Phone = ac.Phone;
tmpObj.Website = ac.Website;
System.debug(LoggingLevel.ERROR, '@@@ ac: ' + ac );
System.debug(LoggingLevel.ERROR, '@@@ acsoap: ' + tmpObj );
sObjects.add(tmpObj);
}
//partnerSoapSforceCom.SaveResult[]srs = con.create(sObjects);
partnerSoapSforceCom.SaveResult[]srs = con.update_x(sObjects);
String s = '';
for ( partnerSoapSforceCom.SaveResult sr: srs ) {
if (!sr.success ) {
for ( partnerSoapSforceCom.Error e: sr.errors) {
s += 'SC: ' + e.statusCode + ' | EM: ' + e.message + ' | fields: ' + (e.fields == null ? '' : String.join(e.fields, '; '));
}
throw new WebS2SDMLException(s);
}
}
}
}
This is the version without using imported WDSL classes:
public static String[] login() {
//Beginning of XML Generation for Partner API SOAP Login Call
string bodyToSendLogin;
string outCallResultLogin;
string sessionID;
string userName = 'Me@Me.com'; //A great place to keep these values is in a custom object with an encrypted field for the password.
string password = 'MyPassword';
String loginURL;
//This beginning piece of XML can be re-used for the subsequent calls
String loginTemplate = '<?xml version="1.0" encoding="UTF-8"?><env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><env:Header /><env:Body><login xmlns="urn:partner.soap.sforce.com"><username>{0}</username><password>{1}</password></login></env:Body></env:Envelope>';
bodyToSendLogin = String.format(loginTemplate, new String[]{userName, password});
Boolean isSandbox = true;
if (isSandbox){ //Keep the setting on whether this is a production or sandbox in a custom object.
loginURL = 'https://test.salesforce.com/services/Soap/u/26.0';
}else{
loginURL = 'https://login.salesforce.com/services/Soap/u/26.0';
}
outCallResultLogin = makeHTTPCall(loginURL,bodyToSendLogin);
system.debug('outCallResultLogin Output: ' + outCallResultLogin);
String serverUrl = getValueFromXMLString(outCallResultLogin, 'serverUrl');
String session = getValueFromXMLString(outCallResultLogin, 'sessionId');
return new String[]{serverUrl, session};
//End of XML Generation for Partner API SOAP Login Call
}
private static string makeHTTPCall(string endPoint, string soapBody){
Http hLLogin = new Http();
HttpRequest reqLLogin = new HttpRequest();
reqLLogin.setTimeout(60000);
reqLLogin.setEndpoint(endPoint);
reqLLogin.setMethod('POST');
reqLLogin.setHeader('SFDC_STACK_DEPTH', '1');
reqLLogin.setHeader('SOAPAction','DoesNotMatter');
//reqLLogin.setHeader('User-Agent', 'SFDC-Callout/22.0');
reqLLogin.setHeader('Accept','text/xml');
reqLLogin.setHeader('Content-type','text/xml');
reqLLogin.setHeader('charset','UTF-8');
system.debug('Request: ' + reqLLogin);
reqLLogin.setBody(soapBody);
HttpResponse resLLogin = hLLogin.send(reqLLogin);
string outCallResultLogin = '';
outCallResultLogin = resLLogin.getBody();
return outCallResultLogin;
}
private static string getValueFromXMLString(string xmlString, string keyField){
string valueFound = '';
if(xmlString.contains('<' + keyField + '>') && xmlString.contains('</' + keyField + '>')){
try{
valueFound = xmlString.substringBetween('<' + keyField + '>', '</' + keyField + '>');
}catch (exception e){
system.debug('Error in getValueFromXMLString. Details: ' + e.getMessage() + ' keyfield: ' + keyfield);
}
}
return valueFound;
}
private static string makeHTTPPatchCall(string endPoint, String sessionId, string soapBody){
Http hLLogin = new Http();
HttpRequest reqLLogin = new HttpRequest();
reqLLogin.setTimeout(60000);
reqLLogin.setEndpoint(endPoint);
reqLLogin.setMethod('POST');
reqLLogin.setHeader('SFDC_STACK_DEPTH', '1');
reqLLogin.setHeader('Authorization', 'Bearer ' + sessionId );
reqLLogin.setHeader('SOAPAction','DoesNotMatter');
//reqLLogin.setHeader('User-Agent', 'SFDC-Callout/22.0');
reqLLogin.setHeader('Accept','text/xml');
reqLLogin.setHeader('Content-type','text/xml');
reqLLogin.setHeader('charset','UTF-8');
system.debug('Request: ' + reqLLogin);
reqLLogin.setBody(soapBody);
HttpResponse resLLogin = hLLogin.send(reqLLogin);
string outCallResultLogin = '';
outCallResultLogin = resLLogin.getBody();
return outCallResultLogin;
}
public static String upd(String soType, List<Map<String, String>> fieldsMaps ) {
String fieldTemplate = '<{0} xmlns="urn:sobject.partner.soap.sforce.com">{1}</{0}>';
String sobjectTemplate = '<sObjects><type xmlns="urn:sobject.partner.soap.sforce.com">{0}</type>{1}</sObjects>';
String scope = '';
for ( Map<String, String> fieldsMap: fieldsMaps ) {
String fieldValues = '';
for ( String key: fieldsMap.keySet() ) {
String value = fieldsMap.get( key );
fieldValues += String.format(fieldTemplate, new String[]{key, value});
}
scope += String.format(sobjectTemplate, new String[]{soType, fieldValues});
}
String[] susi = login();
String template = '<?xml version="1.0" encoding="UTF-8"?><env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><env:Header><SessionHeader xmlns="urn:partner.soap.sforce.com"><sessionId>{0}</sessionId></SessionHeader></env:Header><env:Body><update xmlns="urn:partner.soap.sforce.com">{1}</update></env:Body></env:Envelope>';
System.debug(LoggingLevel.ERROR, '@@@ xml request: ' + String.format(template, new String[]{susi[1], scope}) );
return makeHTTPPatchCall( susi[0], susi[1], String.format(template, new String[]{susi[1], scope}) );
}
/**
* upcs means abbreviation of Update-Patch using Custom Setting
*/
webservice static void upcs(SObject[] scope) {
Map<String, WebS2SFieldCopy__c> fieldsMap = WebS2SFieldCopy__c.getAll();
if ( fieldsMap.isEmpty() ) {
throw new WebS2SConfigException('Custom setting WebS2SFieldCopy__c is not setup');
}
List<Account> acct = (Account[]) scope;
List<Map<String, String>> fieldsValuesMaps = new List<Map<String, String>>();
for ( Account a: acct ) {
Map<String, String> fieldsValuesMap = new Map<String, String>();
fieldsValuesMap.put('Id', a.FF_Account_Id__c);
for ( WebS2SFieldCopy__c webS2SFieldCopy: fieldsMap.values() ) {
fieldsValuesMap.put( webS2SFieldCopy.Partner_Field_Name__c, String.valueOf( a.get( webS2SFieldCopy.Name ) ) );
}
fieldsValuesMaps.add( fieldsValuesMap );
}
String result = upd('Account', fieldsValuesMaps);
Boolean success = getValueFromXMLString(result, 'success') == 'true';
if ( !success ) {
String s = getValueFromXMLString(result, 'message');
throw new WebS2SDMLException(s);
}
}