Salesforce - Developer - Apex REST

salesforce-developer

https://trailhead.salesforce.com/apex_integration_services/apex_integration_webservices

http://bit.ly/RestContext
http://bit.ly/RestRequest
http://bit.ly/RestResponse
http://bit.ly/PutOrPost
http://github.com/sfdcmatt/DF13ApexRest
http://bit.ly/UsingApexforREST
https://github.com/mkplabs/StackExchange-API-for-Force.com
https://developer.salesforce.com/page/Apex_Web_Services_and_Callouts

Exposing Apex classes as REST services
REST Callout
Misc

// Salesforce - Developer - Apex - REST:

@RestResource(urlMapping='/MyRestContextExample/*')
global with sharing class MyRestContextExample {
  @HttpGet
  global static Account doGet() {
    RestRequest req = RestContext.request;
    RestResponse res = RestContext.response;
    String accountId = req.requestURI.substring(req.requestURI.lastIndexOf('/')+1);
    Account result = [
      SELECT Id, Name, Phone, Website 
      FROM Account 
      WHERE Id = :accountId
    ];
    return result;
  }
} 

Use the System.RestContext class to access the RestRequest and RestResponse 
objects in your Apex REST methods.  The above example shows how to use 
RestContext to access the RestRequest and RestResponse objects in an Apex REST 
method.

The RestRequest object provides access to all aspects of the request.  Inbound 
requests are automatically deserialized into a RestRequest instance.  We can get 
HTTP headers, HTTP methods, params, remoteAddress, requestBody, requestURI, 
resourcePath from the RestRequest object.

The RestResponse provides access to all aspects of the response.  We can get
the statusCode, responseBody, headers from the RestResponse object.  Method 
return value is automatically serialized into the responseBody.

String searchTerm = req.params.get('searchTerm');

@RestResource(urlMapping='/v2/accounts/*')
global with sharing class REST_AccountService_V2 {

    @HttpGet
    global static AccountWrapper doGet() {
        RestRequest req = RestContext.request;
        RestResponse res = RestContext.response;
        AccountWrapper response = new AccountWrapper();

        String accountId = req.requestURI.substring(req.requestURI.lastIndexOf('/')+1);

        List<Account> result = [
            SELECT Id, Name, Phone, Website 
            FROM Account 
            WHERE External_Id__c = :accountId
        ];

        if(result != null && result.size() > 0) {
            response.acct = result[0];
            response.status = 'Success';
        }
        else {
            response.acct = null;
            response.status = 'Error';
            response.message = 'This account could not be found, please try again.';
            res.StatusCode = 404;
        }

        return response;
    }

    global class AccountWrapper {
        public Account acct;
        public String status;
        public String message;

        public AccountWrapper(){}
    }
}

@RestResource(urlMapping='/v3/accounts/*')
global with sharing class REST_AccountService_V3 {

    @HttpGet
    global static AccountWrapper doGet() {
        RestRequest req = RestContext.request;
        RestResponse res = RestContext.response;
        AccountWrapper response = new AccountWrapper();

        String accountId = req.requestURI.substring(req.requestURI.lastIndexOf('/')+1);

        if(doSearch(accountId)) {
            searchAccounts(req, res, response);
        }
        else {
            findAccount(res, response, accountId);
        }

        return response;
    }

    // If the item to the right of the last forward slash is "accounts", the 
    // request went to v3/accounts?Name=United
    // Else the request went to v3/accounts/<something>, which is not a search, 
    // but a specific entity
    private static boolean doSearch(String accountId) {
        if(accountId == 'accounts') {
            return true;
        }
        return false;
    }

    //If the request came to /v3/accounts, then we want to execute a search
    private static void searchAccounts(RestRequest req, RestResponse res, 
        AccountWrapper response) {

        //Use the RestRequest's params to fetch the Name parameter
        String searchTerm = req.params.get('Name');

        if(searchTerm == null || searchTerm == '') {
            response.status = 'Error';
            response.message = 'You must provide a Name for your search term.';
            res.StatusCode = 400;
        }
        else {
            String searchText = '%'+searchTerm+'%';
            List<Account> searchResults = [
                SELECT Id, Name, Phone, Website 
                FROM Account 
                WHERE Name LIKE : searchText
            ];

            if(searchResults != null && searchResults.size() > 0) {
                response.acctList = searchResults;
                response.status = 'Success';
                response.message = searchResults.size() + ' Accounts were found 
                    that matched your search term.';
            }
            else {
                response.status = 'Error';
                response.message = 'No Accounts where found based on that Name, 
                    please search again.';
            }
        }
    }

    // If the request came to v3/accounts/<external_Id>, then we want to find a 
    // specific account
    private static void findAccount(RestResponse res, AccountWrapper response, 
        String accountId) {

        // Provided we recevied an External Id, perform the search and return 
        // the results
        if(accountId != null && accountId != '') {
            List<Account> result = [
                SELECT Id, Name, Phone, Website 
                FROM Account 
                WHERE External_Id__c =: accountId
            ];

            if(result != null && result.size() > 0) {
                response.acctList.add(result[0]);
                response.status = 'Success';
            }
            else {
                response.status = 'Error';
                response.message = 'This account could not be found, please try again.';
                res.StatusCode = 404;
            }
        }
        // If the request came to /v3/accounts/ (without an Account Id), return an error
        else {
            response.status = 'Error';
            response.message = 'You must specify an External Id.';
            res.StatusCode = 400;
        }
    }

    global class AccountWrapper {
        public List<Account> acctList;
        public String status;
        public String message;

        public AccountWrapper(){
            acctList = new List<Account>();
        }
    }
}

