Search

Sunday 25 November 2012

REST Call To Enterprise WSDL From Apex Class


First of all I need to give you a bit of background to why I would even want to do this.
I had a problem where I needed to get all the objects and their fields of an org and their full xml, similar to what you see in Eclipse. This was going to be part of a new App for the Appexchange I was working on so it needed to work in any org of any data model.
So the obvious problem here would be that I couldn't determine how many objects an org actually had, so if I used Schema.describes I could easily hit 100 limit of calls.
So I decided that I would need to use the Enterprise wsdl which is a strictly typed api containing the full data model of the org. Perfect! But I didnt want to call out of Salesforce to some service, just to call back to give me the results. I wanted this to be completely native to Salesforce.

The solution as a theory would be to use Rest to call the Enterprise wsdl natively within Salesforce.
Problem with this is many fold. What is the xml that needs to be sent to Rest? What is the best way of connecting to Salesforce, I could use Userinfo.getSessionid(), but that wouldn't necessarily guarantee that the user had api enabled. So I decided to use the partner wsdl to first connect to Salesforce with a specific user, which will be gauranteed to always be active, always have api enabled and be a sys admin to have access to the entire data structure.

So first I needed to consume the partner wsdl in Salesforce. Well I worked out a why of doing this with a bit of messing around and then later I found an article that explained what to do,luckily it was exactly what I did.

http://developer.force.com/cookbook/recipe/calling-salesforce-web-services-using-apex

Now I could login into Salesforce with a user I specify

String session_ID;
       partnerSoapSforceCom.Soap myPartnerSoap;
if (myPartnerSoap == null) {
RestLocalLogin__c locallogin = RestLocalLogin__c.getInstance('login'); //custom setting
system.debug('###login ' +locallogin.username__c + ' '+locallogin.password__c);
if (locallogin != null){
myPartnerSoap = new partnerSoapSforceCom.Soap();//partner wsdl consumed

// call login
partnerLoginResult = myPartnerSoap.login(locallogin.username__c,locallogin.password__c);
system.debug('###partnerLoginResult ' + partnerLoginResult);
// from here, the session id can be accessed via partnerLoginResult.sessionId

// now construct a SessionHeader element for your webservice and set the session id
system.debug('session ' +partnerLoginResult);

session_ID = partnerLoginResult.sessionId;
}
else
session_ID = userinfo.getSessionId();
}



I looked at the Salesforce documentation of how to make a Rest call to the Enterprise wsdl. My advice, dont, because the Salesforce documentation is WRONG. I spent some time just trusting Salesforce on this but my good friend Tomasz Duda pointed out that the Salesforce documentation was incorrect.

With a bit of playing around as a test I got:-

Http http = new Http();
HttpRequest req = new HttpRequest();
req.setEndpoint('https://cs7.salesforce.com/services/Soap/c/26.0');
req.setMethod('POST');
req.setHeader('Content-Type', 'text/xml; charset=utf-8');
req.SetHeader('SOAPAction','""');

String body = '<?xml version="1.0" encoding="utf-8"?>' +
+'<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:enterprise.soap.sforce.com">' +
+'<soap:Header><urn:SessionHeader><urn:sessionId>' + UserInfo.getSessionId() +'</urn:sessionId></urn:SessionHeader></soap:Header>'
+'<soap:Body><urn:describeGlobal /></soap:Body></soap:Envelope>';

req.setBody(body);

HTTPResponse res = http.send(req);
String output = res.getBody();
System.debug(output);


Now I needed to construct the xml to make specific calls. So first I needed to consume the enterprise wsdl in Salesforce so I could see how it was constructed. Well I could have made a java class outside of Salesforce using the wsdl, but I was intrigued to see if I could do this as well.


To consume enterprise wsdl into SF directly you get this error
Unsupported schema type: {http://www.w3.org/2001/XMLSchema}anyType

So follow these steps
1. Export enterprise wsdl
2. Replace all occurrences of anyType with 'string' in enterpriseSoapSforceCom
3. Now try to create apex classes from wsdl - this will fail but you will be able to copy contents of each apex class it tries to create
4. Go to Eclipse create class sobjectEnterpriseSoapSforceCom but comment out all public enterpriseSoapSforceCom and save
5. Then change any methods like merge() etc in enterpriseSoapSforceCom to mergeRecord() which cause problems, then save
6. Go back to sobjectEnterpriseSoapSforceCom class and remove comments and save again

Done

Now you can see easily the individual methods in the Apex class and you can now easily construct your Rest xml requests.

Note: Replace the UserInfo.getSessionId() with the session id from logging in using the partner wsdl
This Rest call with grab all field xml for custom objects VVV__c and XXX__c


String body = '<?xml version="1.0" encoding="utf-8"?>' +
+'<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:enterprise.soap.sforce.com">' +
+'<soap:Header><urn:SessionHeader><urn:sessionId>' + UserInfo.getSessionId() +'</urn:sessionId></urn:SessionHeader></soap:Header>'
+'<soap:Body><urn:describeSObjects><urn:sObjectType>VVV__c</urn:sObjectType><urn:sObjectType>XXX__c</urn:sObjectType></urn:describeSObjects></soap:Body></soap:Envelope>';

Note: describeSObjects is the name of the function call in the consumed Enterprise wsdl class and sObjectType is the name of the parameter passed to the function


This Rest call will get the fields xml for just 1 object for Project__c - The function is describeSObject()

String body = '<?xml version="1.0" encoding="utf-8"?>' +
+'<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:enterprise.soap.sforce.com">' +
+'<soap:Header><urn:SessionHeader><urn:sessionId>' + UserInfo.getSessionId() +'</urn:sessionId></urn:SessionHeader></soap:Header>'
+'<soap:Body><urn:describeSObject><urn:sObjectType>Project__c</urn:sObjectType></urn:describeSObjects></soap:Body></soap:Envelope>';


This Rest call can be used to get names of all objects in the org - The function is describeGlobal()

String body = '<?xml version="1.0" encoding="utf-8"?>' +
+'<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:enterprise.soap.sforce.com">' +
+'<soap:Header><urn:SessionHeader><urn:sessionId>' + UserInfo.getSessionId() +'</urn:sessionId></urn:SessionHeader></soap:Header>'
+'<soap:Body><urn:describeGlobal /></soap:Body></soap:Envelope>';




No comments:

Post a Comment

Note: only a member of this blog may post a comment.