2  * ============LICENSE_START=======================================================
 
   4  * ================================================================================
 
   5  * Copyright (C) 2018 Huawei Technologies Co., Ltd. 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.infrastructure.scripts;
 
  25 import static org.apache.commons.lang3.StringUtils.*;
 
  27 import javax.ws.rs.NotFoundException
 
  29 import org.apache.commons.lang3.*
 
  30 import org.camunda.bpm.engine.delegate.BpmnError
 
  31 import org.camunda.bpm.engine.delegate.DelegateExecution
 
  32 import org.json.JSONArray
 
  33 import org.json.JSONObject
 
  34 import org.onap.aai.domain.yang.ServiceInstance
 
  35 import org.onap.so.bpmn.common.scripts.AbstractServiceTaskProcessor
 
  36 import org.onap.so.bpmn.common.scripts.ExceptionUtil
 
  37 import org.onap.so.bpmn.common.scripts.MsoUtils
 
  38 import org.onap.so.bpmn.core.WorkflowException
 
  39 import org.onap.so.bpmn.core.domain.Resource
 
  40 import org.onap.so.bpmn.core.json.JsonUtils
 
  41 import org.onap.so.bpmn.core.UrnPropertiesReader
 
  42 import org.onap.so.client.aai.AAIObjectType
 
  43 import org.onap.so.client.aai.AAIResourcesClient
 
  44 import org.onap.so.client.aai.entities.AAIResultWrapper
 
  45 import org.onap.so.client.aai.entities.uri.AAIResourceUri
 
  46 import org.onap.so.client.aai.entities.uri.AAIUriFactory
 
  47 import org.springframework.web.util.UriUtils
 
  48 import org.slf4j.Logger
 
  49 import org.slf4j.LoggerFactory
 
  54  * This groovy class supports the <class>UpdateCustomE2EServiceInstance.bpmn</class> process.
 
  55  * AlaCarte flow for 1702 ServiceInstance Update
 
  58 public class UpdateCustomE2EServiceInstance extends AbstractServiceTaskProcessor {
 
  59         private static final Logger logger = LoggerFactory.getLogger( UpdateCustomE2EServiceInstance.class);
 
  61         String Prefix="UPDSI_"
 
  62         ExceptionUtil exceptionUtil = new ExceptionUtil()
 
  63         JsonUtils jsonUtil = new JsonUtils()
 
  66         public void preProcessRequest (DelegateExecution execution) {
 
  67                 def isDebugEnabled=execution.getVariable("isDebugLogEnabled")
 
  68                 execution.setVariable("prefix",Prefix)
 
  70                 logger.info( " *** preProcessRequest() *** ")
 
  74                         String siRequest = execution.getVariable("bpmnRequest")
 
  76                         String requestId = execution.getVariable("mso-request-id")
 
  77                         execution.setVariable("msoRequestId", requestId)
 
  78                         logger.info( "Input Request:" + siRequest + " reqId:" + requestId)
 
  80                         String serviceInstanceId = execution.getVariable("serviceInstanceId")
 
  81                         if (isBlank(serviceInstanceId)) {
 
  82                                 msg = "Input serviceInstanceId' is null"
 
  83                                 exceptionUtil.buildAndThrowWorkflowException(execution, 500, msg)
 
  86                         //subscriberInfo for aai
 
  87                         String globalSubscriberId = jsonUtil.getJsonValue(siRequest, "requestDetails.subscriberInfo.globalSubscriberId")
 
  88                         if (isBlank(globalSubscriberId)) {
 
  89                                 msg = "Input globalSubscriberId' is null"
 
  90                                 exceptionUtil.buildAndThrowWorkflowException(execution, 500, msg)
 
  92                                 execution.setVariable("globalSubscriberId", globalSubscriberId)
 
  96                         execution.setVariable("source", jsonUtil.getJsonValue(siRequest, "requestDetails.requestInfo.source"))
 
  97                         execution.setVariable("serviceInstanceName", jsonUtil.getJsonValue(siRequest, "requestDetails.requestInfo.instanceName"))
 
  98                         execution.setVariable("disableRollback", jsonUtil.getJsonValue(siRequest, "requestDetails.requestInfo.suppressRollback"))
 
  99                         String productFamilyId = jsonUtil.getJsonValue(siRequest, "requestDetails.requestInfo.productFamilyId")
 
 100                         if (isBlank(productFamilyId))
 
 102                                 msg = "Input productFamilyId is null"
 
 105                                 execution.setVariable("productFamilyId", productFamilyId)
 
 109                  String userParams = jsonUtil.getJsonValue(siRequest, "requestDetails.requestParameters.userParams")
 
 110              logger.info( "userParams:" + userParams)
 
 111                  List<String> paramList = jsonUtil.StringArrayToList(execution, userParams)
 
 112                  String uuiRequest = jsonUtil.getJsonValue(paramList.get(0), "UUIRequest")
 
 113                         if (isBlank(uuiRequest)) {
 
 114                                 msg = "Input uuiRequest is null"
 
 116                                 exceptionUtil.buildAndThrowWorkflowException(execution, 500, msg)
 
 119                                 execution.setVariable("uuiRequest", uuiRequest)
 
 122                         logger.info( "uuiRequest:\n" + uuiRequest)
 
 124                         //serviceType for aai
 
 125                         String serviceType = jsonUtil.getJsonValue(uuiRequest, "service.serviceType")
 
 126                         if (isBlank(serviceType)) {
 
 127                                 msg = "Input serviceType is null"
 
 129                                 exceptionUtil.buildAndThrowWorkflowException(execution, 500, msg)
 
 131                                 execution.setVariable("serviceType", serviceType)
 
 135                         String modelInvariantUuid = jsonUtil.getJsonValue(uuiRequest, "service.serviceInvariantUuid")
 
 136                         logger.info("modelInvariantUuid: " + modelInvariantUuid)
 
 137                         execution.setVariable("modelInvariantUuid", modelInvariantUuid)
 
 138                         execution.setVariable("model-invariant-id-target", modelInvariantUuid)
 
 140                         String modelUuid = jsonUtil.getJsonValue(uuiRequest, "service.serviceUuid")
 
 141                         logger.info("modelUuid: " + modelUuid)
 
 142                         execution.setVariable("modelUuid", modelUuid)
 
 143                         execution.setVariable("model-version-id-target", modelUuid)
 
 145                         String serviceModelName = jsonUtil.getJsonValue(uuiRequest, "service.parameters.templateName")
 
 146                         logger.info("serviceModelName: " + serviceModelName)
 
 147                         if(serviceModelName == null) {
 
 148                                 serviceModelName = ""
 
 150                         execution.setVariable("serviceModelName", serviceModelName)
 
 153                         String operationId = jsonUtil.getJsonValue(siRequest, "operationId")
 
 154                         if (isBlank(operationId)) {
 
 155                                 operationId = UUID.randomUUID().toString()
 
 157                         execution.setVariable("operationId", operationId)
 
 158                         execution.setVariable("operationType", "update")
 
 159                         execution.setVariable("hasResourcetoUpdate", false)
 
 161                 } catch (BpmnError e) {
 
 163                 } catch (Exception ex){
 
 164                         msg = "Exception in preProcessRequest " + ex.getMessage()
 
 166                         exceptionUtil.buildAndThrowWorkflowException(execution, 7000, msg)
 
 168                 logger.info(" ***** Exit preProcessRequest *****")
 
 172          * Gets the service instance and its relationships from aai
 
 174         public void getServiceInstance(DelegateExecution execution) {
 
 176                         String serviceInstanceId = execution.getVariable('serviceInstanceId')
 
 177                         String globalSubscriberId = execution.getVariable('globalSubscriberId')
 
 178                         String serviceType = execution.getVariable('serviceType')
 
 180                         AAIResourcesClient resourceClient = new AAIResourcesClient()
 
 181                         AAIResourceUri serviceInstanceUri = AAIUriFactory.createResourceUri(AAIObjectType.SERVICE_INSTANCE, globalSubscriberId, serviceType, serviceInstanceId)
 
 182                         AAIResultWrapper wrapper = resourceClient.get(serviceInstanceUri, NotFoundException.class)
 
 184                         Optional<ServiceInstance> si = wrapper.asBean(ServiceInstance.class)
 
 185                         execution.setVariable("serviceInstanceName", si.get().getServiceInstanceName())
 
 186                         execution.setVariable("model-invariant-id-original", si.get().getModelInvariantId())
 
 187                         execution.setVariable("model-version-id-original", si.get().getModelVersionId())
 
 189                         JSONObject ob = new JSONObject(wrapper.getJson())
 
 190                         JSONArray ar = ob.getJSONObject("relationship-list").getJSONArray("relationship")
 
 192                         execution.setVariable("serviceInstanceData-original", si.get())
 
 193                         execution.setVariable("serviceRelationShip", ar.toString())
 
 196                 }catch(BpmnError e) {
 
 198                 }catch(NotFoundException e) {
 
 199                         exceptionUtil.buildAndThrowWorkflowException(execution, 404, "Service-instance does not exist AAI")
 
 200                 }catch(Exception ex) {
 
 201                         String msg = "Internal Error in getServiceInstance: " + ex.getMessage()
 
 202                         exceptionUtil.buildAndThrowWorkflowException(execution, 7000, msg)
 
 206         public void preCompareModelVersions(DelegateExecution execution) {
 
 207                 def isDebugEnabled=execution.getVariable("isDebugLogEnabled")
 
 210         public void postCompareModelVersions(DelegateExecution execution) {
 
 211                 def isDebugEnabled=execution.getVariable("isDebugLogEnabled")
 
 212                 logger.debug( " ======== STARTED postCompareModelVersions Process ======== ")
 
 214                 def hasResourcetoUpdate = false
 
 215                 def hasResourcetoAdd = false
 
 216                 def hasResourcetoDelete = false
 
 217                 List<Resource> addResourceList =  execution.getVariable("addResourceList")
 
 218                 List<Resource> delResourceList =  execution.getVariable("delResourceList")
 
 220                 if(addResourceList != null && !addResourceList.isEmpty()) {
 
 221                         hasResourcetoAdd = true
 
 224                 if(delResourceList != null && !delResourceList.isEmpty()) {
 
 225                         hasResourcetoDelete = true
 
 228                 hasResourcetoUpdate = hasResourcetoAdd || hasResourcetoDelete
 
 229                 execution.setVariable("hasResourcetoUpdate", hasResourcetoUpdate)
 
 231                 logger.debug( "======== COMPLETED postCompareModelVersions Process ======== ")
 
 235          * Init the service Operation Status
 
 237         public void prepareInitServiceOperationStatus(DelegateExecution execution){
 
 238                 def isDebugEnabled = execution.getVariable("isDebugLogEnabled")
 
 239                 logger.debug( " ======== STARTED prepareInitServiceOperationStatus Process ======== ")
 
 241                         String serviceId = execution.getVariable("serviceInstanceId")
 
 242                         String operationId = execution.getVariable("operationId")
 
 243                         String operationType = execution.getVariable("operationType")
 
 245                         String result = "processing"
 
 246                         String progress = "0"
 
 248                         String operationContent = "Prepare service updating"
 
 249                         logger.debug( "Generated new operation for Service Instance serviceId:" + serviceId + " operationId:" + operationId)
 
 250                         serviceId = UriUtils.encode(serviceId,"UTF-8")
 
 251                         execution.setVariable("serviceInstanceId", serviceId)
 
 252                         execution.setVariable("operationId", operationId)
 
 253                         execution.setVariable("operationType", operationType)
 
 255                         def dbAdapterEndpoint = UrnPropertiesReader.getVariable("mso.adapters.openecomp.db.endpoint", execution)
 
 256                         execution.setVariable("CVFMI_dbAdapterEndpoint", dbAdapterEndpoint)
 
 257                         logger.debug( "DB Adapter Endpoint is: " + dbAdapterEndpoint)
 
 260                                 """<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
 
 261                         xmlns:ns="http://org.onap.so/requestsdb">
 
 264                             <ns:updateServiceOperationStatus xmlns:ns="http://org.onap.so/requestsdb">
 
 265                             <serviceId>${MsoUtils.xmlEscape(serviceId)}</serviceId>
 
 266                             <operationId>${MsoUtils.xmlEscape(operationId)}</operationId>
 
 267                             <operationType>${MsoUtils.xmlEscape(operationType)}</operationType>
 
 268                             <userId>${MsoUtils.xmlEscape(userId)}</userId>
 
 269                             <result>${MsoUtils.xmlEscape(result)}</result>
 
 270                             <operationContent>${MsoUtils.xmlEscape(operationContent)}</operationContent>
 
 271                             <progress>${MsoUtils.xmlEscape(progress)}</progress>
 
 272                             <reason>${MsoUtils.xmlEscape(reason)}</reason>
 
 273                         </ns:updateServiceOperationStatus>
 
 275                 </soapenv:Envelope>"""
 
 277                         payload = utils.formatXml(payload)
 
 278                         execution.setVariable("CVFMI_updateServiceOperStatusRequest", payload)
 
 279                         logger.error( "Outgoing updateServiceOperStatusRequest: \n" + payload)
 
 282                         logger.debug( "Exception Occured Processing prepareInitServiceOperationStatus. Exception is:\n" + e)
 
 283                         execution.setVariable("CVFMI_ErrorResponse", "Error Occurred during prepareInitServiceOperationStatus Method:\n" + e.getMessage())
 
 285                 logger.debug( "======== COMPLETED prepareInitServiceOperationStatus Process ======== ")
 
 289          * Update the service Operation Status
 
 291         public void preUpdateServiceOperationStatus(DelegateExecution execution){
 
 292                 def method = getClass().getSimpleName() + '.preUpdateServiceOperationStatus(' +'execution=' + execution.getId() +')'
 
 293                 def isDebugEnabled = execution.getVariable("isDebugLogEnabled")
 
 294                 logger.info("Entered " + method)
 
 297                         String serviceId = execution.getVariable("serviceInstanceId")
 
 298                         String operationId = execution.getVariable("operationId")
 
 299                         String operationType = execution.getVariable("operationType")
 
 300                         String serviceName = execution.getVariable("serviceInstanceName")
 
 301                         String result = execution.getVariable("operationResult")
 
 302                         String progress = execution.getVariable("progress")
 
 303                         String reason = execution.getVariable("operationReason")
 
 305                         logger.info( "progress: " + progress )
 
 307                         String operationContent = "Prepare service : " + execution.getVariable("operationStatus")
 
 309                         logger.info( "Generated new operation for Service Instance serviceId:" + serviceId + " operationId:" + operationId)
 
 310                         serviceId = UriUtils.encode(serviceId,"UTF-8")
 
 311                         execution.setVariable("serviceInstanceId", serviceId)
 
 312                         execution.setVariable("operationId", operationId)
 
 313                         execution.setVariable("operationType", operationType)
 
 315             def dbAdapterEndpoint = UrnPropertiesReader.getVariable("mso.adapters.openecomp.db.endpoint", execution)
 
 316             execution.setVariable("CVFMI_dbAdapterEndpoint", dbAdapterEndpoint)
 
 317                         logger.info( "DB Adapter Endpoint is: " + dbAdapterEndpoint)
 
 320                                 """<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
 
 321                         xmlns:ns="http://org.onap.so/requestsdb">
 
 324                             <ns:updateServiceOperationStatus xmlns:ns="http://org.onap.so/requestsdb">
 
 325                             <serviceId>${MsoUtils.xmlEscape(serviceId)}</serviceId>
 
 326                             <operationId>${MsoUtils.xmlEscape(operationId)}</operationId>
 
 327                             <operationType>${MsoUtils.xmlEscape(operationType)}</operationType>
 
 328                             <userId>${MsoUtils.xmlEscape(userId)}</userId>
 
 329                             <result>${MsoUtils.xmlEscape(result)}</result>
 
 330                             <operationContent>${MsoUtils.xmlEscape(operationContent)}</operationContent>
 
 331                             <progress>${MsoUtils.xmlEscape(progress)}</progress>
 
 332                             <reason>${MsoUtils.xmlEscape(reason)}</reason>
 
 333                         </ns:updateServiceOperationStatus>
 
 335                 </soapenv:Envelope>"""
 
 337                         payload = utils.formatXml(payload)
 
 338                         execution.setVariable("CVFMI_updateServiceOperStatusRequest", payload)
 
 339                         logger.error( "Outgoing preUpdateServiceOperationStatus: \n" + payload)
 
 343                         logger.info( "Exception Occured Processing preUpdateServiceOperationStatus. Exception is:\n" + e)
 
 344                         execution.setVariable("CVFMI_ErrorResponse", "Error Occurred during preUpdateServiceOperationStatus Method:\n" + e.getMessage())
 
 346                 logger.info( "======== COMPLETED preUpdateServiceOperationStatus Process ======== ")
 
 347                 logger.info( "Exited " + method)
 
 350         public void sendSyncResponse (DelegateExecution execution) {
 
 351                 def isDebugEnabled=execution.getVariable("isDebugLogEnabled")
 
 352                 logger.info( " *** sendSyncResponse *** ")
 
 355                         String operationId = execution.getVariable("operationId")
 
 356                         def hasResourcetoUpdate = execution.getVariable("hasResourcetoUpdate")
 
 358                         String updateServiceResp = ""
 
 359                         if(hasResourcetoUpdate) {
 
 360                                 // RESTResponse for API Handler (APIH) Reply Task
 
 361                                 updateServiceResp = """{"operationId":"${operationId}"}""".trim()
 
 364                                 updateServiceResp =  """{"OperationResult":"No Resource to Add or Delete or Service Instance not found in AAI."}"""
 
 367                         logger.info( " sendSyncResponse to APIH:" + "\n" + updateServiceResp)
 
 368                         sendWorkflowResponse(execution, 202, updateServiceResp)
 
 369                         execution.setVariable("sentSyncResponse", true)
 
 371                 } catch (Exception ex) {
 
 372                         String msg = "Exceptuion in sendSyncResponse:" + ex.getMessage()
 
 374                         exceptionUtil.buildAndThrowWorkflowException(execution, 7000, msg)
 
 376                 logger.info(" ***** Exit sendSyncResopnse *****")
 
 379         public void sendSyncError (DelegateExecution execution) {
 
 380                 def isDebugEnabled=execution.getVariable("isDebugLogEnabled")
 
 381                 logger.info( " *** sendSyncError *** ")
 
 384                         String errorMessage = ""
 
 386                         if (execution.getVariable("WorkflowException") instanceof WorkflowException) {
 
 387                                 WorkflowException wfe = execution.getVariable("WorkflowException")
 
 388                                 errorMessage = wfe.getErrorMessage()
 
 389                                 errorCode = wfe.getErrorCode()
 
 391                                 errorMessage = "Sending Sync Error."
 
 394                         String buildworkflowException =
 
 395                                         """<aetgt:WorkflowException xmlns:aetgt="http://org.onap/so/workflow/schema/v1">
 
 396                                         <aetgt:ErrorMessage>${MsoUtils.xmlEscape(errorMessage)}</aetgt:ErrorMessage>
 
 397                                         <aetgt:ErrorCode>${MsoUtils.xmlEscape(errorCode)}</aetgt:ErrorCode>
 
 398                                    </aetgt:WorkflowException>"""
 
 400                         sendWorkflowResponse(execution, 500, buildworkflowException)
 
 402                 } catch (Exception ex) {
 
 403                         logger.info( " Sending Sync Error Activity Failed. " + "\n" + ex.getMessage())
 
 408         public void prepareCompletionRequest (DelegateExecution execution) {
 
 409                 def isDebugEnabled=execution.getVariable("isDebugLogEnabled")
 
 410                 logger.info( " *** prepareCompletion *** ")
 
 413                         String requestId = execution.getVariable("msoRequestId")
 
 414                         String serviceInstanceId = execution.getVariable("serviceInstanceId")
 
 415                         String source = execution.getVariable("source")
 
 417                         String msoCompletionRequest =
 
 418                                         """<aetgt:MsoCompletionRequest xmlns:aetgt="http://org.onap/so/workflow/schema/v1"
 
 419                                                                 xmlns:ns="http://org.onap/so/request/types/v1">
 
 420                                                 <request-info xmlns="http://org.onap/so/infra/vnf-request/v1">
 
 421                                                         <request-id>${MsoUtils.xmlEscape(requestId)}</request-id>
 
 422                                                         <action>UPDATE</action>
 
 423                                                         <source>${MsoUtils.xmlEscape(source)}</source>
 
 425                                                 <status-message>Service Instance was updated successfully.</status-message>
 
 426                                                 <serviceInstanceId>${MsoUtils.xmlEscape(serviceInstanceId)}</serviceInstanceId>
 
 427                                                 <mso-bpel-name>UpdateCustomE2EServiceInstance</mso-bpel-name>
 
 428                                         </aetgt:MsoCompletionRequest>"""
 
 431                         String xmlMsoCompletionRequest = utils.formatXml(msoCompletionRequest)
 
 433                         execution.setVariable("completionRequest", xmlMsoCompletionRequest)
 
 434                         logger.info( " Overall SUCCESS Response going to CompleteMsoProcess - " + "\n" + xmlMsoCompletionRequest)
 
 436                 } catch (Exception ex) {
 
 437                         String msg = " Exception in prepareCompletion:" + ex.getMessage()
 
 439                         exceptionUtil.buildAndThrowWorkflowException(execution, 7000, msg)
 
 441                 logger.info( "*** Exit prepareCompletionRequest ***")
 
 444         public void prepareFalloutRequest(DelegateExecution execution){
 
 445                 def isDebugEnabled=execution.getVariable("isDebugLogEnabled")
 
 446                 logger.info( " *** prepareFalloutRequest *** ")
 
 449                         WorkflowException wfex = execution.getVariable("WorkflowException")
 
 450                         logger.info( " Input Workflow Exception: " + wfex.toString())
 
 451                         String requestId = execution.getVariable("msoRequestId")
 
 452                         String source = execution.getVariable("source")
 
 454                                         """<request-info xmlns="http://org.onap/so/infra/vnf-request/v1">
 
 455                                         <request-id>${MsoUtils.xmlEscape(requestId)}</request-id>
 
 456                                         <action>UPDATE</action>
 
 457                                         <source>${MsoUtils.xmlEscape(source)}</source>
 
 460                         String falloutRequest = exceptionUtil.processMainflowsBPMNException(execution, requestInfo)
 
 461                         execution.setVariable("falloutRequest", falloutRequest)
 
 462                 } catch (Exception ex) {
 
 463                         logger.info( "Exception prepareFalloutRequest:" + ex.getMessage())
 
 464                         String errorException = "  Bpmn error encountered in UpdateCustomE2EServiceInstance flow. FalloutHandlerRequest,  buildErrorResponse() - " + ex.getMessage()
 
 465                         String requestId = execution.getVariable("msoRequestId")
 
 466                         String falloutRequest =
 
 467                                         """<aetgt:FalloutHandlerRequest xmlns:aetgt="http://org.onap/so/workflow/schema/v1"
 
 468                                                                      xmlns:ns="http://org.onap/so/request/types/v1"
 
 469                                                                      xmlns:wfsch="http://org.onap/so/workflow/schema/v1">
 
 470                                            <request-info xmlns="http://org.onap/so/infra/vnf-request/v1">
 
 471                                               <request-id>${MsoUtils.xmlEscape(requestId)}</request-id>
 
 472                                               <action>UPDATE</action>
 
 475                                                 <aetgt:WorkflowException xmlns:aetgt="http://org.onap/so/workflow/schema/v1">
 
 476                                                         <aetgt:ErrorMessage>${MsoUtils.xmlEscape(errorException)}</aetgt:ErrorMessage>
 
 477                                                         <aetgt:ErrorCode>7000</aetgt:ErrorCode>
 
 478                                                 </aetgt:WorkflowException>
 
 479                                         </aetgt:FalloutHandlerRequest>"""
 
 481                         execution.setVariable("falloutRequest", falloutRequest)
 
 483                 logger.info( "*** Exit prepareFalloutRequest ***")