@RestResource(urlMapping='/v7/accounts/*')
global with sharing class REST_AccountService_V7 {

    @HttpPost
    global static PostResponseWrapper doPost(RequestWrapper rqst) {
        RestRequest req = RestContext.request;
        RestResponse res = RestContext.response;
        PostResponseWrapper response = new PostResponseWrapper();

        try {
            insert rqst.acct;
            response.acct = rqst.acct;

            for(Contact con : rqst.contList) {
                con.AccountId = rqst.acct.Id;
            }

            insert rqst.contList;
            response.contList = rqst.contList;

            response.status = 'Success';
            response.message = 'Your Accounts have been created successfully';
        }
        catch(Exception exc) {
            res.StatusCode = 500;
            response.acct = null;
            response.contList = null;
            response.status = 'Error';
            response.message = 'Your request failed with the following error: ' 
                + exc.getMessage();
        }

        return response;
    }

    @HttpGet
    global static GetResponseWrapper doGet() {
        RestRequest req = RestContext.request;
        RestResponse res = RestContext.response;
        GetResponseWrapper response = new GetResponseWrapper();

        String accountId = req.requestURI.substring(req.requestURI.lastIndexOf('/')+1);

        if(doSearch(accountId)) {
            searchAccounts(req, res, response);
        }
        else {
            findAccount(res, response, accountId);
        }

        return response;
    }

    // If the item to the right of the last forward slash is "accounts", the 
    // request went to v3/accounts?Name=United
    // Else the request went to v3/accounts/<something>, which is not a search, 
    // but a specific entity
    private static boolean doSearch(String accountId) {
        if(accountId == 'accounts') {
            return true;
        }
        return false;
    }

    //If the request came to /v3/accounts, then we want to execute a search
    private static void searchAccounts(RestRequest req, RestResponse res, 
        GetResponseWrapper response) {

        //Use the RestRequest's params to fetch the Name parameter
        String searchTerm = req.params.get('Name');

        if(searchTerm == null || searchTerm == '') {
            response.status = 'Error';
            response.message = 'You must provide a Name for your search term.';
            res.StatusCode = 400;
        }
        else {
            String searchText = '%'+searchTerm+'%';
            List<Account> searchResults = [
                SELECT Id, Name, Phone, Website 
                FROM Account 
                WHERE Name LIKE : searchText
            ];

            if(searchResults != null && searchResults.size() > 0) {
                response.acctList = searchResults;
                response.status = 'Success';
                response.message = searchResults.size() + ' Accounts were found 
                    that matched your search term.';
            }
            else {
                response.status = 'Error';
                response.message = 'No Accounts where found based on that 
                    Name, please search again.';
            }
        }
    }

    //If the request came to v3/accounts/<external_Id>, then we want to find a specific account
    private static void findAccount(RestResponse res, GetResponseWrapper response, 
        String accountId) {

        // Provided we recevied an External Id, perform the search and return 
        // the results
        if(accountId != null && accountId != '') {
            List<Account> result = [
                SELECT Id, Name, Phone, Website 
                FROM Account 
                WHERE External_Id__c =: accountId
            ];

            if(result != null && result.size() > 0) {
                response.acctList.add(result[0]);
                response.status = 'Success';
            }
            else {
                response.status = 'Error';
                response.message = 'This account could not be found, please try again.';
                res.StatusCode = 404;
            }
        }
        // If the request came to /v3/accounts/ (without an Account Id), return an error
        else {
            response.status = 'Error';
            response.message = 'You must specify an External Id.';
            res.StatusCode = 400;
        }
    }

    global class RequestWrapper {
        Account acct;
        List<Contact> contList;
    }

    global class PostResponseWrapper {
        Account acct;
        List<Contact> contList;
        String status;
        String message;

        public PostResponseWrapper(){

        }
    }

    global class GetResponseWrapper {
        public List<Account> acctList;
        public String status;
        public String message;

        public GetResponseWrapper(){
            acctList = new List<Account>();
        }
    }
}
// Salesforce - Developer - Apex REST - Callouts:

