Cleaned up content of MsoLogger
[so.git] / bpmn / so-bpmn-infrastructure-common / src / main / groovy / org / onap / so / bpmn / infrastructure / scripts / DoCreateE2EServiceInstance.groovy
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP - SO
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6  * Copyright (C) 2017 Huawei Technologies Co., Ltd. All rights reserved.
7  * ================================================================================
8  * Modifications Copyright (c) 2019 Samsung
9  * ================================================================================
10  * Licensed under the Apache License, Version 2.0 (the "License");
11  * you may not use this file except in compliance with the License.
12  * You may obtain a copy of the License at
13  *
14  *      http://www.apache.org/licenses/LICENSE-2.0
15  *
16  * Unless required by applicable law or agreed to in writing, software
17  * distributed under the License is distributed on an "AS IS" BASIS,
18  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19  * See the License for the specific language governing permissions and
20  * limitations under the License.
21  * ============LICENSE_END=========================================================
22  */
23 package org.onap.so.bpmn.infrastructure.scripts;
24
25 import static org.apache.commons.lang3.StringUtils.*;
26
27 import javax.ws.rs.NotFoundException
28
29 import org.camunda.bpm.engine.delegate.BpmnError
30 import org.camunda.bpm.engine.delegate.DelegateExecution
31 import org.onap.aai.domain.yang.ServiceInstance
32 import org.onap.so.bpmn.common.scripts.AbstractServiceTaskProcessor
33 import org.onap.so.bpmn.common.scripts.ExceptionUtil
34 import org.onap.so.bpmn.common.scripts.MsoUtils
35 import org.onap.so.bpmn.core.RollbackData
36 import org.onap.so.bpmn.core.WorkflowException
37 import org.onap.so.bpmn.core.domain.Resource
38 import org.onap.so.bpmn.core.domain.ServiceDecomposition
39 import org.onap.so.bpmn.core.json.JsonUtils
40 import org.onap.so.client.aai.AAIObjectType
41 import org.onap.so.client.aai.AAIResourcesClient
42 import org.onap.so.client.aai.entities.AAIResultWrapper
43 import org.onap.so.client.aai.entities.uri.AAIResourceUri
44 import org.onap.so.bpmn.infrastructure.workflow.service.ServicePluginFactory
45 import org.onap.so.client.aai.entities.uri.AAIUriFactory
46 import org.onap.so.logger.MessageEnum
47 import org.onap.so.logger.MsoLogger
48 import org.slf4j.Logger
49 import org.slf4j.LoggerFactory
50
51 import org.springframework.web.util.UriUtils
52 import org.onap.so.bpmn.core.UrnPropertiesReader
53
54 /**
55  * This groovy class supports the <class>DoCreateServiceInstance.bpmn</class> process.
56  *
57  * Inputs:
58  * @param - msoRequestId
59  * @param - globalSubscriberId
60  * @param - subscriptionServiceType
61  * @param - serviceInstanceId
62  * @param - serviceInstanceName - O
63  * @param - serviceModelInfo
64  * @param - productFamilyId
65  * @param - disableRollback
66  * @param - failExists - TODO
67  * @param - serviceInputParams (should contain aic_zone for serviceTypes TRANSPORT,ATM)
68  * @param - sdncVersion ("1610")
69  * @param - serviceDecomposition - Decomposition for R1710
70  * (if macro provides serviceDecompsition then serviceModelInfo, serviceInstanceId & serviceInstanceName will be ignored)
71  *
72  * Outputs:
73  * @param - rollbackData (localRB->null)
74  * @param - rolledBack (no localRB->null, localRB F->false, localRB S->true)
75  * @param - WorkflowException
76  * @param - serviceInstanceName - (GET from AAI if null in input)
77  *
78  */
79 public class DoCreateE2EServiceInstance extends AbstractServiceTaskProcessor {
80     private static final Logger logger = LoggerFactory.getLogger( DoCreateE2EServiceInstance.class);
81
82
83         String Prefix="DCRESI_"
84         ExceptionUtil exceptionUtil = new ExceptionUtil()
85         JsonUtils jsonUtil = new JsonUtils()
86
87         public void preProcessRequest (DelegateExecution execution) {
88                 String msg = ""
89                 logger.trace("preProcessRequest ")
90
91                 try {
92                         execution.setVariable("prefix", Prefix)
93                         //Inputs
94                         //requestDetails.subscriberInfo. for AAI GET & PUT & SDNC assignToplology
95                         String globalSubscriberId = execution.getVariable("globalSubscriberId") //globalCustomerId
96                         logger.info(" ***** globalSubscriberId *****" + globalSubscriberId)
97                         //requestDetails.requestParameters. for AAI PUT & SDNC assignTopology
98                         String serviceType = execution.getVariable("serviceType")
99                         logger.info(" ***** serviceType *****" + serviceType)
100                         //requestDetails.requestParameters. for SDNC assignTopology
101                         String productFamilyId = execution.getVariable("productFamilyId") //AAI productFamilyId
102
103                         if (isBlank(globalSubscriberId)) {
104                                 msg = "Input globalSubscriberId is null"
105                                 logger.info(msg)
106                                 exceptionUtil.buildAndThrowWorkflowException(execution, 500, msg)
107                         }
108
109                         if (isBlank(serviceType)) {
110                                 msg = "Input serviceType is null"
111                                 logger.info(msg)
112                                 exceptionUtil.buildAndThrowWorkflowException(execution, 500, msg)
113                         }
114
115                         if (productFamilyId == null) {
116                                 execution.setVariable("productFamilyId", "")
117                         }
118
119                         String sdncCallbackUrl = UrnPropertiesReader.getVariable("mso.workflow.sdncadapter.callback", execution)
120                         if (isBlank(sdncCallbackUrl)) {
121                                 msg = "URN_mso_workflow_sdncadapter_callback is null"
122                                 logger.info(msg)
123                                 exceptionUtil.buildAndThrowWorkflowException(execution, 500, msg)
124                         }
125                         execution.setVariable("sdncCallbackUrl", sdncCallbackUrl)
126                         logger.info("SDNC Callback URL: " + sdncCallbackUrl)
127
128                         //requestDetails.modelInfo.for AAI PUT servieInstanceData
129                         //requestDetails.requestInfo. for AAI GET/PUT serviceInstanceData
130                         String serviceInstanceName = execution.getVariable("serviceInstanceName")
131                         String serviceInstanceId = execution.getVariable("serviceInstanceId")
132                         String uuiRequest = execution.getVariable("uuiRequest")
133                         String modelInvariantUuid = jsonUtil.getJsonValue(uuiRequest, "service.serviceInvariantUuid")
134                         String modelUuid = jsonUtil.getJsonValue(uuiRequest, "service.serviceUuid")
135                         String serviceModelName = jsonUtil.getJsonValue(uuiRequest, "service.parameters.templateName")
136                         execution.setVariable("serviceModelName", serviceModelName)
137                         //aai serviceType and Role can be setted as fixed value now.
138                         String aaiServiceType = "E2E Service"
139                         String aaiServiceRole = "E2E Service"
140
141                         execution.setVariable("modelInvariantUuid", modelInvariantUuid)
142                         execution.setVariable("modelUuid", modelUuid)
143
144                         //AAI PUT
145                         String oStatus = execution.getVariable("initialStatus") ?: ""
146                         if ("TRANSPORT".equalsIgnoreCase(serviceType))
147                         {
148                                 oStatus = "Created"
149                         }
150
151                         org.onap.aai.domain.yang.ServiceInstance si = new org.onap.aai.domain.yang.ServiceInstance()
152                         si.setServiceInstanceName(serviceInstanceName)
153                         si.setServiceType(aaiServiceType)
154                         si.setServiceRole(aaiServiceRole)
155                         si.setOrchestrationStatus(oStatus)
156                         si.setModelInvariantId(modelInvariantUuid)
157                         si.setModelVersionId(modelUuid)
158                         si.setInputParameters(uuiRequest)
159                         execution.setVariable("serviceInstanceData", si)
160
161                 } catch (BpmnError e) {
162                         throw e;
163                 } catch (Exception ex){
164                         msg = "Exception in preProcessRequest " + ex.getMessage()
165                         logger.info(msg)
166                         exceptionUtil.buildAndThrowWorkflowException(execution, 7000, msg)
167                 }
168                 logger.trace("Exit preProcessRequest ")
169         }
170
171    public void prepareDecomposeService(DelegateExecution execution) {
172         try {
173             logger.trace("Inside prepareDecomposeService of create generic e2e service ")
174             String modelInvariantUuid = execution.getVariable("modelInvariantUuid")
175             String modelUuid = execution.getVariable("modelUuid")
176             //here modelVersion is not set, we use modelUuid to decompose the service.
177             String serviceModelInfo = """{
178             "modelInvariantUuid":"${modelInvariantUuid}",
179             "modelUuid":"${modelUuid}",
180             "modelVersion":""
181              }"""
182             execution.setVariable("serviceModelInfo", serviceModelInfo)
183
184             logger.trace("Completed prepareDecomposeService of  create generic e2e service ")
185         } catch (Exception ex) {
186             // try error in method block
187             String exceptionMessage = "Bpmn error encountered in  create generic e2e service flow. Unexpected Error from method prepareDecomposeService() - " + ex.getMessage()
188             exceptionUtil.buildAndThrowWorkflowException(execution, 7000, exceptionMessage)
189         }
190      }
191
192     public void processDecomposition(DelegateExecution execution) {
193         logger.trace("Inside processDecomposition() of  create generic e2e service flow ")
194         try {
195             ServiceDecomposition serviceDecomposition = execution.getVariable("serviceDecomposition")
196         } catch (Exception ex) {
197             String exceptionMessage = "Bpmn error encountered in  create generic e2e service flow. processDecomposition() - " + ex.getMessage()
198             logger.debug(exceptionMessage)
199             exceptionUtil.buildAndThrowWorkflowException(execution, 7000, exceptionMessage)
200         }
201     }
202
203     public void doServicePreOperation(DelegateExecution execution){
204        //we need a service plugin platform here.
205         ServiceDecomposition serviceDecomposition = execution.getVariable("serviceDecomposition")
206         String uuiRequest = execution.getVariable("uuiRequest")
207         String newUuiRequest = ServicePluginFactory.getInstance().preProcessService(serviceDecomposition, uuiRequest);
208         execution.setVariable("uuiRequest", newUuiRequest)
209     }
210
211     public void doServiceHoming(DelegateExecution execution) {
212         //we need a service plugin platform here.
213         ServiceDecomposition serviceDecomposition = execution.getVariable("serviceDecomposition")
214         String uuiRequest = execution.getVariable("uuiRequest")
215         String newUuiRequest = ServicePluginFactory.getInstance().doServiceHoming(serviceDecomposition, uuiRequest);
216         execution.setVariable("uuiRequest", newUuiRequest)
217     }
218
219         public void postProcessAAIGET(DelegateExecution execution) {
220                 logger.trace("postProcessAAIGET ")
221                 String msg = ""
222
223                 try {
224                         String serviceInstanceName = execution.getVariable("serviceInstanceName")
225                         boolean succInAAI = execution.getVariable("GENGS_SuccessIndicator")
226                         if(!succInAAI){
227                                 logger.info("Error getting Service-instance from AAI", + serviceInstanceName)
228                                 WorkflowException workflowException = execution.getVariable("WorkflowException")
229                                 logger.debug("workflowException: " + workflowException)
230                                 if(workflowException != null){
231                                         exceptionUtil.buildAndThrowWorkflowException(execution, workflowException.getErrorCode(), workflowException.getErrorMessage())
232                                 }
233                                 else
234                                 {
235                                         msg = "Failure in postProcessAAIGET GENGS_SuccessIndicator:" + succInAAI
236                                         logger.info(msg)
237                                         exceptionUtil.buildAndThrowWorkflowException(execution, 2500, msg)
238                                 }
239                         }
240                         else
241                         {
242                                 boolean foundInAAI = execution.getVariable("GENGS_FoundIndicator")
243                                 if(foundInAAI){
244                                         logger.info("Found Service-instance in AAI")
245                                         msg = "ServiceInstance already exists in AAI:" + serviceInstanceName
246                                         logger.info(msg)
247                                         exceptionUtil.buildAndThrowWorkflowException(execution, 2500, msg)
248                                 }
249                         }
250                 } catch (BpmnError e) {
251                         throw e;
252                 } catch (Exception ex) {
253                         msg = "Exception in DoCreateServiceInstance.postProcessAAIGET. " + ex.getMessage()
254                         logger.info(msg)
255                         exceptionUtil.buildAndThrowWorkflowException(execution, 7000, msg)
256                 }
257                 logger.trace("Exit postProcessAAIGET ")
258         }
259
260         //TODO use create if not exist
261         public void createServiceInstance(DelegateExecution execution) {
262                 logger.trace("createServiceInstance ")
263                 String msg = ""
264                 String serviceInstanceId = execution.getVariable("serviceInstanceId")
265                 try {
266                         org.onap.aai.domain.yang.ServiceInstance si = execution.getVariable("serviceInstanceData")
267
268                         AAIResourcesClient client = new AAIResourcesClient()
269                         AAIResourceUri uri = AAIUriFactory.createResourceUri(AAIObjectType.SERVICE_INSTANCE, execution.getVariable("globalSubscriberId"), execution.getVariable("serviceType"), serviceInstanceId)
270                         client.create(uri, si)
271
272                 } catch (BpmnError e) {
273                         throw e;
274                 } catch (Exception ex) {
275                         //start rollback set up
276                         RollbackData rollbackData = new RollbackData()
277                         def disableRollback = execution.getVariable("disableRollback")
278                         rollbackData.put("SERVICEINSTANCE", "disableRollback", disableRollback.toString())
279                         rollbackData.put("SERVICEINSTANCE", "rollbackAAI", "true")
280                         rollbackData.put("SERVICEINSTANCE", "serviceInstanceId", serviceInstanceId)
281                         rollbackData.put("SERVICEINSTANCE", "subscriptionServiceType", execution.getVariable("subscriptionServiceType"))
282                         rollbackData.put("SERVICEINSTANCE", "globalSubscriberId", execution.getVariable("globalSubscriberId"))
283                         execution.setVariable("rollbackData", rollbackData)
284
285                         msg = "Exception in DoCreateServiceInstance.createServiceInstance. " + ex.getMessage()
286                         logger.info(msg)
287                         exceptionUtil.buildAndThrowWorkflowException(execution, 7000, msg)
288                 }
289                 logger.trace("Exit createServiceInstance ")
290         }
291
292         /**
293          * Gets the service instance and its relationships from aai
294          */
295         public void getServiceInstance(DelegateExecution execution) {
296                 try {
297                         String serviceInstanceId = execution.getVariable('serviceInstanceId')
298                         String globalSubscriberId = execution.getVariable('globalSubscriberId')
299                         String serviceType = execution.getVariable('subscriptionServiceType')
300
301                         AAIResourcesClient resourceClient = new AAIResourcesClient()
302                         AAIResourceUri serviceInstanceUri = AAIUriFactory.createResourceUri(AAIObjectType.SERVICE_INSTANCE, globalSubscriberId, serviceType, serviceInstanceId)
303                         AAIResultWrapper wrapper = resourceClient.get(serviceInstanceUri, NotFoundException.class)
304
305                         Optional<ServiceInstance> si = wrapper.asBean(ServiceInstance.class)
306                         execution.setVariable("serviceInstanceName", si.get().getServiceInstanceName())
307
308                 }catch(BpmnError e) {
309                         throw e;
310                 }catch(Exception ex) {
311                         String msg = "Internal Error in getServiceInstance: " + ex.getMessage()
312                         exceptionUtil.buildAndThrowWorkflowException(execution, 7000, msg)
313                 }
314         }
315
316         public void postProcessAAIGET2(DelegateExecution execution) {
317                 logger.trace("postProcessAAIGET2 ")
318                 String msg = ""
319
320                 try {
321                         String serviceInstanceName = execution.getVariable("serviceInstanceName")
322                         boolean succInAAI = execution.getVariable("GENGS_SuccessIndicator")
323                         if(!succInAAI){
324                                 logger.info("Error getting Service-instance from AAI in postProcessAAIGET2", + serviceInstanceName)
325                                 WorkflowException workflowException = execution.getVariable("WorkflowException")
326                                 logger.debug("workflowException: " + workflowException)
327                                 if(workflowException != null){
328                                         exceptionUtil.buildAndThrowWorkflowException(execution, workflowException.getErrorCode(), workflowException.getErrorMessage())
329                                 }
330                                 else
331                                 {
332                                         msg = "Failure in postProcessAAIGET2 GENGS_SuccessIndicator:" + succInAAI
333                                         logger.info(msg)
334                                         exceptionUtil.buildAndThrowWorkflowException(execution, 2500, msg)
335                                 }
336                         }
337                         else
338                         {
339                                 boolean foundInAAI = execution.getVariable("GENGS_FoundIndicator")
340                                 if(foundInAAI){
341                                         String aaiService = execution.getVariable("GENGS_service")
342                                         if (!isBlank(aaiService) && (utils.nodeExists(aaiService, "service-instance-name"))) {
343                                                 execution.setVariable("serviceInstanceName",  utils.getNodeText(aaiService, "service-instance-name"))
344                                                 logger.info("Found Service-instance in AAI.serviceInstanceName:" + execution.getVariable("serviceInstanceName"))
345                                         }
346                                 }
347                         }
348                 } catch (BpmnError e) {
349                         throw e;
350                 } catch (Exception ex) {
351                         msg = "Exception in DoCreateServiceInstance.postProcessAAIGET2 " + ex.getMessage()
352                         logger.info(msg)
353                         exceptionUtil.buildAndThrowWorkflowException(execution, 7000, msg)
354                 }
355                 logger.trace("Exit postProcessAAIGET2 ")
356         }
357
358         public void preProcessRollback (DelegateExecution execution) {
359                 logger.trace("preProcessRollback ")
360                 try {
361
362                         Object workflowException = execution.getVariable("WorkflowException");
363
364                         if (workflowException instanceof WorkflowException) {
365                                 logger.info("Prev workflowException: " + workflowException.getErrorMessage())
366                                 execution.setVariable("prevWorkflowException", workflowException);
367                                 //execution.setVariable("WorkflowException", null);
368                         }
369                 } catch (BpmnError e) {
370                         logger.info("BPMN Error during preProcessRollback")
371                 } catch(Exception ex) {
372                         String msg = "Exception in preProcessRollback. " + ex.getMessage()
373                         logger.info(msg)
374                 }
375                 logger.trace("Exit preProcessRollback ")
376         }
377
378         public void postProcessRollback (DelegateExecution execution) {
379                 logger.trace("postProcessRollback ")
380                 String msg = ""
381                 try {
382                         Object workflowException = execution.getVariable("prevWorkflowException");
383                         if (workflowException instanceof WorkflowException) {
384                                 logger.info("Setting prevException to WorkflowException: ")
385                                 execution.setVariable("WorkflowException", workflowException);
386                         }
387                         execution.setVariable("rollbackData", null)
388                 } catch (BpmnError b) {
389                         logger.info("BPMN Error during postProcessRollback")
390                         throw b;
391                 } catch(Exception ex) {
392                         msg = "Exception in postProcessRollback. " + ex.getMessage()
393                         logger.info(msg)
394                 }
395                 logger.trace("Exit postProcessRollback ")
396         }
397
398         public void preInitResourcesOperStatus(DelegateExecution execution){
399         logger.trace("STARTED preInitResourcesOperStatus Process ")
400         try{
401             String serviceId = execution.getVariable("serviceInstanceId")
402             String operationId = execution.getVariable("operationId")
403             String operationType = execution.getVariable("operationType")
404             String resourceTemplateUUIDs = ""
405             String result = "processing"
406             String progress = "0"
407             String reason = ""
408             String operationContent = "Prepare service creation"
409             logger.info("Generated new operation for Service Instance serviceId:" + serviceId + " operationId:" + operationId + " operationType:" + operationType)
410             serviceId = UriUtils.encode(serviceId,"UTF-8")
411             execution.setVariable("serviceInstanceId", serviceId)
412             execution.setVariable("operationId", operationId)
413             execution.setVariable("operationType", operationType)
414             ServiceDecomposition serviceDecomposition = execution.getVariable("serviceDecomposition")
415             List<Resource>  resourceList = serviceDecomposition.getServiceResources()
416
417             for(Resource resource : resourceList){
418                     resourceTemplateUUIDs  = resourceTemplateUUIDs + resource.getModelInfo().getModelCustomizationUuid() + ":"
419             }
420
421             def dbAdapterEndpoint = UrnPropertiesReader.getVariable("mso.adapters.db.endpoint")
422             execution.setVariable("CVFMI_dbAdapterEndpoint", dbAdapterEndpoint)
423             logger.info("DB Adapter Endpoint is: " + dbAdapterEndpoint)
424
425             String payload =
426                 """<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
427                         xmlns:ns="http://org.onap.so/requestsdb">
428                         <soapenv:Header/>
429                         <soapenv:Body>
430                             <ns:initResourceOperationStatus xmlns:ns="http://org.onap.so/requestsdb">
431                                                                 <serviceId>${MsoUtils.xmlEscape(serviceId)}</serviceId>
432                                                                 <operationId>${MsoUtils.xmlEscape(operationId)}</operationId>
433                                                                 <operationType>${MsoUtils.xmlEscape(operationType)}</operationType>
434                                                                 <resourceTemplateUUIDs>${MsoUtils.xmlEscape(resourceTemplateUUIDs)}</resourceTemplateUUIDs>
435                             </ns:initResourceOperationStatus>
436                         </soapenv:Body>
437                         </soapenv:Envelope>"""
438
439             payload = utils.formatXml(payload)
440             execution.setVariable("CVFMI_initResOperStatusRequest", payload)
441             logger.info("Outgoing initResourceOperationStatus: \n" + payload)
442             logger.debug("CreateVfModuleInfra Outgoing initResourceOperationStatus Request: " + payload)
443
444         }catch(Exception e){
445             logger.error("{} {} {} {} {}", MessageEnum.BPMN_GENERAL_EXCEPTION_ARG.toString(),
446                                         "Exception Occured Processing preInitResourcesOperStatus.", "BPMN",
447                                         MsoLogger.ErrorCode.UnknownError.getValue(), e);
448             execution.setVariable("CVFMI_ErrorResponse", "Error Occurred during preInitResourcesOperStatus Method:\n" + e.getMessage())
449         }
450         logger.trace("COMPLETED preInitResourcesOperStatus Process ")
451         }
452
453         // if site location is in local Operator, create all resources in local ONAP;
454         // if site location is in 3rd Operator, only process sp-partner to create all resources in 3rd ONAP
455         public void doProcessSiteLocation(DelegateExecution execution){
456                 logger.trace("======== Start doProcessSiteLocation Process ======== ")
457                 ServiceDecomposition serviceDecomposition = execution.getVariable("serviceDecomposition")
458                 String uuiRequest = execution.getVariable("uuiRequest")
459                 uuiRequest = ServicePluginFactory.getInstance().doProcessSiteLocation(serviceDecomposition, uuiRequest);
460                 execution.setVariable("uuiRequest", uuiRequest)
461                 execution.setVariable("serviceDecomposition", serviceDecomposition)
462
463                 logger.trace("======== COMPLETED doProcessSiteLocation Process ======== ")
464         }
465
466         // Allocate cross link TPs(terminal points) for sotn network only
467         public void doTPResourcesAllocation(DelegateExecution execution){
468                 logger.trace("======== Start doTPResourcesAllocation Process ======== ")
469                 ServiceDecomposition serviceDecomposition = execution.getVariable("serviceDecomposition")
470                 String uuiRequest = execution.getVariable("uuiRequest")
471                 uuiRequest = ServicePluginFactory.getInstance().doTPResourcesAllocation(execution, uuiRequest);
472                 execution.setVariable("uuiRequest", uuiRequest)
473                 logger.trace("======== COMPLETED doTPResourcesAllocation Process ======== ")
474         }
475
476         // prepare input param for using DoCreateResources.bpmn
477         public void preProcessForAddResource(DelegateExecution execution) {
478                 logger.trace("STARTED preProcessForAddResource Process ")
479
480                 ServiceDecomposition serviceDecomposition = execution.getVariable("serviceDecomposition")
481                 List<Resource> addResourceList = serviceDecomposition.getServiceResources()
482                 execution.setVariable("addResourceList", addResourceList)
483
484                 logger.trace("COMPLETED preProcessForAddResource Process ")
485         }
486
487         public void postProcessForAddResource(DelegateExecution execution) {
488                 // do nothing now
489
490         }
491
492 }