Visualization of the VF Module Sequencing
[vid.git] / vid-webpack-master / src / app / shared / services / defaultDataServiceGenerator / default.data.generator.service.ts
1 import {Injectable} from '@angular/core';
2 import * as _ from 'lodash';
3
4 import {NgRedux} from '@angular-redux/store';
5 import {AppState} from '../../store/reducers';
6 import {VnfTreeNode} from "../../models/vnfTreeNode";
7 import {VfModuleInstance} from "../../models/vfModuleInstance";
8 import {VfModule} from "../../models/vfModule";
9 import {VfModuleTreeNode} from "../../models/vfModuleTreeNode";
10 import {InputType} from "../../models/inputTypes";
11 import {ServiceNodeTypes} from "../../models/ServiceNodeTypes";
12 import {Constants} from "../../utils/constants";
13 import {Utils} from "../../utils/utils";
14 import {NetworkTreeNode} from "../../models/networkTreeNode";
15 import {createVNFInstance} from "../../storeUtil/utils/vnf/vnf.actions";
16 import {changeInstanceCounter} from "../../storeUtil/utils/general/general.actions";
17 import {createNetworkInstance} from "../../storeUtil/utils/network/network.actions";
18 import {createVFModuleInstance, updateVFModulePosition} from "../../storeUtil/utils/vfModule/vfModule.actions";
19 import {createVnfGroupInstance} from "../../storeUtil/utils/vnfGroup/vnfGroup.actions";
20 import {VnfGroupTreeNode} from "../../models/vnfGroupTreeNode";
21 import {ModelInfo} from "../../models/modelInfo";
22 import {ServiceInstanceActions} from "../../models/serviceInstanceActions";
23 import Parameter = Constants.Parameter;
24
25 @Injectable()
26 export class DefaultDataGeneratorService {
27   static controlsFieldsStatus = {};
28   public requiredFields = {
29     VF: [InputType.LCP_REGION, InputType.TENANT, InputType.PLATFORM],
30     Network: [InputType.LCP_REGION, InputType.TENANT, InputType.PLATFORM],
31     VL: [InputType.LCP_REGION, InputType.TENANT, InputType.PLATFORM],
32     VFmodule: [],
33     VnfGroup: []
34   };
35
36   constructor(private store: NgRedux<AppState>) {
37   }
38
39   public getArbitraryInputs(inputs) {
40     let parameter;
41     let parameterList = [];
42     for (let key in inputs) {
43       parameter = {
44         id: key,
45         type: Parameter.STRING,
46         name: key,
47         value: inputs[key][Parameter.DEFAULT],
48         isRequired: inputs[key][Parameter.REQUIRED],
49         description: inputs[key][Parameter.DESCRIPTION]
50       };
51       switch (inputs[key][Parameter.TYPE]) {
52         case Parameter.INTEGER:
53           parameter.type = Parameter.NUMBER;
54           break;
55         case Parameter.BOOLEAN:
56           parameter.type = Parameter.BOOLEAN;
57           break;
58         case Parameter.RANGE:
59           break;
60         case Parameter.LIST:
61           parameter.type = Parameter.LIST;
62           break;
63         case Parameter.MAP:
64           parameter.type = Parameter.MAP;
65           break;
66       }
67       if (Utils.hasContents(inputs[key][Parameter.CONSTRAINTS])
68         && ( inputs[key][Parameter.CONSTRAINTS].length > 0 )) {
69         let constraintsArray = inputs[key][Parameter.CONSTRAINTS];
70         this.addConstraintParameters(parameterList, constraintsArray, key, inputs, parameter);
71       }
72       else {
73
74         parameterList.push(parameter);
75       }
76     }
77     return parameterList;
78   }
79
80   private addConstraintParameters(parameterList, constraintsArray, key, inputs, parameter) {
81     // If there are constraints and the operator is "valid_values",
82     // use a select parameter type.
83     let i: number = constraintsArray.length;
84     let parameterPushed: boolean = false;
85     if (i > 0) {
86       while ((i--) && (!parameterPushed)) {
87         let keys = Object.keys(constraintsArray[i]);
88         for (let operator in keys) {
89           switch (keys[operator]) {
90             case Parameter.VALID_VALUES:
91               let j: number = constraintsArray[i][Parameter.VALID_VALUES].length;
92               if (j > 0) {
93                 let oList = [];
94                 let option;
95                 while (j--) {
96                   option = {
97                     name: constraintsArray[i][Parameter.VALID_VALUES][j],
98                     isDefault: false
99                   };
100                   if ((Utils.hasContents(inputs[key][Parameter.DEFAULT]) )
101                     && (inputs[key][Parameter.DEFAULT] === constraintsArray[i][Parameter.VALID_VALUES][j] )) {
102                     option = {
103                       name: constraintsArray[i][Parameter.VALID_VALUES][j],
104                       isDefault: true
105                     }
106                   }
107                   oList.push(option);
108                 }
109                 parameter.type = Parameter.SELECT;
110                 parameter.optionList = oList;
111                 parameterList.push(parameter);
112                 parameterPushed = true;
113               }
114               break;
115
116             case Parameter.EQUAL:
117               if (constraintsArray[i][Parameter.EQUAL] != null) {
118                 parameter.type = Parameter.STRING;
119                 parameter.isReadOnly = true;
120                 parameter.value = constraintsArray[i][Parameter.EQUAL];
121                 parameterList.push(parameter);
122                 parameterPushed = true;
123               }
124               break;
125
126             case Parameter.LENGTH:
127               if (constraintsArray[i][Parameter.LENGTH] != null) {
128                 parameter.minLength = constraintsArray[i][Parameter.LENGTH];
129                 parameter.maxLength = constraintsArray[i][Parameter.LENGTH];
130                 parameterList.push(parameter);
131                 parameterPushed = true;
132               }
133               break;
134             case Parameter.MAX_LENGTH:
135               if (constraintsArray[i][Parameter.MAX_LENGTH] != null) {
136                 parameter.maxLength = constraintsArray[i][Parameter.MAX_LENGTH];
137                 parameterList.push(parameter);
138                 parameterPushed = true;
139               }
140               break;
141             case Parameter.MIN_LENGTH:
142               if (constraintsArray[i][Parameter.MIN_LENGTH] != null) {
143                 parameter.minLength = constraintsArray[i][Parameter.MIN_LENGTH];
144                 parameterList.push(parameter);
145                 parameterPushed = true;
146               }
147               break;
148             case Parameter.IN_RANGE:
149               if (constraintsArray[i][Parameter.IN_RANGE] != null) {
150                 if (constraintsArray[i][Parameter.IN_RANGE].length > 1) {
151                   parameter.min = constraintsArray[i][Parameter.IN_RANGE][0];
152                   parameter.max = constraintsArray[i][Parameter.IN_RANGE][1];
153                   parameter.type = Parameter.NUMBER;
154                   parameter.value = inputs[key][Parameter.DEFAULT];
155                   parameterList.push(parameter);
156                   parameterPushed = true;
157                 }
158               }
159               break;
160             case Parameter.GREATER_THAN:
161               if (constraintsArray[i][Parameter.GREATER_THAN] != null) {
162                 parameter.type = Parameter.NUMBER;
163                 parameter.min = constraintsArray[i][Parameter.GREATER_THAN];
164                 parameter.value = inputs[key][Parameter.DEFAULT];
165                 parameterList.push(parameter);
166                 parameterPushed = true;
167               }
168               break;
169           }
170         }
171       }
172     }
173   };
174
175   updateDynamicInputsVnfDataFromModel(modelType: string, model: any): any[] {
176     let displayInputs;
177     if (modelType === ServiceNodeTypes.VFmodule) {
178       displayInputs = model.inputs;
179     }
180     return _.isEmpty(displayInputs) ? [] : this.getArbitraryInputs(displayInputs);
181   }
182
183   updateNetworksOnFirstSet(serviceId: string, formServiceValues: any){
184     const serviceHierarchy = this.store.getState().service.serviceHierarchy[serviceId];
185     if (serviceHierarchy && !_.isEmpty(serviceHierarchy.networks)) {
186       for (let networkUUID in serviceHierarchy.networks) {
187         const isEcompGeneratedNaming = this.getIsEcompGeneratedNaming(serviceHierarchy.networks[networkUUID]);
188         let min_vnf_instances_greater_than_0 = serviceHierarchy.networks[networkUUID].properties['min_instances'] && serviceHierarchy.networks[networkUUID].properties['min_instances'] > 0;
189         if(min_vnf_instances_greater_than_0)
190         {
191           this.createNetworkInstanceReduxIfNotExist(
192             serviceId,
193             this.generateNetworkData(serviceHierarchy, networkUUID, formServiceValues, isEcompGeneratedNaming)
194           );
195         }
196       }
197     }
198   }
199
200   updateVnfGroupsOnFirstSet(serviceId: string, formServiceValues: any){
201     const serviceHierarchy = this.store.getState().service.serviceHierarchy[serviceId];
202     if (serviceHierarchy && !_.isEmpty(serviceHierarchy.vnfGroups)) {
203       for (let vnfGroupUUID in serviceHierarchy.vnfGroups) {
204         const isEcompGeneratedNaming = this.getIsEcompGeneratedNaming(serviceHierarchy.vnfGroups[vnfGroupUUID]);
205         let min_vnf_group_instances_greater_than_0 = serviceHierarchy.vnfGroups[vnfGroupUUID].properties['min_instances'] && serviceHierarchy.vnfGroups[vnfGroupUUID].properties['min_instances'] > 0;
206         if(min_vnf_group_instances_greater_than_0)
207         {
208           this.createVnfGroupInstanceReduxIfNotExist(
209             serviceId,
210             this.generateVnfGroupData(serviceHierarchy, vnfGroupUUID, formServiceValues, isEcompGeneratedNaming)
211           );
212         }
213       }
214     }
215   }
216
217   updateReduxOnFirstSet(serviceId: string, formServiceValues: any): void {
218     this.updateNetworksOnFirstSet(serviceId, formServiceValues);
219     this.updateVnfGroupsOnFirstSet(serviceId, formServiceValues);
220     const serviceHierarchy = this.store.getState().service.serviceHierarchy[serviceId];
221     if (serviceHierarchy && !_.isEmpty(serviceHierarchy.vnfs)) {
222       for (let vnfUUID in serviceHierarchy.vnfs) {
223         const isEcompGeneratedNaming = this.getIsEcompGeneratedNaming(serviceHierarchy.vnfs[vnfUUID]);
224         for (let vnfModuleUUID in serviceHierarchy.vnfs[vnfUUID].vfModules) {
225           const vfModuleModel = serviceHierarchy.vnfs[vnfUUID].vfModules[vnfModuleUUID];
226           if (vfModuleModel.properties.minCountInstances > 0) {
227             let vfModule = this.generateVFModule(vfModuleModel, this.updateDynamicInputsVnfDataFromModel(ServiceNodeTypes.VFmodule, vfModuleModel), isEcompGeneratedNaming, formServiceValues.isALaCarte);
228             if (vfModuleModel.properties.initialCount > 0) {
229               this.createVNFInstanceReduxIfNotExist(
230                 serviceId,
231                 this.generateVNFData(serviceHierarchy, vnfUUID, formServiceValues, isEcompGeneratedNaming)
232               );
233
234               this.addDefaultVfModulesInRedux(
235                 serviceId,
236                 vfModuleModel.properties.initialCount,
237                 vfModuleModel.properties.baseModule,
238                 vfModule,
239                 vnfModuleUUID,
240                 vnfUUID
241               )
242
243             }
244           }
245         }
246
247         let min_vnf_instances_greater_than_0 = serviceHierarchy.vnfs[vnfUUID].properties['min_instances'] && serviceHierarchy.vnfs[vnfUUID].properties['min_instances'] > 0;
248         if(min_vnf_instances_greater_than_0)
249         {
250           this.createVNFInstanceReduxIfNotExist(
251             serviceId,
252             this.generateVNFData(serviceHierarchy, vnfUUID, formServiceValues, isEcompGeneratedNaming)
253           );
254         }
255       }
256     }
257   }
258
259
260   private getIsEcompGeneratedNaming(vnfJson) {
261     const ecompGeneratedNaming = vnfJson.properties.ecomp_generated_naming;
262     return ecompGeneratedNaming === "true";
263   };
264
265   createVNFInstanceReduxIfNotExist(serviceId: string, vnfData: any): void {
266     if(!this.store.getState().service.serviceInstance[serviceId].vnfs[vnfData.modelInfo.modelCustomizationName]){
267       this.store.dispatch(createVNFInstance(vnfData, vnfData.modelInfo.modelCustomizationName, serviceId));
268       this.store.dispatch(changeInstanceCounter(vnfData.modelInfo.modelUniqueId, serviceId, 1, <any> {data : {type : 'VF'}}));
269     }
270   }
271
272   createNetworkInstanceReduxIfNotExist(serviceId: string, networkData: any): void {
273     if(!this.store.getState().service.serviceInstance[serviceId].vnfs[networkData.modelInfo.modelCustomizationName]){
274       this.store.dispatch(createNetworkInstance(networkData, networkData.modelInfo.modelCustomizationName, serviceId));
275       this.store.dispatch(changeInstanceCounter(networkData.modelInfo.modelUniqueId, serviceId, 1, <any> {data : {type : 'VL'}}));
276     }
277   }
278
279   createVnfGroupInstanceReduxIfNotExist(serviceId: string, vnfGroupData: any): void {
280     if(!this.store.getState().service.serviceInstance[serviceId].vnfGroups[vnfGroupData.modelInfo.modelCustomizationName]){
281       this.store.dispatch(createVnfGroupInstance(vnfGroupData, vnfGroupData.modelInfo.modelCustomizationName, serviceId));
282       this.store.dispatch(changeInstanceCounter(vnfGroupData.modelInfo.modelUniqueId , serviceId, 1, <any> {data : {type : 'VnfGroup'}}));
283     }
284   }
285
286   addDefaultVfModulesInRedux(serviceId: string, numberOfVfModules: number,baseModule:boolean, vfModuleData: any, vfModuleName: string, vnfUUID : string){
287     for (let i = 0; i < numberOfVfModules; i++) {
288       if(baseModule) {
289         this.store.dispatch(createVFModuleInstance(vfModuleData, vfModuleName, serviceId, 1, vnfUUID));
290       } else {
291         this.store.dispatch(createVFModuleInstance(vfModuleData, vfModuleName, serviceId, i+1, vnfUUID));
292       }
293
294     }
295   }
296
297   generateVnfGroupInstance(vnfGroupModel: any, isEcompGeneratedNaming : boolean, isALaCarte: boolean, instanceName: string) {
298     let modelInfo = new ModelInfo(vnfGroupModel);
299     let instanceParams = {};
300     return {
301       'uuid' : modelInfo.uuid,
302       'action': ServiceInstanceActions.Create,
303       'instanceName': (!isEcompGeneratedNaming) ? instanceName : null,
304       'isMissingData' : false,
305       'modelInfo': modelInfo,
306       'rollbackOnFailure' : "true",
307       'instanceParams': [
308         instanceParams
309       ],
310       'trackById': DefaultDataGeneratorService.createRandomTrackById()
311     };
312   }
313
314
315   generateVFModule(vfModule: any, dynamicInputs: any, isEcompGeneratedNaming : boolean, isALaCarte: boolean) {
316     let instanceParams = {};
317     dynamicInputs.forEach(field => {
318       instanceParams[field.id] = field.value;
319     });
320     return {
321       'isMissingData' : this.setIsMissingData(ServiceNodeTypes.VFmodule, dynamicInputs, isEcompGeneratedNaming, isALaCarte),
322       'sdncPreReload': null,
323       'modelInfo': {
324         'modelType': 'VFmodule',
325         'modelInvariantId': vfModule.invariantUuid,
326         'modelVersionId': vfModule.uuid,
327         'modelName': vfModule.name,
328         'modelVersion': vfModule.version,
329         'modelCustomizationId': vfModule.customizationUuid,
330         'modelCustomizationName': vfModule.modelCustomizationName,
331         'modelUniqueId' : vfModule.customizationUuid || vfModule.uuid
332       },
333       'instanceParams': [
334         instanceParams
335       ],
336       'trackById': DefaultDataGeneratorService.createRandomTrackById(),
337       'rollbackOnFailure' : isALaCarte ? true : null,
338       'position': vfModule.position
339
340     };
341   }
342
343   setIsMissingData(type: string, dynamicInputs: any, isEcompGeneratedNaming: boolean, isAlaCarte?: boolean): boolean {
344     if (isAlaCarte || !isEcompGeneratedNaming || this.requiredFields[type].length > 0) {
345       return true;
346     }
347
348     if (dynamicInputs) {
349       for(let input of dynamicInputs) {
350         if (input.isRequired && _.isEmpty(input.value)) {
351           return true;
352         }
353       }
354     }
355     return false;
356   }
357
358   generateVNFData(serviceHierarchy: any, vnfName: string, formValues: any, isEcompGeneratedNaming) {
359     return {
360       'uuid' : serviceHierarchy.vnfs[vnfName].uuid,
361       'isMissingData' :this.setIsMissingData(ServiceNodeTypes.VF, [], isEcompGeneratedNaming),
362       'productFamilyId': formValues.productFamilyId,
363       'lcpCloudRegionId': null,
364       'tenantId': null,
365       'lineOfBusiness': null,
366       'platformName': null,
367       'modelInfo': {
368         'modelType': 'VF',
369         'modelInvariantId': serviceHierarchy.vnfs[vnfName].invariantUuid,
370         'modelVersionId': serviceHierarchy.vnfs[vnfName].uuid,
371         'modelName': serviceHierarchy.vnfs[vnfName].name,
372         'modelVersion': serviceHierarchy.vnfs[vnfName].version,
373         'modelCustomizationId': serviceHierarchy.vnfs[vnfName].customizationUuid,
374         'modelCustomizationName': serviceHierarchy.vnfs[vnfName].modelCustomizationName,
375         'modelUniqueId' : serviceHierarchy.vnfs[vnfName].customizationUuid || serviceHierarchy.vnfs[vnfName].uuid,
376       },
377       'trackById': DefaultDataGeneratorService.createRandomTrackById(),
378     }
379   }
380
381   generateNetworkData(serviceHierarchy: any, networkName: string, formValues: any, isEcompGeneratedNaming) {
382       return {
383         'uuid' : serviceHierarchy.network[networkName].uuid,
384         'isMissingData' :this.setIsMissingData(ServiceNodeTypes.VL, [], isEcompGeneratedNaming),
385         'productFamilyId': formValues.productFamilyId,
386         'lcpCloudRegionId': null,
387         'tenantId': null,
388         'lineOfBusiness': null,
389         'platformName': null,
390         'modelInfo': {
391           'modelType': 'VF',
392           'modelInvariantId': serviceHierarchy.network[networkName].invariantUuid,
393           'modelVersionId': serviceHierarchy.network[networkName].uuid,
394           'modelName': serviceHierarchy.network[networkName].name,
395           'modelVersion': serviceHierarchy.network[networkName].version,
396           'modelCustomizationId': serviceHierarchy.network[networkName].modelCustomizationId,
397           'modelCustomizationName': serviceHierarchy.network[networkName].modelCustomizationName,
398           'modelUniqueId' : serviceHierarchy.network[networkName].modelCustomizationId || serviceHierarchy.network[networkName].uuid,
399         },
400         'trackById': DefaultDataGeneratorService.createRandomTrackById(),
401       }
402     }
403
404   generateVnfGroupData(serviceHierarchy: any, vnfGroupName: string, formValues: any, isEcompGeneratedNaming) {
405     return {
406       'uuid' : serviceHierarchy.vnfGroups[vnfGroupName].uuid,
407       'isMissingData' :this.setIsMissingData(ServiceNodeTypes.VnfGroup, [], isEcompGeneratedNaming),
408       'platformName': null,
409       'modelInfo': {
410         'modelType': 'VnfGroup',
411         'modelInvariantId': serviceHierarchy.vnfGroups[vnfGroupName].invariantUuid,
412         'modelVersionId': serviceHierarchy.vnfGroups[vnfGroupName].uuid,
413         'modelName': serviceHierarchy.vnfGroups[vnfGroupName].name,
414         'modelVersion': serviceHierarchy.vnfGroups[vnfGroupName].version,
415         'modelCustomizationId': serviceHierarchy.vnfGroups[vnfGroupName].modelCustomizationId,
416         'modelCustomizationName': serviceHierarchy.vnfGroups[vnfGroupName].modelCustomizationName,
417         'modelUniqueId' : serviceHierarchy.vnfGroups[vnfGroupName].modelCustomizationId || serviceHierarchy.vnfGroups[vnfGroupName].uuid,
418
419       },
420       'trackById': DefaultDataGeneratorService.createRandomTrackById(),
421     }
422   }
423
424
425   static createRandomTrackById() {
426     return Math.random().toString(36).slice(2);
427   }
428
429   private checkMissingData(instance, type: string, dynamicInputs: any, isEcompGeneratedNaming: boolean): boolean {
430     if (!isEcompGeneratedNaming && _.isEmpty(instance.instanceName)) {
431       return true;
432     }
433
434     for (let field of this.requiredFields[type]) {
435       if (_.isEmpty(instance[field])) {
436         return true;
437       }
438     }
439
440     for (let field of dynamicInputs) {
441       if (field.isRequired && !_.isNil(instance.instanceParams) && _.isEmpty(instance.instanceParams[0][field.id])) {
442         return true;
443       }
444     }
445
446     return false;
447   }
448
449   createNewTreeNode(instance: any, model: any, storeKey : string, type : string): VnfTreeNode {
450     let tmp  = null;
451     if(type === 'vnfs') {
452       tmp = new VnfTreeNode(instance, model, storeKey);
453     }else if (type === 'vnfGroups') {
454       tmp = new VnfGroupTreeNode(instance, model, storeKey);
455     }else {
456       tmp = new NetworkTreeNode(instance, model, storeKey);
457     }
458     tmp.missingData = this.checkMissingData(instance, ServiceNodeTypes.VF, [], model.isEcompGeneratedNaming);
459
460     return tmp;
461   }
462
463   createNewVfModuleTreeNode(instance: VfModuleInstance, vfModuleModel: VfModule, vfModuleModelName: string, isEcompGeneratedNamig: boolean, dynamicInputs, dynamicModelName  :string): VfModuleTreeNode {
464     let newVfModule: VfModuleTreeNode = new VfModuleTreeNode(instance, vfModuleModel, vfModuleModelName, dynamicInputs, isEcompGeneratedNamig, dynamicModelName);
465     newVfModule.missingData = this.checkMissingData(instance, ServiceNodeTypes.VFmodule, dynamicInputs, isEcompGeneratedNamig);
466     return newVfModule;
467   }
468
469   calculatePositionOfVfmodule(serviceModelId:string) {
470     const serviceInstance = this.store.getState().service.serviceInstance[serviceModelId];
471     const serviceHierarchy = this.store.getState().service.serviceHierarchy[serviceModelId];
472
473     const vnfList = serviceInstance.vnfs;
474     let totalSecondListLength =1;
475     if (!_.isEmpty(vnfList)) {
476       for (let vnfStoreKey in vnfList) {
477         const firstVfModuleInstanceList = vnfList[vnfStoreKey].vfModules;
478         if (!_.isEmpty(firstVfModuleInstanceList)) {
479           for (let vfModuleInstanceKey in firstVfModuleInstanceList) {
480             let secondVfModuleInstanceList = firstVfModuleInstanceList[vfModuleInstanceKey];
481             let numSecondVfModuleInstanceList = Object.keys(secondVfModuleInstanceList).length;
482               totalSecondListLength = totalSecondListLength + numSecondVfModuleInstanceList;
483
484           }
485         } else {
486           //No instances added yet , hence start from 1
487           totalSecondListLength = 1;
488         }
489
490       }
491     }
492
493     return totalSecondListLength;
494   }
495
496
497   updatePositionForRemainingVfModules(serviceModelId: string) {
498
499     const serviceInstance = this.store.getState().service.serviceInstance[serviceModelId];
500     const vnfList = serviceInstance.vnfs;
501     if (!_.isEmpty(vnfList)) {
502       for (let vnfStoreKey in vnfList) {
503         const firstVfModuleInstanceList = vnfList[vnfStoreKey].vfModules;
504         if (!_.isEmpty(firstVfModuleInstanceList)) {
505           for (let vfModuleInstanceKey in firstVfModuleInstanceList) {
506             let secondVfModuleInstanceList = firstVfModuleInstanceList[vfModuleInstanceKey];
507             for(let secondVfModuleInstanceKey in secondVfModuleInstanceList) {
508               let secondVfModuleObj = secondVfModuleInstanceList[secondVfModuleInstanceKey];
509               if(!_.isNil(secondVfModuleObj.position)) {
510                 this.store.dispatch(updateVFModulePosition(vfModuleInstanceKey,secondVfModuleInstanceKey, secondVfModuleObj.position+1,serviceModelId, vnfStoreKey));
511               }
512             }
513
514           }
515         }
516
517       }
518     }
519   }
520 }