Friday, 24 October 2014

Introduction to Apex Web Services and Callouts

Apex Code supports the ability to expose Apex methods as a Web service. This article will refer to this as 'Apex Web Services.' Apex also supports the ability to invoke external web services and this article will refer to this concept as 'Callouts.' The former is involved in creating a web service that a client can invoke, while the latter is invoking an external web service.
For Apex Web Services, Apex supports the ability to write Apex logic and expose the logic as a web service. Therefore an external application can invoke this Apex web service to perform custom logic. The next section of this article will go into the syntax and some examples of how to enable an Apex Web Service.
With 'Callouts', where Apex invokes an external web service, Apex provides integration with Web services that utilize SOAP and WSDL, or HTTP services (RESTful services). Apex supports the importing of WSDLs to auto-generate the corresponding Apex classes. Additionally, Apex supports HTTP services to use HTTP Request and Response objects to invoke the external web service. These concepts are covered in theApex Callouts section.
For a more general overview of integration options on the Force.com platform, see Integrating with the Force.com Platform.

Remote Site Administration and Security

Before any Apex callout can call an external site, that site must be registered in the Remote Site Settings page, or the call will fail. The platform, by default, prevents calls to unauthorized network addresses.
To access the page, click Setup | Security Controls | Remote Site Settings. This page displays a list of any remote sites already registered and provides additional information about each site, including remote site name and URL.
RemoteSite.jpg
For security reasons, the platform restricts the outbound ports you may specify to one of the following:
  • 80: This port only accepts HTTP connections.
  • 443: This port only accepts HTTPS connections.
  • 1024–66535 (inclusive): These ports accept HTTP or HTTPS connections.
Please make sure to add your external site domains to the Remote Site Settings prior to invoke any third party web services.

Apex Web Services

Apex Web Services allow you to write Apex logic and expose the logic as a web service that an external application can invoke. This allows you to expose your Apex scripts so that external applications can access your code and your Force.com application. In other words, you can write you own custom web services with Apex.
Let's take the example of wanting to create a custom Apex Web Service to handle the integration between a custom Account Planning application and Force.com. Therefore, the requirement is to enable an Apex Web Service to receive an Account Plan from this external application and synchronize the data in Force.com. The following sections will walk through building the necessary Apex Classes to enable the Apex Web Service for creating Account Plans.

Syntax

There are just a few simple syntactical steps to enable an Apex method as a custom web service.
1. First, create and define an Apex class as global. Add the global access identifier to the class definition as shown below.
1global class AccountPlan {
2 
3}
The global access modifier declares that the class is visible to all Apex scripts everywhere. This means the class can be used by any Apex code, not just the Apex in the same application.

2. Secondly, create and define an Apex method using both the static and webservice modifiers.
1global class AccountPlan {
2 
3      webservice static Plan createAccountPlan(Plan vPlan) {
4          
5               //add logic here......
6       }
7 
8}
That's it. The Force.com platform will automatically provision the web service for you. To find the details of the web service, you can use the automatically created WSDL describing the web service.
To access the generated WSDL, login and go to Setup | App Setup | Develop | Apex Classes. Find the specific class where the web service is defined and click on the WSDL hyperlink. This will download the WSDL which can then be imported into the external application(s) that will be invoking the web service.

Define Web Service Member Variables or Classes

