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 groovy.json.JsonSlurper
25 import org.camunda.bpm.engine.delegate.BpmnError
26 import org.camunda.bpm.engine.delegate.DelegateExecution
27 import org.camunda.bpm.engine.variable.VariableMap
28 import org.camunda.bpm.engine.variable.Variables
29 import org.camunda.bpm.engine.variable.Variables.SerializationDataFormats
30 import org.camunda.bpm.engine.variable.impl.value.ObjectValueImpl
31 import org.onap.so.bpmn.common.workflow.context.WorkflowCallbackResponse
32 import org.onap.so.bpmn.common.workflow.context.WorkflowContextHolder
33 import org.onap.so.bpmn.core.WorkflowException
34 import org.onap.so.bpmn.core.UrnPropertiesReader
35 import org.springframework.web.util.UriUtils
37 public abstract class AbstractServiceTaskProcessor implements ServiceTaskProcessor {
38 public MsoUtils utils = new MsoUtils()
42 * Logs a message at the ERROR level.
43 * @param message the message
45 public void logError(String message) {
46 log('ERROR', message, null, "true")
50 * Logs a message at the ERROR level.
51 * @param message the message
52 * @param cause the cause (stracktrace will be included in the output)
54 public void logError(String message, Throwable cause) {
55 log('ERROR', message, cause, "true")
59 * Logs a message at the WARN level.
60 * @param message the message
62 public void logWarn(String message) {
63 log('WARN', message, null, "true")
67 * Logs a message at the WARN level.
68 * @param message the message
69 * @param cause the cause (stracktrace will be included in the output)
71 public void logWarn(String message, Throwable cause) {
72 log('WARN', message, cause, "true")
76 * Logs a message at the DEBUG level.
77 * @param message the message
78 * @param isDebugLogEnabled a flag indicating if DEBUG level is enabled
80 public void logDebug(String message, String isDebugLogEnabled) {
81 log('DEBUG', message, null, isDebugLogEnabled)
85 * Logs a message at the DEBUG level.
86 * @param message the message
87 * @param cause the cause (stracktrace will be included in the output)
88 * @param isDebugLogEnabled a flag indicating if DEBUG level is enabled
90 public void logDebug(String message, Throwable cause, String isDebugLogEnabled) {
91 log('DEBUG', message, cause, isDebugLogEnabled)
95 * Logs a message at the specified level.
96 * @param level the level (DEBUG, INFO, WARN, ERROR)
97 * @param message the message
98 * @param isLevelEnabled a flag indicating if the level is enabled
99 * (used only at the DEBUG level)
101 public void log(String level, String message, String isLevelEnabled) {
102 log(level, message, null, isLevelEnabled)
106 * Logs a message at the specified level.
107 * @param level the level (DEBUG, INFO, WARN, ERROR)
108 * @param message the message
109 * @param cause the cause (stracktrace will be included in the output)
110 * @param isLevelEnabled a flag indicating if the level is enabled
111 * (used only at the DEBUG level)
113 public void log(String level, String message, Throwable cause, String isLevelEnabled) {
115 utils.log(level, message, isLevelEnabled);
117 StringWriter stringWriter = new StringWriter();
118 PrintWriter printWriter = new PrintWriter(stringWriter);
119 printWriter.println(message);
120 cause.printStackTrace(printWriter);
121 utils.log(level, stringWriter.toString(), isLevelEnabled);
127 * Logs a WorkflowException at the ERROR level with the specified message.
128 * @param execution the execution
130 public void logWorkflowException(DelegateExecution execution, String message) {
131 def workflowException = execution.getVariable("WorkflowException")
133 if (workflowException == null) {
136 logError(message + ": " + workflowException)
141 * Saves the WorkflowException in the execution to the specified variable,
142 * clearing the WorkflowException variable so the workflow can continue
143 * processing (perhaps catching another WorkflowException).
144 * @param execution the execution
145 * @return the name of the destination variable
147 public saveWorkflowException(DelegateExecution execution, String variable) {
148 if (variable == null) {
149 throw new NullPointerException();
152 execution.setVariable(variable, execution.getVariable("WorkflowException"))
153 execution.setVariable("WorkflowException", null)
158 * Validates that the request exists and that the mso-request-id variable is set.
159 * Additional required variables may be checked by specifying their names.
160 * NOTE: services requiring mso-service-instance-id must specify it explicitly!
161 * If a problem is found, buildAndThrowWorkflowException builds a WorkflowException
162 * and throws an MSOWorkflowException. This method also sets up the log context for
165 * @param execution the execution
166 * @return the validated request
168 public String validateRequest(DelegateExecution execution, String... requiredVariables) {
169 ExceptionUtil exceptionUtil = new ExceptionUtil()
170 def method = getClass().getSimpleName() + '.validateRequest(' +
171 'execution=' + execution.getId() +
172 ', requredVariables=' + requiredVariables +
174 def isDebugLogEnabled = execution.getVariable('isDebugLogEnabled')
175 logDebug('Entered ' + method, isDebugLogEnabled)
177 String processKey = getProcessKey(execution)
178 def prefix = execution.getVariable("prefix")
180 if (prefix == null) {
181 exceptionUtil.buildAndThrowWorkflowException(execution, 1002, processKey + " prefix is null")
185 def request = execution.getVariable(prefix + 'Request')
187 if (request == null) {
188 request = execution.getVariable(processKey + 'Request')
190 if (request == null) {
191 request = execution.getVariable('bpmnRequest')
194 setVariable(execution, processKey + 'Request', null)
195 setVariable(execution, 'bpmnRequest', null)
196 setVariable(execution, prefix + 'Request', request)
199 if (request == null) {
200 exceptionUtil.buildAndThrowWorkflowException(execution, 1002, processKey + " request is null")
203 // All requests must have a request ID.
204 // Some requests (e.g. SDN-MOBILITY) do not have a service instance ID.
206 String requestId = null
207 String serviceInstanceId = null
209 List<String> allRequiredVariables = new ArrayList<String>()
210 allRequiredVariables.add("mso-request-id")
212 if (requiredVariables != null) {
213 for (String variable : requiredVariables) {
214 if (!allRequiredVariables.contains(variable)) {
215 allRequiredVariables.add(variable)
220 for (String variable : allRequiredVariables) {
221 def value = execution.getVariable(variable)
222 if (value == null || ((value instanceof CharSequence) && value.length() == 0)) {
223 exceptionUtil.buildAndThrowWorkflowException(execution, 1002, processKey +
224 " request was received with no '" + variable + "' variable")
227 if ("mso-request-id".equals(variable)) {
228 requestId = (String) value
229 } else if ("mso-service-instance-id".equals(variable)) {
230 serviceInstanceId = (String) value
234 if (serviceInstanceId == null) {
235 serviceInstanceId = (String) execution.getVariable("mso-service-instance-id")
238 utils.logContext(requestId, serviceInstanceId)
239 logDebug('Incoming message: ' + System.lineSeparator() + request, isDebugLogEnabled)
240 logDebug('Exited ' + method, isDebugLogEnabled)
242 } catch (BpmnError e) {
244 } catch (Exception e) {
245 logError('Caught exception in ' + method, e)
246 exceptionUtil.buildAndThrowWorkflowException(execution, 1002, "Invalid Message")
251 * gets vars stored in a JSON object in prefix+Request and returns as a LazyMap
252 * setting log context here too
253 * @param execution the execution
254 * @return the inputVars
256 public Map validateJSONReq(DelegateExecution execution) {
257 def method = getClass().getSimpleName() + '.validateJSONReq(' +
258 'execution=' + execution.getId() +
260 def isDebugLogEnabled = execution.getVariable('isDebugLogEnabled')
261 logDebug('Entered ' + method, isDebugLogEnabled)
263 String processKey = getProcessKey(execution);
264 def prefix = execution.getVariable("prefix")
266 def requestId =getVariable(execution, "mso-request-id")
267 def serviceInstanceId = getVariable(execution, "mso-service-instance-id")
268 if(requestId!=null && serviceInstanceId!=null){
269 utils.logContext(requestId, serviceInstanceId)
273 def request = getVariable(execution, prefix + 'Request')
275 if (request == null) {
276 request = getVariable(execution, processKey + 'Request')
278 if (request == null) {
279 request = getVariable(execution, 'bpmnRequest')
281 execution.setVariable(prefix + 'Request', request)
284 def jsonSlurper = new JsonSlurper()
285 def parsed = jsonSlurper.parseText(request)
288 logDebug('Incoming message: ' + System.lineSeparator() + request, isDebugLogEnabled)
289 logDebug('Exited ' + method, isDebugLogEnabled)
295 * Sends a response to the workflow service that invoked the process. This method
296 * may only be used by top-level processes that were directly invoked by the
297 * asynchronous workflow service.
298 * @param execution the execution
299 * @param responseCode the response code
300 * @param content the message content
301 * @throws IllegalArgumentException if the response code is invalid
303 * @throws UnsupportedOperationException if not invoked by an asynchronous,
305 * @throws IllegalStateException if a response has already been sent
307 protected void sendWorkflowResponse(DelegateExecution execution, Object responseCode, String response) {
308 def isDebugLogEnabled = execution.getVariable('isDebugLogEnabled')
310 String processKey = getProcessKey(execution);
312 // isAsyncProcess is injected by the workflow service that started the flow
313 if (!String.valueOf(execution.getVariable("isAsyncProcess")).equals("true")) {
314 throw new UnsupportedOperationException(processKey + ": " +
315 "sendWorkflowResponse is valid only in asynchronous workflows");
318 if (String.valueOf(execution.getVariable(processKey + "WorkflowResponseSent")).equals("true")) {
319 logDebug("Sync response has already been sent for " + processKey, isDebugLogEnabled)
322 logDebug("Building " + processKey + " response ", isDebugLogEnabled)
327 intResponseCode = Integer.parseInt(String.valueOf(responseCode));
329 if (intResponseCode < 100 || intResponseCode > 599) {
330 throw new NumberFormatException(String.valueOf(responseCode));
332 } catch (NumberFormatException e) {
333 throw new IllegalArgumentException("Process " + processKey
334 + " provided an invalid HTTP response code: " + responseCode);
337 // Only 2XX responses are considered "Success"
338 String status = (intResponseCode >= 200 && intResponseCode <= 299) ?
341 // TODO: Should deprecate use of processKey+Response variable for the response. Will use "WorkflowResponse" instead
342 execution.setVariable(processKey + "ResponseCode", String.valueOf(intResponseCode))
343 execution.setVariable(processKey + "Response", response);
344 execution.setVariable(processKey + "Status", status);
345 execution.setVariable("WorkflowResponse", response)
347 logDebug("Sending response for " + processKey
348 + " ResponseCode=" + intResponseCode
349 + " Status=" + status
350 + " Response=\n" + response,
353 // TODO: ensure that this flow was invoked asynchronously?
355 WorkflowCallbackResponse callbackResponse = new WorkflowCallbackResponse()
356 callbackResponse.setStatusCode(intResponseCode)
357 callbackResponse.setMessage(status)
358 callbackResponse.setResponse(response)
360 // TODO: send this data with HTTP POST
362 WorkflowContextHolder.getInstance().processCallback(
364 execution.getProcessInstanceId(),
365 execution.getVariable("mso-request-id"),
368 execution.setVariable(processKey + "WorkflowResponseSent", "true");
371 } catch (Exception ex) {
372 logError("Unable to send workflow response to client ....", ex)
377 * Returns true if a workflow response has already been sent.
378 * @param execution the execution
380 protected boolean isWorkflowResponseSent(DelegateExecution execution) {
381 def isDebugLogEnabled = execution.getVariable('isDebugLogEnabled')
382 String processKey = getProcessKey(execution);
383 return String.valueOf(execution.getVariable(processKey + "WorkflowResponseSent")).equals("true");
387 * Returns the process definition key (i.e. the process name) of the
390 * @param execution the execution
392 public String getProcessKey(DelegateExecution execution) {
393 def testKey = execution.getVariable("testProcessKey")
397 return execution.getProcessEngineServices().getRepositoryService()
398 .getProcessDefinition(execution.getProcessDefinitionId()).getKey()
402 * Returns the process definition key (i.e. the process name) of the
404 * @param execution the execution
406 public String getMainProcessKey(DelegateExecution execution) {
407 DelegateExecution exec = execution
410 DelegateExecution parent = exec.getSuperExecution()
412 if (parent == null) {
413 parent = exec.getParent()
415 if (parent == null) {
423 return execution.getProcessEngineServices().getRepositoryService()
424 .getProcessDefinition(exec.getProcessDefinitionId()).getKey()
428 * Gets the node for the named element from the given xml. If the element
429 * does not exist in the xml or is empty, a WorkflowException is created
430 * (and as a result, a MSOWorkflowException event is thrown).
432 * @param execution The flow's execution.
433 * @param xml Xml to search.
434 * @param elementName Name of element to search for.
435 * @return The element node, if found in the xml.
437 protected String getRequiredNodeXml(DelegateExecution execution, String xml, String elementName) {
438 ExceptionUtil exceptionUtil = new ExceptionUtil()
439 def element = utils.getNodeXml(xml, elementName, false)
440 if (element.trim().isEmpty()) {
441 def msg = 'Required element \'' + elementName + '\' is missing or empty'
443 exceptionUtil.buildAndThrowWorkflowException(execution, 2000, msg)
450 * Gets the value of the named element from the given xml. If the element
451 * does not exist in the xml or is empty, a WorkflowException is created
452 * (and as a result, a MSOWorkflowException event is thrown).
454 * @param execution The flow's execution.
455 * @param xml Xml to search.
456 * @param elementName Name of element to whose value to get.
457 * @return The non-empty value of the element, if found in the xml.
459 protected String getRequiredNodeText(DelegateExecution execution, String xml, String elementName) {
460 ExceptionUtil exceptionUtil = new ExceptionUtil()
461 def elementText = utils.getNodeText(xml, elementName)
462 if ((elementText == null) || (elementText.isEmpty())) {
463 def msg = 'Required element \'' + elementName + '\' is missing or empty'
465 exceptionUtil.buildAndThrowWorkflowException(execution, 2000, msg)
472 * Get the text for the specified element from the specified xml. If
473 * the element does not exist, return the specified default value.
475 * @param xml Xml from which to get the element's text
476 * @param elementName Name of element whose text to get
477 * @param defaultValue the default value
478 * @return the element's text or the default value if the element does not
479 * exist in the given xml
481 protected String getNodeText(String xml, String elementName, String defaultValue) {
482 def nodeText = utils.getNodeText(xml, elementName)
483 return (nodeText == null) ? defaultValue : nodeText
487 * Get the text for the specified element from the specified xml. If
488 * the element does not exist, return an empty string.
490 * @param xml Xml from which to get the element's text.
491 * @param elementName Name of element whose text to get.
492 * @return the element's text or an empty string if the element does not
493 * exist in the given xml.
495 protected String getNodeTextForce(String xml, String elementName) {
496 return getNodeText(xml, elementName, '');
500 *Store the variable as typed with java serialization type
505 public void setVariable(DelegateExecution execution, String name, Object value) {
506 VariableMap variables = Variables.createVariables()
507 variables.putValueTyped('payload', Variables.objectValue(value)
508 .serializationDataFormat(SerializationDataFormats.JAVA) // tells the engine to use java serialization for persisting the value
510 execution.setVariable(name,variables)
513 //TODO not sure how this will look in Cockpit
516 * Returns the variable map
521 public String getVariable(DelegateExecution execution, String name) {
522 def myObj = execution.getVariable(name)
523 if(myObj instanceof VariableMap){
524 VariableMap serializedObjectMap = (VariableMap) myObj
525 ObjectValueImpl payloadObj = serializedObjectMap.getValueTyped('payload')
526 return payloadObj.getValue()
534 * Returns true if a value equals one of the provided set. Equality is
535 * determined by using the equals method if the value object and the
536 * object in the provided set have the same class. Otherwise, the objects
537 * are converted to strings and then compared. Nulls are permitted for
538 * the value as well as in the provided set
541 * def statusCode = getStatusCode()
542 * isOneOf(statusCode, 200, 201, 204)
544 * @param value the value to test
545 * @param these a set of permissable values
546 * @return true if the value is in the provided set
548 public boolean isOneOf(Object value, Object... these) {
549 for (Object thisOne : these) {
550 if (thisOne == null) {
556 if (value.getClass() == thisOne.getClass()) {
557 if (value.equals(thisOne)) {
561 if (String.valueOf(value).equals(String.valueOf(thisOne))) {
572 * Sets flows success indicator variable.
575 public void setSuccessIndicator(DelegateExecution execution, boolean isSuccess) {
576 String prefix = execution.getVariable('prefix')
577 def isDebugLogEnabled = execution.getVariable('isDebugLogEnabled')
579 logDebug('Entered SetSuccessIndicator Method', isDebugLogEnabled)
580 execution.setVariable(prefix+'SuccessIndicator', isSuccess)
581 logDebug('Outgoing SuccessIndicator is: ' + execution.getVariable(prefix+'SuccessIndicator') + '', isDebugLogEnabled)
585 * Sends a Error Sync Response
588 public void sendSyncError(DelegateExecution execution) {
589 def isDebugEnabled=execution.getVariable("isDebugLogEnabled")
590 String requestId = execution.getVariable("mso-request-id")
591 logDebug('sendSyncError, requestId: ' + requestId, isDebugEnabled)
592 WorkflowException workflowExceptionObj = execution.getVariable("WorkflowException")
593 if (workflowExceptionObj != null) {
594 String errorMessage = workflowExceptionObj.getErrorMessage()
595 def errorCode = workflowExceptionObj.getErrorCode()
596 logDebug('sendSyncError, requestId: ' + requestId + ' | errorMessage: ' + errorMessage + ' | errorCode: ' + errorCode, isDebugEnabled)
597 sendWorkflowResponse(execution, errorCode, errorMessage)
602 * Executes a named groovy script method in the current object
604 public void executeMethod(String methodName, Object... args) {
606 if (args != null && args.size() > 0) {
608 // First argument of method to call is always the execution object
609 DelegateExecution execution = (DelegateExecution) args[0]
611 def classAndMethod = getClass().getSimpleName() + '.' + methodName + '(execution=' + execution.getId() + ')'
612 def isDebugEnabled = execution.getVariable('isDebugLogEnabled')
614 logDebug('Entered ' + classAndMethod, isDebugEnabled)
615 logDebug('Received parameters: ' + args, isDebugEnabled)
618 def methodToCall = this.metaClass.getMetaMethod(methodName, args)
619 logDebug('Method to call: ' + methodToCall, isDebugEnabled)
620 methodToCall?.invoke(this, args)
622 catch(BpmnError bpmnError) {
623 logDebug('Rethrowing BpmnError ' + bpmnError.getMessage(), isDebugEnabled)
628 logDebug('Unexpected error encountered - ' + e.getMessage(), isDebugEnabled)
629 (new ExceptionUtil()).buildAndThrowWorkflowException(execution, 9999, e.getMessage())
632 logDebug('Exited ' + classAndMethod, isDebugEnabled)
638 *This method determines and adds the appropriate ending to come
639 *after a number (-st, -nd, -rd, or -th)
643 *@return String ending - number with suffix
645 public static String labelMaker(Object n) {
647 if(n instanceof String){
648 num = Integer.parseInt(n)
653 String ending = ""; //the end to be added to the number
655 if ((num % 10 == 1) && (num != 11)) {
657 } else if ((num % 10 == 2) && (num != 12)) {
659 } else if ((num % 10 == 3) && (num != 13)) {
669 * Constructs a workflow message callback URL for the specified message type and correlator.
670 * This type of callback URL is used when a workflow wants an MSO adapter (like the SDNC
671 * adapter) to call it back. In other words, this is for callbacks internal to the MSO
672 * complex. Use <code>createWorkflowMessageAdapterCallbackURL</code> if the callback
673 * will come from outside the MSO complex.
674 * @param messageType the message type (e.g. SDNCAResponse or VNFAResponse)
675 * @param correlator the correlator value (e.g. a request ID)
677 public String createCallbackURL(DelegateExecution execution, String messageType, String correlator) {
678 String endpoint = UrnPropertiesReader.getVariable("mso.workflow.message.endpoint", execution)
680 if (endpoint == null || endpoint.isEmpty()) {
681 ExceptionUtil exceptionUtil = new ExceptionUtil()
682 exceptionUtil.buildAndThrowWorkflowException(execution, 2000,
683 'mso:workflow:message:endpoint URN mapping is not set')
686 while (endpoint.endsWith('/')) {
687 endpoint = endpoint.substring(0, endpoint.length()-1)
691 '/' + UriUtils.encodePathSegment(messageType, 'UTF-8') +
692 '/' + UriUtils.encodePathSegment(correlator, 'UTF-8')
697 * Constructs a workflow message callback URL for the specified message type and correlator.
698 * This type of callback URL is used when a workflow wants a system outside the MSO complex
699 * to call it back through the Workflow Message Adapter.
700 * @param messageType the message type (e.g. SNIROResponse)
701 * @param correlator the correlator value (e.g. a request ID)
703 public String createWorkflowMessageAdapterCallbackURL(DelegateExecution execution, String messageType, String correlator) {
704 String endpoint = UrnPropertiesReader.getVariable("mso.adapters.workflow.message.endpoint", execution)
706 if (endpoint == null || endpoint.isEmpty()) {
707 ExceptionUtil exceptionUtil = new ExceptionUtil()
708 exceptionUtil.buildAndThrowWorkflowException(execution, 2000,
709 'mso:adapters:workflow:message:endpoint URN mapping is not set')
712 while (endpoint.endsWith('/')) {
713 endpoint = endpoint.substring(0, endpoint.length()-1)
717 '/' + UriUtils.encodePathSegment(messageType, 'UTF-8') +
718 '/' + UriUtils.encodePathSegment(correlator, 'UTF-8')
721 public void setRollbackEnabled(DelegateExecution execution, isDebugLogEnabled) {
724 def prefix = execution.getVariable('prefix')
725 def disableRollback = execution.getVariable("disableRollback")
726 def defaultRollback = UrnPropertiesReader.getVariable("mso.rollback", execution).toBoolean()
728 logDebug('disableRollback: ' + disableRollback, isDebugLogEnabled)
729 logDebug('defaultRollback: ' + defaultRollback, isDebugLogEnabled)
733 if(disableRollback == null || disableRollback == '' ) {
734 // get from default urn settings for mso_rollback
735 disableRollback = !defaultRollback
736 rollbackEnabled = defaultRollback
737 logDebug('disableRollback is null or empty!', isDebugLogEnabled)
740 if(disableRollback == true) {
741 rollbackEnabled = false
743 else if(disableRollback == false){
744 rollbackEnabled = true
747 rollbackEnabled = defaultRollback
751 execution.setVariable(prefix+"backoutOnFailure", rollbackEnabled)
752 logDebug('rollbackEnabled (aka backoutOnFailure): ' + rollbackEnabled, isDebugLogEnabled)
755 public void setBasicDBAuthHeader(DelegateExecution execution, isDebugLogEnabled) {
757 String basicAuthValueDB = UrnPropertiesReader.getVariable("mso.adapters.db.auth", execution)
758 utils.log("DEBUG", " Obtained BasicAuth userid password for Catalog DB adapter: " + basicAuthValueDB, isDebugLogEnabled)
760 def encodedString = utils.getBasicAuth(basicAuthValueDB, UrnPropertiesReader.getVariable("mso.msoKey", execution))
761 execution.setVariable("BasicAuthHeaderValueDB",encodedString)
762 } catch (IOException ex) {
763 String dataErrorMessage = " Unable to encode Catalog DB user/password string - " + ex.getMessage()
764 utils.log("DEBUG", dataErrorMessage, isDebugLogEnabled)
765 (new ExceptionUtil()).buildAndThrowWorkflowException(execution, 2500, dataErrorMessage)