By now we’re well into the Spring ’13 pre-release cycle: the release preview webinar is on Wednesday, selected sandboxes and NA1 already have the new release, and the remaining instances will be updated over the next couple of weekends. Techie that I am, my favorite new feature is the Tooling API, generally available (GA) in Spring ’13.

The Tooling API (Developer’s Guide HTML | PDF) provides REST and SOAP interfaces that enable custom development tools for the Force.com platform. This first release allows you to

  • Create/read/update/delete code artifacts such as Apex Classes and Triggers, and Visualforce Pages and Components,
  • Manage debugging features such as trace flags, heap dump markers and Apex/SOQL overlays
  • List debugging artifacts such as debug logs and heap dumps

As a demo for the upcoming webinar, I wrote a simple Apex Class browser/editor as a Java web application, showing just how to work with the REST Tooling API. Since I’m a somewhat polyglot programmer, I tend to use REST, even from Java, since I can quickly hack things out from the command line with curl, then code it up using Simple JSON, but many Java and C# developers prefer the SOAP API. If you’re in that camp, then from your org, go to Your Name | Setup | Develop | API, download the Tooling API WSDL, generate typed proxies, and use the appropriate create(), retrieve() etc method calls.

In any case, my code is all on GitHub, and if you have a pre-release login, you can go try it out on Heroku. Here are a few snippets; they all use the ToolingApi class, a very thin layer over Apache HttpClient that creates an HTTP request with the correct endpoint, access token etc, sends it, and parses the JSON response. In particular, ToolingApi prepends the instance URL and Tooling API prefix, for example https://na1.salesforce.com/services/data/v27.0/tooling/, to the URL path. Note also, that, to keep things simple, I haven’t shown error handling here, but you can find it in the ApexClassesController class.

Query

Use SOQL SELECT to retrieve a list of ApexClass objects.

JSONObject queryResponse = ToolingApi.get("query/?q="
		+ URLEncoder.encode(query, "UTF-8"));
System.out.println("Got "
		+ ((JSONArray) queryResponse.get("records")).size()
		+ " classes");

Create

To create an Apex Class, post JSON containing the class name and body to sobjects/ApexClass.

String name = "Demo";
String body = "public class " + name + " {\n\n}";

JSONObject apexClassRequest = new JSONObject();
apexClassRequest.put("Name", name);
apexClassRequest.put("Body", body);
JSONObject apexClassResponse = ToolingApi.post(
		"sobjects/ApexClass", apexClassRequest);
System.out.println("ApexClass id: " + apexClassResponse.get("id"));

Read

Just GET sobjects/ApexClass/ID.

JSONObject apexClassResponse = ToolingApi.get("sobjects/ApexClass/" + id);
System.out.println("ApexClass body: " + apexClassResponse.get("body"));

Update

Since a change might involve several code artifacts, update is a little more involved. You will need to create a MetadataContainer to hold ApexClassMember, ApexTriggerMember, ApexPageMember and ApexComponentMember objects, then create an ContainerAsyncRequest to compile and deploy the new code. Here’s a simple example with a single ApexClassMember.

//First, the MetadataContainer
JSONObject metadataContainerRequest = new JSONObject();
metadataContainerRequest.put("Name", "SaveClass" + id); // Any unique name
JSONObject metadataContainerResponse = ToolingApi.post(
		"sobjects/MetadataContainer", metadataContainerRequest);
System.out.println("MetadataContainer id: "
		+ metadataContainerResponse.get("id"));

// Then an ApexClassMember with our updated code
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"));

// Now we can create a containerAsyncRequest
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"));

// We need to wait until the request is done
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);
}

// A real development tool would likely reuse the MetadataContainer
// but we just delete it here to be tidy
ToolingApi.delete("sobjects/MetadataContainer/"
		+ metadataContainerResponse.get("id"));

System.out.println("Request done with state: " + state)

Delete

DELETE sobjects/ApexClass/ID.

JSONObject apexClassResponse = ToolingApi.delete("sobjects/ApexClass/" + id);

What’s Next

The next few releases will bring additional Tooling API functionality – access to more resources, such as debug logs and code coverage results, and more operations, such as execute anonymous blocks of code, and run tests. We will also rewrite the Force.com IDE (Eclipse plugin) to use the Tooling API, and open source it for you to use as a template for adding Force.com capability to your favorite development environment.

Join me and Samantha Ready for the Spring ’13 Release Developer Preview webinar, this Wednesday, Jan 30, and learn more about the Tooling API and other new features!

Bookmark the permalink. Trackbacks are closed, but you can post a comment.
  • Anonymous

    Great Job, nice article Pat

    -Harshit
    http://www.forcelabs.net
    @forcelabs

  • Anonymous

    Thanks for sharing Pat.. I am excited to play with

  • http://twitter.com/FishOfPrey Daniel Ballinger

    How do you use the Tooling API to, say, create a brand new Trigger rather than update an existing one?
    If I don’t specify the ContentEntityId on the ApexTriggerMember I get an “insufficient access rights on cross-reference id” error.

  • Mouse Liu

    I made a Sublime Apex Development Plugin by Tooling API on github, this plugin is written by python, the access url is https://github.com/xjsender/SublimeApex, however, there have some flaw in it, for example, can’t execute code and get the apex log and can’t create a new brand trigger, does it have any update on the brand new trigger flaw in tooling api or execute apex code by tooling api?

    If Any body want to improve this plugin with me, feel free contact with me, my email contact is: mouse.mliu@gmail.com

    • http://blog.superpat.com/ Pat Patterson

      To create a trigger, you need to use a URL without the ‘tooling’ component – see the answer I just posted at http://salesforce.stackexchange.com/a/11388/67

      There is an undocumented mechanism for getting Log data – see https://github.com/metadaddy-sfdc/force-tooling-demo/blob/master/src/main/java/com/example/util/ToolingApi.java#L78 – note – this is not a supported API – it may change without warning in a future release!

      Right now you still need to use the SOAP API to execute Apex code.

      • Mouse Liu

        As I know, if we need to do it like this, we need to create a log user before that.
        I tried to create a ApexTestQueueItem and query the ApexTestResult by requested QueueItemId, however, there is no ApexLogId in the ApexTestResult response, so I just got the Test Result but not raw log detail.

        Can I touch with you by email?

  • Roberto Gonzalez

    Pat,

    I’m having an issue with updating an existing component. I am using REST inside of Apex and I can query for the component but when I try to update it I get the error “Content Entity ID: id value of incorrect type:” I was using the ID returned from the query but it’s obviously using the wrong Id. How do I get the Content Entity ID of an existing component?