Send a Data Integration Report by Email — No Pipeline Needed
Run a restricted Oracle EPM Cloud Data Integration report through a Groovy business rule and automatically email the output to the user who launched the rule.
Send a Data Integration Report by Email — No Pipeline Needed
Run a protected Data Integration report through Groovy and REST, then automatically deliver the result to the user who launched the rule.
Why is this automation needed?
The TB All Columns (Per,Cat,Loc) Data Integration report can only be executed by a System Administrator. Business users may still need the output, but granting them administrative access is neither appropriate nor secure. This rule bridges the gap: the report runs through an administrator-authenticated named connection, while the generated file is delivered to the actual user who launched the Groovy rule.
At a glance
Protected execution
The REST request uses the administrator credentials stored in the named connection.
User-aware delivery
The script resolves the email address of the actual rule executor through Access Control.
No Pipeline dependency
No pipeline code, process lookup, proxy-user comparison, or pipeline submitter resolution is required.
Required configuration
| Component | Required value | Purpose |
|---|---|---|
| Named Connection | EPM_REST | Authenticates the Data Integration, Access Control, and Send Mail REST calls. |
| Provider | Other Web Service Provider | Creates the REST connection used by the Groovy business rule. |
| Substitution Variable | DataPush | Supplies the report period, for example JUL-2026. |
| Runtime Prompts | vDM_Category vDM_Location vDM_RuleName | Pass the Category, Location, and Integration Name into the report request. |
| Data Integration Report | TB All Columns (Per,Cat,Loc) | The exact executable report definition used in the REST payload. |
Setup guide
Create the named connection
Create a connection named EPM_REST and select Other Web Service Provider. The credentials stored in this connection must belong to a user with the rights required to execute the report and send email.
Create the substitution variable
Create the application-level substitution variable DataPush. Store the period in the format expected by the Data Integration report, for example JUL-2026.
Create the Runtime Prompts
Add text RTPs named vDM_Category, vDM_Location, and vDM_RuleName. These values must match the report selectors configured in Data Integration.
Execution flow
Full Groovy script
The script below is fully displayed and can be copied directly into an Oracle EPM Cloud Groovy business rule.
/*RTPS: {vDM_Category} {vDM_Location} {vDM_RuleName} */
import groovy.json.JsonOutput
import groovy.json.JsonSlurper
import oracle.epm.api.model.User
/* =========================================================
STEP 1 - CONFIGURATION
========================================================= */
final String CONNECTION_NAME = "EPM_REST"
final String PERIOD_SUBVAR_NAME = "DataPush"
final String REPORT_NAME = "TB All Columns (Per,Cat,Loc)"
final String DEFAULT_REPORT_FORMAT = "XLSX"
final String EMAIL_SUBJECT_PREFIX = "Data Integration Report"
final int REPORT_MAX_POLL_COUNT = 60
final int MAIL_MAX_POLL_COUNT = 30
final long INITIAL_POLL_DELAY_MS = 1000L
final long MAX_POLL_DELAY_MS = 10000L
Map periodSelectorMap = new LinkedHashMap()
Map categorySelectorMap = new LinkedHashMap()
Map locationSelectorMap = new LinkedHashMap()
Map integrationNameSelectorMap = new LinkedHashMap()
/* =========================================================
STEP 2 - HELPER FUNCTIONS
========================================================= */
String json(Object value) {
return JsonOutput.toJson(value)
}
String stripOuterQuotes(String inputValue) {
String value = inputValue == null ? "" : inputValue.trim()
if (value.length() >= 2) {
boolean doubleQuoted = value.startsWith("\"") && value.endsWith("\"")
boolean singleQuoted = value.startsWith("'") && value.endsWith("'")
if (doubleQuoted || singleQuoted) {
value = value.substring(1, value.length() - 1).trim()
}
}
return value
}
String requireText(Object rawValue, String label) {
String value = rawValue == null ? "" : rawValue.toString()
value = stripOuterQuotes(value)
if (value.length() == 0 || value.equalsIgnoreCase("null")) {
throwVetoException("Missing or empty value: " + label)
}
return value
}
String getMapText(Map sourceMap, String keyName) {
if (sourceMap == null || keyName == null) {
return ""
}
Object value = sourceMap.get(keyName)
if (value == null) {
return ""
}
return value.toString()
}
List getListFromPossibleKeys(Map sourceMap, List keyNames) {
if (sourceMap == null) {
return []
}
for (Object keyObj : keyNames) {
String key = keyObj.toString()
Object value = sourceMap.get(key)
if (value instanceof List) {
return (List) value
}
}
return []
}
Map parseJsonToMap(String responseBody) {
if (responseBody == null || responseBody.trim().length() == 0) {
return new LinkedHashMap()
}
Object parsed = new JsonSlurper().parseText(responseBody)
if (parsed instanceof Map) {
return (Map) parsed
}
throwVetoException(
"Expected a JSON object but received: " + responseBody
)
return new LinkedHashMap()
}
int getStatusFromMap(Map sourceMap) {
String statusValue = getMapText(sourceMap, "status")
if (statusValue != null && statusValue.trim().length() > 0) {
try {
return statusValue.toInteger()
} catch (Exception ignored) {
String upperStatus = statusValue.toUpperCase()
if (upperStatus == "RUNNING" ||
upperStatus == "PENDING" ||
upperStatus == "STARTED" ||
upperStatus == "PROCESSING" ||
upperStatus == "IN_PROGRESS") {
return -1
}
if (upperStatus == "SUCCESS" ||
upperStatus == "SUCCEEDED" ||
upperStatus == "COMPLETED") {
return 0
}
if (upperStatus == "FAILED" || upperStatus == "ERROR") {
return 1
}
}
}
String jobStatusValue = getMapText(sourceMap, "jobStatus")
if (jobStatusValue != null && jobStatusValue.trim().length() > 0) {
String upperJobStatus = jobStatusValue.toUpperCase()
if (upperJobStatus == "RUNNING" ||
upperJobStatus == "PENDING" ||
upperJobStatus == "STARTED" ||
upperJobStatus == "PROCESSING" ||
upperJobStatus == "IN_PROGRESS") {
return -1
}
if (upperJobStatus == "SUCCESS" ||
upperJobStatus == "SUCCEEDED" ||
upperJobStatus == "COMPLETED") {
return 0
}
if (upperJobStatus == "FAILED" || upperJobStatus == "ERROR") {
return 1
}
}
return 999
}
String sanitizeFilePart(String inputValue) {
String value = inputValue == null ? "" : inputValue.trim()
value = value.replaceAll("[\\\\/:*?\"<>|]", "_")
value = value.replaceAll("\\s+", "_")
value = value.replaceAll("_+", "_")
if (value.length() == 0) {
value = "UNKNOWN"
}
return value
}
String getRequiredApplicationSubVarValue(String subVarName) {
String subVarValue = ""
try {
subVarValue = operation.application.getSubstitutionVariableValue(subVarName)
} catch (Exception e) {
throwVetoException(
"Could not retrieve the application-level substitution variable. " +
"Substitution Variable: [" + subVarName + "]. " +
"Error: " + e.getMessage()
)
}
subVarValue = stripOuterQuotes(subVarValue)
if (subVarValue.length() == 0 || subVarValue.equalsIgnoreCase("null")) {
throwVetoException(
"The substitution variable is empty or does not exist. " +
"Substitution Variable: [" + subVarName + "]."
)
}
return subVarValue
}
String mapSelectorValue(Map selectorMap, String inputValue, String label) {
String input = inputValue == null ? "" : inputValue.trim()
if (input.length() == 0) {
throwVetoException("Empty selector input value: " + label)
}
if (selectorMap == null || selectorMap.isEmpty()) {
return input
}
Object mapped = selectorMap.get(input)
if (mapped == null) {
for (Object entryObj : selectorMap.entrySet()) {
Map.Entry entry = (Map.Entry) entryObj
if (entry.getKey() != null &&
entry.getKey().toString().equalsIgnoreCase(input)) {
mapped = entry.getValue()
break
}
}
}
if (mapped == null) {
println(
"WARNING: No selector mapping was found for " +
label + " = [" + input + "]. The original value will be used."
)
return input
}
String output = mapped.toString().trim()
if (output.length() == 0) {
throwVetoException(
"The selector mapping returned an empty value. " +
"Selector: " + label + ", Input: [" + input + "]."
)
}
println(
"Selector mapping: " + label +
" | input=[" + input + "] -> reportValue=[" + output + "]"
)
return output
}
String extractJobIdFromResponse(Map responseMap) {
Object linksObj = responseMap.get("links")
if (linksObj instanceof List) {
List linksList = (List) linksObj
for (Object linkObj : linksList) {
if (!(linkObj instanceof Map)) {
continue
}
Map linkMap = (Map) linkObj
String rel = getMapText(linkMap, "rel")
String href = getMapText(linkMap, "href")
if (href != null && href.trim().length() > 0) {
if (rel.equalsIgnoreCase("Job Status") ||
href.indexOf("/jobs/") >= 0) {
int lastSlash = href.lastIndexOf("/")
if (lastSlash >= 0 && lastSlash < href.length() - 1) {
return href.substring(lastSlash + 1)
}
}
}
}
}
String jobId = getMapText(responseMap, "jobId")
if (jobId != null && jobId.trim().length() > 0) {
return jobId.trim()
}
return ""
}
String normalizeReportOutputPath(
String outputFileName,
String jobId,
String reportFormat
) {
String extension = reportFormat == null ? "xlsx" : reportFormat.toLowerCase()
String outputPath = outputFileName == null
? ""
: outputFileName.trim().replace("\\", "/")
if (outputPath.length() == 0 || outputPath.equalsIgnoreCase("null")) {
return "outbox/reports/" + jobId + "." + extension
}
if (outputPath.equalsIgnoreCase("outbox/reports") ||
outputPath.equalsIgnoreCase("outbox/report")) {
return outputPath + "/" + jobId + "." + extension
}
if (outputPath.endsWith("/")) {
return outputPath + jobId + "." + extension
}
if (!outputPath.toLowerCase().startsWith("outbox/") &&
!outputPath.toLowerCase().startsWith("inbox/") &&
outputPath.indexOf("/") < 0) {
return "outbox/reports/" + outputPath
}
return outputPath
}
String getUserEmailByLogin(String connectionName, String userLogin) {
String requestedLogin = userLogin == null ? "" : userLogin.trim()
if (requestedLogin.length() == 0) {
throwVetoException("The rule executor user login is empty.")
}
Map userPayload = new LinkedHashMap()
userPayload.put("userlogin", requestedLogin)
userPayload.put("epmgroups", false)
userPayload.put("granularroles", false)
userPayload.put("idcsgroups", false)
userPayload.put("applicationroles", false)
userPayload.put("indirect", false)
println("")
println("====================================================")
println("Resolving rule executor email address")
println("Rule Executor Login : " + requestedLogin)
println("====================================================")
def userResponse = operation.application
.getConnection(connectionName)
.post("/interop/rest/security/v1/users/list")
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.body(json(userPayload))
.asString()
int httpStatus = userResponse.status as int
String body = userResponse.body == null ? "" : userResponse.body.toString()
println("List Users HTTP status: " + httpStatus)
if (httpStatus < 200 || httpStatus > 299) {
throwVetoException(
"The Access Control List Users request failed. " +
"User Login: [" + requestedLogin + "]. " +
"HTTP status: " + httpStatus + ". " +
"Response: " + body
)
}
Map userMap = parseJsonToMap(body)
String statusText = getMapText(userMap, "status")
if (statusText != null && statusText.trim().length() > 0) {
try {
int apiStatus = statusText.toInteger()
if (apiStatus != 0) {
throwVetoException(
"The Access Control List Users API returned an error. " +
"Status: " + apiStatus + ". " +
"User Login: [" + requestedLogin + "]. " +
"Response: " + body
)
}
} catch (NumberFormatException ignored) {
println("WARNING: List Users status is not numeric: " + statusText)
}
}
List usersList = getListFromPossibleKeys(
userMap,
["details", "items", "users"]
)
if (usersList == null || usersList.isEmpty()) {
if (requestedLogin.contains("@")) {
println(
"WARNING: Access Control did not return a user record. " +
"The login is in email format and will be used as the recipient."
)
return requestedLogin
}
throwVetoException(
"Access Control did not find the rule executor user. " +
"User Login: [" + requestedLogin + "]. " +
"Response: " + body
)
}
Map selectedUser = null
for (Object rowObj : usersList) {
if (!(rowObj instanceof Map)) {
continue
}
Map row = (Map) rowObj
String returnedLogin = getMapText(row, "userlogin")
if (returnedLogin == null || returnedLogin.trim().length() == 0) {
returnedLogin = getMapText(row, "login")
}
if (returnedLogin == null || returnedLogin.trim().length() == 0) {
returnedLogin = getMapText(row, "userName")
}
if (returnedLogin != null &&
returnedLogin.equalsIgnoreCase(requestedLogin)) {
selectedUser = row
break
}
}
if (selectedUser == null &&
usersList.size() == 1 &&
usersList.get(0) instanceof Map) {
selectedUser = (Map) usersList.get(0)
}
if (selectedUser == null) {
throwVetoException(
"Access Control returned multiple users but no exact login match was found. " +
"Requested Login: [" + requestedLogin + "]. " +
"Response: " + body
)
}
String email = getMapText(selectedUser, "email")
if (email == null || email.trim().length() == 0) {
email = getMapText(selectedUser, "emailAddress")
}
if (email == null || email.trim().length() == 0) {
email = getMapText(selectedUser, "mail")
}
email = email == null ? "" : email.trim()
if (email.length() == 0 || email.equalsIgnoreCase("null")) {
if (requestedLogin.contains("@")) {
println(
"WARNING: The Access Control email field is empty. " +
"The login is in email format and will be used as the recipient."
)
email = requestedLogin
} else {
throwVetoException(
"No email address is configured for the rule executor. " +
"User Login: [" + requestedLogin + "]."
)
}
}
if (!email.contains("@") ||
email.startsWith("@") ||
email.endsWith("@")) {
throwVetoException(
"Access Control returned an invalid email address for the rule executor. " +
"User Login: [" + requestedLogin + "], Email: [" + email + "]."
)
}
println("Resolved Rule Executor Login : " + requestedLogin)
println("Resolved Recipient Email : " + email)
return email
}
Map getReportJobStatus(String connectionName, String jobId) {
def statusResponse = operation.application
.getConnection(connectionName)
.get("/aif/rest/V1/jobs/" + jobId)
.asString()
int httpStatus = statusResponse.status as int
String body = statusResponse.body == null ? "" : statusResponse.body.toString()
if (httpStatus < 200 || httpStatus > 299) {
throwVetoException(
"The Data Management report status request failed. " +
"Job ID: " + jobId + ". " +
"HTTP status: " + httpStatus + ". " +
"Response: " + body
)
}
return parseJsonToMap(body)
}
Map getMailJobStatus(String connectionName, String jobId) {
def statusResponse = operation.application
.getConnection(connectionName)
.get("/interop/rest/v2/status/jobs/" + jobId)
.asString()
int httpStatus = statusResponse.status as int
String body = statusResponse.body == null ? "" : statusResponse.body.toString()
if (httpStatus < 200 || httpStatus > 299) {
throwVetoException(
"The Send Mail status request failed. " +
"Job ID: " + jobId + ". " +
"HTTP status: " + httpStatus + ". " +
"Response: " + body
)
}
return parseJsonToMap(body)
}
/* =========================================================
STEP 3 - VALIDATE CONNECTION AND EXECUTOR
========================================================= */
println("====================================================")
println("Starting FCCS Groovy Report Email Rule")
println("Connection : " + CONNECTION_NAME)
println("Period SubVar : " + PERIOD_SUBVAR_NAME)
println("Report : " + REPORT_NAME)
println("====================================================")
if (!operation.application.hasConnection(CONNECTION_NAME)) {
throwVetoException("Connection not found: " + CONNECTION_NAME)
}
User executorUser = operation.getUser()
if (executorUser == null) {
throwVetoException("Could not determine the user who executed the rule.")
}
String executorLogin = executorUser.getName()
if (executorLogin == null || executorLogin.trim().length() == 0) {
throwVetoException("The rule executor user ID is empty.")
}
executorLogin = executorLogin.trim()
String sSendTo = getUserEmailByLogin(
CONNECTION_NAME,
executorLogin
)
/* =========================================================
STEP 4 - READ INPUT VALUES
========================================================= */
String inputPeriod = getRequiredApplicationSubVarValue(PERIOD_SUBVAR_NAME)
String inputCategory = requireText(
rtps.vDM_Category == null ? "" : rtps.vDM_Category.toString(),
"vDM_Category"
)
String inputLocation = requireText(
rtps.vDM_Location == null ? "" : rtps.vDM_Location.toString(),
"vDM_Location"
)
String inputIntegrationName = requireText(
rtps.vDM_RuleName == null ? "" : rtps.vDM_RuleName.toString(),
"vDM_RuleName"
)
/* =========================================================
STEP 5 - APPLY SELECTOR MAPPING
========================================================= */
String sPeriod = mapSelectorValue(
periodSelectorMap,
inputPeriod,
"Period"
)
String sCategory = mapSelectorValue(
categorySelectorMap,
inputCategory,
"Category"
)
String sLocation = mapSelectorValue(
locationSelectorMap,
inputLocation,
"Location"
)
String sIntegrationName = mapSelectorValue(
integrationNameSelectorMap,
inputIntegrationName,
"Integration Name"
)
/* =========================================================
STEP 6 - BUILD REPORT REQUEST
========================================================= */
Map reportParameters = new LinkedHashMap()
reportParameters.put("Period", sPeriod)
reportParameters.put("Category", sCategory)
reportParameters.put("Location", sLocation)
reportParameters.put("Integration Name", sIntegrationName)
Map reportPayload = new LinkedHashMap()
reportPayload.put("jobType", "REPORT")
reportPayload.put("jobName", REPORT_NAME)
reportPayload.put("reportFormatType", DEFAULT_REPORT_FORMAT)
reportPayload.put("parameters", reportParameters)
/* =========================================================
STEP 7 - SUBMIT REPORT
========================================================= */
println("")
println("====================================================")
println("Starting Data Management Report Execution")
println("Execution Method : REST API")
println("Connection : " + CONNECTION_NAME)
println("Rule Executor : " + executorLogin)
println("Recipient : " + sSendTo)
println("Report : " + REPORT_NAME)
println("Format : " + DEFAULT_REPORT_FORMAT)
println("Period Source : Substitution Variable [" + PERIOD_SUBVAR_NAME + "]")
println("Period Value : " + sPeriod)
println("====================================================")
println("")
println("Final report parameter values:")
for (Object keyObj : reportParameters.keySet()) {
String key = keyObj.toString()
println(key + " = [" + reportParameters.get(key) + "]")
}
println("")
println("Report request payload:")
println(json(reportPayload))
def submitResponse = operation.application
.getConnection(CONNECTION_NAME)
.post("/aif/rest/V1/jobs")
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.body(json(reportPayload))
.asString()
int submitHttpStatus = submitResponse.status as int
String submitBody = submitResponse.body == null ? "" : submitResponse.body.toString()
println("")
println("Submit report HTTP status: " + submitHttpStatus)
println("Submit report response:")
println(submitBody)
if (submitHttpStatus < 200 || submitHttpStatus > 299) {
throwVetoException(
"The Data Management report request failed. " +
"Report: " + REPORT_NAME + ". " +
"HTTP status: " + submitHttpStatus + ". " +
"Response: " + submitBody
)
}
Map submitMap = parseJsonToMap(submitBody)
String reportJobId = getMapText(submitMap, "jobId")
if (reportJobId == null || reportJobId.trim().length() == 0) {
reportJobId = extractJobIdFromResponse(submitMap)
}
reportJobId = reportJobId == null ? "" : reportJobId.trim()
if (reportJobId.length() == 0) {
throwVetoException(
"The report request returned a successful HTTP response but no job ID. " +
"Response: " + submitBody
)
}
println("")
println("Report submitted.")
println("Report Job ID: " + reportJobId)
/* =========================================================
STEP 8 - WAIT FOR REPORT COMPLETION
========================================================= */
int finalStatus = getStatusFromMap(submitMap)
String finalJobStatusText = getMapText(submitMap, "jobStatus")
String finalLogFileName = getMapText(submitMap, "logFileName")
String finalOutputFileName = getMapText(submitMap, "outputFileName")
String finalDetails = getMapText(submitMap, "details")
String finalRawBody = submitBody
if (finalStatus == 999) {
finalStatus = -1
}
int pollCount = 0
long delay = INITIAL_POLL_DELAY_MS
while (finalStatus == -1 && pollCount < REPORT_MAX_POLL_COUNT) {
sleep(delay)
pollCount = pollCount + 1
delay = delay * 2L
if (delay > MAX_POLL_DELAY_MS) {
delay = MAX_POLL_DELAY_MS
}
Map statusMap = getReportJobStatus(CONNECTION_NAME, reportJobId)
finalStatus = getStatusFromMap(statusMap)
finalJobStatusText = getMapText(statusMap, "jobStatus")
finalLogFileName = getMapText(statusMap, "logFileName")
finalOutputFileName = getMapText(statusMap, "outputFileName")
finalDetails = getMapText(statusMap, "details")
finalRawBody = statusMap.toString()
println(
"Report polling " + pollCount +
" | Job ID: " + reportJobId +
" | status: " + finalStatus +
" | jobStatus: " + finalJobStatusText +
" | outputFileName: " + finalOutputFileName
)
}
if (finalStatus == -1) {
throwVetoException(
"The report job did not complete within the polling limit. " +
"Report: " + REPORT_NAME + ". " +
"Job ID: " + reportJobId + ". " +
"Last response: " + finalRawBody
)
}
if (finalStatus != 0) {
throwVetoException(
"The Data Management report job failed. " +
"Report: " + REPORT_NAME +
", Job ID: " + reportJobId +
", Status: " + finalStatus +
", Job Status: " + finalJobStatusText +
", Details: " + finalDetails +
", Raw Response: " + finalRawBody
)
}
if ((finalOutputFileName == null || finalOutputFileName.trim().length() == 0) &&
finalDetails != null &&
finalDetails.toLowerCase().endsWith("." + DEFAULT_REPORT_FORMAT.toLowerCase())) {
finalOutputFileName = finalDetails
}
String attachmentPath = normalizeReportOutputPath(
finalOutputFileName,
reportJobId,
DEFAULT_REPORT_FORMAT
)
println("")
println("Report completed successfully.")
println("Report Job ID : " + reportJobId)
println("Output File Name : " + finalOutputFileName)
println("Attachment Path : " + attachmentPath)
println("Log File : " + finalLogFileName)
/* =========================================================
STEP 9 - BUILD EMAIL
========================================================= */
String desiredFileName =
sanitizeFilePart(sIntegrationName) + "_" +
sanitizeFilePart(sPeriod) + "_" +
"TB_All_Columns." + DEFAULT_REPORT_FORMAT.toLowerCase()
String emailSubject =
EMAIL_SUBJECT_PREFIX + " - " +
sanitizeFilePart(sIntegrationName) + " - " +
sanitizeFilePart(sPeriod)
String emailBody =
"<html><body>" +
"<p>Data Integration report completed successfully.</p>" +
"<p><strong>Rule executor:</strong> " + executorLogin + "</p>" +
"<p><strong>Recipient:</strong> " + sSendTo + "</p>" +
"<p><strong>Report:</strong> " + REPORT_NAME + "</p>" +
"<p><strong>Period source:</strong> Substitution Variable " + PERIOD_SUBVAR_NAME + "</p>" +
"<p><strong>Period:</strong> " + sPeriod + "</p>" +
"<p><strong>Category:</strong> " + sCategory + "</p>" +
"<p><strong>Location:</strong> " + sLocation + "</p>" +
"<p><strong>Integration Name:</strong> " + sIntegrationName + "</p>" +
"<p><strong>Requested business file name:</strong> " + desiredFileName + "</p>" +
"<p><strong>Attached EPM repository file:</strong> " + attachmentPath + "</p>" +
"<p>Note: EPM generated the physical attachment name. The business file name is shown above.</p>" +
"</body></html>"
println("")
println("Email subject : " + emailSubject)
println("Requested file name : " + desiredFileName)
println("Actual attachment : " + attachmentPath)
println("Recipient : " + sSendTo)
/* =========================================================
STEP 10 - SEND EMAIL
========================================================= */
Map mailParameters = new LinkedHashMap()
mailParameters.put("attachments", attachmentPath)
Map mailPayload = new LinkedHashMap()
mailPayload.put("to", sSendTo)
mailPayload.put("subject", emailSubject)
mailPayload.put("body", emailBody)
mailPayload.put("parameters", mailParameters)
println("")
println("Submitting Send Mail REST API v2.")
println("Recipient : " + sSendTo)
println("Subject : " + emailSubject)
println("Attachments : " + attachmentPath)
println("")
println("Send Mail payload:")
println(json(mailPayload))
def mailResponse = operation.application
.getConnection(CONNECTION_NAME)
.post("/interop/rest/v2/mails/send")
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.body(json(mailPayload))
.asString()
int mailHttpStatus = mailResponse.status as int
String mailBody = mailResponse.body == null ? "" : mailResponse.body.toString()
println("")
println("Send Mail HTTP status: " + mailHttpStatus)
println("Send Mail response:")
println(mailBody)
if (mailHttpStatus < 200 || mailHttpStatus > 299) {
throwVetoException(
"The Send Mail REST API v2 request failed. " +
"HTTP status: " + mailHttpStatus + ". " +
"Response: " + mailBody
)
}
Map mailMap = parseJsonToMap(mailBody)
int mailStatus = getStatusFromMap(mailMap)
String mailJobId = extractJobIdFromResponse(mailMap)
if (mailStatus == 999 &&
mailJobId != null &&
mailJobId.trim().length() > 0) {
mailStatus = -1
}
println("Send Mail initial status: " + mailStatus)
println("Send Mail job ID : " + mailJobId)
/* =========================================================
STEP 11 - WAIT FOR EMAIL COMPLETION
========================================================= */
if (mailStatus == -1 &&
mailJobId != null &&
mailJobId.trim().length() > 0) {
int mailPollCount = 0
long mailDelay = INITIAL_POLL_DELAY_MS
while (mailStatus == -1 && mailPollCount < MAIL_MAX_POLL_COUNT) {
sleep(mailDelay)
mailPollCount = mailPollCount + 1
mailDelay = mailDelay * 2L
if (mailDelay > MAX_POLL_DELAY_MS) {
mailDelay = MAX_POLL_DELAY_MS
}
Map mailStatusMap = getMailJobStatus(CONNECTION_NAME, mailJobId)
mailStatus = getStatusFromMap(mailStatusMap)
println(
"Send Mail polling " + mailPollCount +
" | Job ID: " + mailJobId +
" | status: " + mailStatus +
" | details: " + getMapText(mailStatusMap, "details")
)
}
}
if (mailStatus == -1) {
throwVetoException(
"The email job did not complete within the polling limit. " +
"Mail Job ID: " + mailJobId
)
}
if (mailStatus != 0) {
throwVetoException(
"Email delivery failed. " +
"Send Mail status: " + mailStatus + ". " +
"Initial response: " + mailBody
)
}
/* =========================================================
STEP 12 - FINAL LOG
========================================================= */
println("")
println("====================================================")
println("Data Management report completed and email sent successfully.")
println("Rule Executor : " + executorLogin)
println("Period SubVar : " + PERIOD_SUBVAR_NAME)
println("Period Value : " + sPeriod)
println("Report Name : " + REPORT_NAME)
println("Report Job ID : " + reportJobId)
println("Actual Attachment : " + attachmentPath)
println("Requested File Name : " + desiredFileName)
println("Sent To : " + sSendTo)
println("====================================================")
Production checks
| Check | Expected result |
|---|---|
| Named connection exists | operation.application.hasConnection("EPM_REST") returns true. |
| Executor is provisioned | The Access Control API returns the exact login and a valid email address. |
| DataPush is application-level | The Groovy API can retrieve the value regardless of the cube context. |
| Report definition name is exact | The payload uses TB All Columns (Per,Cat,Loc), not the underlying query name. |
| RTP values are valid | Category, Location, and Integration Name exist and return report data. |
| Mail permissions are available | The Send Mail REST job completes with status 0. |
