2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6 * ================================================================================
7 * Modifications Copyright (c) 2019 Samsung
8 * ================================================================================
9 * Licensed under the Apache License, Version 2.0 (the "License");
10 * you may not use this file except in compliance with the License.
11 * You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing, software
16 * distributed under the License is distributed on an "AS IS" BASIS,
17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 * See the License for the specific language governing permissions and
19 * limitations under the License.
20 * ============LICENSE_END=========================================================
23 package org.onap.so.bpmn.common.scripts;
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.UrnPropertiesReader
34 import org.onap.so.bpmn.core.WorkflowException
35 import org.onap.so.client.HttpClientFactory
36 import org.onap.aaiclient.client.aai.AAIResourcesClient
37 import org.springframework.web.util.UriUtils
38 import org.slf4j.Logger
39 import org.slf4j.LoggerFactory
41 import groovy.json.JsonSlurper
43 public abstract class AbstractServiceTaskProcessor implements ServiceTaskProcessor {
44 private static final Logger logger = LoggerFactory.getLogger( MsoUtils.class);
46 public MsoUtils utils = new MsoUtils()
49 * Logs a WorkflowException at the ERROR level with the specified message.
50 * @param execution the execution
52 public void logWorkflowException(DelegateExecution execution, String message) {
53 def workflowException = execution.getVariable("WorkflowException")
55 if (workflowException == null) {
56 logger.error(message);
58 logger.error('{}: {}', message, workflowException)
63 * Saves the WorkflowException in the execution to the specified variable,
64 * clearing the WorkflowException variable so the workflow can continue
65 * processing (perhaps catching another WorkflowException).
66 * @param execution the execution
67 * @return the name of the destination variable
69 public saveWorkflowException(DelegateExecution execution, String variable) {
70 if (variable == null) {
71 throw new NullPointerException();
74 execution.setVariable(variable, execution.getVariable("WorkflowException"))
75 execution.setVariable("WorkflowException", null)
80 * Validates that the request exists and that the mso-request-id variable is set.
81 * Additional required variables may be checked by specifying their names.
82 * NOTE: services requiring mso-service-instance-id must specify it explicitly!
83 * If a problem is found, buildAndThrowWorkflowException builds a WorkflowException
84 * and throws an MSOWorkflowException. This method also sets up the log context for
87 * @param execution the execution
88 * @return the validated request
90 public String validateRequest(DelegateExecution execution, String... requiredVariables) {
91 ExceptionUtil exceptionUtil = new ExceptionUtil()
92 def method = getClass().getSimpleName() + '.validateRequest(' +
93 'execution=' + execution.getId() +
94 ', requredVariables=' + requiredVariables +
96 def isDebugLogEnabled = execution.getVariable('isDebugLogEnabled')
97 logger.debug('Entered ' + method)
99 String processKey = getProcessKey(execution)
100 def prefix = execution.getVariable("prefix")
102 if (prefix == null) {
103 exceptionUtil.buildAndThrowWorkflowException(execution, 1002, processKey + " prefix is null")
107 def request = execution.getVariable(prefix + 'Request')
109 if (request == null) {
110 request = execution.getVariable(processKey + 'Request')
112 if (request == null) {
113 request = execution.getVariable('bpmnRequest')
116 setVariable(execution, processKey + 'Request', null)
117 setVariable(execution, 'bpmnRequest', null)
118 setVariable(execution, prefix + 'Request', request)
121 if (request == null) {
122 exceptionUtil.buildAndThrowWorkflowException(execution, 1002, processKey + " request is null")
125 // All requests must have a request ID.
126 // Some requests (e.g. SDN-MOBILITY) do not have a service instance ID.
128 String requestId = null
129 String serviceInstanceId = null
131 List<String> allRequiredVariables = new ArrayList<String>()
132 allRequiredVariables.add("mso-request-id")
134 if (requiredVariables != null) {
135 for (String variable : requiredVariables) {
136 if (!allRequiredVariables.contains(variable)) {
137 allRequiredVariables.add(variable)
142 for (String variable : allRequiredVariables) {
143 def value = execution.getVariable(variable)
144 if (value == null || ((value instanceof CharSequence) && value.length() == 0)) {
145 exceptionUtil.buildAndThrowWorkflowException(execution, 1002, processKey +
146 " request was received with no '" + variable + "' variable")
149 if ("mso-request-id".equals(variable)) {
150 requestId = (String) value
151 } else if ("mso-service-instance-id".equals(variable)) {
152 serviceInstanceId = (String) value
156 if (serviceInstanceId == null) {
157 serviceInstanceId = (String) execution.getVariable("mso-service-instance-id")
160 logger.debug('Incoming message: ' + System.lineSeparator() + request)
161 logger.debug('Exited ' + method)
163 } catch (BpmnError e) {
165 } catch (Exception e) {
166 logger.error('Caught exception in {}: {}', method, e.getMessage(), e)
167 exceptionUtil.buildAndThrowWorkflowException(execution, 1002, "Invalid Message")
172 * gets vars stored in a JSON object in prefix+Request and returns as a LazyMap
173 * setting log context here too
174 * @param execution the execution
175 * @return the inputVars
177 public Map validateJSONReq(DelegateExecution execution) {
178 def method = getClass().getSimpleName() + '.validateJSONReq(' +
179 'execution=' + execution.getId() +
181 def isDebugLogEnabled = execution.getVariable('isDebugLogEnabled')
182 logger.debug('Entered ' + method)
184 String processKey = getProcessKey(execution);
185 def prefix = execution.getVariable("prefix")
187 def request = getVariable(execution, prefix + 'Request')
189 if (request == null) {
190 request = getVariable(execution, processKey + 'Request')
192 if (request == null) {
193 request = getVariable(execution, 'bpmnRequest')
195 execution.setVariable(prefix + 'Request', request)
198 def jsonSlurper = new JsonSlurper()
199 def parsed = jsonSlurper.parseText(request)
202 logger.debug('Incoming message: ' + System.lineSeparator() + request)
203 logger.debug('Exited ' + method)
209 * Sends a response to the workflow service that invoked the process. This method
210 * may only be used by top-level processes that were directly invoked by the
211 * asynchronous workflow service.
212 * @param execution the execution
213 * @param responseCode the response code
214 * @param content the message content
215 * @throws IllegalArgumentException if the response code is invalid
217 * @throws UnsupportedOperationException if not invoked by an asynchronous,
219 * @throws IllegalStateException if a response has already been sent
221 protected void sendWorkflowResponse(DelegateExecution execution, Object responseCode, String response) {
222 def isDebugLogEnabled = execution.getVariable('isDebugLogEnabled')
224 String processKey = getProcessKey(execution);
226 // isAsyncProcess is injected by the workflow service that started the flow
227 if (!String.valueOf(execution.getVariable("isAsyncProcess")).equals("true")) {
228 throw new UnsupportedOperationException(processKey + ": " +
229 "sendWorkflowResponse is valid only in asynchronous workflows");
232 if (String.valueOf(execution.getVariable(processKey + "WorkflowResponseSent")).equals("true")) {
233 logger.debug("Sync response has already been sent for " + processKey)
236 logger.debug("Building " + processKey + " response ")
241 intResponseCode = Integer.parseInt(String.valueOf(responseCode));
243 if (intResponseCode < 100 || intResponseCode > 599) {
244 throw new NumberFormatException(String.valueOf(responseCode));
246 } catch (NumberFormatException e) {
247 throw new IllegalArgumentException("Process " + processKey
248 + " provided an invalid HTTP response code: " + responseCode);
251 // Only 2XX responses are considered "Success"
252 String status = (intResponseCode >= 200 && intResponseCode <= 299) ?
255 // TODO: Should deprecate use of processKey+Response variable for the response. Will use "WorkflowResponse" instead
256 execution.setVariable(processKey + "ResponseCode", String.valueOf(intResponseCode))
257 execution.setVariable(processKey + "Response", response);
258 execution.setVariable(processKey + "Status", status);
259 execution.setVariable("WorkflowResponse", response)
261 logger.debug("Sending response for " + processKey
262 + " ResponseCode=" + intResponseCode
263 + " Status=" + status
264 + " Response=\n" + response)
266 // TODO: ensure that this flow was invoked asynchronously?
268 WorkflowCallbackResponse callbackResponse = new WorkflowCallbackResponse()
269 callbackResponse.setStatusCode(intResponseCode)
270 callbackResponse.setMessage(status)
271 callbackResponse.setResponse(response)
273 // TODO: send this data with HTTP POST
275 WorkflowContextHolder.getInstance().processCallback(
277 execution.getProcessInstanceId(),
278 execution.getVariable("mso-request-id"),
281 execution.setVariable(processKey + "WorkflowResponseSent", "true");
284 } catch (Exception ex) {
285 logger.error("Unable to send workflow response to client ....", ex)
290 * Returns true if a workflow response has already been sent.
291 * @param execution the execution
293 protected boolean isWorkflowResponseSent(DelegateExecution execution) {
294 def isDebugLogEnabled = execution.getVariable('isDebugLogEnabled')
295 String processKey = getProcessKey(execution);
296 return String.valueOf(execution.getVariable(processKey + "WorkflowResponseSent")).equals("true");
300 * Returns the process definition key (i.e. the process name) of the
303 * @param execution the execution
305 public String getProcessKey(DelegateExecution execution) {
306 def testKey = execution.getVariable("testProcessKey")
310 return execution.getProcessEngineServices().getRepositoryService()
311 .getProcessDefinition(execution.getProcessDefinitionId()).getKey()
315 * Returns the process definition key (i.e. the process name) of the
317 * @param execution the execution
319 public String getMainProcessKey(DelegateExecution execution) {
320 DelegateExecution exec = execution
323 DelegateExecution parent = exec.getSuperExecution()
325 if (parent == null) {
326 parent = exec.getParent()
328 if (parent == null) {
336 return execution.getProcessEngineServices().getRepositoryService()
337 .getProcessDefinition(exec.getProcessDefinitionId()).getKey()
341 * Gets the node for the named element from the given xml. If the element
342 * does not exist in the xml or is empty, a WorkflowException is created
343 * (and as a result, a MSOWorkflowException event is thrown).
345 * @param execution The flow's execution.
346 * @param xml Xml to search.
347 * @param elementName Name of element to search for.
348 * @return The element node, if found in the xml.
350 protected String getRequiredNodeXml(DelegateExecution execution, String xml, String elementName) {
351 ExceptionUtil exceptionUtil = new ExceptionUtil()
352 def element = utils.getNodeXml(xml, elementName, false)
353 if (element.trim().isEmpty()) {
354 def msg = 'Required element \'' + elementName + '\' is missing or empty'
356 exceptionUtil.buildAndThrowWorkflowException(execution, 2000, msg)
363 * Gets the value of the named element from the given xml. If the element
364 * does not exist in the xml or is empty, a WorkflowException is created
365 * (and as a result, a MSOWorkflowException event is thrown).
367 * @param execution The flow's execution.
368 * @param xml Xml to search.
369 * @param elementName Name of element to whose value to get.
370 * @return The non-empty value of the element, if found in the xml.
372 protected String getRequiredNodeText(DelegateExecution execution, String xml, String elementName) {
373 ExceptionUtil exceptionUtil = new ExceptionUtil()
374 def elementText = utils.getNodeText(xml, elementName)
375 if ((elementText == null) || (elementText.isEmpty())) {
376 def msg = 'Required element \'' + elementName + '\' is missing or empty'
378 exceptionUtil.buildAndThrowWorkflowException(execution, 2000, msg)
385 * Get the text for the specified element from the specified xml. If
386 * the element does not exist, return the specified default value.
388 * @param xml Xml from which to get the element's text
389 * @param elementName Name of element whose text to get
390 * @param defaultValue the default value
391 * @return the element's text or the default value if the element does not
392 * exist in the given xml
394 protected String getNodeText(String xml, String elementName, String defaultValue) {
395 def nodeText = utils.getNodeText(xml, elementName)
396 return (nodeText == null) ? defaultValue : nodeText
400 * Get the text for the specified element from the specified xml. If
401 * the element does not exist, return an empty string.
403 * @param xml Xml from which to get the element's text.
404 * @param elementName Name of element whose text to get.
405 * @return the element's text or an empty string if the element does not
406 * exist in the given xml.
408 protected String getNodeTextForce(String xml, String elementName) {
409 return getNodeText(xml, elementName, '');
413 *Store the variable as typed with java serialization type
418 public void setVariable(DelegateExecution execution, String name, Object value) {
419 VariableMap variables = Variables.createVariables()
420 variables.putValueTyped('payload', Variables.objectValue(value)
421 .serializationDataFormat(SerializationDataFormats.JAVA) // tells the engine to use java serialization for persisting the value
423 execution.setVariable(name,variables)
426 //TODO not sure how this will look in Cockpit
429 * Returns the variable map
434 public static String getVariable(DelegateExecution execution, String name) {
435 def myObj = execution.getVariable(name)
436 if(myObj instanceof VariableMap){
437 VariableMap serializedObjectMap = (VariableMap) myObj
438 ObjectValueImpl payloadObj = serializedObjectMap.getValueTyped('payload')
439 return payloadObj.getValue()
447 * Returns true if a value equals one of the provided set. Equality is
448 * determined by using the equals method if the value object and the
449 * object in the provided set have the same class. Otherwise, the objects
450 * are converted to strings and then compared. Nulls are permitted for
451 * the value as well as in the provided set
454 * def statusCode = getStatusCode()
455 * isOneOf(statusCode, 200, 201, 204)
457 * @param value the value to test
458 * @param these a set of permissable values
459 * @return true if the value is in the provided set
461 public boolean isOneOf(Object value, Object... these) {
462 for (Object thisOne : these) {
463 if (thisOne == null) {
469 if (value.getClass() == thisOne.getClass()) {
470 if (value.equals(thisOne)) {
474 if (String.valueOf(value).equals(String.valueOf(thisOne))) {
485 * Sets flows success indicator variable.
488 public void setSuccessIndicator(DelegateExecution execution, boolean isSuccess) {
489 String prefix = execution.getVariable('prefix')
490 def isDebugLogEnabled = execution.getVariable('isDebugLogEnabled')
492 logger.debug('Entered SetSuccessIndicator Method')
493 execution.setVariable(prefix+'SuccessIndicator', isSuccess)
494 logger.debug('Outgoing SuccessIndicator is: ' + execution.getVariable(prefix+'SuccessIndicator') + '')
498 * Sends a Error Sync Response
501 public void sendSyncError(DelegateExecution execution) {
502 def isDebugLogEnabled = execution.getVariable('isDebugLogEnabled')
503 String requestId = execution.getVariable("mso-request-id")
504 logger.debug('sendSyncError, requestId: ' + requestId)
505 WorkflowException workflowExceptionObj = execution.getVariable("WorkflowException")
506 if (workflowExceptionObj != null) {
507 String errorMessage = workflowExceptionObj.getErrorMessage()
508 def errorCode = workflowExceptionObj.getErrorCode()
509 logger.debug('sendSyncError, requestId: ' + requestId + ' | errorMessage: ' + errorMessage + ' | errorCode: ' + errorCode)
510 sendWorkflowResponse(execution, errorCode, errorMessage)
515 * Executes a named groovy script method in the current object
517 public void executeMethod(String methodName, Object... args) {
519 if (args != null && args.size() > 0) {
521 // First argument of method to call is always the execution object
522 DelegateExecution execution = (DelegateExecution) args[0]
524 def classAndMethod = getClass().getSimpleName() + '.' + methodName + '(execution=' + execution.getId() + ')'
525 def isDebugLogEnabled = execution.getVariable('isDebugLogEnabled')
527 logger.debug('Entered ' + classAndMethod)
528 logger.debug('Received parameters: ' + args)
531 def methodToCall = this.metaClass.getMetaMethod(methodName, args)
532 logger.debug('Method to call: ' + methodToCall)
533 methodToCall?.invoke(this, args)
535 catch(BpmnError bpmnError) {
536 logger.debug('Rethrowing BpmnError ' + bpmnError.getMessage())
540 logger.debug('Unexpected error encountered - {}', e.getMessage(), e)
541 (new ExceptionUtil()).buildAndThrowWorkflowException(execution, 9999, e.getMessage())
544 logger.debug('Exited ' + classAndMethod)
550 *This method determines and adds the appropriate ending to come
551 *after a number (-st, -nd, -rd, or -th)
555 *@return String ending - number with suffix
557 public static String labelMaker(Object n) {
559 if(n instanceof String){
560 num = Integer.parseInt(n)
565 String ending = ""; //the end to be added to the number
567 if ((num % 10 == 1) && (num != 11)) {
569 } else if ((num % 10 == 2) && (num != 12)) {
571 } else if ((num % 10 == 3) && (num != 13)) {
581 * Constructs a workflow message callback URL for the specified message type and correlator.
582 * This type of callback URL is used when a workflow wants an MSO adapter (like the SDNC
583 * adapter) to call it back. In other words, this is for callbacks internal to the MSO
584 * complex. Use <code>createWorkflowMessageAdapterCallbackURL</code> if the callback
585 * will come from outside the MSO complex.
586 * @param messageType the message type (e.g. SDNCAResponse or VNFAResponse)
587 * @param correlator the correlator value (e.g. a request ID)
589 public String createCallbackURL(DelegateExecution execution, String messageType, String correlator) {
590 String endpoint = UrnPropertiesReader.getVariable("mso.workflow.message.endpoint", execution)
592 if (endpoint == null || endpoint.isEmpty()) {
593 ExceptionUtil exceptionUtil = new ExceptionUtil()
594 exceptionUtil.buildAndThrowWorkflowException(execution, 2000,
595 'mso:workflow:message:endpoint URN mapping is not set')
598 while (endpoint.endsWith('/')) {
599 endpoint = endpoint.substring(0, endpoint.length()-1)
603 '/' + UriUtils.encodePathSegment(messageType, 'UTF-8') +
604 '/' + UriUtils.encodePathSegment(correlator, 'UTF-8')
609 * Constructs a workflow message callback URL for the specified message type and correlator.
610 * This type of callback URL is used when a workflow wants a system outside the MSO complex
611 * to call it back through the Workflow Message Adapter.
612 * @param messageType the message type (e.g. SNIROResponse)
613 * @param correlator the correlator value (e.g. a request ID)
615 public String createWorkflowMessageAdapterCallbackURL(DelegateExecution execution, String messageType, String correlator) {
616 String endpoint = UrnPropertiesReader.getVariable("mso.adapters.workflow.message.endpoint", execution)
618 if (endpoint == null || endpoint.isEmpty()) {
619 ExceptionUtil exceptionUtil = new ExceptionUtil()
620 exceptionUtil.buildAndThrowWorkflowException(execution, 2000,
621 'mso:adapters:workflow:message:endpoint URN mapping is not set')
624 while (endpoint.endsWith('/')) {
625 endpoint = endpoint.substring(0, endpoint.length()-1)
629 '/' + UriUtils.encodePathSegment(messageType, 'UTF-8') +
630 '/' + UriUtils.encodePathSegment(correlator, 'UTF-8')
633 public void setRollbackEnabled(DelegateExecution execution, isDebugLogEnabled) {
636 def prefix = execution.getVariable('prefix')
637 def disableRollback = execution.getVariable("disableRollback")
638 def defaultRollback = UrnPropertiesReader.getVariable("mso.rollback", execution).toBoolean()
640 logger.debug('disableRollback: ' + disableRollback)
641 logger.debug('defaultRollback: ' + defaultRollback)
645 if(disableRollback == null || disableRollback == '' ) {
646 // get from default urn settings for mso_rollback
647 disableRollback = !defaultRollback
648 rollbackEnabled = defaultRollback
649 logger.debug('disableRollback is null or empty!')
652 if(disableRollback == true) {
653 rollbackEnabled = false
655 else if(disableRollback == false){
656 rollbackEnabled = true
659 rollbackEnabled = defaultRollback
663 execution.setVariable(prefix+"backoutOnFailure", rollbackEnabled)
664 logger.debug('rollbackEnabled (aka backoutOnFailure): ' + rollbackEnabled)
667 public void setBasicDBAuthHeader(DelegateExecution execution, isDebugLogEnabled) {
669 String basicAuthValueDB = UrnPropertiesReader.getVariable("mso.adapters.db.auth", execution)
670 def encodedString = utils.getBasicAuth(basicAuthValueDB, UrnPropertiesReader.getVariable("mso.msoKey", execution))
671 execution.setVariable("BasicAuthHeaderValueDB",encodedString)
672 } catch (IOException ex) {
673 String dataErrorMessage = " Unable to encode Catalog DB user/password string - " + ex.getMessage()
674 logger.debug(dataErrorMessage)
675 (new ExceptionUtil()).buildAndThrowWorkflowException(execution, 2500, dataErrorMessage)
678 public AAIResourcesClient getAAIClient(){
679 return new AAIResourcesClient();
682 HttpClientFactory getHttpClientFactory(){
683 return new HttpClientFactory()