Skip to content

Instantly share code, notes, and snippets.

@afawcett
Created December 23, 2013 00:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save afawcett/8090245 to your computer and use it in GitHub Desktop.
Save afawcett/8090245 to your computer and use it in GitHub Desktop.
Apex script for parsing and updating generated WSDL2Apex from Metadata API WSDL, uses Tooling REST API to dynamically create the modified code.
public with sharing class MetadataServicePatcher {
// Perhaps parse these from the WSDL in the future
private static final Map<String, String> METADATA_TYPES =
new Map<String, String> {
'CustomSite' => 'Metadata',
'InstalledPackage' => 'Metadata',
'CustomField' => 'Metadata',
'FieldSet' => 'Metadata',
'PicklistValue' => 'Metadata',
'RecordType' => 'Metadata',
'WebLink' => 'Metadata',
'AddressSettings' => 'Metadata',
'CaseSettings' => 'Metadata',
'CustomObject' => 'Metadata',
'Layout' => 'Metadata',
'ApexPage' => 'MetadataWithContent',
'ApexClass' => 'MetadataWithContent',
'StaticResource' => 'MetadataWithContent'
};
public static void patch()
{
// Query the current Apex Class
ApexClass apexClass =
[select Id, Body
from ApexClass
where Name = 'MetadataServiceImported'];
// Process and scan for lines to modify and/or insert
LineReader lineReader = new LineReader(apexClass.Body);
List<String> newLines = new List<String>();
while(lineReader.hasNext())
{
// Read line
String line = lineReader.next();
String trimmedLine = line.trim();
// Adjust class name?
if(trimmedLine.contains('MetadataServiceImported'))
line = line.replace('MetadataServiceImported', 'MetadataService');
// Adjust end point logic?
if(trimmedLine.startsWith('public String endpoint_x'))
line = ' public String endpoint_x = URL.getSalesforceBaseUrl().toExternalForm() + \'/services/Soap/m/30.0\';';
// Adjust update_x method name?
else if(trimmedLine.contains('update_x('))
line = line.replace('update_x', 'updateMetadata');
// Adjust delete_x method name?
else if(trimmedLine.contains('delete_x('))
line = line.replace('delete_x', 'deleteMetadata');
// Adjust retrieve_x method name?
else if(trimmedLine.contains('retrieve_x('))
line = line.replace('retrieve_x', 'retrieve');
// Make Metadata virtual?
else if(trimmedLine.startsWith('public class Metadata '))
line = line.replace('public class', 'public virtual class');
// Make MetadataWithContent virtual?
else if(trimmedLine.startsWith('public class MetadataWithContent '))
line = line.replace('public class MetadataWithContent', 'public virtual class MetadataWithContent extends Metadata');
// Class definition?
else if(trimmedLine.startsWith('public class'))
{
List<String> parts = trimmedLine.split(' ');
String className = parts[2];
if(METADATA_TYPES.containsKey(className))
{
// Adjust class to extend base class and add base class members (XML serialiser does not support inheritance)
String extendsClassName = METADATA_TYPES.get(className);
line = line.replace('public class ' + className, 'public class ' + className + ' extends ' + extendsClassName);
newLines.add(line);
newLines.add(' public String type = \'' + className + '\';');
newLines.add(' public String fullName;');
if(extendsClassName.equals('MetadataWithContent'))
newLines.add(' public String content;');
// Move forward until field_order_type_info member
while(lineReader.hasNext())
{
line = (String) lineReader.next();
trimmedLine = line.trim();
// Adjust class name?
if(trimmedLine.contains('MetadataServiceImported'))
line = line.replace('MetadataServiceImported', 'MetadataService');
else if(trimmedLine.startsWith('private String[] field_order_type_info'))
{
// Add type info descriptors and adjust field_order_type_info list
newLines.add(' private String[] type_att_info = new String[]{\'xsi:type\'};');
newLines.add(' private String[] fullName_type_info = new String[]{\'fullName\',\'http://www.w3.org/2001/XMLSchema\',\'string\',\'0\',\'1\',\'false\'};');
if(extendsClassName.equals('MetadataWithContent'))
{
newLines.add(' private String[] content_type_info = new String[]{\'content\',\'http://www.w3.org/2001/XMLSchema\',\'base64Binary\',\'0\',\'1\',\'false\'};');
line = line.replace('new String[]{', 'new String[]{\'fullName\', \'content\', ');
}
else
{
line = line.replace('new String[]{', 'new String[]{\'fullName\', ');
}
newLines.add(line);
break;
}
newLines.add(line);
}
continue;
}
}
newLines.add(line);
}
// Query for the patched ApexClass (via Tooling REST API)?
Http h = new Http();
HttpRequest queryReq = new HttpRequest();
queryReq.setEndpoint(URL.getSalesforceBaseUrl().toExternalForm() + '/services/data/v29.0/tooling/query/?q=SELECT+Id+from+ApexClass+Where+Name=\'MetadataService\'');
queryReq.setHeader('Authorization', 'OAuth ' + UserInfo.getSessionId());
queryReq.setHeader('Content-Type', 'application/json');
queryReq.setMethod('GET');
HttpResponse queryRes = h.send(queryReq);
if(queryRes.getStatusCode() != 200)
throw new PatchException(queryReq.getEndpoint() + ' ' + queryRes.getStatusCode() + ' ' + queryRes.getBody());
Map<String, Object> queryData = (Map<String, Object>) JSON.deserializeUntyped(queryRes.getBody());
Integer size = (Integer) queryData.get('size');
if(size > 0)
{
// Delete the patch ApexClass (via Tooling REST API)
List<Object> records = (List<Object>) queryData.get('records');
Map<String, Object> recordList = (Map<String, Object>) records[0];
String classId = (String) recordList.get('Id');
HttpRequest deleteReq = new HttpRequest();
deleteReq.setEndpoint(URL.getSalesforceBaseUrl().toExternalForm() + '/services/data/v29.0/tooling/sobjects/ApexClass/'+classId);
deleteReq.setHeader('Authorization', 'OAuth ' + UserInfo.getSessionId());
deleteReq.setHeader('Content-Type', 'application/json');
deleteReq.setMethod('DELETE');
HttpResponse deleteRes = h.send(deleteReq);
if(deleteRes.getStatusCode() != 204)
throw new PatchException(deleteReq.getEndpoint() + ' ' + deleteRes.getStatusCode() + ' ' + deleteRes.getBody());
}
// Create the patched ApexClass (via Tooling REST API)
String patchClass = String.join(newLines, '\n');
JSONGenerator body = JSON.createGenerator(false);
body.writeStartObject();
body.writeStringField('Name', 'MetadataService');
body.writeStringField('Body', patchClass);
body.writeEndObject();
HttpRequest createReq = new HttpRequest();
createReq.setEndpoint(URL.getSalesforceBaseUrl().toExternalForm() + '/services/data/v29.0/tooling/sobjects/ApexClass');
createReq.setBody( body.getAsString() );
createReq.setHeader('Authorization', 'OAuth ' + UserInfo.getSessionId());
createReq.setHeader('Content-Type', 'application/json');
createReq.setMethod('POST');
HttpResponse createRes = h.send(createReq);
if(createRes.getStatusCode() != 201)
throw new PatchException(createRes.getStatusCode() + ' ' + createRes.getBody());
}
public class PatchException extends Exception { }
/**
* Utility class to iterate over lines in Apex source code
**/
public class LineReader
implements Iterator<string>, Iterable<string>
{
private String LF = '\n';
private String textData;
public LineReader(String textData)
{
this.textData = textData;
}
public Boolean hasNext()
{
return textData.length() > 0 ? true : false;
}
public String next()
{
String row = null;
Integer endPos = textData.indexOf(LF);
if(endPos == -1)
{
row = textData;
textData = '';
}
else
{
row = textData.subString(0, endPos);
textData = textData.subString(endPos + LF.length(), textData.length());
}
return row;
}
public Iterator<String> Iterator()
{
return this;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment