2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6 * ================================================================================
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 * ============LICENSE_END=========================================================
21 package org.onap.so.bpmn.common.scripts;
23 import org.camunda.bpm.engine.delegate.BpmnError
24 import org.camunda.bpm.engine.delegate.DelegateExecution
25 import org.camunda.bpm.engine.variable.VariableMap
26 import org.camunda.bpm.engine.variable.Variables
27 import org.camunda.bpm.engine.variable.Variables.SerializationDataFormats
28 import org.camunda.bpm.engine.variable.impl.value.ObjectValueImpl
29 import org.onap.so.bpmn.common.workflow.context.WorkflowCallbackResponse
30 import org.onap.so.bpmn.common.workflow.context.WorkflowContextHolder
31 import org.onap.so.bpmn.core.UrnPropertiesReader
32 import org.onap.so.bpmn.core.WorkflowException
33 import org.onap.so.client.aai.AAIResourcesClient
34 import org.springframework.web.util.UriUtils
36 import groovy.json.JsonSlurper
38 public abstract class AbstractServiceTaskProcessor implements ServiceTaskProcessor {
39 public MsoUtils utils = new MsoUtils()
43 * Logs a message at the ERROR level.
44 * @param message the message
46 public void logError(String message) {
47 log('ERROR', message, null, "true")
51 * Logs a message at the ERROR level.
52 * @param message the message
53 * @param cause the cause (stracktrace will be included in the output)
55 public void logError(String message, Throwable cause) {
56 log('ERROR', message, cause, "true")
60 * Logs a message at the WARN level.
61 * @param message the message
63 public void logWarn(String message) {
64 log('WARN', message, null, "true")
68 * Logs a message at the WARN level.
69 * @param message the message
70 * @param cause the cause (stracktrace will be included in the output)
72 public void logWarn(String message, Throwable cause) {
73 log('WARN', message, cause, "true")
77 * Logs a message at the DEBUG level.
78 * @param message the message
79 * @param isDebugLogEnabled a flag indicating if DEBUG level is enabled
81 public void logDebug(String message, String isDebugLogEnabled) {
82 log('DEBUG', message, null, isDebugLogEnabled)
86 * Logs a message at the DEBUG level.
87 * @param message the message
88 * @param cause the cause (stracktrace will be included in the output)
89 * @param isDebugLogEnabled a flag indicating if DEBUG level is enabled
91 public void logDebug(String message, Throwable cause, String isDebugLogEnabled) {
92 log('DEBUG', message, cause, isDebugLogEnabled)
96 * Logs a message at the specified level.
97 * @param level the level (DEBUG, INFO, WARN, ERROR)
98 * @param message the message
99 * @param isLevelEnabled a flag indicating if the level is enabled
100 * (used only at the DEBUG level)
102 public void log(String level, String message, String isLevelEnabled) {
103 log(level, message, null, isLevelEnabled)
107 * Logs a message at the specified level.
108 * @param level the level (DEBUG, INFO, WARN, ERROR)
109 * @param message the message
110 * @param cause the cause (stracktrace will be included in the output)
111 * @param isLevelEnabled a flag indicating if the level is enabled
112 * (used only at the DEBUG level)
114 public void log(String level, String message, Throwable cause, String isLevelEnabled) {
116 utils.log(level, message, isLevelEnabled);
118 StringWriter stringWriter = new StringWriter();
119 PrintWriter printWriter = new PrintWriter(stringWriter);
120 printWriter.println(message);
121 cause.printStackTrace(printWriter);
122 utils.log(level, stringWriter.toString(), isLevelEnabled);
128 * Logs a WorkflowException at the ERROR level with the specified message.
129 * @param execution the execution
131 public void logWorkflowException(DelegateExecution execution, String message) {
132 def workflowException = execution.getVariable("WorkflowException")
134 if (workflowException == null) {
137 logError(message + ": " + workflowException)
142 * Saves the WorkflowException in the execution to the specified variable,
143 * clearing the WorkflowException variable so the workflow can continue
144 * processing (perhaps catching another WorkflowException).
145 * @param execution the execution
146 * @return the name of the destination variable
148 public saveWorkflowException(DelegateExecution execution, String variable) {
149 if (variable == null) {
150 throw new NullPointerException();
153 execution.setVariable(variable, execution.getVariable("WorkflowException"))
154 execution.setVariable("WorkflowException", null)
159 * Validates that the request exists and that the mso-request-id variable is set.
160 * Additional required variables may be checked by specifying their names.
161 * NOTE: services requiring mso-service-instance-id must specify it explicitly!
162 * If a problem is found, buildAndThrowWorkflowException builds a WorkflowException
163 * and throws an MSOWorkflowException. This method also sets up the log context for
166 * @param execution the execution
167 * @return the validated request
169 public String validateRequest(DelegateExecution execution, String... requiredVariables) {
170 ExceptionUtil exceptionUtil = new ExceptionUtil()
171 def method = getClass().getSimpleName() + '.validateRequest(' +
172 'execution=' + execution.getId() +
173 ', requredVariables=' + requiredVariables +
175 def isDebugLogEnabled = execution.getVariable('isDebugLogEnabled')
176 logDebug('Entered ' + method, isDebugLogEnabled)
178 String processKey = getProcessKey(execution)
179 def prefix = execution.getVariable("prefix")
181 if (prefix == null) {
182 exceptionUtil.buildAndThrowWorkflowException(execution, 1002, processKey + " prefix is null")
186 def request = execution.getVariable(prefix + 'Request')
188 if (request == null) {
189 request = execution.getVariable(processKey + 'Request')
191 if (request == null) {
192 request = execution.getVariable('bpmnRequest')
195 setVariable(execution, processKey + 'Request', null)
196 setVariable(execution, 'bpmnRequest', null)
197 setVariable(execution, prefix + 'Request', request)
200 if (request == null) {
201 exceptionUtil.buildAndThrowWorkflowException(execution, 1002, processKey + " request is null")
204 // All requests must have a request ID.
205 // Some requests (e.g. SDN-MOBILITY) do not have a service instance ID.
207 String requestId = null
208 String serviceInstanceId = null
210 List<String> allRequiredVariables = new ArrayList<String>()
211 allRequiredVariables.add("mso-request-id")
213 if (requiredVariables != null) {
214 for (String variable : requiredVariables) {
215 if (!allRequiredVariables.contains(variable)) {
216 allRequiredVariables.add(variable)
221 for (String variable : allRequiredVariables) {
222 def value = execution.getVariable(variable)
223 if (value == null || ((value instanceof CharSequence) && value.length() == 0)) {
224 exceptionUtil.buildAndThrowWorkflowException(execution, 1002, processKey +
225 " request was received with no '" + variable + "' variable")
228 if ("mso-request-id".equals(variable)) {
229 requestId = (String) value
230 } else if ("mso-service-instance-id".equals(variable)) {
231 serviceInstanceId = (String) value
235 if (serviceInstanceId == null) {
236 serviceInstanceId = (String) execution.getVariable("mso-service-instance-id")
239 utils.logContext(requestId, serviceInstanceId)
240 logDebug('Incoming message: ' + System.lineSeparator() + request, isDebugLogEnabled)
241 logDebug('Exited ' + method, isDebugLogEnabled)
243 } catch (BpmnError e) {
245 } catch (Exception e) {
246 logError('Caught exception in ' + method, e)
247 exceptionUtil.buildAndThrowWorkflowException(execution, 1002, "Invalid Message")
252 * gets vars stored in a JSON object in prefix+Request and returns as a LazyMap
253 * setting log context here too
254 * @param execution the execution
255 * @return the inputVars
257 public Map validateJSONReq(DelegateExecution execution) {
258 def method = getClass().getSimpleName() + '.validateJSONReq(' +
259 'execution=' + execution.getId() +
261 def isDebugLogEnabled = execution.getVariable('isDebugLogEnabled')
262 logDebug('Entered ' + method, isDebugLogEnabled)
264 String processKey = getProcessKey(execution);
265 def prefix = execution.getVariable("prefix")
267 def requestId =getVariable(execution, "mso-request-id")
268 def serviceInstanceId = getVariable(execution, "mso-service-instance-id")
269 if(requestId!=null && serviceInstanceId!=null){
270 utils.logContext(requestId, serviceInstanceId)
274 def request = getVariable(execution, prefix + 'Request')
276 if (request == null) {
277 request = getVariable(execution, processKey + 'Request')
279 if (request == null) {
280 request = getVariable(execution, 'bpmnRequest')
282 execution.setVariable(prefix + 'Request', request)
285 def jsonSlurper = new JsonSlurper()
286 def parsed = jsonSlurper.parseText(request)
289 logDebug('Incoming message: ' + System.lineSeparator() + request, isDebugLogEnabled)
290 logDebug('Exited ' + method, isDebugLogEnabled)
296 * Sends a response to the workflow service that invoked the process. This method
297 * may only be used by top-level processes that were directly invoked by the
298 * asynchronous workflow service.
299 * @param execution the execution
300 * @param responseCode the response code
301 * @param content the message content
302 * @throws IllegalArgumentException if the response code is invalid
304 * @throws UnsupportedOperationException if not invoked by an asynchronous,
306 * @throws IllegalStateException if a response has already been sent
308 protected void sendWorkflowResponse(DelegateExecution execution, Object responseCode, String response) {
309 def isDebugLogEnabled = execution.getVariable('isDebugLogEnabled')
311 String processKey = getProcessKey(execution);
313 // isAsyncProcess is injected by the workflow service that started the flow
314 if (!String.valueOf(execution.getVariable("isAsyncProcess")).equals("true")) {
315 throw new UnsupportedOperationException(processKey + ": " +
316 "sendWorkflowResponse is valid only in asynchronous workflows");
319 if (String.valueOf(execution.getVariable(processKey + "WorkflowResponseSent")).equals("true")) {
320 logDebug("Sync response has already been sent for " + processKey, isDebugLogEnabled)
323 logDebug("Building " + processKey + " response ", isDebugLogEnabled)
328 intResponseCode = Integer.parseInt(String.valueOf(responseCode));
330 if (intResponseCode < 100 || intResponseCode > 599) {
331 throw new NumberFormatException(String.valueOf(responseCode));
333 } catch (NumberFormatException e) {
334 throw new IllegalArgumentException("Process " + processKey
335 + " provided an invalid HTTP response code: " + responseCode);
338 // Only 2XX responses are considered "Success"
339 String status = (intResponseCode >= 200 && intResponseCode <= 299) ?
342 // TODO: Should deprecate use of processKey+Response variable for the response. Will use "WorkflowResponse" instead
343 execution.setVariable(processKey + "ResponseCode", String.valueOf(intResponseCode))
344 execution.setVariable(processKey + "Response", response);
345 execution.setVariable(processKey + "Status", status);
346 execution.setVariable("WorkflowResponse", response)
348 logDebug("Sending response for " + processKey
349 + " ResponseCode=" + intResponseCode
350 + " Status=" + status
351 + " Response=\n" + response,
354 // TODO: ensure that this flow was invoked asynchronously?
356 WorkflowCallbackResponse callbackResponse = new WorkflowCallbackResponse()
357 callbackResponse.setStatusCode(intResponseCode)
358 callbackResponse.setMessage(status)
359 callbackResponse.setResponse(response)
361 // TODO: send this data with HTTP POST
363 WorkflowContextHolder.getInstance().processCallback(
365 execution.getProcessInstanceId(),
366 execution.getVariable("mso-request-id"),
369 execution.setVariable(processKey + "WorkflowResponseSent", "true");
372 } catch (Exception ex) {
373 logError("Unable to send workflow response to client ....", ex)
378 * Returns true if a workflow response has already been sent.
379 * @param execution the execution
381 protected boolean isWorkflowResponseSent(DelegateExecution execution) {
382 def isDebugLogEnabled = execution.getVariable('isDebugLogEnabled')
383 String processKey = getProcessKey(execution);
384 return String.valueOf(execution.getVariable(processKey + "WorkflowResponseSent")).equals("true");
388 * Returns the process definition key (i.e. the process name) of the
391 * @param execution the execution
393 public String getProcessKey(DelegateExecution execution) {
394 def testKey = execution.getVariable("testProcessKey")
398 return execution.getProcessEngineServices().getRepositoryService()
399 .getProcessDefinition(execution.getProcessDefinitionId()).getKey()
403 * Returns the process definition key (i.e. the process name) of the
405 * @param execution the execution
407 public String getMainProcessKey(DelegateExecution execution) {
408 DelegateExecution exec = execution
411 DelegateExecution parent = exec.getSuperExecution()
413 if (parent == null) {
414 parent = exec.getParent()
416 if (parent == null) {
424 return execution.getProcessEngineServices().getRepositoryService()
425 .getProcessDefinition(exec.getProcessDefinitionId()).getKey()
429 * Gets the node for the named element from the given xml. If the element
430 * does not exist in the xml or is empty, a WorkflowException is created
431 * (and as a result, a MSOWorkflowException event is thrown).
433 * @param execution The flow's execution.
434 * @param xml Xml to search.
435 * @param elementName Name of element to search for.
436 * @return The element node, if found in the xml.
438 protected String getRequiredNodeXml(DelegateExecution execution, String xml, String elementName) {
439 ExceptionUtil exceptionUtil = new ExceptionUtil()
440 def element = utils.getNodeXml(xml, elementName, false)
441 if (element.trim().isEmpty()) {
442 def msg = 'Required element \'' + elementName + '\' is missing or empty'
444 exceptionUtil.buildAndThrowWorkflowException(execution, 2000, msg)
451 * Gets the value of the named element from the given xml. If the element
452 * does not exist in the xml or is empty, a WorkflowException is created
453 * (and as a result, a MSOWorkflowException event is thrown).
455 * @param execution The flow's execution.
456 * @param xml Xml to search.
457 * @param elementName Name of element to whose value to get.
458 * @return The non-empty value of the element, if found in the xml.
460 protected String getRequiredNodeText(DelegateExecution execution, String xml, String elementName) {
461 ExceptionUtil exceptionUtil = new ExceptionUtil()
462 def elementText = utils.getNodeText(xml, elementName)
463 if ((elementText == null) || (elementText.isEmpty())) {
464 def msg = 'Required element \'' + elementName + '\' is missing or empty'
466 exceptionUtil.buildAndThrowWorkflowException(execution, 2000, msg)
473 * Get the text for the specified element from the specified xml. If
474 * the element does not exist, return the specified default value.
476 * @param xml Xml from which to get the element's text
477 * @param elementName Name of element whose text to get
478 * @param defaultValue the default value
479 * @return the element's text or the default value if the element does not
480 * exist in the given xml
482 protected String getNodeText(String xml, String elementName, String defaultValue) {
483 def nodeText = utils.getNodeText(xml, elementName)
484 return (nodeText == null) ? defaultValue : nodeText
488 * Get the text for the specified element from the specified xml. If
489 * the element does not exist, return an empty string.
491 * @param xml Xml from which to get the element's text.
492 * @param elementName Name of element whose text to get.
493 * @return the element's text or an empty string if the element does not
494 * exist in the given xml.
496 protected String getNodeTextForce(String xml, String elementName) {
497 return getNodeText(xml, elementName, '');
501 *Store the variable as typed with java serialization type
506 public void setVariable(DelegateExecution execution, String name, Object value) {
507 VariableMap variables = Variables.createVariables()
508 variables.putValueTyped('payload', Variables.objectValue(value)
509 .serializationDataFormat(SerializationDataFormats.JAVA) // tells the engine to use java serialization for persisting the value
511 execution.setVariable(name,variables)
514 //TODO not sure how this will look in Cockpit
517 * Returns the variable map
522 public String getVariable(DelegateExecution execution, String name) {
523 def myObj = execution.getVariable(name)
524 if(myObj instanceof VariableMap){
525 VariableMap serializedObjectMap = (VariableMap) myObj
526 ObjectValueImpl payloadObj = serializedObjectMap.getValueTyped('payload')
527 return payloadObj.getValue()
535 * Returns true if a value equals one of the provided set. Equality is
536 * determined by using the equals method if the value object and the
537 * object in the provided set have the same class. Otherwise, the objects
538 * are converted to strings and then compared. Nulls are permitted for
539 * the value as well as in the provided set
542 * def statusCode = getStatusCode()
543 * isOneOf(statusCode, 200, 201, 204)
545 * @param value the value to test
546 * @param these a set of permissable values
547 * @return true if the value is in the provided set
549 public boolean isOneOf(Object value, Object... these) {
550 for (Object thisOne : these) {
551 if (thisOne == null) {
557 if (value.getClass() == thisOne.getClass()) {
558 if (value.equals(thisOne)) {
562 if (String.valueOf(value).equals(String.valueOf(thisOne))) {
573 * Sets flows success indicator variable.
576 public void setSuccessIndicator(DelegateExecution execution, boolean isSuccess) {
577 String prefix = execution.getVariable('prefix')
578 def isDebugLogEnabled = execution.getVariable('isDebugLogEnabled')
580 logDebug('Entered SetSuccessIndicator Method', isDebugLogEnabled)
581 execution.setVariable(prefix+'SuccessIndicator', isSuccess)
582 logDebug('Outgoing SuccessIndicator is: ' + execution.getVariable(prefix+'SuccessIndicator') + '', isDebugLogEnabled)
586 * Sends a Error Sync Response
589 public void sendSyncError(DelegateExecution execution) {
590 def isDebugEnabled=execution.getVariable("isDebugLogEnabled")
591 String requestId = execution.getVariable("mso-request-id")
592 logDebug('sendSyncError, requestId: ' + requestId, isDebugEnabled)
593 WorkflowException workflowExceptionObj = execution.getVariable("WorkflowException")
594 if (workflowExceptionObj != null) {
595 String errorMessage = workflowExceptionObj.getErrorMessage()
596 def errorCode = workflowExceptionObj.getErrorCode()
597 logDebug('sendSyncError, requestId: ' + requestId + ' | errorMessage: ' + errorMessage + ' | errorCode: ' + errorCode, isDebugEnabled)
598 sendWorkflowResponse(execution, errorCode, errorMessage)
603 * Executes a named groovy script method in the current object
605 public void executeMethod(String methodName, Object... args) {
607 if (args != null && args.size() > 0) {
609 // First argument of method to call is always the execution object
610 DelegateExecution execution = (DelegateExecution) args[0]
612 def classAndMethod = getClass().getSimpleName() + '.' + methodName + '(execution=' + execution.getId() + ')'
613 def isDebugEnabled = execution.getVariable('isDebugLogEnabled')
615 logDebug('Entered ' + classAndMethod, isDebugEnabled)
616 logDebug('Received parameters: ' + args, isDebugEnabled)
619 def methodToCall = this.metaClass.getMetaMethod(methodName, args)
620 logDebug('Method to call: ' + methodToCall, isDebugEnabled)
621 methodToCall?.invoke(this, args)
623 catch(BpmnError bpmnError) {
624 logDebug('Rethrowing BpmnError ' + bpmnError.getMessage(), isDebugEnabled)
629 logDebug('Unexpected error encountered - ' + e.getMessage(), isDebugEnabled)
630 (new ExceptionUtil()).buildAndThrowWorkflowException(execution, 9999, e.getMessage())
633 logDebug('Exited ' + classAndMethod, isDebugEnabled)
639 *This method determines and adds the appropriate ending to come
640 *after a number (-st, -nd, -rd, or -th)
644 *@return String ending - number with suffix
646 public static String labelMaker(Object n) {
648 if(n instanceof String){
649 num = Integer.parseInt(n)
654 String ending = ""; //the end to be added to the number
656 if ((num % 10 == 1) && (num != 11)) {
658 } else if ((num % 10 == 2) && (num != 12)) {
660 } else if ((num % 10 == 3) && (num != 13)) {
670 * Constructs a workflow message callback URL for the specified message type and correlator.
671 * This type of callback URL is used when a workflow wants an MSO adapter (like the SDNC
672 * adapter) to call it back. In other words, this is for callbacks internal to the MSO
673 * complex. Use <code>createWorkflowMessageAdapterCallbackURL</code> if the callback
674 * will come from outside the MSO complex.
675 * @param messageType the message type (e.g. SDNCAResponse or VNFAResponse)
676 * @param correlator the correlator value (e.g. a request ID)
678 public String createCallbackURL(DelegateExecution execution, String messageType, String correlator) {
679 String endpoint = UrnPropertiesReader.getVariable("mso.workflow.message.endpoint", execution)
681 if (endpoint == null || endpoint.isEmpty()) {
682 ExceptionUtil exceptionUtil = new ExceptionUtil()
683 exceptionUtil.buildAndThrowWorkflowException(execution, 2000,
684 'mso:workflow:message:endpoint URN mapping is not set')
687 while (endpoint.endsWith('/')) {
688 endpoint = endpoint.substring(0, endpoint.length()-1)
692 '/' + UriUtils.encodePathSegment(messageType, 'UTF-8') +
693 '/' + UriUtils.encodePathSegment(correlator, 'UTF-8')
698 * Constructs a workflow message callback URL for the specified message type and correlator.
699 * This type of callback URL is used when a workflow wants a system outside the MSO complex
700 * to call it back through the Workflow Message Adapter.
701 * @param messageType the message type (e.g. SNIROResponse)
702 * @param correlator the correlator value (e.g. a request ID)
704 public String createWorkflowMessageAdapterCallbackURL(DelegateExecution execution, String messageType, String correlator) {
705 String endpoint = UrnPropertiesReader.getVariable("mso.adapters.workflow.message.endpoint", execution)
707 if (endpoint == null || endpoint.isEmpty()) {
708 ExceptionUtil exceptionUtil = new ExceptionUtil()
709 exceptionUtil.buildAndThrowWorkflowException(execution, 2000,
710 'mso:adapters:workflow:message:endpoint URN mapping is not set')
713 while (endpoint.endsWith('/')) {
714 endpoint = endpoint.substring(0, endpoint.length()-1)
718 '/' + UriUtils.encodePathSegment(messageType, 'UTF-8') +
719 '/' + UriUtils.encodePathSegment(correlator, 'UTF-8')
722 public void setRollbackEnabled(DelegateExecution execution, isDebugLogEnabled) {
725 def prefix = execution.getVariable('prefix')
726 def disableRollback = execution.getVariable("disableRollback")
727 def defaultRollback = UrnPropertiesReader.getVariable("mso.rollback", execution).toBoolean()
729 logDebug('disableRollback: ' + disableRollback, isDebugLogEnabled)
730 logDebug('defaultRollback: ' + defaultRollback, isDebugLogEnabled)
734 if(disableRollback == null || disableRollback == '' ) {
735 // get from default urn settings for mso_rollback
736 disableRollback = !defaultRollback
737 rollbackEnabled = defaultRollback
738 logDebug('disableRollback is null or empty!', isDebugLogEnabled)
741 if(disableRollback == true) {
742 rollbackEnabled = false
744 else if(disableRollback == false){
745 rollbackEnabled = true
748 rollbackEnabled = defaultRollback
752 execution.setVariable(prefix+"backoutOnFailure", rollbackEnabled)
753 logDebug('rollbackEnabled (aka backoutOnFailure): ' + rollbackEnabled, isDebugLogEnabled)
756 public void setBasicDBAuthHeader(DelegateExecution execution, isDebugLogEnabled) {
758 String basicAuthValueDB = UrnPropertiesReader.getVariable("mso.adapters.db.auth", execution)
759 def encodedString = utils.getBasicAuth(basicAuthValueDB, UrnPropertiesReader.getVariable("mso.msoKey", execution))
760 execution.setVariable("BasicAuthHeaderValueDB",encodedString)
761 } catch (IOException ex) {
762 String dataErrorMessage = " Unable to encode Catalog DB user/password string - " + ex.getMessage()
763 utils.log("DEBUG", dataErrorMessage, isDebugLogEnabled)
764 (new ExceptionUtil()).buildAndThrowWorkflowException(execution, 2500, dataErrorMessage)
767 public AAIResourcesClient getAAIClient(){
768 return new AAIResourcesClient();