
We often want to fetch specific data of Apex Triggers, Classes, Aura Definition Bundle and Lightning Component Bundle. We either want them to get deleted or download them as a package. We can do this by using SOQL, Tooling API and MetaData API in Apex. We implemented a functionality where we retrieve the data of Triggers, Classes, Aura Definition Bundle and Lightning Component Bundle, a certain profile has been written over a period of time and make it as package.xml format which then is sent to another class where it gets deleted. How did we do it? What are the challenges we faced? How did we overcome those challenges?
❝Let’s take a look at it step by step. But before understanding the actual process let’s unlearn what Tooling API and MetaData API in Salesforce are:❞
Tooling API
The Tooling API in Salesforce is a REST-based API that provides programmatic access to custom metadata types, Apex classes, and other tools for customizing and extending the Salesforce platform. It is typically used by integrated development environments (IDEs) and other tools for automating the development and deployment of custom code and metadata in Salesforce. The Tooling API enables developers to perform operations such as creating, updating, and deleting Apex classes, triggers, Visualforce pages, and custom metadata types, as well as retrieving information about the metadata and data in a Salesforce organization.
MetaData API
The Metadata API in Salesforce is a SOAP-based API that provides programmatic access to the metadata of Salesforce, including the customization of standard objects, fields, and other components. It is typically used by development tools and integrations to automate the process of deploying and managing customizations across multiple Salesforce orgs.
With the Metadata API, developers can perform operations such as creating, updating, and deleting custom objects, fields, and other components, retrieving metadata information, and deploying metadata changes between different Salesforce organizations. The Metadata API also provides access to more advanced features, such as the creation of custom tabs, page layouts, and workflows.
The first class we are going to understand is DataRetrieverUsingToolingAPI.apxc. This is constructed to retrieve the data of Apex Triggers, Classes, Aura Definition Bundle, Lightning Component Bundle written by particular profile. We have to break this class down into two parts. Why? Because Apex Triggers, Classes and Aura Definition Bundle are something which can be fetched by writing SOQL query in the SOQL query editor. But Lightning Component Bundle cannot be fetched like that, we have to enable the Tooling API option which does return the data we intend to have.
The first part is writing methods to fetch Apex Triggers, Classes, Aura Definition Bundle and second part is to fetch Lightning Component Bundle. Let’s take a look at the process of retrieving the Apex Triggers which will be applicable for fetching the Classes and Aura Definition Bundle as well.
Here you can see we declared a variable which is null and then wrote a query to fetch all the triggers written by the CandidateUser (specific to our internal use case, it varies). And we are checking the size of the list and then updating the variable with a string ‘<types>’ and then looping over the list to form members which you can see as we are appending to the string we defined at the beginning. Now that we have the members we need to make the code understand that this is of type Apex Trigger. Hence, outside the for loop we are appending the Apex Trigger part for the other class to understand that is Apex Trigger. The same is applicable for the Apex Classes and Aura Definition bundle where there will be a change in the defining.
public static String FetchApexTriggers() {
String apexrTriggerString = '';
List<ApexTrigger> apexTriggerList = [SELECT Id, Name FROM ApexTrigger WHERE CreatedBy.Name = 'CandidateUser'];
if(apexTriggerList.size() > 0) {
apexrTriggerString = '<types>';
for(ApexTrigger apexTriggerVar: apexTriggerList) {
apexrTriggerString+='<members>'+apexTriggerVar.Name+'</members>';
}
apexrTriggerString+='<name>ApexTrigger</name></types>';
return apexrTriggerString;
} else {
return apexrTriggerString;
}
}
Here you can see we declared a variable which is null and then wrote a query to fetch all the triggers written by the CandidateUser (specific to our internal use case, it varies). And we are checking the size of the list and then updating the variable with a string ‘<types>’ and then looping over the list to form members which you can see as we are appending to the string we defined at the beginning. Now that we have the members we need to make the code understand that this is of type Apex Trigger. Hence, outside the for loop we are appending the Apex Trigger part for the other class to understand that is Apex Trigger. The same is applicable for the Apex Classes and Aura Definition bundle where there will be a change in the defining.
The second part which will be very interesting and it personally left me intrigued while developing this because I proceeded by writing the same part for the Lightning Component bundle as well, but later realized that there is something that we are missing here and after understanding what the error is we overcame the challenge. Here how we fetched the Apex Triggers, Classes, and Aura Definition Bundle it won’t be possible to fetch the Lightning Component Bundle. For that reason, we have to use the Tooling API and how can we do this? The following part has been broken down into four methods:
1) FetchLightningComponentBundle(List<Object> responseList)
Method which forms the xml format when the response is received from the restGet method
2) RestGet(String endPoint, String method, String sid)
Method which helps in hitting the end point and retrieving the Lightning Component Bundle details
3) ToolingAPISOQL(String query)
Method to call restGet method which takes three parameters which are as follows: endpoint, HTTP request method and session id
4) FetchLightningComponentBundleQuery()
Method to call toolingAPISOQL by passing the query as string.
/**
* @description : to form a string with the type of lightning component bundle and it's respective members written by the candidate user
* @author Sangamesh Gella - ABSYZ Software Consulting Private Limited | 01-27-2023
* @param List<Object> responseList
* @return String
**/
public static String FetchLightningComponentBundle(List<Object> responseList) {
String lightningComponentBundleString = '';
if(responseList.size() > 0) {
lightningComponentBundleString = '<types>';
for(Object lightningComponentBundleVar: responseList) {
Map<String, Object> componentsMap = (Map<String, Object>) JSON.deserializeUntyped(JSON.serialize(lightningComponentBundleVar));
lightningComponentBundleString+='<members>'+componentsMap.get('DeveloperName')+'</members>';
}
lightningComponentBundleString+='<name>LightningComponentBundle</name></types>';
return lightningComponentBundleString;
} else {
return lightningComponentBundleString;
}
}
/**
* @description : to fetch the lightning web components written by the candidate using tooling API structure
* @author Sangamesh Gella - ABSYZ Software Consulting Private Limited | 01-27-2023
* @param String endPoint
* @param String method
* @param String sid
* @return String
**/
public static String restGet(String endPoint, String method, String sid) {
List<String> lwcNames = new List<String>();
Map<String, Object> responseMap = new Map<String, Object>();
List<Object> responseList = new List<Object>();
try {
Http httpVar = new Http();
HttpRequest httpReqVar = new HttpRequest();
httpReqVar.setHeader('Authorization', 'Bearer ' + sid);
httpReqVar.setTimeout(60000);
httpReqVar.setEndpoint(endpoint);
httpReqVar.setMethod(method);
HttpResponse responseVar = httpVar.send(httpReqVar);
System.debug(responseVar);
if(responseVar.getStatusCode() == 200) {
responseMap = (Map<String, Object>) JSON.deserializeUntyped(responseVar.getBody());
responseList = (List<Object>) responseMap.get('records');
System.debug('Response List is: ' + responseList);
if(responseList.size() > 0) {
FetchLightningComponentBundle(responseList);
}
return '';
}
return '';
} catch (Exception ex) {
System.debug('Exception in tooling API Call: ' + ex.getMessage());
return ex.getMessage();
}
}
/**
* @description : method to form query in the form we write for the workbench as a Tooling API
* @author Sangamesh Gella - ABSYZ Software Consulting Private Limited | 01-27-2023
* @param String query
* @return String
**/
public static String toolingAPISOQL(String query) {
String baseURL = 'https://absyz213-dev-ed.develop.my.salesforce.com//services/data/v56.0/tooling/query?';
System.debug('Query is: ' + restGet(baseURL +'q='+ (query.replace(' ' , '+')),'GET', UserInfo.getSessionId().subString(15)));
return restGet( baseURL +'q='+ (query.replace(' ' , '+')), 'GET', UserInfo.getSessionId().subString(15));
}
/**
* @description : method for query as a string to fetch the lightning web components written by the candidate
* @author Sangamesh Gella - ABSYZ Software Consulting Private Limited | 01-27-2023
* @return String
**/
public static String FetchLightningComponentBundleQuery() {
String soql = 'SELECT Id,DeveloperName FROM LightningComponentBundle WHERE CreatedBy.Name = \'CandidateUser\'';
String body = toolingAPISOQL(soql);
System.debug('Body is: ' + body);
return body;
}
}
We can retrieve the apex triggers, classes, aura definition bundle, lightning component bundle, vf pages using SOQL and Metadata API in Salesforce. We can further download or delete the data on a basis. For further details, you can contact Gella Sangamesh Gupta at sangamesh.gella@absyz.com or Vijay Sai Mahajan at vijaysai.mahajan@absyz.com