public static HttpResponse callout(String httpMethod, String endpoint, String body) {
  HttpRequest req = new HttpRequest();
  req.setMethod(httpMethod);
  req.setEndPoint(endpoint);
  if (String.isNotBlank(body)) {
    req.setBody(body);
    req.setHeader('Content-Length', String.valueOf(body.length()));
  }

  req.setTimeout(120000);
  req.setHeader('Accept-Encoding','gzip, deflate');
  req.setCompressed(true); // Tell Apex to compress the body prior to delivery
  HttpResponse res = new http().send(req);

  System.debug(res.toString());
  System.debug(res.getBody());
  return res;
}

public with sharing class Integration

  // class to hold callout response for parsing
  public class Response {
    public integer code { get; set; }
    public string body { get; set; }
    public boolean success { get; set; }
    public string theType { get; set; }
    public decimal confidence { get; set; }
    public string errorText { get; set; }

    public Response(integer code, string body) {
      this.code = code;
      this.body = body;
      this.success = (code == 200 || code == 201);
    }
  }

  // internal class to store API response (for deserialization)
  public class ClassifyContainer {
    public String classifier_id { get; set; }
    public String url { get; set; }
    public String text { get; set; }
    public String top_class { get; set; }
    public List<ClassifyClass> clases { get; set; }
  }

  // internal class to store a list under the deserialized API Response
  pub class ClassifyClass {
    public string class_name { get; set; }
    public decimal confidence { get; set; }
  }

  // Methods to perform callouts
  public Response MakeCallout(string description) {

    // define a response to caller
    Response resp;

    // define basic information for later
    String endpoint = integrationcore.getIntSetting().APIEndpoint__c;
    String resource = integrationcore.getIntSetting().watsonClassifyResource__c;
    String method = 'GET';

    // check to ensure a callout can be performed
    if (Limits.getCallouts() >= Limits.getLimitCallouts()) {
      resp.ErrorText = 'Maximum number of callouts has been reached.';
      integrationCore.Log('Limit Exception', resource, method, 'error',
        null, null, SS20, resp.ErrorText);
    } else if (string.isBlank(IntegrationCore.getIntSetting().APIEndpoint__c) ||
      string.isBlank(IntegrationCore.getIntSetting().Username__c)) {
      // check for credential error
      resp.ErrorText = 'Please verify your API credentials on the "Integration
        Configuration" tab.';
      IntegrationCore.Log('Credential Exception', resource, method, 'error',
        null, null, SS23, resp.ErrorText);
    } else {
      // configure and perform callouts
      // define transaction variables
      HttpRequest req = new HttpRequest();
      HttpResponse res = new HttpResponse();
      Http h = new Http();

      // Configure the request
      req.setEndPoint(endpoint + resource);
      req.setMethod(method);
      req.setTimeout(120000);

      // Add basic authentication to header
      Blob headerValue = Blob.valueOf(Integration.getIntSetting().Username__c +
        ':' + IntegrationCore.getIntSetting().Password__c);
      String authorizationHeader = 'Basic ' + 
        EncodingUtil.base64Encode(headerValue);
      req.setHeader('Authorization', authorizationHeader);

      // Configure standard header
      req.setHeader('Accept', '*/*');
      req.setHeader('Content-Type', 'application/json');

      // Set the body json with the description parameter
      req.setBody('{"text":"' + description + '"}');

      // Attempt the callout
      try {
        // Perform callout and set response
        res = h.send(req);
        resp = new Response(res.getStatusCode(), res.getBody());
        // check response
        if (resp.success) {
          // deserialize the response into the inner class
          ClassifyContainer classification = (ClassifyContainer) 
            JSON.deserialize(res.getBody(), ClassifyContainer.class);
          if (classification == null || classification.classes == null ||
            classification.classes.isEmpty()) {
            resp.errorText = 'Could not parse the response.';
            IntegrationCore.Log('Failed Transaction', resource, method,
              'error', req, res, resp.code, resp.errorText);
          } else {
            IntegrationCore.Log('Successful Transaction', resource, method,
              'debug', req, res, resp.code, resp.body);
            // set the type returned from the callout
            resp.theType = classification.top_class;

            // find the top class in the list of classes returned to get
            // the confidence score
            for (ClassifyClass cc : classification.classes) {
              if (cc.class_name == classification.top_class) {
                resp.confidence = cc.confidence * 100;
                break;
              }
            }
          }
        } else {
          // callout failed
          resp.ErrorText = 'Callout failed.  Please see the log for details.';
          IntegrationCore.Log('Callout failed', resource, method, 'error',
            null, null, 5551, resp.ErrorText);
        }
      } catch (Exception e) {
        resp.errorText = 'An exception happened: ' + e.getMessage();
        IntegrationCore.Log('Callout Exception', resource, method, 'error',
          null, null, resp.ErrorText);
      }
    // return the response
    return resp;
  }
}

