306 lines
9.9 KiB
OpenEdge ABL
306 lines
9.9 KiB
OpenEdge ABL
public inherited sharing class MetadataApiClient {
|
|
public static final String NAMED_CREDENTIAL = 'Salesforce_Metadata';
|
|
public static final String API_VERSION = '65.0';
|
|
|
|
private static final String METADATA_NS = 'http://soap.sforce.com/2006/04/metadata';
|
|
private static final String SOAP_NS = 'http://schemas.xmlsoap.org/soap/envelope/';
|
|
private static final String XSI_NS = 'http://www.w3.org/2001/XMLSchema-instance';
|
|
|
|
public class MetadataApiException extends Exception {
|
|
}
|
|
|
|
public class SaveResult {
|
|
public Boolean created;
|
|
public String fullName;
|
|
}
|
|
|
|
public class RetrieveResult {
|
|
public Boolean done;
|
|
public String id;
|
|
public String status;
|
|
public String errorMessage;
|
|
public Blob zipFile;
|
|
}
|
|
|
|
public class DeployResult {
|
|
public Boolean done;
|
|
public Boolean success;
|
|
public String id;
|
|
public String status;
|
|
public String errorMessage;
|
|
}
|
|
|
|
public static Dom.XmlNode readMetadata(String metadataType, String fullName) {
|
|
String operation =
|
|
'<met:readMetadata>' +
|
|
'<met:type>' +
|
|
MetadataXml.escape(metadataType) +
|
|
'</met:type>' +
|
|
'<met:fullNames>' +
|
|
MetadataXml.escape(fullName) +
|
|
'</met:fullNames>' +
|
|
'</met:readMetadata>';
|
|
|
|
Dom.XmlNode body = send(operation);
|
|
Dom.XmlNode records = MetadataXml.findDescendant(body, 'records');
|
|
if (
|
|
records == null ||
|
|
'true'.equalsIgnoreCase(records.getAttributeValue('nil', XSI_NS))
|
|
) {
|
|
return null;
|
|
}
|
|
return records;
|
|
}
|
|
|
|
public static SaveResult updateMetadata(
|
|
String metadataType,
|
|
String metadataChildrenXml
|
|
) {
|
|
return save('updateMetadata', metadataType, metadataChildrenXml);
|
|
}
|
|
|
|
public static SaveResult upsertMetadata(
|
|
String metadataType,
|
|
String metadataChildrenXml
|
|
) {
|
|
return save('upsertMetadata', metadataType, metadataChildrenXml);
|
|
}
|
|
|
|
public static String startRetrieve(String member, String metadataType) {
|
|
Dom.XmlNode body = send(
|
|
'<met:retrieve><met:retrieveRequest>' +
|
|
'<met:apiVersion>' +
|
|
API_VERSION +
|
|
'</met:apiVersion><met:singlePackage>true</met:singlePackage>' +
|
|
'<met:unpackaged><met:members>' +
|
|
MetadataXml.escape(member) +
|
|
'</met:members><met:name>' +
|
|
MetadataXml.escape(metadataType) +
|
|
'</met:name></met:unpackaged>' +
|
|
'</met:retrieveRequest></met:retrieve>'
|
|
);
|
|
String id = MetadataXml.text(
|
|
MetadataXml.findDescendant(body, 'result'),
|
|
'id'
|
|
);
|
|
if (String.isBlank(id)) {
|
|
throw new MetadataApiException(
|
|
'The Metadata API did not return a retrieve job ID.'
|
|
);
|
|
}
|
|
return id;
|
|
}
|
|
|
|
public static RetrieveResult checkRetrieve(String retrieveId) {
|
|
Dom.XmlNode body = send(
|
|
'<met:checkRetrieveStatus><met:asyncProcessId>' +
|
|
MetadataXml.escape(retrieveId) +
|
|
'</met:asyncProcessId><met:includeZip>true</met:includeZip>' +
|
|
'</met:checkRetrieveStatus>'
|
|
);
|
|
Dom.XmlNode resultNode = MetadataXml.findDescendant(body, 'result');
|
|
RetrieveResult result = new RetrieveResult();
|
|
result.done = MetadataXml.booleanValue(resultNode, 'done');
|
|
result.id = MetadataXml.text(resultNode, 'id');
|
|
result.status = MetadataXml.text(resultNode, 'status');
|
|
result.errorMessage = MetadataXml.text(resultNode, 'errorMessage');
|
|
String zipFile = MetadataXml.text(resultNode, 'zipFile');
|
|
result.zipFile = String.isBlank(zipFile)
|
|
? null
|
|
: EncodingUtil.base64Decode(zipFile);
|
|
return result;
|
|
}
|
|
|
|
public static String startDeploy(Blob zipFile) {
|
|
Dom.XmlNode body = send(
|
|
'<met:deploy><met:ZipFile>' +
|
|
EncodingUtil.base64Encode(zipFile) +
|
|
'</met:ZipFile><met:DeployOptions>' +
|
|
'<met:allowMissingFiles>false</met:allowMissingFiles>' +
|
|
'<met:autoUpdatePackage>false</met:autoUpdatePackage>' +
|
|
'<met:checkOnly>false</met:checkOnly>' +
|
|
'<met:ignoreWarnings>false</met:ignoreWarnings>' +
|
|
'<met:performRetrieve>false</met:performRetrieve>' +
|
|
'<met:purgeOnDelete>false</met:purgeOnDelete>' +
|
|
'<met:rollbackOnError>true</met:rollbackOnError>' +
|
|
'<met:singlePackage>true</met:singlePackage>' +
|
|
'<met:testLevel>NoTestRun</met:testLevel>' +
|
|
'</met:DeployOptions></met:deploy>'
|
|
);
|
|
String id = MetadataXml.text(
|
|
MetadataXml.findDescendant(body, 'result'),
|
|
'id'
|
|
);
|
|
if (String.isBlank(id)) {
|
|
throw new MetadataApiException(
|
|
'The Metadata API did not return a deployment job ID.'
|
|
);
|
|
}
|
|
return id;
|
|
}
|
|
|
|
public static DeployResult checkDeploy(String deployId) {
|
|
Dom.XmlNode body = send(
|
|
'<met:checkDeployStatus><met:asyncProcessId>' +
|
|
MetadataXml.escape(deployId) +
|
|
'</met:asyncProcessId><met:includeDetails>true</met:includeDetails>' +
|
|
'</met:checkDeployStatus>'
|
|
);
|
|
Dom.XmlNode resultNode = MetadataXml.findDescendant(body, 'result');
|
|
DeployResult result = new DeployResult();
|
|
result.done = MetadataXml.booleanValue(resultNode, 'done');
|
|
result.success = MetadataXml.booleanValue(resultNode, 'success');
|
|
result.id = MetadataXml.text(resultNode, 'id');
|
|
result.status = MetadataXml.text(resultNode, 'status');
|
|
result.errorMessage = MetadataXml.text(resultNode, 'errorMessage');
|
|
if (String.isBlank(result.errorMessage)) {
|
|
Dom.XmlNode problem = MetadataXml.findDescendant(resultNode, 'problem');
|
|
result.errorMessage = problem == null ? null : problem.getText();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public static String toolingAutoResponseRuleName(Id ruleId) {
|
|
HttpRequest request = new HttpRequest();
|
|
request.setEndpoint(
|
|
'callout:' +
|
|
NAMED_CREDENTIAL +
|
|
'/services/data/v' +
|
|
API_VERSION +
|
|
'/tooling/query/?q=' +
|
|
EncodingUtil.urlEncode(
|
|
'SELECT Id, Name, EntityDefinitionId FROM AutoResponseRule WHERE Id = \'' +
|
|
String.escapeSingleQuotes(String.valueOf(ruleId)) +
|
|
'\' LIMIT 1',
|
|
'UTF-8'
|
|
)
|
|
);
|
|
request.setMethod('GET');
|
|
request.setTimeout(120000);
|
|
HttpResponse response = new Http().send(request);
|
|
if (response.getStatusCode() < 200 || response.getStatusCode() >= 300) {
|
|
throw new MetadataApiException(
|
|
'Tooling API returned HTTP ' +
|
|
response.getStatusCode() +
|
|
': ' +
|
|
response.getBody()
|
|
);
|
|
}
|
|
Map<String, Object> payload = (Map<String, Object>) JSON.deserializeUntyped(
|
|
response.getBody()
|
|
);
|
|
List<Object> records = (List<Object>) payload.get('records');
|
|
if (records == null || records.isEmpty()) {
|
|
throw new MetadataApiException('Auto Response Rule Id was not found.');
|
|
}
|
|
return (String) ((Map<String, Object>) records[0]).get('Name');
|
|
}
|
|
|
|
private static SaveResult save(
|
|
String operationName,
|
|
String metadataType,
|
|
String metadataChildrenXml
|
|
) {
|
|
String operation =
|
|
'<met:' +
|
|
operationName +
|
|
'><met:metadata xmlns="' +
|
|
METADATA_NS +
|
|
'" xsi:type="met:' +
|
|
MetadataXml.escape(metadataType) +
|
|
'">' +
|
|
metadataChildrenXml +
|
|
'</met:metadata></met:' +
|
|
operationName +
|
|
'>';
|
|
|
|
Dom.XmlNode body = send(operation);
|
|
Dom.XmlNode resultNode = MetadataXml.findDescendant(body, 'result');
|
|
if (resultNode == null) {
|
|
throw new MetadataApiException(
|
|
'The Metadata API returned no save result.'
|
|
);
|
|
}
|
|
|
|
Boolean success = MetadataXml.booleanValue(resultNode, 'success');
|
|
if (!success) {
|
|
Dom.XmlNode errors = MetadataXml.child(resultNode, 'errors');
|
|
String statusCode = errors == null
|
|
? null
|
|
: MetadataXml.text(errors, 'statusCode');
|
|
String message = errors == null
|
|
? null
|
|
: MetadataXml.text(errors, 'message');
|
|
throw new MetadataApiException(
|
|
'Metadata API save failed' +
|
|
(String.isBlank(statusCode) ? '' : ' [' + statusCode + ']') +
|
|
(String.isBlank(message) ? '.' : ': ' + message)
|
|
);
|
|
}
|
|
|
|
SaveResult result = new SaveResult();
|
|
result.created = MetadataXml.booleanValue(resultNode, 'created');
|
|
result.fullName = MetadataXml.text(resultNode, 'fullName');
|
|
return result;
|
|
}
|
|
|
|
private static Dom.XmlNode send(String operationXml) {
|
|
HttpRequest request = new HttpRequest();
|
|
request.setEndpoint(
|
|
'callout:' + NAMED_CREDENTIAL + '/services/Soap/m/' + API_VERSION
|
|
);
|
|
request.setMethod('POST');
|
|
request.setHeader('Content-Type', 'text/xml; charset=UTF-8');
|
|
request.setHeader('SOAPAction', '""');
|
|
request.setTimeout(120000);
|
|
request.setBody(
|
|
'<?xml version="1.0" encoding="UTF-8"?>' +
|
|
'<soapenv:Envelope xmlns:soapenv="' +
|
|
SOAP_NS +
|
|
'" xmlns:met="' +
|
|
METADATA_NS +
|
|
'" xmlns:xsi="' +
|
|
XSI_NS +
|
|
'"><soapenv:Header/><soapenv:Body>' +
|
|
operationXml +
|
|
'</soapenv:Body></soapenv:Envelope>'
|
|
);
|
|
|
|
HttpResponse response = new Http().send(request);
|
|
Dom.Document document = new Dom.Document();
|
|
try {
|
|
document.load(response.getBody());
|
|
} catch (Exception exceptionValue) {
|
|
throw new MetadataApiException(
|
|
'Metadata API returned HTTP ' +
|
|
response.getStatusCode() +
|
|
' with an unreadable response: ' +
|
|
response.getBody()
|
|
);
|
|
}
|
|
|
|
Dom.XmlNode body = MetadataXml.findDescendant(
|
|
document.getRootElement(),
|
|
'Body'
|
|
);
|
|
Dom.XmlNode fault = MetadataXml.findDescendant(body, 'Fault');
|
|
if (fault != null) {
|
|
String faultMessage = MetadataXml.text(fault, 'faultstring');
|
|
throw new MetadataApiException(
|
|
String.isBlank(faultMessage)
|
|
? 'Unknown Metadata API SOAP fault.'
|
|
: faultMessage
|
|
);
|
|
}
|
|
if (response.getStatusCode() < 200 || response.getStatusCode() >= 300) {
|
|
throw new MetadataApiException(
|
|
'Metadata API returned HTTP ' +
|
|
response.getStatusCode() +
|
|
': ' +
|
|
response.getBody()
|
|
);
|
|
}
|
|
return body;
|
|
}
|
|
}
|