1

We have continuous need to move some configuration related data from Salesforce Sandbox to Salesforce Production environment.

End users are not comfortable with using data loader and need some simple way within salesforce to move data from salesforce sandbox to salesforce production environment.

After looking at, How to connect Salesforce to Salesforce in Apex

and also considering the limitation that Salesforce-to-Salesforce can only be between 2 sandboxes or 2 production environments and not between Production and sandbox, Looks like the only way is to do this in Apex.

So I plan to - 1. get enterprise WSDL from Production environment 2. generate Apex class from this WSDL in Sandbox environment. 3. Use this apex class to connect to Production environment and perform query and insert operation using SOAP APIs. (this will be a apex callout from Sandbox environment)

Is this approach correct ? Has anyone tried to connect to other SF environment using WSDL and Apex callouts ?

Any insight on this would be very helpful.

  • I haven't used SOAP but I do use APEX REST API Callouts from org A into org B to fetch data from org B into org A. I use a scheduled job to do this but it could easily be on-demand. I do these calls from sandbox to prod orgs. – cropredy Jul 6 '15 at 19:49
  • @crop1645, If I understand right, are you using Apex HTTPRequest/HttpResponse classes in org A to call Apex REST API in org B? and you also have connected app setup in org B ? – apn Jul 6 '15 at 21:28
  • yes - that is correct; plus you need remote site settings in org A to allow callout to org B's domain name – cropredy Jul 6 '15 at 22:02
1

I can't speak for SOAP but APEX REST API works well SFDC org A to Org B

I've used a couple of approaches successfully

  1. APEX callout from Org A to Org B using Analytics API. In my case, it was to import Users to Territories - something that otherwise requires multiple SOQL calls. So, I built the report on Org B and used Analytics API to handle the JSON response (of course, you are limited to 2000 rows this way - which was fine for my use case
  2. APEX callout from Org A to Org B using REST API. Anything you can do in SOQL works here so you can get back a complex structure of multiple lookups and multiple child relationships. The caveats for this approach is you need to build in a queryMore mechanism and also to handle retrying if you get a timeout from Org B (the retry logic is more applicable to a scheduled solution rather than an on-demand solution where you can just tell the user to 'try again')

In either case, you'll need testmethods and clever callout mocks for good code coverage and asserts. I tend to use StaticResources for my mock responses so they can easily be edited in a text editor and run through a Json validator.

A sandbox Org A can call out to a production Org B (although I would only do this for queries, never DML).

You'll need a Connected App in Org B and Remote Site settings in Org A.

As far as SFDC is concerned, org A is making a callout to some remote system that returns JSON. As far as org B is concerned, it is providing standard SFDC REST API services to some authenticated client.

  • Looks like REST API would be better choice that SOAP API. I don't want to have apex proxy classes for SOAP API and worry about code coverage. – apn Jul 7 '15 at 16:20
1

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);
    }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.