Oauth: We need to implement a separate authentication handshake, store the
tokens in a setting, and use the tokens in callouts.

@future (callout = true)

HttpRequest req = new HttpRequest();

//Set the HTTP verb being use (REQUIRED)
req.setMethod(httpMethod);
req.setEndPoint(endpoint); // set the URI of the 3rd party service (REQUIRED)

if (string.isNotBlank(body)) {
  req.setBody(body);
  req.setHeader('Content-Length', string.valueOf(body.length()));
}

Http h = new Http();
HttpResponse res = h.send(req);

Integer statusCode = res.getStatusCode();
String status = res.getStatus();  // The status message
String entireStatus = res.toString();
String body = res.getBody();
List<String> headerKeys = res.getHeaderKeys();
String headerX = res.getHeader(KEY) // Get a specific header key

String jsonString = JSON.serialize(r);
Object o = JSON.deserialize(jsonString, response.class);
responseEntity r2 = (responseEntity) o; // cast the object to the desired type.

Apex prevents us from doing a callout inside a test method.  

public with sharing class stackExchangeAPI {
  public static String BASE_URL = 'https://api.stackexchange.com/2.2/';

  //Method to perform a callout and return an httpResponse
  public static httpResponse callout(String httpMethod, String endpoint, String body){
    //Instantiate an httpRequest and set the required attributes
    httpRequest req = new httpRequest();
    req.setMethod(httpMethod);
    req.setEndpoint(endpoint);

    // Optional attributes are often required to conform to the 3rd Party Web 
    // Service Requirements
    req.setHeader('Accept-Encoding','gzip, deflate');

    req.setTimeout(120000);

    //Use the HTTP Class to send the httpRequest and receive an httpResposne
    httpResponse res = new http().send(req);
    return res;
  }

  // Method to deserialize the response body
  public static responseResource deserialize(httpResponse res) {
    return (responseResource)JSON.deserialize(res.getBody(),responseResource.class);
  }

  // Apex Inner Classes to represent StackExchange objects

  // Outermost class included in the response from StackExchange
  public class responseResource {
        public Integer error_id {get;set;}
        public String error_message {get;set;}
        public String error_name {get;set;}
        public Boolean hasMore {get;set;}
        public List<questionResource> items {get;set;}
        public Integer quote_max {get;set;}
        public Integer quote_remaining {get;set;}
  }

  // Class to represent a question posted on StackExchange
  public class questionResource {
        public List<String> tags {get;set;}
        public userResource owner {get;set;}
        public Boolean is_answered {get;set;}
        public Integer view_count {get;set;}
        public Integer answer_count {get;set;}
        public Integer score {get;set;}
        public long last_activity_date {get;set;}
        public long creation_date {get;set;}
        public long last_edit_date {get;set;}
        public integer question_id {get;set;}
        public String link {get;set;}
        public String title {get;set;}
  }

  // Class to represent a registered User of StackExchange
  public class userResource {
        public String reputation {get;set;}
        public String user_id {get;set;}
        public String user_type {get;set;}
        public String profile_image {get;set;}
        public String display_name {get;set;}
        public String link {get;set;}
  }

  // Other useful methods

  // http://api.stackexchange.com/2.2/questions/unanswered?page=1&
  // pagesize=50&fromdate=1396310400&todate=1398816000&order=desc&min=1396310400
  // &max=1397606400&sort=activity&tagged=google-compute-engine
  // &site=stackoverflow
  public static responseResource questions_Unanswered(String site, Integer page, 
    Integer pagesize, Date fromdate, Date todate, String order, Date min, 
    Date max, String sortParam, String tagged) {
    httpResponse res = callout('GET', 
      compileEndpoint('questions/unanswered',site,page,pagesize,fromdate,todate,
        order,min,max,sortParam,tagged) , null);
    if ( res.getStatusCode() == 200 && string.isNotBlank(res.getBody()) ) {
      return deserialize(res);
    }
    return null;
  }

  public static String compileEndpoint(String call, String site, Integer page, 
    Integer pagesize, Date fromdate, Date todate, String order, Date min, 
    Date max, String sortParam, String tagged) {
    PageReference endpoint = new PageReference(BASE_URL+call);
    if ( string.isNotBlank(site) ){
      endpoint.getParameters().put('site',site);
    }
    if ( page != null && page > 0 ){
      endpoint.getParameters().put('page',string.valueOf(page));
    }
    if ( pagesize != null ) {
      endpoint.getParameters().put('pagesize',string.valueOf(pagesize));
    }
    if ( fromdate != null ) {
      endpoint.getParameters().put('fromdate',
        string.valueOf(
          dateTime.newInstance(fromdate,time.newInstance(0,0,0,0)).
          getTime()/1000)
        );
    }
    if ( todate != null ) {
      endpoint.getParameters().put('todate',string.valueOf(dateTime.
        newInstance(todate,time.newInstance(0,0,0,0)).getTime()/1000));
    }
    endpoint.getParameters().put('order','desc');
    if ( string.isNotBlank(order) ) {
      endpoint.getParameters().put('order',order);
    }
    if ( min != null ) {
      endpoint.getParameters().put('min',string.valueOf(dateTime.
      newInstance(min,time.newInstance(0,0,0,0)).getTime()/1000));
    }
    if ( max != null ) {
      endpoint.getParameters().put('max',string.valueOf(dateTime.
      newInstance(max,time.newInstance(0,0,0,0)).getTime()/1000));
    }
    endpoint.getParameters().put('sort','activity');
    if ( string.isNotBlank(sortParam) ) {
      endpoint.getParameters().put('sort',sortParam);
    }
    if ( string.isNotBlank(tagged) ) {
      endpoint.getParameters().put('tagged',tagged);
    }
    return endpoint.getURL();
  }
}

