/*- * ============LICENSE_START======================================================= * OPENECOMP - MSO * ================================================================================ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ============LICENSE_END========================================================= */ package com.att.bpm.scripts; import javax.xml.transform.Transformer import javax.xml.transform.TransformerException; import org.camunda.bpm.engine.delegate.BpmnError import org.camunda.bpm.engine.impl.core.variable.value.ObjectValueImpl import org.camunda.bpm.engine.runtime.Execution import org.camunda.bpm.engine.variable.VariableMap import org.openecomp.mso.bpmn.core.WorkflowException import org.openecomp.mso.bpmn.gamma.workflow.service.WorkflowCallbackResponse import org.openecomp.mso.bpmn.gamma.workflow.service.WorkflowContextHolder import groovy.json.JsonSlurper import org.openecomp.mso.rest.APIResponse; import org.openecomp.mso.rest.RESTClient import org.openecomp.mso.rest.RESTConfig import org.camunda.bpm.engine.variable.VariableMap; import org.camunda.bpm.engine.variable.Variables; import org.camunda.bpm.engine.variable.Variables.SerializationDataFormats; import org.w3c.dom.Node; public abstract class AbstractServiceTaskProcessor implements ServiceTaskProcessor { public MsoUtils utils = new MsoUtils() /** * Logs a message at the ERROR level. * @param message the message */ public void logError(String message) { log('ERROR', message, null, "true") } /** * Logs a message at the ERROR level. * @param message the message * @param cause the cause (stracktrace will be included in the output) */ public void logError(String message, Throwable cause) { log('ERROR', message, cause, "true") } /** * Logs a message at the WARN level. * @param message the message */ public void logWarn(String message) { log('WARN', message, null, "true") } /** * Logs a message at the WARN level. * @param message the message * @param cause the cause (stracktrace will be included in the output) */ public void logWarn(String message, Throwable cause) { log('WARN', message, cause, "true") } /** * Logs a message at the INFO level. * @param message the message */ public void logInfo(String message) { log('INFO', message, null, "true") } /** * Logs a message at the INFO level. * @param message the message * @param cause the cause (stracktrace will be included in the output) */ public void logInfo(String message, Throwable cause) { log('INFO', message, cause, "true") } /** * Logs a message at the DEBUG level. * @param message the message * @param isDebugLogEnabled a flag indicating if DEBUG level is enabled */ public void logDebug(String message, String isDebugLogEnabled) { log('DEBUG', message, null, isDebugLogEnabled) } /** * Logs a message at the DEBUG level. * @param message the message * @param cause the cause (stracktrace will be included in the output) * @param isDebugLogEnabled a flag indicating if DEBUG level is enabled */ public void logDebug(String message, Throwable cause, String isDebugLogEnabled) { log('DEBUG', message, cause, isDebugLogEnabled) } /** * Logs a message at the specified level. * @param level the level (DEBUG, INFO, WARN, ERROR) * @param message the message * @param isLevelEnabled a flag indicating if the level is enabled * (used only at the DEBUG level) */ public void log(String level, String message, String isLevelEnabled) { log(level, message, null, isLevelEnabled) } /** * Logs a message at the specified level. * @param level the level (DEBUG, INFO, WARN, ERROR) * @param message the message * @param cause the cause (stracktrace will be included in the output) * @param isLevelEnabled a flag indicating if the level is enabled * (used only at the DEBUG level) */ public void log(String level, String message, Throwable cause, String isLevelEnabled) { if (cause == null) { utils.log(level, message, isLevelEnabled); } else { StringWriter stringWriter = new StringWriter(); PrintWriter printWriter = new PrintWriter(stringWriter); printWriter.println(message); cause.printStackTrace(printWriter); utils.log(level, stringWriter.toString(), isLevelEnabled); printWriter.close(); } } /** * Logs a WorkflowException at the ERROR level with the specified message. * @param execution the execution */ public void logWorkflowException(Execution execution, String message) { def workflowException = execution.getVariable("WorkflowException") if (workflowException == null) { logError(message); } else { logError(message + ": " + workflowException) } } /** * Saves the WorkflowException in the execution to the specified variable, * clearing the WorkflowException variable so the workflow can continue * processing (perhaps catching another WorkflowException). * @param execution the execution * @return the name of the destination variable */ public saveWorkflowException(Execution execution, String variable) { if (variable == null) { throw new NullPointerException(); } execution.setVariable(variable, execution.getVariable("WorkflowException")) execution.setVariable("WorkflowException", null) } /** * Builds a success response from the specified message content and numeric * response code. The response code may be an integer or a string representation * of an integer. The response is stored in the execution where it may be * picked up by the Workflow service. *

* IMPORTANT: the activity that executes this method should have an * asynchronous continuation after it to ensure the execution variables * are persisted to the database. * @param execution the execution * @param content the message content * @param responseCode the message response code */ @Deprecated public void buildResponse(Execution execution, String content, Object responseCode) { buildResponse(execution, content, responseCode, true) } /** * Builds a standard error response containing the specified error message and * numeric response code. The response code may be an integer or a string * representation of an integer. The response is stored in the execution where * it may be picked up by the Workflow service. *

* IMPORTANT: the activity that executes this method should have an * asynchronous continuation after it to ensure the execution variables * are persisted to the database. * @param execution the execution * @param content the message content * @param errorCode the message response code */ @Deprecated public void buildErrorResponse(Execution execution, String errorMessage, Object errorCode) { def encErrorMessage = errorMessage.replace("&", "&").replace("<", "<").replace(">", ">") def content = """ ${encErrorMessage} ${errorCode} """ buildResponse(execution, content, errorCode, false) } // BEGIN LEGACY SUPPORT. TODO: REMOVE THIS CODE /** * Builds a standard error response containing the specified error message * and a numeric response code. The response code is obtained from the * prefix+"ResponseCode" execution variable. The response is stored in the * execution where it may be picked up by the Workflow service. *

* IMPORTANT: the activity that executes this method should have an * asynchronous continuation after it to ensure the execution variables * are persisted to the database. *

* This method is deprecated. Methods that accept a response code should * be used instead. * @param execution the execution * @param errorMessage the error message for the error response */ @Deprecated public void buildErrorResponse(Execution execution, String errorMessage) { buildErrorResponse(execution, errorMessage, null) } // END LEGACY SUPPORT. TODO: REMOVE THIS CODE /** * Builds a response from the specified message content and numeric response * code. The response code may be an integer or a string representation of * an integer. The response is stored in the execution where it may be * picked up by the Workflow service. *

* IMPORTANT: the activity that executes this method should have an * asynchronous continuation after it to ensure the execution variables * are persisted to the database. * @param execution the execution * @param content the message content * @param responseCode the message response code * @param isSuccess true if this is a success response */ @Deprecated protected void buildResponse(Execution execution, String content, Object responseCode, boolean isSuccess) { def isDebugLogEnabled = execution.getVariable('isDebugLogEnabled') String processKey = getProcessKey(execution); logDebug("Building " + processKey + " response", isDebugLogEnabled) Map responseMap = new HashMap() if (isSuccess) { responseMap.put("Status", "Success") } else { responseMap.put("Status", "Fail") } // BEGIN LEGACY SUPPORT. TODO: REMOVE THIS CODE def prefix = execution.getVariable("prefix") if (responseCode == null) { responseCode = execution.getVariable(prefix+"ResponseCode") } else { execution.setVariable(prefix+"ResponseCode", String.valueOf(responseCode)) } // END LEGACY SUPPORT. TODO: REMOVE THIS CODE responseMap.put("ResponseCode", String.valueOf(responseCode)) if (isSuccess) { responseMap.put("Status", "Success") // TODO: Should deprecate use of processKey+Response variable for the response. Will use "WorkflowResponse" instead execution.setVariable("WorkflowResponse", content) // BEGIN LEGACY SUPPORT. TODO: REMOVE THIS CODE execution.setVariable(processKey+"Response", content) execution.setVariable(prefix+"ErrorResponse", null) // END LEGACY SUPPORT. TODO: REMOVE THIS CODE } else { responseMap.put("Status", "Fail") // BEGIN LEGACY SUPPORT. TODO: REMOVE THIS CODE execution.setVariable(prefix+"ErrorResponse", content) execution.setVariable(prefix+"Response", null) // END LEGACY SUPPORT. TODO: REMOVE THIS CODE } responseMap.put("Response", content) logDebug(processKey + " ResponseCode=" + responseMap.get("ResponseCode") + " Status=" + responseMap.get("Status") + " Response=\n" + responseMap.get("Response"), isDebugLogEnabled) execution.setVariable(processKey + "ResponseMap", responseMap) } /** * Builds an error response (if one has not already been built) and throws * a BpmnError of type "MSOWorkflowException" that can be caught as a * boundary event. * @param execution the execution * @param errorMessage the error message for the error response * @param responseCode the message response code */ @Deprecated public void workflowException(Execution execution, String errorMessage, Object responseCode) { String processKey = getProcessKey(execution); buildErrorResponse(execution, errorMessage, responseCode) throw new BpmnError("MSOWorkflowException") } /** * Puts a WorkflowException into the execution * @param execution the execution * @param errorCode the error code (normally a 4-digit number) * @param errorMessage the error message */ @Deprecated public void newWorkflowException(Execution execution, int errorCode, String errorMessage) { def isDebugLogEnabled = execution.getVariable('isDebugLogEnabled') String processKey = getProcessKey(execution); logDebug("Building a " + processKey + " WorkflowException", isDebugLogEnabled) if (errorCode < 1000) { throw new IllegalArgumentException("ErrorCode must be a number greater than 1000"); } WorkflowException exception = new WorkflowException(processKey, errorCode, errorMessage); execution.setVariable("WorkflowException", exception); } /** * Puts a WorkflowException into the execution and throws an MSOWorkflowException event. * @param execution the execution * @param errorCode the error code (normally a 4-digit number) * @param errorMessage the error message */ // TODO: rename this method to be throwWorkflowException @Deprecated public void createWorkflowException(Execution execution, int errorCode, String errorMessage) { newWorkflowException(execution, errorCode, errorMessage) throw new BpmnError("MSOWorkflowException", "errorCode:" + errorCode + ", errorMessage:" + errorMessage) } /** * Puts a WorkflowException into the execution and throws an MSOWorkflowException event. * @param execution the execution * @param errorCode the error code (normally a 4-digit number) * @param errorMessage the error message */ @Deprecated public void commonWorkflowException(Execution execution, int errorCode, String errorMessage) { def isDebugLogEnabled = execution.getVariable('isDebugLogEnabled') String processKey = getProcessKey(execution); logDebug("Building a " + processKey + " WorkflowException", isDebugLogEnabled) logError(errorMessage) WorkflowException exception = new WorkflowException(processKey, errorCode, errorMessage); execution.setVariable("WorkflowException", exception); throw new BpmnError("MSOWorkflowException","errorCode:" + errorCode + ",errorMessage:" + errorMessage) } /** * Puts a WorkflowException into the execution and throws an MSOWorkflowException event. * @param execution the execution * @param errorCode the error code (normally a 4-digit number) * @param errorMessage the error message */ @Deprecated public void commonWorkflowException(Execution execution, String errorCode, String errorMessage) { int intRespCode try{ intRespCode = Integer.parseInt(errorCode) }catch (Exception e){ intRespCode = 400 } commonWorkflowException(execution, intRespCode, errorMessage) } /** * Validates that the request exists and that the att-mso-request-id variable is set. * Additional required variables may be checked by specifying their names. * NOTE: services requiring att-mso-service-instance-id must specify it explicitly! * If a problem is found, buildAndThrowWorkflowException builds a WorkflowException * and throws an MSOWorkflowException. This method also sets up the log context for * the workflow. * * @param execution the execution * @return the validated request */ public String validateRequest(Execution execution, String... requiredVariables) { ExceptionUtil exceptionUtil = new ExceptionUtil() def method = getClass().getSimpleName() + '.validateRequest(' + 'execution=' + execution.getId() + ', requredVariables=' + requiredVariables + ')' def isDebugLogEnabled = execution.getVariable('isDebugLogEnabled') logDebug('Entered ' + method, isDebugLogEnabled) String processKey = getProcessKey(execution) def prefix = execution.getVariable("prefix") if (prefix == null) { exceptionUtil.buildAndThrowWorkflowException(execution, 1002, processKey + " prefix is null") } try { def request = execution.getVariable(prefix + 'Request') if (request == null) { request = execution.getVariable(processKey + 'Request') if (request == null) { request = execution.getVariable('bpmnRequest') } setVariable(execution, processKey + 'Request', null) setVariable(execution, 'bpmnRequest', null) setVariable(execution, prefix + 'Request', request) } if (request == null) { exceptionUtil.buildAndThrowWorkflowException(execution, 1002, processKey + " request is null") } // All requests must have a request ID. // Some requests (e.g. SDN-MOBILITY) do not have a service instance ID. String requestId = null String serviceInstanceId = null List allRequiredVariables = new ArrayList() allRequiredVariables.add("att-mso-request-id") if (requiredVariables != null) { for (String variable : requiredVariables) { if (!allRequiredVariables.contains(variable)) { allRequiredVariables.add(variable) } } } for (String variable : allRequiredVariables) { def value = execution.getVariable(variable) if (value == null || ((value instanceof CharSequence) && value.length() == 0)) { exceptionUtil.buildAndThrowWorkflowException(execution, 1002, processKey + " request was received with no '" + variable + "' variable") } if ("att-mso-request-id".equals(variable)) { requestId = (String) value } else if ("att-mso-service-instance-id".equals(variable)) { serviceInstanceId = (String) value } } if (serviceInstanceId == null) { serviceInstanceId = (String) execution.getVariable("att-mso-service-instance-id") } utils.logContext(requestId, serviceInstanceId) logDebug('Incoming message: ' + System.lineSeparator() + request, isDebugLogEnabled) logDebug('Exited ' + method, isDebugLogEnabled) return request } catch (BpmnError e) { throw e } catch (Exception e) { logError('Caught exception in ' + method, e) exceptionUtil.buildAndThrowWorkflowException(execution, 1002, "Invalid Message") } } /** * gets vars stored in a JSON object in prefix+Request and returns as a LazyMap * setting log context here too * @param execution the execution * @return the inputVars */ public Map validateJSONReq(Execution execution) { def method = getClass().getSimpleName() + '.validateJSONReq(' + 'execution=' + execution.getId() + ')' def isDebugLogEnabled = execution.getVariable('isDebugLogEnabled') logDebug('Entered ' + method, isDebugLogEnabled) String processKey = getProcessKey(execution); def prefix = execution.getVariable("prefix") def requestId =getVariable(execution, "att-mso-request-id") def serviceInstanceId = getVariable(execution, "att-mso-service-instance-id") if(requestId!=null && serviceInstanceId!=null){ utils.logContext(requestId, serviceInstanceId) } def request = getVariable(execution, prefix + 'Request') if (request == null) { request = getVariable(execution, processKey + 'Request') if (request == null) { request = getVariable(execution, 'bpmnRequest') } execution.setVariable(prefix + 'Request', request) } def jsonSlurper = new JsonSlurper() def parsed = jsonSlurper.parseText(request) logDebug('Incoming message: ' + System.lineSeparator() + request, isDebugLogEnabled) logDebug('Exited ' + method, isDebugLogEnabled) return parsed } /** * Sends a response to the workflow service that invoked the process. This method * may only be used by top-level processes that were directly invoked by the * asynchronous workflow service. * @param execution the execution * @param responseCode the response code * @param content the message content * @throws IllegalArgumentException if the response code is invalid * by HTTP standards * @throws UnsupportedOperationException if not invoked by an asynchronous, * top-level process * @throws IllegalStateException if a response has already been sent */ protected void sendWorkflowResponse(Execution execution, Object responseCode, String response) { def isDebugLogEnabled = execution.getVariable('isDebugLogEnabled') try { String processKey = getProcessKey(execution); // isAsyncProcess is injected by the workflow service that started the flow if (!String.valueOf(execution.getVariable("isAsyncProcess")).equals("true")) { throw new UnsupportedOperationException(processKey + ": " + "sendWorkflowResponse is valid only in asynchronous workflows"); } if (String.valueOf(execution.getVariable(processKey + "WorkflowResponseSent")).equals("true")) { logDebug("Sync response has already been sent for " + processKey, isDebugLogEnabled) }else{ logDebug("Building " + processKey + " response ", isDebugLogEnabled) int intResponseCode; try { intResponseCode = Integer.parseInt(String.valueOf(responseCode)); if (intResponseCode < 100 || intResponseCode > 599) { throw new NumberFormatException(String.valueOf(responseCode)); } } catch (NumberFormatException e) { throw new IllegalArgumentException("Process " + processKey + " provided an invalid HTTP response code: " + responseCode); } // Only 2XX responses are considered "Success" String status = (intResponseCode >= 200 && intResponseCode <= 299) ? "Success" : "Fail"; // TODO: Should deprecate use of processKey+Response variable for the response. Will use "WorkflowResponse" instead execution.setVariable(processKey + "ResponseCode", String.valueOf(intResponseCode)) execution.setVariable(processKey + "Response", response); execution.setVariable(processKey + "Status", status); execution.setVariable("WorkflowResponse", response) logDebug("Sending response for " + processKey + " ResponseCode=" + intResponseCode + " Status=" + status + " Response=\n" + response, isDebugLogEnabled) // TODO: ensure that this flow was invoked asynchronously? WorkflowCallbackResponse callbackResponse = new WorkflowCallbackResponse() callbackResponse.setStatusCode(intResponseCode) callbackResponse.setMessage(status) callbackResponse.setResponse(response) // TODO: send this data with HTTP POST WorkflowContextHolder.getInstance().processCallback( processKey, execution.getProcessInstanceId(), execution.getVariable("att-mso-request-id"), callbackResponse) execution.setVariable(processKey + "WorkflowResponseSent", "true"); } } catch (Exception ex) { logError("Unable to send workflow response to client ....", ex) } } /** * Returns true if a workflow response has already been sent. * @param execution the execution */ protected boolean isWorkflowResponseSent(Execution execution) { def isDebugLogEnabled = execution.getVariable('isDebugLogEnabled') String processKey = getProcessKey(execution); return String.valueOf(execution.getVariable(processKey + "WorkflowResponseSent")).equals("true"); } /** * Returns the process definition key (i.e. the process name) from the * execution. * @param execution the execution */ public String getProcessKey(Execution execution) { def testKey = execution.getVariable("testProcessKey") if(testKey!=null){ return testKey } return execution.getProcessEngineServices().getRepositoryService() .getProcessDefinition(execution.getProcessDefinitionId()).getKey() } /** * Gets the node for the named element from the given xml. If the element * does not exist in the xml or is empty, a WorkflowException is created * (and as a result, a MSOWorkflowException event is thrown). * * @param execution The flow's execution. * @param xml Xml to search. * @param elementName Name of element to search for. * @return The element node, if found in the xml. */ protected String getRequiredNodeXml(Execution execution, String xml, String elementName) { def element = utils.getNodeXml(xml, elementName, false) if (element.trim().isEmpty()) { def msg = 'Required element \'' + elementName + '\' is missing or empty' logError(msg) createWorkflowException(execution, 2000, msg) } else { return element } } /** * Gets the value of the named element from the given xml. If the element * does not exist in the xml or is empty, a WorkflowException is created * (and as a result, a MSOWorkflowException event is thrown). * * @param execution The flow's execution. * @param xml Xml to search. * @param elementName Name of element to whose value to get. * @return The value of the element, if found in the xml. */ protected String getRequiredNodeText(Execution execution, String xml, String elementName) { def elementText = utils.getNodeText1(xml, elementName) if (elementText == null) { def msg = 'Required element \'' + elementName + '\' is missing or empty' logError(msg) createWorkflowException(execution, 2000, msg) } else { return elementText } } /** * Get the text for the specified element from the specified xml. If * the element does not exist, return an empty string. * * @param xml Xml from which to get the element's text. * @param elementName Name of element whose text to get. * @return the element's text or an empty string if the element does not * exist in the given xml. */ protected String getNodeTextForce(String xml, String elementName) { def nodeText = utils.getNodeText1(xml, elementName) return (nodeText == null) ? '' : nodeText } /** * Sends the empty, synchronous response back to the API Handler. * @param execution the execution */ @Deprecated public void sendResponse(Execution execution) { def method = getClass().getSimpleName() + '.sendResponse(' + 'execution=' + execution.getId() + ')' def isDebugLogEnabled = execution.getVariable('isDebugLogEnabled') logDebug('Entered ' + method, isDebugLogEnabled) try { buildResponse(execution, "", 200) logDebug('Exited ' + method, isDebugLogEnabled) } catch (BpmnError e) { throw e; } catch (Exception e) { logError('Caught exception in ' + method, e) workflowException(execution, 'Internal Error', 9999) // TODO: what message and error code? } } /** *Store the variable as typed with java serialization type *@param execution *@param name *@param value */ public void setVariable(Execution execution, String name, Object value) { VariableMap variables = Variables.createVariables() variables.putValueTyped('payload', Variables.objectValue(value) .serializationDataFormat(SerializationDataFormats.JAVA) // tells the engine to use java serialization for persisting the value .create()) execution.setVariable(name,variables) } //TODO not sure how this will look in Cockpit /** * Returns the variable map *@param execution *@param name *@return **/ public String getVariable(Execution execution, String name) { def myObj = execution.getVariable(name) if(myObj instanceof VariableMap){ VariableMap serializedObjectMap = (VariableMap) myObj ObjectValueImpl payloadObj = serializedObjectMap.getValueTyped('payload') return payloadObj.getValue() }else{ return myObj } } /** * Returns true if a value equals one of the provided set. Equality is * determined by using the equals method if the value object and the * object in the provided set have the same class. Otherwise, the objects * are converted to strings and then compared. Nulls are permitted for * the value as well as in the provided set * Example: *

	 *     def statusCode = getStatusCode()
	 *     isOneOf(statusCode, 200, 201, 204)
	 * 
* @param value the value to test * @param these a set of permissable values * @return true if the value is in the provided set */ public boolean isOneOf(Object value, Object... these) { for (Object thisOne : these) { if (thisOne == null) { if (value == null) { return true } } else { if (value != null) { if (value.getClass() == thisOne.getClass()) { if (value.equals(thisOne)) { return true } } else { if (String.valueOf(value).equals(String.valueOf(thisOne))) { return true } } } } } return false } public void setSuccessIndicator(Execution execution, boolean isSuccess) { String prefix = execution.getVariable('prefix') def isDebugLogEnabled = execution.getVariable('isDebugLogEnabled') logDebug('Entered SetSuccessIndicator Method', isDebugLogEnabled) execution.setVariable(prefix+'SuccessIndicator', isSuccess) logDebug('Outgoing SuccessIndicator is: ' + execution.getVariable(prefix+'SuccessIndicator') + '', isDebugLogEnabled) } public void sendSyncError(Execution execution) { def isDebugEnabled=execution.getVariable("isDebugLogEnabled") String requestId = execution.getVariable("att-mso-request-id") logDebug('sendSyncError, requestId: ' + requestId, isDebugEnabled) WorkflowException workflowExceptionObj = execution.getVariable("WorkflowException") if (workflowExceptionObj != null) { String errorMessage = workflowExceptionObj.getErrorMessage() def errorCode = workflowExceptionObj.getErrorCode() logDebug('sendSyncError, requestId: ' + requestId + ' | errorMessage: ' + errorMessage + ' | errorCode: ' + errorCode, isDebugEnabled) sendWorkflowResponse(execution, errorCode, errorMessage) } } /** * Create a WorkflowException - uses ExceptionUtil to build a WorkflowException * @param execution * @param errorCode * @param errorMessage * @param isDebugEnabled */ public void buildWorkflowException(Execution execution, int errorCode, String errorMessage, boolean isDebugEnabled) { (new ExceptionUtil()).buildWorkflowException(execution, errorCode, errorMessage) } /** * Executes a named groovy script method in the current object */ public void executeMethod(String methodName, Object... args) { if (args != null && args.size() > 0) { // First argument of method to call is always the execution object Execution execution = (Execution) args[0] def classAndMethod = getClass().getSimpleName() + '.' + methodName + '(execution=' + execution.getId() + ')' def isDebugEnabled = execution.getVariable('isDebugLogEnabled') logDebug('Entered ' + classAndMethod, isDebugEnabled) logDebug('Received parameters: ' + args, isDebugEnabled) try{ def methodToCall = this.metaClass.getMetaMethod(methodName, args) logDebug('Method to call: ' + methodToCall, isDebugEnabled) methodToCall?.invoke(this, args) } catch(BpmnError bpmnError) { logDebug('Rethrowing BpmnError ' + bpmnError.getMessage(), isDebugEnabled) throw bpmnError } catch(Exception e) { e.printStackTrace() logDebug('Unexpected error encountered - ' + e.getMessage(), isDebugEnabled) (new ExceptionUtil()).buildAndThrowWorkflowException(execution, 9999, e.getMessage()) } finally { logDebug('Exited ' + classAndMethod, isDebugEnabled) } } } }