Now let's add to this Account Planning scenario. Since an external application is invoking the Apex Web Service, called createAccountPlan(), it is ideal if the external application did not have to transform its Account Plan data structure to an object in the Force.com schema. It would be ideal if the external application could simply pass its data representation of an Account Plan to the Apex Web Service and the Apex Web Service would have the logic to translate it to the proper Force.com object model.
The following section illustrates how to expose member variables and inner Apex classes to be accessed by external applications. In more detail, by exposing the member variables and inner Apex Classes with the webservice modifier, those attributes will be included in the custom Apex WSDL created by this Web service-enabled Apex Class. Therefore, the external application that imports the WSDL has access to operate against those members or objects as it would any other data type or object.
01global class AccountPlan {
02   
03   webservice String area;
04   webservice String region;
05    
06   //Define an object in apex that is exposed in apex web service
07   global class Plan {
08      webservice String name;
09      webservice Integer planNumber;
10      webservice Date planningPeriod;
11      webservice Id planId;
12   }
13 
14   webservice static Plan createAccountPlan(Plan vPlan) {
15        
16       //A plan maps to the Account object in salesforce.com.
17       //So need to map the Plan class object to Account standard object
18       Account acct = new Account();
19       acct.Name = vPlan.name;
20       acct.AccountNumber = String.valueOf(vPlan.planNumber);
21       insert acct;
22        
23       vPlan.planId=acct.Id;
24       return vPlan;
25  }
26   
27 
28}
Now, the WSDL auto-generated by Apex for this class will contain 2 additional String members for Area and Region. Additionally, the WSDL will contain a new object for Plan since the inner class Plan has members annotated with the webservice modifier.
All together, the external application can import this WSDL and instantiate those objects and pass them into the web service as appropriate. In the example above, the external application can build a Plan object and pass it as the input argument for the createAccountPlan web service.

Important Considerations

When using the webservice keyword, keep the following considerations in mind:
  • You can use the webservice modifier to define top-level, outer class methods and variables, and member variables of an inner class. It can't be used on a class itself, or an interface or interface methods or variables.
  • An Apex trigger can execute a callout when the callout is invoked within a method defined as asynchronous: that is, defined with the @future keyword. The @future annotation signifies that the Apex method executes asynchronously. For more information on the @future annotation, please read the documentation.
  • All classes that contain methods or variables defined with the webservice keyword must be declared as global. If a method, variable or inner class is declared as global, the outer, top-level class must also be defined as global.
  • You must define any method that uses the webservice keyword as static.
  • Because there are no SOAP analogs for certain Apex elements, methods defined with thewebservice keyword cannot take the following elements as parameters. While these elements can be used within the method, they cannot be used as return values.
Maps
Sets
Pattern objects
Matcher objects
Exception objects
  • You must use the webservice keyword with any member variables that you want to expose as part of a web service. You should not mark these member variables as static. Please see thissection for more information.

Apex Callouts

Whereas Apex Web Services allows an external application to invoke Apex methods through web services, Apex Callouts enable Apex to invoke external web services. This allows you to connect to 3rd party web services such as Google, Amazon, Facebook, and any other external web service.
There are two main ways to develop callouts with Apex: (a) Import a WSDL into Apex (b) HTTP (RESTful) Services classes.

WSDL2Apex

WSDL2Apex allows for Apex classes to be automatically generated from a WSDL document. The generating Apex classes automatically handle the construction of the SOAP XML, the data transmission, and parsing the response XML into Apex objects. So instead of developing the logic to construct and parse the XML of the web service messages, let the Apex classes generated by WSDL2Apex internally handle all of that overhead. If you are familiar with WSDL2Java or importing a WSDL as a Web Reference in .NET, this is similar functionality for Apex.
To import a WSDL file into Apex, login, go to Setup | Develop | Apex Classes, and click Generate from WSDL.
WSDL2Apex.jpg
The successfully generated Apex class includes stub and type classes for calling the third-party Web service represented by the WSDL document. These classes allow you to call the external Web service from Apex.
There are some caveats:
  • The WSDL document may not exceed a maximum size. If it does, try and remove any unnecessary methods or data types from the WSDL. See the online documentation for the current size limit.
  • The auto-generated Apex classes may not exceed a maximum size. If the generated class does exceed this limit, WSDL2Apex will generate the Apex code, display the code in the salesforce.com user interface along with the appropriate error message. It will not be able to save it. But again, since the code is generated in the user interface, a workaround is to copy the Apex code that is generated and break it out into 2 or more Apex classes.
  • If a WSDL document contains an Apex reserved word, the word is appended with _x when the Apex class is generated.

WSDL2Apex Code Samples

This section shows an example of an WSDL2Apex generated Apex method that invokes an Amazon Web Service.
01public S3.Status DeleteObject(String Bucket,String Key,StringAWSAccessKeyId,DateTime Timestamp,String Signature,String Credential) {
02          S3.DeleteObject_element request_x = new S3.DeleteObject_element();
03          S3.DeleteObjectResponse_element response_x;
04          request_x.Bucket = Bucket;
05          request_x.Key = Key;
06          request_x.AWSAccessKeyId = AWSAccessKeyId;
07          request_x.Timestamp = Timestamp;
08          request_x.Signature = Signature;
09          request_x.Credential = Credential;
10          Map<String, S3.DeleteObjectResponse_element> response_map_x = newMap<String, S3.DeleteObjectResponse_element>();
11          response_map_x.put('response_x', response_x);
12          WebServiceCallout.invoke(
13            this,
14            request_x,
15            response_map_x,
16            new String[]{endpoint_x,
17            '',
18            'http://s3.amazonaws.com/doc/2006-03-01/',
19            'DeleteObject',
20            'http://s3.amazonaws.com/doc/2006-03-01/',
21            'DeleteObjectResponse',
22            'S3.DeleteObjectResponse_element'}
23          );
24          response_x = response_map_x.get('response_x');
25          return response_x.DeleteObjectResponse;
26      }
By calling this Apex method, a web service callout will be invoked, call the Amazon S3 web service, and this Apex method will return the results in an Apex Class called Status. Notice how there is no XML construction or XML parsing involved to invoke this web service. Instead, that is all handled internally within Apex. The actual web service is invoked by the line of code that executes the Apex methodWebServiceCallout.invoke. In general, you needn't ever look at these automatically generated Apex classes.

HTTP Header Support

The WSDL2Apex generated code supports HTTP Headers. For example, you can use this feature to set the value of a cookie in an authorization header. To set HTTP headers, add inputHttpHeaders_x and outputHttpHeaders_x to the stub.
Here's an example that sets input HTTP Headers:
01docSample.DocSamplePort stub = new docSample.DocSamplePort();
02stub.inputHttpHeaders_x = new Map<StringString>();
03 
04//Setting a basic authentication header
05stub.inputHttpHeaders_x.put('Authorization''Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==');
06 
07//Setting a cookie header
08stub.inputHttpHeaders_x.put('Cookie''name=value');
09 
10//Setting a custom HTTP header
11stub.inputHttpHeaders_x.put('myHeader''myValue');
12....
Here's one that accesses output HTTP Headers information:
01docSample.DocSamplePort stub = new docSample.DocSamplePort();
02stub.outputHttpHeaders_x = new Map<StringString>();
03String input = 'This is the input string';
04String output = stub.EchoString(input);
05 
06//Getting cookie header
07String cookie = stub.outputHttpHeaders_x.get('Set-Cookie');
08 
09//Getting custom header
10String myHeader = stub.outputHttpHeaders_x.get('My-Header');
There is additional information, including samples, in the Apex Code Language Guide.

Debugging WSDL2Apex

As mentioned earlier, the key benefit of using WSDL2Apex is to let Apex handle all of the hard work of invoking a web service. But during development, it is often key to debug your Apex logic including getting the output of the Apex generated SOAP XML messages (requests and responses).
Here's a way to troubleshoot WSDL2Apex callouts. It requires familiarity with the Force.com IDE.
The debugging leverages the Execute Anonymous view within the Force.com IDE. So to start, open up the Execute Anonymous view in the IDE, and write a test case in Apex code in the dialog window. At a minimum, the test case should prepare the test data required to invoke the Apex method that performs the callout.
ExecuteAnon2.jpg
Next, when the test case is ready to be executed, click the 'Execute Anonymous' button and the Results panel below will be populated with the results, including the Callout details.ExecuteAnon1.jpg
So using a combination of the Force.com IDE, Execute Anonymous view, and writing test cases to invoke the Apex callout, you can output the actual SOAP XML messages to help assist with debugging and troubleshooting, as you can see in the above screenshot.

Supported WSDL Features

Apex supports only the document literal wrapped WSDL style and the following schema types:
  • String
  • Int
  • Double
  • Float
  • Long
  • Boolean
  • Short
  • Datetime
  • Date
  • anyURI
  • integer
  • language
  • Name
  • NCName
  • NMTOKEN
  • NMTOKENS
  • normalizedString
  • token
  • unsignedLong
  • unsignedShort
  • nonPositiveInteger
  • positiveInteger