public with sharing class calloutViewer_Controller {

  public String requestEndpoint {get;set;}
  public String requestMethod {get;set;}
  public String requestBody {get;set;}
  public String responseStatus {get;set;}
  public Integer responseStatusCode {get;set;}
  public String responseBody {get;set;}
  public stackExchangeAPI.responseResource response {get;set;}
  public List<stackExchangeAPI.questionResource> deserializedQuestions {get;set;}

  public calloutViewer_Controller() {
    requestEndpoint = stackExchangeAPI.compileEndpoint(
            'questions/unanswered',
            'salesforce',
            1,
            50,
            system.today().addDays(-1),
            system.today(),
            'desc',
            system.today().addDays(-1),
            system.today(),
            'activity',
            ''
        );
  }

  public void callout() {
    httpResponse res = stackExchangeAPI.callout(
      requestMethod, requestEndpoint, requestBody
    );
    responseStatus = res.getStatus();
    responseStatusCode = res.getStatusCode();
    responseBody = res.getBody();
  }

  public void deserializeQuestions(){
    if ( string.isNotBlank(responseBody) ){
      response = (stackExchangeAPI.responseResource)JSON.deserialize(responseBody,stackExchangeAPI.responseResource.class);
      deserializedQuestions = response.items;
    }    
  }

  public List<SelectOption> getMethodOptions() {
    List<SelectOption> options = new List<SelectOption>();
    options.add(new SelectOption('GET','GET'));
    options.add(new SelectOption('POST','GET'));
    options.add(new SelectOption('PUT','GET'));
    options.add(new SelectOption('DELETE','GET'));
    options.add(new SelectOption('HEAD','GET'));
    return options;
  }
}

