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 .

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";
}
  • 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 '14 at 5:27
  • You shouldn't need to query them - you already have the ids. – metadaddy Jan 16 '14 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 '14 at 8:54
  • Not sure if it needs to be the same sessionID. It might work with a different one. – metadaddy Jan 16 '14 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 '14 at 8:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.