Reference the online documentation for further clarification and the definitive list.
Apex also supports the following schema constructs:
  • Element
  • Sequence
  • All (in Apex Code saved using API version 15.0 or later)
  • Annotation (in Apex Code saved using API version 15.0 or later)
  • Attributes (in Apex Code saved using API version 15.0 or later)
  • Choice (in Apex Code saved using API version 15.0 or later)
  • Ref
The following data types are only supported when used as call ins, that is, when an external Web service calls an Apex Web service method. These data types are not supported as callouts, that is, when an Apex Web service method calls an external Web service.
  • blob
  • decimal
  • enum
Apex does not support any other WSDL constructs, types, or services, including:
  • RPC/encoded services
  • WSDL files with mulitple portTypes, multiple services, or multiple bindings
  • WSDL files with imports
  • Any schema types not documented in the previous table

HTTP (RESTful) Services

Apex supports HTTP Services with several built in Apex classes to creating HTTP requests like GET, POST, PUT, and DELETE. There are 3 main classes:
  • HTTP Class: Use this class to initiate an HTTP request and response.
  • HttpRequest Class: Use this class to programmatically create HTTP requests.
  • HttpResponse Class: Use this class to handle the HTTP response returned by the HTTP.Send() operation.
Together, these classes support the ability to develop HTTP request/response functionality within Apex. Using these HTTP classes supports the ability to integrate to REST-based services. It also enables the ability to integrate to SOAP-based web services as an alternate option to leveraging WSDL2Apex. By using the HTTP classes, instead of WSDL2Apex, the developer has more responsibility to handling the construction of the SOAP message both for the request and the response.
The following sections will show a few examples of how to use these classes to make a REST-based web service call. Check out projects on Code Share for more code - for example the Force.com Toolkit for Google Data APIs makes extensive use of these types of callouts.

HTTP Code Sample

Here is a simple example of using the HTTPHTTPRequest, and HTTPResponse classes to construct a REST-based PUT message and receive the response. The example is making a REST-based web service call to the Amazon S3 service.
01HttpRequest req = new HttpRequest();
02 
03//Set HTTPRequest Method
04req.setMethod('PUT');
05 
06//Set HTTPRequest header properties
07req.setHeader('content-type''image/gif');
08req.setHeader('Content-Length','1024');
09req.setHeader('Host','s3.amazonaws.com');
10req.setHeader('Connection','keep-alive');
11req.setEndpoint( this.serviceEndPoint + this.bucket +'/' this.key);
12req.setHeader('Date',getDateString());
13 
14//Set the HTTPRequest body 
15req.setBody(body); 
16 
17Http http = new Http();
18 
19 try {
20   
21      //Execute web service call here      
22      HTTPResponse res = http.send(req);   
23 
24      //Helpful debug messages
25      System.debug(res.toString());
26      System.debug('STATUS:'+res.getStatus());
27      System.debug('STATUS_CODE:'+res.getStatusCode());
28             
29 catch(System.CalloutException e) {
30        //Exception handling goes here....
31 }     
This example illustrates the setting of all required HTTP Headers, setting the body of the HTTPRequestobject to the contents of the REST-based message, and checks the HTTPResponse status to make sure it was successfully processed.

Additional Considerations

  • Callout timeout is configurable in Apex Code (up to a maximum setting for a single callout):
The following is an example of setting a custom timeout for Web services callouts generated by WSDL2Apex:
1docSample.DocSamplePort stub = new docSample.DocSamplePort();
2stub.timeout_x = 2000// timeout in milliseconds
The following is an example of setting a custom timeout for HTTP callouts using the Apex HTTPRequest object:
1HttpRequest req = new HttpRequest();
2req.setTimeout(2000); // timeout in milliseconds

  • There is a maximum cumulative timeout for callouts by a single Apex transaction (currently 120 seconds - see the Apex Language Reference Guide for a definitive number). This time is additive across all callouts invoked by the Apex transaction.
  • The Apex callout request size must be smaller than a set maximum limit.
  • A governor limit is set on the number of web service calls or callouts per trigger or Apex class invocation.

No comments:

Post a Comment