Problem
Most, if not all, of the Web service examples and tutorials use an external programming language to call/invoke the salesforce Web services. However, this requires external hardware and software to run and is not native to the salesforce platform. With a few tweaks to the salesforce Web services' wsdl and Apex generated classes, it's possible to invoke them through native Apex.
Solution
To begin, you always need to have either an Apex Partner or Enterprise Web service class so that you can login and get a session id, which will be used in any future Web service calls.
Let's generate an Apex Partner Web service class first.
- Go to Setup | Develop | API -> Click on "Generate Partner WSDL".
- Save the file and name it "Partner.wsdl".
- Go to Setup | Develop | Apex Classes -> Click on "Generate from WSDL".
- Select the "Partner.wsdl" file and click the "Parse" button.
- Accept the default class names and click the "Generate Apex code" button.
- Now, you'll see the following message "The following generated class(es) have compilation errors:
Error: partnerSoapSforceCom
Error: unexpected token: 'delete' at 670:51". This results from some of the partner web methods being the same as Apex DML keywords. To resolve this, simply change the name of the Apex method names.
- Copy all the partnerSoapSforceCom Apex code in the textbox.
- Go to Setup | Develop | Apex Classes -> New.
- Paste the partnetSoapSforceCom Apex code.
- Change
public partnerSoapSforceCom.DeleteResult[] delete(String[] ids)
to public partnerSoapSforceCom.DeleteResult[] deleteSObjects(String[] ids)
- Change
public partnerSoapSforceCom.UpsertResult[] upsert
to public partnerSoapSforceCom.UpsertResult[] upsertSObjects
- Change
public partnerSoapSforceCom.MergeResult[] merge
to public partnerSoapSforceCom.MergeResult[] mergeSObjects
- Change
public partnerSoapSforceCom.SaveResult[] update
to public partnerSoapSforceCom.SaveResult[] updateSObjects
- Change
public partnerSoapSforceCom.UndeleteResult[] undelete
to public partnerSoapSforceCom.UndeleteResult[] undeleteSObjects
Now you have an Apex Partner class.
Next, let's generate an Apex class for the Apex Web service.
- Go to Setup | Develop | API -> click the "Generate Apex WSDL" button.
- Save the file as "Apex.wsdl".
- Go to Setup | Develop | Apex Classes -> Click on "Generate from WSDL".
- Select the "Apex.wsdl" file and click the "Parse" button.
- Keep the default "soapSforceCom200608Apex" name and click the "Generate Apex code" button.
Now, you should see the following message "The following generated class(es) compiled successfully with no errors: soapSforceCom200608Apex", which indicates that you now have an Apex class for the Apex Web service.
Before we can use some code to demonstrate that this works, we have to configure our org so that we're allowed to call the Web services. Not a biggie.
- Go to Setup | Security Controls | Remote Site Settings
- Click "New Remote Site".
- Enter "SalesforceLogin" for the Site Name.
- Enter either "https://login.salesforce.com" or "https://test.salesforce.com" as the Remote Site URL depending on your org.
- Click Save. Now, you have granted your org the ability to login, but you still have to grant it access to the correct salesforce web node to allow the Apex Web service to be called, which we'll do next.
- Go to Setup | Security Controls | Remote Site Settings
- Click "New Remote Site".
- Enter "SalesforceApex" for the Site Name.
- Enter "https://<Prefix>-api.salesforce.com" where <Prefix> is the salesforce sub-domain you're using, which can be found by looking at the URL in your browser. Since I'm on "https://na12-api.salesforce.com", I would enter "https://na12-api.salesforce.com".
- Click "Save"
Now, we're ready to run a quick example that will run all the unit tests in our org in a synchronous fashion.
partnerSoapSforceCom.Soap sp = new partnerSoapSforceCom.Soap();
/* For demonstration purposes only, enter your credentials on the following
lines, but if you're going to use this a lot or in production, encrypt your credentials and store them somewhere and then decrypt them here.
*/
String username = '<Your username here>';
String password = '<Your password here>';
partnerSoapSforceCom.LoginResult loginResult = sp.login(username, password);
system.debug(' loginResult ' + loginResult);
soapSforceCom200608Apex.Apex apexWebSvc = new soapSforceCom200608Apex.Apex();
soapSforceCom200608Apex.SessionHeader_element sessionHeader = new soapSforceCom200608Apex.SessionHeader_element();
sessionHeader.sessionId = loginResult.sessionId;
// The Web services have a maximum timeout of 2 minutes. The timeout value
// is in milliseconds.
apexWebSvc.timeout_x = 120000;
apexWebSvc.SessionHeader = sessionHeader;
soapSforceCom200608Apex.RunTestsRequest testsRequest = new soapSforceCom200608Apex.RunTestsRequest();
testsRequest.allTests = true;
soapSforceCom200608Apex.RunTestsResult testResults = apexWebSvc.runTests(testsRequest);
/* Do something worthwhile with the test results here */
Discussion
This is just the tip of the iceberg, since we're only using the Apex Web service. With a little bit of effort, you can generate an Apex Metadata class for the Metadata Web service. This could "theoretically", for example, let you deploy code from one org to another using a "deployment" org without the need of the migration tool.
Another example would be creating an automated testing scheduled job, using the above code as a starting point, and emailing the results to your development team. Keep in mind that if you have a lot of tests, it may take longer than 120 seconds and this approach won't work. You'll have to switch to the asynchronous "ApexTestQueueItem" and "ApexTestResult" objects, but they don't currently provide nearly as much information as the synchronous "RunTestsResult" object.
From here, try out different things and please share your findings. I'm certain I'm not the only interested party.
I hope this helps and happy coding.
Luke
Recipe Activity - Please Log in to write a comment
Thanks for explaining in detail !
Oops link got corrupted, https://github.com/financialforcedev/apex-mdapi
Steve, take a look at this, ttps://github.com/financialforcedev/apex-mdapi, I've taken the pain out of importing the Metadata WSDL and provided some sample code to help. That said if you want to do a describeGlobal that is already supported in native Apex. Take a look at the Apex develoeprs guide for this.
Id also like to call the enterprise wsdl from Rest within Salesforce, which Ive been able to do, but I've only been able to call describeglobal. Id also like to call to get each field of every object.
The xml Im using is
This is fine to get describeGlobal but Im not sure what to pass to get the fields?
I'd like to consume the metadata api in Salesforce but Im also having issues doing this, not sure what parts of the wsdl I should change, can anyone help
test
https://github.com/financialforcedev/apex-mdapi
I have been successful in getting parts of the Metadata API to work from Apex.
I've uploaded the code and a write up to Github, include pros and cons of this approach.
Enjoy!
Hi Mohammed,
Did you manage to tweak the Metadata API and call it from Apex ?
It is nice.. i tried and it is working for me.
Thanks
Somes
I'm totally impressed!
It's a very good article.
Nice!... But I'd really like to do the same with the Enterprise wsdl... can you?
I get a different erros, as noted above by Ponybai.
Thanks
David
It's wonderful!
I have successfully import Parter WSDL and Apex WSDL.
But when I import Enterprise WSDL and Metadata WSDL,there have error happened.
ERROR MESSAGE:
Apex Generation Failed
Unsupported schema type: {http://www.w3.org/2001/XMLSchema}anyType
Apex Generation Failed
Class name 'Metadata' already in use. Please edit WSDL to remove repeated names
Awesome!!
Nicely written recipe and just what I was looking for to link a few of my orgs together!