[SalesForce] REST API to update throws INSUFFICIENT_ACCESS_ON_CROSS_REFERENCE_ENTITY error!

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 .

Best Answer

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";
}
Related Topic