public with sharing class testHttpCalloutMock implements HttpCalloutMock {

  //A single method that acts as a webservice to simulate 3rd party services
  public HTTPResponse respond(HTTPRequest req){
    //Look at the httpRequest that was sent via Apex to possibly determine how we will respond
    system.debug(req.getBody());
    system.debug(req.getMethod());
    system.debug(req.getEndpoint());

    //Construct the object that we want to respond with
    stackExchangeAPI.responseResource response = new stackExchangeAPI.responseResource();

    response.items = new List<stackExchangeAPI.questionResource>{new stackExchangeAPI.questionResource()};
    response.items[0].tags = new List<String>{'test'};
    response.items[0].is_answered = true;
    response.items[0].view_count = 0;
    response.items[0].answer_count = 0;
    response.items[0].score = 100;
    response.items[0].last_activity_date = system.now().getTime();
    response.items[0].creation_date = system.now().getTime();
    response.items[0].last_edit_date = system.now().getTime();
    response.items[0].question_id = 1;
    response.items[0].link = 'Test Link';
    response.items[0].title = 'Test Title';

    response.items[0].owner = new stackExchangeAPI.userResource();
    response.items[0].owner.reputation = 'Test';
    response.items[0].owner.user_id = 'Test';
    response.items[0].owner.user_type = 'Test';
    response.items[0].owner.profile_image = 'Test';
    response.items[0].owner.display_name = 'Test';
    response.items[0].owner.link = 'Test';

    // Instantiate a new httpResponse
    httpResponse res = new httpResponse();

    // Set the Status
    res.setStatus('OK');

    // Set the StatusCode
    res.setStatusCode(200);

    // Set the Body to the serialized form of the instance of the stackExchangeAPI.responseResource
    res.setBody(JSON.serialize(response));

    // Return the httpResponse
    return res;
  }
}

@isTest
private class stackExchangeAPI_Tests {

  static testMethod void stackExchangeAPI_UnitTest() {
    test.startTest();
    test.setMock(HttpCalloutMock.class, new testHttpCalloutMock());

    calloutViewer_Controller controller = new calloutViewer_Controller();
    controller.getMethodOptions();
    controller.requestEndpoint = 'https://api.stackexchange.com/2.2/questions/unanswered?site=salesforce';
    controller.requestMethod = 'GET';
    controller.requestBody = '';
    controller.callout();
    controller.deserializeQuestions();
    test.stopTest();
  }
}
// Salesforce - Developer - Apex - REST - Misc:

Instead of using custom Apex code for REST and SOAP services, external 
applications can integrate with Salesforce by using Salesforce’s REST and SOAP 
APIs. These APIs let you create, update, and delete records. However, the 
advantage of using Apex web services is that Apex methods can encapsulate 
complex logic. This logic is hidden from the consuming application. Also, the 
Apex class operations can be faster than making individual API calls, because 
fewer roundtrips are performed between the client and the Salesforce servers. 
With an Apex web service call, there is only one request sent, and all 
operations within the method are performed on the server.
// Salesforce - Developer - Apex - REST - Security:

The security context under which Apex web service methods run differs from the 
security context of Salesforce APIs. Unlike Salesforce APIs, Apex web service 
methods run with system privileges and don’t respect the user’s object and field 
permissions. However, Apex web service methods enforce sharing rules when 
declared with the with sharing keyword.
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License