Sunday, February 15, 2015

Salesforce Integration - Creating XML Request using DOM + Custom Settings


Recently I got a requirement from one of my customer where I need to create the XML request and pass the same to a third party application using SOAP protocol. Initially I though it is very easy to do with Apex DOM APIs, but then I came to know that the other party (developer from that Third Party application) change their application quite frequently and as a result they need XML response in a different structure everytime they made some changes. Definitely my customer was very much irritated with this as they need to do code changes every time the other party do the change. So the important part of the requirement was to design something or do something so that with minimum (preferably no code change) changes Salesforce admin can handle the request from other party.

Definitely quite a good challenge. Then after some discussion with the other party and Salesforce admin, I designed the application. The most complex part was how to create the XML Request dynamically(without any code change from Salesforce side) with new XML structure requirement.

In this post, I will explain how I implemented the logic of creating XML Request with the help of Custom Settings. Yes, you are correct. Custom Settings is so powerful and important that you can play with it in many different ways and this post is one of the way.

Let's get started. Below is the XML request format for an Account record -

Now below are the Custom Settings I have created -
Custom Settings: SD Namespaces
Description: This custom settings will holds all the namespaces required in the request XML. If the other party adds a new namespace, Salesforce admin can always add the new namespace. No code change is required for addition of new namespaces or modification of existing one or deletion of new one.

Custom Settings: SD Service Fields
Description: This custom settings stores the details and structure of messages used in the SD Integration process. The field names are quite self explanatory. This custom settings is very important and later in the post, I will show how a simple change in this custom settings can completely change the XML Request structure.


Now let's write down the apex code -
public class RequestController {
    
    public void generateXmlRequest(){
        DOM.Document body = new DOM.Document();
        String prefix = 'SD';
        String nameSpace = selectedOperation;
        String method = 'createAccountRequest';
        
        DOM.XmlNode root = body.createRootElement('topResponseTag', nameSpace, prefix);
        
        List<String> allRequieredFields = fetchAccountFields();
        String requiredFields = String.join(allRequieredFields, ',');
        
        List<Account> allAccounts = Database.query('SELECT ' + requiredFields + ', (select id, name, email from Contacts) FROM ACCOUNT');
        
        List<SD_Service_Fields__c> serviceFields = [
                SELECT Name, Request_Name__c, Sequence__c, Namespace_Prefix__c, Parent_Node__c, Type__c, XmlLabel__c, Source_Object__c, Field_Name__c
                FROM SD_Service_Fields__c 
                WHERE Request_Name__c = :method
                ORDER BY Sequence__c];
        System.Debug('Found: ' + serviceFields.size());
        
        //Add Account Details one by one
        for(Account eachAccount : allAccounts){
            Dom.XmlNode accountNode = root.addChildElement(method, nameSpace, prefix);
            appendSObjectToXml(accountNode, eachAccount, serviceFields);
        }
    }
    
    private List<String> fetchAccountFields(){
        List<SD_Service_Fields__c> allServiceFields = [
                SELECT Name, Field_Name__c FROM SD_Service_Fields__c 
                WHERE Type__c = 'Field' and Source_Object__c = 'Account'];
        List<String> allFields = new List<String>();
        for(SD_Service_Fields__c aServiceField : allServiceFields){
            allFields.add(aServiceField.Field_Name__c);
        }
        return allFields;
    }
    
    private void appendSObjectToXml(Dom.XmlNode accountNode, Account eachAccount, List<SD_Service_Fields__c> serviceFields){
        Map<string, dom.xmlnode> xmlnodemap = new Map<string, dom.xmlnode>();
        //get Namespaces from custom setting
        Map<String,SD_Namespaces__c> allNameSpaces = SD_Namespaces__c.getall();
            
        for(SD_Service_Fields__c field : serviceFields){
            if(field.Type__c == 'XmlNode'){
                if(field.Parent_Node__c == 'root'){
                    xmlnodemap.put(field.XmlLabel__c, accountNode);
                }else{
                    xmlnodemap.put(field.XmlLabel__c, 
                                    xmlnodemap.get(field.parent_Node__c).addChildElement(field.XmlLabel__c, allNameSpaces.get(field.Namespace_Prefix__c).namespace__c, field.Namespace_Prefix__c));
                }
            }else if(field.Type__c == 'Field'){
                String innerText = '';
                Boolean isNull = true;
                
                //get the required field 
                String fieldRequired = field.Field_Name__c;
                sObject anAccount = eachAccount;
                Object value = anAccount.get(fieldRequired);
                System.Debug('Field: ' + fieldRequired + ' Value: ' + String.valueOf(value));
                
                if(value != null){
                    innerText = String.valueOf(value);
                    isNull = false;
                }
                
                if(!isNull){
                    DOM.XmlNode xmlField = xmlnodemap.get(field.parent_Node__c).addChildElement(field.xmlLabel__c, allNameSpaces.get(field.Namespace_Prefix__c).namespace__c, field.Namespace_Prefix__c);                                
                    xmlField.addTextNode(innerText); 
                }
            }else if(field.Type__c == 'Constant'){
                
            }
        }
    }
    
    public List<SelectOption> getPossibleOperations(){
        List<SelectOption> possibleOperations = new List<SelectOption>();
        
        //Fetch all namespaces available from Custom Settings
        Map<String,SD_Namespaces__c> allNameSpaces = SD_Namespaces__c.getall();
        
        //Create the select option
        for(String operationName: allNameSpaces.keySet()){
            possibleOperations.add(new SelectOption(allNameSpaces.get(operationName).Namespace__c, allNameSpaces.get(operationName).Name));
        }
        
        return possibleOperations;
    }
}

The above code will help to create the XML Request complete based on the Custom Settings.
The generated XML Request snippet is given below -

Now let's see what we need to do if we want to change the XML Structure

Let's say now we have to make the below changes in the XML request structure  -
  • Pass "Name" field information instead of "AccountNumber" in the first child one.
  • Pass the below account details in sequence under objectDetails XmlNode -
    • Phone
    • Fax
    • Type
    • Rating
In order to do the above changes, we need to do changes only in Custom Settings - "SD Service Fields" as shown below - 
The generated XML is shown below -


Great!!. My customer is very happy with this approach. Appreciate your feedback. Thanks,

0 comments:

Post a Comment