3

I am trying to update a class using REST API which i created before using same REST API .

public static void createClass() {
    // Note the escaping on newlines and quotes
    String classBody = 'public class MyNewClass {\\n'
        + ' public string SayHello() {\\n'
        + '  return \'Hello World\';\\n' 
        + ' }\\n'
        + '}';

    HTTPRequest req = new HTTPRequest();
    req.setEndpoint(baseUrl + 'sobjects/ApexClass');
    req.setMethod('POST');
    // OAuth header
    req.setHeader('Authorization', 'Bearer ' + UserInfo.getSessionId());
    req.setHeader('Content-Type', 'application/json');
    req.setBody('{'+
        '"Body" : "' + classBody +'"'+
    '}');

    Http h = new Http();
    HttpResponse res = h.send(req);
     System.debug('*****'+res.getBody());
    // Response to a create should be 201
    if (res.getStatusCode() != 201) {
        System.debug(res.getBody());

    }

}

I could successfully create a new class with above code. Now i want to update the same class with PATCH method.

public static void updateClass() {
    // Note the escaping on newlines and quotes
    String classBody = 'public class MyNewClass {\\n'
        + ' public string SayHello() {\\n'
        + '  return \'Hello World update\';\\n' 
        + ' }\\n'
        + '}';

    HTTPRequest req = new HTTPRequest();
   req.setEndpoint(baseUrl + 'sobjects/ApexClass/01p9000000417SnAAI?_HttpMethod=PATCH');
    req.setMethod('POST');
    // OAuth header
    req.setHeader('Authorization', 'OAuth ' + UserInfo.getSessionId());
    req.setHeader('Content-Type', 'application/json');
     req.setBody('{'+
        '"Body" : "' + classBody +'"'+
    '}');

    Http h = new Http();
    HttpResponse res = h.send(req);
     System.debug('*****'+res.getBody());
    // Response to a create should be 201
    if (res.getStatusCode() != 201) {
        System.debug(res.getBody());

    }
}

I have hardcoded the ID of apex class for now (for testing).

Request string in debug log looks like this :

System.HttpRequest[Endpoint=https://c.ap1.visual.force.com/services/data/v28.0/tooling/sobjects/ApexClass/01p9000000417SnAAI?_HttpMethod=PATCH, Method=POST]

and response says "BAD request" and the error string looks like this:

[{"fields":[],"message":"insufficient access rights on cross-reference id","errorCode":"INSUFFICIENT_ACCESS_ON_CROSS_REFERENCE_ENTITY"}]

Am I missing something here when trying to update?

Or is there any better way to to these operations like Tooling API for which i don't find good explanation or documentation with examples.

And i want to update a class which need not be compiled because i want to update it with previous compiled/saved code .

1 Answer 1

3

You do need to use the Tooling API to update Apex classes. Updating an Apex class is a more complex operation than creating a class, since you may have a set of updates that need to be applied as a single set of changes, and it's an asynchronous operation.

The Tooling API docs have some sample code for REST and for SOAP. I built a very basic code editor as an example in Java that pulls it together - here is the code to update an Apex Class. I've added some comments to show the flow:

try {
    // The container for the entire request
    JSONObject metadataContainerRequest = new JSONObject();
    metadataContainerRequest.put("Name", "SaveClass" + id);
    JSONObject metadataContainerResponse = ToolingApi.post("sobjects/MetadataContainer", metadataContainerRequest);
    System.out.println("MetadataContainer id: " + metadataContainerResponse.get("id"));

    // Wrapper for changes to a single Apex class - you could add several of these
    // to a single metadata container
    JSONObject apexClassMemberRequest = new JSONObject();
    apexClassMemberRequest.put("MetadataContainerId", metadataContainerResponse.get("id"));
    apexClassMemberRequest.put("ContentEntityId", id);
    apexClassMemberRequest.put("Body", body);
    JSONObject apexClassMemberResponse = ToolingApi.post("sobjects/ApexClassMember", apexClassMemberRequest);
    System.out.println("ApexClassMember id: " + apexClassMemberResponse.get("id"));

    // Apply the changes
    JSONObject containerAsyncRequest = new JSONObject();
    containerAsyncRequest.put("MetadataContainerId", metadataContainerResponse.get("id"));
    containerAsyncRequest.put("isCheckOnly", false);
    JSONObject containerAsyncResponse = ToolingApi.post("sobjects/ContainerAsyncRequest", containerAsyncRequest);
    System.out.println("ContainerAsyncRequest id: " + containerAsyncResponse.get("id"));

    // See if the update is complete, keep checking until it is
    JSONObject result = ToolingApi.get("sobjects/ContainerAsyncRequest/"
        + containerAsyncResponse.get("id"));
    String state = (String) result.get("State");
    System.out.println("State: " + state);
    int wait = 1;
    while (state.equals("Queued")) {
        try {
            System.out.println("Sleeping for " + wait + " second(s)");
            Thread.sleep(wait * 1000);
        } catch (InterruptedException ex) {
            Thread.currentThread().interrupt();
        }

        wait *= 2;

        result = ToolingApi.get("sobjects/ContainerAsyncRequest/" + containerAsyncResponse.get("id"));
        state = (String) result.get("State");
        System.out.println("State: " + state);
    }

    // Housekeeping - delete the container
    ToolingApi.delete("sobjects/MetadataContainer/" + metadataContainerResponse.get("id"));

    if (state.equals("Completed")) {
        // Success!
        return "redirect:../classes";
    } else {
        // Failure - show errors to user
        map.put("body", body);
        map.put("errorMsg", result.get("ErrorMsg"));
        String compilerErrors = (String) result.get("CompilerErrors");
        if (compilerErrors != null) {
            JSONArray parsedErrors = (JSONArray) JSONValue.parse(compilerErrors);
            map.put("compilerErrors", parsedErrors);
        }
        return "classDetail";
    }
} catch (RuntimeException e) {
    map.put("errorMsg", e.getMessage()); // TODO: better looking error
    return "classDetail";
}
5
  • Understood after playing around for sometime. Now I am able to create and update a class. But I ended up with one more problem. How to delete the MetadataContainer,ContainerAsyncRequest,and ApexClassMember once the update is complete. How to query all these sObjects and then delete them. Because i am using apexClass id as metadatacontainer name. And second time when i try udating it will say duplicate name with some id. Once I update I want to query all metadatacontainer and delete them.
    – Soberano
    Jan 16, 2014 at 5:27
  • You shouldn't need to query them - you already have the ids.
    – metadaddy
    Jan 16, 2014 at 6:51
  • I was planning to delete them separately altogether. And do we need to delete them using same sessionID that i get from UserInfo.getSessionId() call when updating ?
    – Soberano
    Jan 16, 2014 at 8:54
  • Not sure if it needs to be the same sessionID. It might work with a different one.
    – metadaddy
    Jan 16, 2014 at 17:09
  • 1
    Yes , It doesn't require same sessionID. And error i was encountering was that I was trying to delete ApexClassMember and ContainerAsyncRequest after deleting MetadataContainer. Fact is that I just had to delete MetadataContainer.
    – Soberano
    Jan 18, 2014 at 8:43

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .