8aff0f715de6cf1193e8144bf67b9bebfeac064c
[so.git] / bpmn / MSOCommonBPMN / src / main / groovy / org / onap / so / bpmn / common / scripts / VnfAdapterRestV1.groovy
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP - SO
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END=========================================================
19  */
20
21 package org.onap.so.bpmn.common.scripts
22
23 import org.onap.so.client.HttpClientFactory
24
25 import javax.ws.rs.core.Response
26 import org.apache.commons.lang3.*
27 import org.camunda.bpm.engine.delegate.BpmnError
28 import org.camunda.bpm.engine.delegate.DelegateExecution
29 import org.onap.so.bpmn.core.UrnPropertiesReader
30 import org.onap.so.client.HttpClient
31 import org.onap.so.logger.MessageEnum
32 import org.onap.so.logger.MsoLogger
33 import org.onap.so.utils.TargetEntity
34 import java.util.UUID
35
36
37
38
39 class VnfAdapterRestV1 extends AbstractServiceTaskProcessor {
40         private static final MsoLogger msoLogger = MsoLogger.getMsoLogger(MsoLogger.Catalog.BPEL, VnfAdapterRestV1.class);
41
42
43         ExceptionUtil exceptionUtil = new ExceptionUtil()
44
45         // VNF Response Processing
46         public void preProcessRequest (DelegateExecution execution) {
47                 def method = getClass().getSimpleName() + '.preProcessRequest(' +
48                         'execution=' + execution.getId() +
49                         ')'
50                 msoLogger.trace('Entered ' + method)
51
52                 def prefix="VNFREST_"
53                 execution.setVariable("prefix", prefix)
54                 setSuccessIndicator(execution, false)
55
56                 try {
57                         String request = validateRequest(execution, "mso-request-id")
58
59                         // Get the request type (the name of the root element) from the request
60
61                         Node root = new XmlParser().parseText(request)
62                         String requestType = root.name()
63                         execution.setVariable(prefix + 'requestType', requestType)
64                         msoLogger.debug(getProcessKey(execution) + ': ' + prefix + 'requestType = ' + requestType)
65
66                         msoLogger.debug('VnfAdapterRestV1, request: ' + request)
67                         // Get the messageId from the request
68
69                         String messageId = getChildText(root, 'messageId')
70
71                         if ('rollbackVolumeGroupRequest'.equals(requestType)) {
72                                 messageId = getMessageIdForVolumeGroupRollback(root)
73                         }
74
75                         if (messageId == null || messageId.isEmpty()) {
76                                 String msg = getProcessKey(execution) + ': no messageId in ' + requestType
77                                 msoLogger.error(MessageEnum.BPMN_GENERAL_EXCEPTION_ARG, msg, "BPMN", MsoLogger.getServiceName(), MsoLogger.ErrorCode.UnknownError, "");
78                                 exceptionUtil.buildAndThrowWorkflowException(execution, 2000, msg)
79                         }
80
81                         execution.setVariable('VNFAResponse_CORRELATOR', messageId)
82                         msoLogger.debug(getProcessKey(execution) + ': VNFAResponse_CORRELATOR = ' + messageId)
83
84                         // Get the notificationUrl from the request
85
86                         String notificationUrl = getChildText(root, 'notificationUrl')
87
88                         if (notificationUrl == null || notificationUrl.isEmpty()) {
89                                 String msg = getProcessKey(execution) + ': no notificationUrl in ' + requestType
90                                 msoLogger.error(MessageEnum.BPMN_GENERAL_EXCEPTION_ARG, msg, "BPMN", MsoLogger.getServiceName(), MsoLogger.ErrorCode.UnknownError, "");
91                                 exceptionUtil.buildAndThrowWorkflowException(execution, 2000, msg)
92                         }
93
94                         execution.setVariable(prefix + 'notificationUrl', notificationUrl)
95                         msoLogger.debug(getProcessKey(execution) + ': ' + prefix + 'notificationUrl = ' + notificationUrl)
96
97                         // Determine the VnfAdapter endpoint
98
99                         String vnfAdapterEndpoint = UrnPropertiesReader.getVariable("mso.adapters.vnf.rest.endpoint", execution)
100
101                         if (vnfAdapterEndpoint == null || vnfAdapterEndpoint.isEmpty()) {
102                                 String msg = getProcessKey(execution) + ': mso:adapters:vnf:rest:endpoint URN mapping is not defined'
103                                 msoLogger.error(MessageEnum.BPMN_GENERAL_EXCEPTION_ARG, msg, "BPMN", MsoLogger.getServiceName(), MsoLogger.ErrorCode.UnknownError, "");
104                                 exceptionUtil.buildAndThrowWorkflowException(execution, 2000, msg)
105                         }
106
107                         while (vnfAdapterEndpoint.endsWith('/')) {
108                                 vnfAdapterEndpoint = vnfAdapterEndpoint.substring(0, vnfAdapterEndpoint.length()-1)
109                         }
110
111                         String vnfAdapterMethod = null
112                         String vnfAdapterUrl = null
113                         String vnfAdapterRequest = request
114
115                         if ('createVfModuleRequest'.equals(requestType)) {
116                                 String vnfId = getChildText(root, 'vnfId')
117
118                                 if (vnfId == null || vnfId.isEmpty()) {
119                                         String msg = getProcessKey(execution) + ': no vnfId in ' + requestType
120                                         msoLogger.error(MessageEnum.BPMN_GENERAL_EXCEPTION_ARG, msg, "BPMN", MsoLogger.getServiceName(), MsoLogger.ErrorCode.UnknownError, "");
121                                         exceptionUtil.buildAndThrowWorkflowException(execution, 2000, msg)
122                                 }
123
124                                 vnfAdapterMethod = 'POST'
125                                 vnfAdapterUrl = vnfAdapterEndpoint + '/' + URLEncoder.encode(vnfId, 'UTF-8') + '/vf-modules'
126
127                         } else if ('updateVfModuleRequest'.equals(requestType)) {
128                                 String vnfId = getChildText(root, 'vnfId')
129
130                                 if (vnfId == null || vnfId.isEmpty()) {
131                                         String msg = getProcessKey(execution) + ': no vnfId in ' + requestType
132                                         msoLogger.error(MessageEnum.BPMN_GENERAL_EXCEPTION_ARG, msg, "BPMN", MsoLogger.getServiceName(), MsoLogger.ErrorCode.UnknownError, "");
133                                         exceptionUtil.buildAndThrowWorkflowException(execution, 2000, msg)
134                                 }
135
136                                 String vfModuleId = getChildText(root, 'vfModuleId')
137
138                                 if (vfModuleId == null || vfModuleId.isEmpty()) {
139                                         String msg = getProcessKey(execution) + ': no vfModuleId in ' + requestType
140                                         msoLogger.error(MessageEnum.BPMN_GENERAL_EXCEPTION_ARG, msg, "BPMN", MsoLogger.getServiceName(), MsoLogger.ErrorCode.UnknownError, "");
141                                         exceptionUtil.buildAndThrowWorkflowException(execution, 2000, msg)
142                                 }
143
144                                 vnfAdapterMethod = 'PUT'
145                                 vnfAdapterUrl = vnfAdapterEndpoint + '/' + URLEncoder.encode(vnfId, 'UTF-8') +
146                                         '/vf-modules/' + URLEncoder.encode(vfModuleId, 'UTF-8')
147
148                         } else if ('deleteVfModuleRequest'.equals(requestType)) {
149                                 String vnfId = getChildText(root, 'vnfId')
150
151                                 if (vnfId == null || vnfId.isEmpty()) {
152                                         String msg = getProcessKey(execution) + ': no vnfId in ' + requestType
153                                         msoLogger.error(MessageEnum.BPMN_GENERAL_EXCEPTION_ARG, msg, "BPMN", MsoLogger.getServiceName(), MsoLogger.ErrorCode.UnknownError, "");
154                                         exceptionUtil.buildAndThrowWorkflowException(execution, 2000, msg)
155                                 }
156
157                                 String vfModuleId = getChildText(root, 'vfModuleId')
158
159                                 if (vfModuleId == null || vfModuleId.isEmpty()) {
160                                         String msg = getProcessKey(execution) + ': no vfModuleId in ' + requestType
161                                         msoLogger.error(MessageEnum.BPMN_GENERAL_EXCEPTION_ARG, msg, "BPMN", MsoLogger.getServiceName(), MsoLogger.ErrorCode.UnknownError, "");
162                                         exceptionUtil.buildAndThrowWorkflowException(execution, 2000, msg)
163                                 }
164
165                                 vnfAdapterMethod = 'DELETE'
166                                 vnfAdapterUrl = vnfAdapterEndpoint + '/' + URLEncoder.encode(vnfId, 'UTF-8') +
167                                         '/vf-modules/' + URLEncoder.encode(vfModuleId, 'UTF-8')
168
169                         } else if ('rollbackVfModuleRequest'.equals(requestType)) {
170                                 Node vfModuleRollbackNode = getChild(root, 'vfModuleRollback')
171
172                                 if (vfModuleRollbackNode == null) {
173                                         String msg = getProcessKey(execution) + ': no vfModuleRollback in ' + requestType
174                                         msoLogger.error(MessageEnum.BPMN_GENERAL_EXCEPTION_ARG, msg, "BPMN", MsoLogger.getServiceName(), MsoLogger.ErrorCode.UnknownError, "");
175                                         exceptionUtil.buildAndThrowWorkflowException(execution, 2000, msg)
176                                 }
177
178                                 String vnfId = getChildText(vfModuleRollbackNode, 'vnfId')
179
180                                 if (vnfId == null || vnfId.isEmpty()) {
181                                         String msg = getProcessKey(execution) + ': no vnfId in ' + requestType
182                                         msoLogger.error(MessageEnum.BPMN_GENERAL_EXCEPTION_ARG, msg, "BPMN", MsoLogger.getServiceName(), MsoLogger.ErrorCode.UnknownError, "");
183                                         exceptionUtil.buildAndThrowWorkflowException(execution, 2000, msg)
184                                 }
185
186                                 String vfModuleId = getChildText(vfModuleRollbackNode, 'vfModuleId')
187
188                                 if (vfModuleId == null || vfModuleId.isEmpty()) {
189                                         String msg = getProcessKey(execution) + ': no vfModuleId in ' + requestType
190                                         msoLogger.error(MessageEnum.BPMN_GENERAL_EXCEPTION_ARG, msg, "BPMN", MsoLogger.getServiceName(), MsoLogger.ErrorCode.UnknownError, "");
191                                         exceptionUtil.buildAndThrowWorkflowException(execution, 2000, msg)
192                                 }
193
194                                 vnfAdapterMethod = 'DELETE'
195                                 vnfAdapterUrl = vnfAdapterEndpoint + '/' + URLEncoder.encode(vnfId, 'UTF-8') +
196                                         '/vf-modules/' + URLEncoder.encode(vfModuleId, 'UTF-8') + '/rollback'
197
198                         } else if ('createVolumeGroupRequest'.equals(requestType)) {
199                                 vnfAdapterMethod = 'POST'
200                                 if (vnfAdapterEndpoint.endsWith('v1/vnfs')) {
201                                         vnfAdapterEndpoint = vnfAdapterEndpoint.substring(0, (vnfAdapterEndpoint.length()-'/vnfs'.length()))
202                                 }
203                                 vnfAdapterUrl = vnfAdapterEndpoint + '/volume-groups'
204
205                         } else if ('updateVolumeGroupRequest'.equals(requestType)) {
206                                 String volumeGroupId = getChildText(root, 'volumeGroupId')
207
208                                 if (volumeGroupId == null || volumeGroupId.isEmpty()) {
209                                         String msg = getProcessKey(execution) + ': no volumeGroupId in ' + requestType
210                                         msoLogger.error(MessageEnum.BPMN_GENERAL_EXCEPTION_ARG, msg, "BPMN", MsoLogger.getServiceName(), MsoLogger.ErrorCode.UnknownError, "");
211                                         exceptionUtil.buildAndThrowWorkflowException(execution, 2000, msg)
212                                 }
213
214                                 vnfAdapterMethod = 'PUT'
215                                 if (vnfAdapterEndpoint.endsWith('v1/vnfs')) {
216                                         vnfAdapterEndpoint = vnfAdapterEndpoint.substring(0, (vnfAdapterEndpoint.length()-'/vnfs'.length()))
217                                 }
218                                 vnfAdapterUrl = vnfAdapterEndpoint + '/volume-groups/' + URLEncoder.encode(volumeGroupId, 'UTF-8')
219
220                         } else if ('deleteVolumeGroupRequest'.equals(requestType)) {
221                                 String volumeGroupId = getChildText(root, 'volumeGroupId')
222
223                                 if (volumeGroupId == null || volumeGroupId.isEmpty()) {
224                                         String msg = getProcessKey(execution) + ': no volumeGroupId in ' + requestType
225                                         msoLogger.error(MessageEnum.BPMN_GENERAL_EXCEPTION_ARG, msg, "BPMN", MsoLogger.getServiceName(), MsoLogger.ErrorCode.UnknownError, "");
226                                         exceptionUtil.buildAndThrowWorkflowException(execution, 2000, msg)
227                                 }
228
229                                 vnfAdapterMethod = 'DELETE'
230                                 if (vnfAdapterEndpoint.endsWith('v1/vnfs')) {
231                                         vnfAdapterEndpoint = vnfAdapterEndpoint.substring(0, (vnfAdapterEndpoint.length()-'/vnfs'.length()))
232                                 }
233                                 vnfAdapterUrl = vnfAdapterEndpoint + '/volume-groups/' + URLEncoder.encode(volumeGroupId, 'UTF-8')
234
235                         } else if ('rollbackVolumeGroupRequest'.equals(requestType)) {
236                                 String volumeGroupId = getVolumeGroupIdFromRollbackRequest(root)
237
238                                 if (volumeGroupId == null || volumeGroupId.isEmpty()) {
239                                         String msg = getProcessKey(execution) + ': no volumeGroupId in ' + requestType
240                                         msoLogger.error(MessageEnum.BPMN_GENERAL_EXCEPTION_ARG, msg, "BPMN", MsoLogger.getServiceName(), MsoLogger.ErrorCode.UnknownError, "");
241                                         exceptionUtil.buildAndThrowWorkflowException(execution, 2000, msg)
242                                 }
243
244                                 vnfAdapterMethod = 'DELETE'
245                                 if (vnfAdapterEndpoint.endsWith('v1/vnfs')) {
246                                         vnfAdapterEndpoint = vnfAdapterEndpoint.substring(0, (vnfAdapterEndpoint.length()-'/vnfs'.length()))
247                                 }
248                                 vnfAdapterUrl = vnfAdapterEndpoint + '/volume-groups/' + URLEncoder.encode(volumeGroupId, 'UTF-8')  + '/rollback'
249
250                         } else {
251                                 String msg = getProcessKey(execution) + ': Unsupported request type: ' + requestType
252                                 msoLogger.error(MessageEnum.BPMN_GENERAL_EXCEPTION_ARG, msg, "BPMN", MsoLogger.getServiceName(), MsoLogger.ErrorCode.UnknownError, "");
253                                 exceptionUtil.buildAndThrowWorkflowException(execution, 2000, msg)
254                         }
255
256                         execution.setVariable(prefix + 'vnfAdapterMethod', vnfAdapterMethod)
257                         msoLogger.debug(getProcessKey(execution) + ': ' + prefix + 'vnfAdapterMethod = ' + vnfAdapterMethod)
258                         execution.setVariable(prefix + 'vnfAdapterUrl', vnfAdapterUrl)
259                         msoLogger.debug(getProcessKey(execution) + ': ' + prefix + 'vnfAdapterUrl = ' + vnfAdapterUrl)
260                         execution.setVariable(prefix + 'vnfAdapterRequest', vnfAdapterRequest)
261                         msoLogger.debug(getProcessKey(execution) + ': ' + prefix + 'vnfAdapterRequest = \n' + vnfAdapterRequest)
262
263                         // Get the Basic Auth credentials for the VnfAdapter
264
265                         String basicAuthValue = UrnPropertiesReader.getVariable("mso.adapters.po.auth", execution)
266
267                         if (basicAuthValue == null || basicAuthValue.isEmpty()) {
268                                 msoLogger.error(MessageEnum.BPMN_GENERAL_EXCEPTION_ARG, getProcessKey(execution) + ": mso:adapters:po:auth URN mapping is not defined", "BPMN", MsoLogger.getServiceName(), MsoLogger.ErrorCode.UnknownError, "");
269                         } else {
270                                 try {
271                                         def encodedString = utils.getBasicAuth(basicAuthValue, UrnPropertiesReader.getVariable("mso.msoKey", execution))
272                                         execution.setVariable(prefix + 'basicAuthHeaderValue', encodedString)
273                                 } catch (IOException ex) {
274                                         msoLogger.error(MessageEnum.BPMN_GENERAL_EXCEPTION_ARG, getProcessKey(execution) + ": Unable to encode BasicAuth credentials for VnfAdapter", "BPMN", MsoLogger.getServiceName(), MsoLogger.ErrorCode.UnknownError, "");
275                                 }
276                         }
277
278                 } catch (BpmnError e) {
279                         msoLogger.debug(" Rethrowing MSOWorkflowException")
280                         throw e
281                 } catch (Exception e) {
282                         String msg = 'Caught exception in ' + method + ": " + e
283                         msoLogger.error(MessageEnum.BPMN_GENERAL_EXCEPTION_ARG, msg, "BPMN", MsoLogger.getServiceName(), MsoLogger.ErrorCode.UnknownError, "");
284                         msoLogger.debug(msg)
285                         exceptionUtil.buildAndThrowWorkflowException(execution, 2000, msg)
286                 }
287         }
288
289         public String getVolumeGroupIdFromRollbackRequest(Node root) {
290                 return root.'volumeGroupRollback'.'volumeGroupId'.text()
291         }
292
293         public String getMessageIdForVolumeGroupRollback(Node root) {
294                 return root.'volumeGroupRollback'.'messageId'.text()
295         }
296
297         /**
298          * This method is used instead of an HTTP Connector task because the
299          * connector does not allow DELETE with a body.
300          */
301         public void sendRequestToVnfAdapter(DelegateExecution execution) {
302                 def method = getClass().getSimpleName() + '.sendRequestToVnfAdapter(' +
303                         'execution=' + execution.getId() +
304                         ')'
305                 msoLogger.trace('Entered ' + method)
306
307                 String prefix = execution.getVariable('prefix')
308
309                 try {
310                         String vnfAdapterMethod = execution.getVariable(prefix + 'vnfAdapterMethod')
311                         String vnfAdapterUrl = execution.getVariable(prefix + 'vnfAdapterUrl')
312                         String vnfAdapterRequest = execution.getVariable(prefix + 'vnfAdapterRequest')
313
314                         URL url = new URL(vnfAdapterUrl);
315
316                         HttpClient httpClient = new HttpClientFactory().newXmlClient(url, TargetEntity.VNF_ADAPTER)
317                         httpClient.addAdditionalHeader("Authorization", execution.getVariable(prefix + "basicAuthHeaderValue"))
318                         
319                         httpClient.addAdditionalHeader("X-ONAP-RequestID", execution.getVariable("mso-request-id"))
320                         httpClient.addAdditionalHeader("X-ONAP-InvocationID", UUID.randomUUID().toString())
321                         httpClient.addAdditionalHeader("X-ONAP-PartnerName", "SO-VNFAdapter")
322                         Response response;
323
324                         if ("GET".equals(vnfAdapterMethod)) {
325                                 response = httpClient.get()
326                         } else if ("PUT".equals(vnfAdapterMethod)) {
327                                 response = httpClient.put(vnfAdapterRequest)
328                         } else if ("POST".equals(vnfAdapterMethod)) {
329                                 response = httpClient.post(vnfAdapterRequest)
330                         } else if ("DELETE".equals(vnfAdapterMethod)) {
331                                 response = httpClient.delete(vnfAdapterRequest)
332                         } else {
333                                 String msg = 'Unsupported HTTP method "' + vnfAdapterMethod + '" in ' + method + ": " + e
334                                 msoLogger.error(MessageEnum.BPMN_GENERAL_EXCEPTION_ARG, msg, "BPMN", MsoLogger.getServiceName(), MsoLogger.ErrorCode.UnknownError, "");
335                                 exceptionUtil.buildAndThrowWorkflowException(execution, 2000, msg)
336                         }
337
338                         execution.setVariable(prefix + "vnfAdapterStatusCode", response.getStatus())
339                         if(response.hasEntity()){
340                                 execution.setVariable(prefix + "vnfAdapterResponse", response.readEntity(String.class))
341                         }
342                 } catch (BpmnError e) {
343                         throw e
344                 } catch (Exception e) {
345                         String msg = 'Caught exception in ' + method + ": " + e
346                         msoLogger.error(MessageEnum.BPMN_GENERAL_EXCEPTION_ARG, msg, "BPMN", MsoLogger.getServiceName(), MsoLogger.ErrorCode.UnknownError, "");
347                         exceptionUtil.buildAndThrowWorkflowException(execution, 2000, msg)
348                 }
349         }
350
351         public void processCallback(DelegateExecution execution){
352                 def method = getClass().getSimpleName() + '.processCallback(' +
353                         'execution=' + execution.getId() +
354                         ')'
355                 msoLogger.trace('Entered ' + method)
356
357                 String callback = execution.getVariable('VNFAResponse_MESSAGE')
358
359                 try {
360                         msoLogger.debug(getProcessKey(execution) + ": received callback:\n" + callback)
361
362                         // The XML callback is available to the calling flow in any case,
363                         // even if a WorkflowException is generated.
364                         execution.setVariable(getProcessKey(execution) + 'Response', callback)
365                         // TODO: Should deprecate use of processKey+Response variable for the response. Will use "WorkflowResponse" instead.
366                         execution.setVariable("WorkflowResponse", callback)
367
368                         Node root = new XmlParser().parseText(callback)
369                         if (root.name().endsWith('Exception')) {
370                                 vnfAdapterWorkflowException(execution, callback)
371                         }
372                 } catch (Exception e) {
373                         e.printStackTrace()
374                         callback = callback == null || String.valueOf(callback).isEmpty() ? "NONE" : callback
375                         String msg = "Received error from VnfAdapter: " + callback
376                         msoLogger.debug(getProcessKey(execution) + ': ' + msg)
377                         exceptionUtil.buildWorkflowException(execution, 7020, msg)
378                 }
379         }
380
381         /**
382          * Tries to parse the response as XML to extract the information to create
383          * a WorkflowException.  If the response cannot be parsed, a more generic
384          * WorkflowException is created.
385          */
386         public void vnfAdapterWorkflowException(DelegateExecution execution, Object response) {
387                 try {
388                         Node root = new XmlParser().parseText(response)
389                         String category = getChildText(root, "category")
390                         category = category == null || category.isEmpty() ? "" : " category='" + category + "'"
391                         String message = getChildText(root, "message")
392                         message = message == null || message.isEmpty() ? "" : " message='" + message + "'"
393                         String rolledBack = getChildText(root, "rolledBack")
394                         rolledBack = rolledBack == null || rolledBack.isEmpty() ? "" : " rolledBack='" + rolledBack + "'"
395                         exceptionUtil.buildWorkflowException(execution, 7020, "Received " + root.name() +
396                                 " from VnfAdapter:" + category + message + rolledBack);
397                 } catch (Exception e) {
398                         response = response == null || String.valueOf(response).isEmpty() ? "NONE" : response
399                         exceptionUtil.buildWorkflowException(execution, 7020, "Received error from VnfAdapter: " + response)
400                 }
401         }
402
403         /**
404          * Gets the named child of the specified node.
405          * @param node the node
406          * @param name the child name
407          * @return the child node, or null if no such child exists
408          */
409         private Node getChild(Node node, String name) {
410                 for (Node child : node.children()) {
411                         if (child.name() == name) {
412                                 return child
413                         }
414                 }
415                 return null
416         }
417
418         /**
419          * Gets the text of the named child of the specified node.
420          * @param node the node
421          * @param name the child name
422          * @return the child node text, or null if no such child exists
423          */
424         private String getChildText(Node node, String name) {
425                 Node child = getChild(node, name)
426                 return child == null ? null : child.text()
427         }
428 }