Calling Salesforce Web Services Using Apex

Source code:

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.

  1. Go to Setup | Develop | API -> Click on "Generate Partner WSDL".
  2. Save the file and name it "Partner.wsdl".
  3. Go to Setup | Develop | Apex Classes -> Click on "Generate from WSDL".
  4. Select the "Partner.wsdl" file and click the "Parse" button.
  5. Accept the default class names and click the "Generate Apex code" button.
  6. 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.
  7. Copy all the partnerSoapSforceCom Apex code in the textbox.
  8. Go to Setup | Develop | Apex Classes -> New.
  9. Paste the partnetSoapSforceCom Apex code.
  10. Change [code apex]public partnerSoapSforceCom.DeleteResult[] delete(String[] ids)[/code] to [code apex]public partnerSoapSforceCom.DeleteResult[] deleteSObjects(String[] ids) [/code]
  11. Change [code apex]public partnerSoapSforceCom.UpsertResult[] upsert[/code] to [code apex]public partnerSoapSforceCom.UpsertResult[] upsertSObjects[/code]
  12. Change [code apex]public partnerSoapSforceCom.MergeResult[] merge[/code] to [code apex]public partnerSoapSforceCom.MergeResult[] mergeSObjects[/code]
  13. Change [code apex]public partnerSoapSforceCom.SaveResult[] update[/code] to [code apex]public partnerSoapSforceCom.SaveResult[] updateSObjects[/code]
  14. Change [code apex]public partnerSoapSforceCom.UndeleteResult[] undelete[/code] to [code apex]public partnerSoapSforceCom.UndeleteResult[] undeleteSObjects[/code]
Now you have an Apex Partner class.

Next, let's generate an Apex class for the Apex Web service.

  1. Go to Setup | Develop | API -> click the "Generate Apex WSDL" button.
  2. Save the file as "Apex.wsdl".
  3. Go to Setup | Develop | Apex Classes -> Click on "Generate from WSDL".
  4. Select the "Apex.wsdl" file and click the "Parse" button.
  5. 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.

  1. Go to Setup | Security Controls | Remote Site Settings
  2. Click "New Remote Site".
  3. Enter "SalesforceLogin" for the Site Name.
  4. Enter either "https://login.salesforce.com" or "https://test.salesforce.com" as the Remote Site URL depending on your org.
  5. 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.
  6. Go to Setup | Security Controls | Remote Site Settings
  7. Click "New Remote Site".
  8. Enter "SalesforceApex" for the Site Name.
  9. Enter "https://-api.salesforce.com" where 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".
  10. 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.

[code apex] 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 = ''; String password = ''; 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 */ [/code]

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