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.aai.AAIResourcesClient
36 import org.springframework.web.util.UriUtils
37 import org.slf4j.Logger
38 import org.slf4j.LoggerFactory
40 import groovy.json.JsonSlurper
42 public abstract class AbstractServiceTaskProcessor implements ServiceTaskProcessor {
43 private static final Logger logger = LoggerFactory.getLogger( MsoUtils.class);
45 public MsoUtils utils = new MsoUtils()
48 * Saves the WorkflowException in the execution to the specified variable,
49 * clearing the WorkflowException variable so the workflow can continue
50 * processing (perhaps catching another WorkflowException).
51 * @param execution the execution
52 * @return the name of the destination variable
54 public saveWorkflowException(DelegateExecution execution, String variable) {
55 if (variable == null) {
56 throw new NullPointerException();
59 execution.setVariable(variable, execution.getVariable("WorkflowException"))
60 execution.setVariable("WorkflowException", null)
65 * Validates that the request exists and that the mso-request-id variable is set.
66 * Additional required variables may be checked by specifying their names.
67 * NOTE: services requiring mso-service-instance-id must specify it explicitly!
68 * If a problem is found, buildAndThrowWorkflowException builds a WorkflowException
69 * and throws an MSOWorkflowException. This method also sets up the log context for
72 * @param execution the execution
73 * @return the validated request
75 public String validateRequest(DelegateExecution execution, String... requiredVariables) {
76 ExceptionUtil exceptionUtil = new ExceptionUtil()
77 def method = getClass().getSimpleName() + '.validateRequest(' +
78 'execution=' + execution.getId() +
79 ', requredVariables=' + requiredVariables +
81 def isDebugLogEnabled = execution.getVariable('isDebugLogEnabled')
82 logger.debug('Entered ' + method)
84 String processKey = getProcessKey(execution)
85 def prefix = execution.getVariable("prefix")
88 exceptionUtil.buildAndThrowWorkflowException(execution, 1002, processKey + " prefix is null")
92 def request = execution.getVariable(prefix + 'Request')
94 if (request == null) {
95 request = execution.getVariable(processKey + 'Request')
97 if (request == null) {
98 request = execution.getVariable('bpmnRequest')
101 setVariable(execution, processKey + 'Request', null)
102 setVariable(execution, 'bpmnRequest', null)
103 setVariable(execution, prefix + 'Request', request)
106 if (request == null) {
107 exceptionUtil.buildAndThrowWorkflowException(execution, 1002, processKey + " request is null")
110 // All requests must have a request ID.
111 // Some requests (e.g. SDN-MOBILITY) do not have a service instance ID.
113 String requestId = null
114 String serviceInstanceId = null
116 List<String> allRequiredVariables = new ArrayList<String>()
117 allRequiredVariables.add("mso-request-id")
119 if (requiredVariables != null) {
120 for (String variable : requiredVariables) {
121 if (!allRequiredVariables.contains(variable)) {
122 allRequiredVariables.add(variable)
127 for (String variable : allRequiredVariables) {
128 def value = execution.getVariable(variable)
129 if (value == null || ((value instanceof CharSequence) && value.length() == 0)) {
130 exceptionUtil.buildAndThrowWorkflowException(execution, 1002, processKey +
131 " request was received with no '" + variable + "' variable")
134 if ("mso-request-id".equals(variable)) {
135 requestId = (String) value
136 } else if ("mso-service-instance-id".equals(variable)) {
137 serviceInstanceId = (String) value
141 if (serviceInstanceId == null) {
142 serviceInstanceId = (String) execution.getVariable("mso-service-instance-id")
145 logger.debug('Incoming message: ' + System.lineSeparator() + request)
146 logger.debug('Exited ' + method)
148 } catch (BpmnError e) {
150 } catch (Exception e) {
151 logger.error('Caught exception in {}: {}', method, e.getMessage(), e)
152 exceptionUtil.buildAndThrowWorkflowException(execution, 1002, "Invalid Message")
157 * gets vars stored in a JSON object in prefix+Request and returns as a LazyMap
158 * setting log context here too
159 * @param execution the execution
160 * @return the inputVars
162 public Map validateJSONReq(DelegateExecution execution) {
163 def method = getClass().getSimpleName() + '.validateJSONReq(' +
164 'execution=' + execution.getId() +
166 def isDebugLogEnabled = execution.getVariable('isDebugLogEnabled')
167 logger.debug('Entered ' + method)
169 String processKey = getProcessKey(execution);
170 def prefix = execution.getVariable("prefix")
172 def request = getVariable(execution, prefix + 'Request')
174 if (request == null) {
175 request = getVariable(execution, processKey + 'Request')
177 if (request == null) {
178 request = getVariable(execution, 'bpmnRequest')
180 execution.setVariable(prefix + 'Request', request)
183 def jsonSlurper = new JsonSlurper()
184 def parsed = jsonSlurper.parseText(request)
187 logger.debug('Incoming message: ' + System.lineSeparator() + request)
188 logger.debug('Exited ' + method)
194 * Sends a response to the workflow service that invoked the process. This method
195 * may only be used by top-level processes that were directly invoked by the
196 * asynchronous workflow service.
197 * @param execution the execution
198 * @param responseCode the response code
199 * @param content the message content
200 * @throws IllegalArgumentException if the response code is invalid
202 * @throws UnsupportedOperationException if not invoked by an asynchronous,
204 * @throws IllegalStateException if a response has already been sent
206 protected void sendWorkflowResponse(DelegateExecution execution, Object responseCode, String response) {
207 def isDebugLogEnabled = execution.getVariable('isDebugLogEnabled')
209 String processKey = getProcessKey(execution);
211 // isAsyncProcess is injected by the workflow service that started the flow
212 if (!String.valueOf(execution.getVariable("isAsyncProcess")).equals("true")) {
213 throw new UnsupportedOperationException(processKey + ": " +
214 "sendWorkflowResponse is valid only in asynchronous workflows");
217 if (String.valueOf(execution.getVariable(processKey + "WorkflowResponseSent")).equals("true")) {
218 logger.debug("Sync response has already been sent for " + processKey)
221 logger.debug("Building " + processKey + " response ")
226 intResponseCode = Integer.parseInt(String.valueOf(responseCode));
228 if (intResponseCode < 100 || intResponseCode > 599) {
229 throw new NumberFormatException(String.valueOf(responseCode));
231 } catch (NumberFormatException e) {
232 throw new IllegalArgumentException("Process " + processKey
233 + " provided an invalid HTTP response code: " + responseCode);
236 // Only 2XX responses are considered "Success"
237 String status = (intResponseCode >= 200 && intResponseCode <= 299) ?
240 // TODO: Should deprecate use of processKey+Response variable for the response. Will use "WorkflowResponse" instead
241 execution.setVariable(processKey + "ResponseCode", String.valueOf(intResponseCode))
242 execution.setVariable(processKey + "Response", response);
243 execution.setVariable(processKey + "Status", status);
244 execution.setVariable("WorkflowResponse", response)
246 logger.debug("Sending response for " + processKey
247 + " ResponseCode=" + intResponseCode
248 + " Status=" + status
249 + " Response=\n" + response)
251 // TODO: ensure that this flow was invoked asynchronously?
253 WorkflowCallbackResponse callbackResponse = new WorkflowCallbackResponse()
254 callbackResponse.setStatusCode(intResponseCode)
255 callbackResponse.setMessage(status)
256 callbackResponse.setResponse(response)
258 // TODO: send this data with HTTP POST
260 WorkflowContextHolder.getInstance().processCallback(
262 execution.getProcessInstanceId(),
263 execution.getVariable("mso-request-id"),
266 execution.setVariable(processKey + "WorkflowResponseSent", "true");
269 } catch (Exception ex) {
270 logger.error("Unable to send workflow response to client ....", ex)
275 * Returns true if a workflow response has already been sent.
276 * @param execution the execution
278 protected boolean isWorkflowResponseSent(DelegateExecution execution) {
279 def isDebugLogEnabled = execution.getVariable('isDebugLogEnabled')
280 String processKey = getProcessKey(execution);
281 return String.valueOf(execution.getVariable(processKey + "WorkflowResponseSent")).equals("true");
285 * Returns the process definition key (i.e. the process name) of the
288 * @param execution the execution
290 public String getProcessKey(DelegateExecution execution) {
291 def testKey = execution.getVariable("testProcessKey")
295 return execution.getProcessEngineServices().getRepositoryService()
296 .getProcessDefinition(execution.getProcessDefinitionId()).getKey()
300 * Returns the process definition key (i.e. the process name) of the
302 * @param execution the execution
304 public String getMainProcessKey(DelegateExecution execution) {
305 DelegateExecution exec = execution
308 DelegateExecution parent = exec.getSuperExecution()
310 if (parent == null) {
311 parent = exec.getParent()
313 if (parent == null) {
321 return execution.getProcessEngineServices().getRepositoryService()
322 .getProcessDefinition(exec.getProcessDefinitionId()).getKey()
326 * Gets the node for the named element from the given xml. If the element
327 * does not exist in the xml or is empty, a WorkflowException is created
328 * (and as a result, a MSOWorkflowException event is thrown).
330 * @param execution The flow's execution.
331 * @param xml Xml to search.
332 * @param elementName Name of element to search for.
333 * @return The element node, if found in the xml.
335 protected String getRequiredNodeXml(DelegateExecution execution, String xml, String elementName) {
336 ExceptionUtil exceptionUtil = new ExceptionUtil()
337 def element = utils.getNodeXml(xml, elementName, false)
338 if (element.trim().isEmpty()) {
339 def msg = 'Required element \'' + elementName + '\' is missing or empty'
341 exceptionUtil.buildAndThrowWorkflowException(execution, 2000, msg)
348 * Gets the value of the named element from the given xml. If the element
349 * does not exist in the xml or is empty, a WorkflowException is created
350 * (and as a result, a MSOWorkflowException event is thrown).
352 * @param execution The flow's execution.
353 * @param xml Xml to search.
354 * @param elementName Name of element to whose value to get.
355 * @return The non-empty value of the element, if found in the xml.
357 protected String getRequiredNodeText(DelegateExecution execution, String xml, String elementName) {
358 ExceptionUtil exceptionUtil = new ExceptionUtil()
359 def elementText = utils.getNodeText(xml, elementName)
360 if ((elementText == null) || (elementText.isEmpty())) {
361 def msg = 'Required element \'' + elementName + '\' is missing or empty'
363 exceptionUtil.buildAndThrowWorkflowException(execution, 2000, msg)
370 * Get the text for the specified element from the specified xml. If
371 * the element does not exist, return the specified default value.
373 * @param xml Xml from which to get the element's text
374 * @param elementName Name of element whose text to get
375 * @param defaultValue the default value
376 * @return the element's text or the default value if the element does not
377 * exist in the given xml
379 protected String getNodeText(String xml, String elementName, String defaultValue) {
380 def nodeText = utils.getNodeText(xml, elementName)
381 return (nodeText == null) ? defaultValue : nodeText
385 * Get the text for the specified element from the specified xml. If
386 * the element does not exist, return an empty string.
388 * @param xml Xml from which to get the element's text.
389 * @param elementName Name of element whose text to get.
390 * @return the element's text or an empty string if the element does not
391 * exist in the given xml.
393 protected String getNodeTextForce(String xml, String elementName) {
394 return getNodeText(xml, elementName, '');
398 *Store the variable as typed with java serialization type
403 public void setVariable(DelegateExecution execution, String name, Object value) {
404 VariableMap variables = Variables.createVariables()
405 variables.putValueTyped('payload', Variables.objectValue(value)
406 .serializationDataFormat(SerializationDataFormats.JAVA) // tells the engine to use java serialization for persisting the value
408 execution.setVariable(name,variables)
411 //TODO not sure how this will look in Cockpit
414 * Returns the variable map
419 public static String getVariable(DelegateExecution execution, String name) {
420 def myObj = execution.getVariable(name)
421 if(myObj instanceof VariableMap){
422 VariableMap serializedObjectMap = (VariableMap) myObj
423 ObjectValueImpl payloadObj = serializedObjectMap.getValueTyped('payload')
424 return payloadObj.getValue()
432 * Returns true if a value equals one of the provided set. Equality is
433 * determined by using the equals method if the value object and the
434 * object in the provided set have the same class. Otherwise, the objects
435 * are converted to strings and then compared. Nulls are permitted for
436 * the value as well as in the provided set
439 * def statusCode = getStatusCode()
440 * isOneOf(statusCode, 200, 201, 204)
442 * @param value the value to test
443 * @param these a set of permissable values
444 * @return true if the value is in the provided set
446 public boolean isOneOf(Object value, Object... these) {
447 for (Object thisOne : these) {
448 if (thisOne == null) {
454 if (value.getClass() == thisOne.getClass()) {
455 if (value.equals(thisOne)) {
459 if (String.valueOf(value).equals(String.valueOf(thisOne))) {
470 * Sets flows success indicator variable.
473 public void setSuccessIndicator(DelegateExecution execution, boolean isSuccess) {
474 String prefix = execution.getVariable('prefix')
475 def isDebugLogEnabled = execution.getVariable('isDebugLogEnabled')
477 logger.debug('Entered SetSuccessIndicator Method')
478 execution.setVariable(prefix+'SuccessIndicator', isSuccess)
479 logger.debug('Outgoing SuccessIndicator is: ' + execution.getVariable(prefix+'SuccessIndicator') + '')
483 * Sends a Error Sync Response
486 public void sendSyncError(DelegateExecution execution) {
487 def isDebugLogEnabled = execution.getVariable('isDebugLogEnabled')
488 String requestId = execution.getVariable("mso-request-id")
489 logger.debug('sendSyncError, requestId: ' + requestId)
490 WorkflowException workflowExceptionObj = execution.getVariable("WorkflowException")
491 if (workflowExceptionObj != null) {
492 String errorMessage = workflowExceptionObj.getErrorMessage()
493 def errorCode = workflowExceptionObj.getErrorCode()
494 logger.debug('sendSyncError, requestId: ' + requestId + ' | errorMessage: ' + errorMessage + ' | errorCode: ' + errorCode)
495 sendWorkflowResponse(execution, errorCode, errorMessage)
500 * Executes a named groovy script method in the current object
502 public void executeMethod(String methodName, Object... args) {
504 if (args != null && args.size() > 0) {
506 // First argument of method to call is always the execution object
507 DelegateExecution execution = (DelegateExecution) args[0]
509 def classAndMethod = getClass().getSimpleName() + '.' + methodName + '(execution=' + execution.getId() + ')'
510 def isDebugLogEnabled = execution.getVariable('isDebugLogEnabled')
512 logger.debug('Entered ' + classAndMethod)
513 logger.debug('Received parameters: ' + args)
516 def methodToCall = this.metaClass.getMetaMethod(methodName, args)
517 logger.debug('Method to call: ' + methodToCall)
518 methodToCall?.invoke(this, args)
520 catch(BpmnError bpmnError) {
521 logger.debug('Rethrowing BpmnError ' + bpmnError.getMessage())
525 logger.debug('Unexpected error encountered - {}', e.getMessage(), e)
526 (new ExceptionUtil()).buildAndThrowWorkflowException(execution, 9999, e.getMessage())
529 logger.debug('Exited ' + classAndMethod)
535 *This method determines and adds the appropriate ending to come
536 *after a number (-st, -nd, -rd, or -th)
540 *@return String ending - number with suffix
542 public static String labelMaker(Object n) {
544 if(n instanceof String){
545 num = Integer.parseInt(n)
550 String ending = ""; //the end to be added to the number
552 if ((num % 10 == 1) && (num != 11)) {
554 } else if ((num % 10 == 2) && (num != 12)) {
556 } else if ((num % 10 == 3) && (num != 13)) {
566 * Constructs a workflow message callback URL for the specified message type and correlator.
567 * This type of callback URL is used when a workflow wants an MSO adapter (like the SDNC
568 * adapter) to call it back. In other words, this is for callbacks internal to the MSO
569 * complex. Use <code>createWorkflowMessageAdapterCallbackURL</code> if the callback
570 * will come from outside the MSO complex.
571 * @param messageType the message type (e.g. SDNCAResponse or VNFAResponse)
572 * @param correlator the correlator value (e.g. a request ID)
574 public String createCallbackURL(DelegateExecution execution, String messageType, String correlator) {
575 String endpoint = UrnPropertiesReader.getVariable("mso.workflow.message.endpoint", execution)
577 if (endpoint == null || endpoint.isEmpty()) {
578 ExceptionUtil exceptionUtil = new ExceptionUtil()
579 exceptionUtil.buildAndThrowWorkflowException(execution, 2000,
580 'mso:workflow:message:endpoint URN mapping is not set')
583 while (endpoint.endsWith('/')) {
584 endpoint = endpoint.substring(0, endpoint.length()-1)
588 '/' + UriUtils.encodePathSegment(messageType, 'UTF-8') +
589 '/' + UriUtils.encodePathSegment(correlator, 'UTF-8')
594 * Constructs a workflow message callback URL for the specified message type and correlator.
595 * This type of callback URL is used when a workflow wants a system outside the MSO complex
596 * to call it back through the Workflow Message Adapter.
597 * @param messageType the message type (e.g. SNIROResponse)
598 * @param correlator the correlator value (e.g. a request ID)
600 public String createWorkflowMessageAdapterCallbackURL(DelegateExecution execution, String messageType, String correlator) {
601 String endpoint = UrnPropertiesReader.getVariable("mso.adapters.workflow.message.endpoint", execution)
603 if (endpoint == null || endpoint.isEmpty()) {
604 ExceptionUtil exceptionUtil = new ExceptionUtil()
605 exceptionUtil.buildAndThrowWorkflowException(execution, 2000,
606 'mso:adapters:workflow:message:endpoint URN mapping is not set')
609 while (endpoint.endsWith('/')) {
610 endpoint = endpoint.substring(0, endpoint.length()-1)
614 '/' + UriUtils.encodePathSegment(messageType, 'UTF-8') +
615 '/' + UriUtils.encodePathSegment(correlator, 'UTF-8')
618 public void setRollbackEnabled(DelegateExecution execution, isDebugLogEnabled) {
621 def prefix = execution.getVariable('prefix')
622 def disableRollback = execution.getVariable("disableRollback")
623 def defaultRollback = UrnPropertiesReader.getVariable("mso.rollback", execution).toBoolean()
625 logger.debug('disableRollback: ' + disableRollback)
626 logger.debug('defaultRollback: ' + defaultRollback)
630 if(disableRollback == null || disableRollback == '' ) {
631 // get from default urn settings for mso_rollback
632 disableRollback = !defaultRollback
633 rollbackEnabled = defaultRollback
634 logger.debug('disableRollback is null or empty!')
637 if(disableRollback == true) {
638 rollbackEnabled = false
640 else if(disableRollback == false){
641 rollbackEnabled = true
644 rollbackEnabled = defaultRollback
648 execution.setVariable(prefix+"backoutOnFailure", rollbackEnabled)
649 logger.debug('rollbackEnabled (aka backoutOnFailure): ' + rollbackEnabled)
652 public void setBasicDBAuthHeader(DelegateExecution execution, isDebugLogEnabled) {
654 String basicAuthValueDB = UrnPropertiesReader.getVariable("mso.adapters.db.auth", execution)
655 def encodedString = utils.getBasicAuth(basicAuthValueDB, UrnPropertiesReader.getVariable("mso.msoKey", execution))
656 execution.setVariable("BasicAuthHeaderValueDB",encodedString)
657 } catch (IOException ex) {
658 String dataErrorMessage = " Unable to encode Catalog DB user/password string - " + ex.getMessage()
659 logger.debug(dataErrorMessage)
660 (new ExceptionUtil()).buildAndThrowWorkflowException(execution, 2500, dataErrorMessage)
663 public AAIResourcesClient getAAIClient(){
664 return new AAIResourcesClient();