Code refactoring
[clamp.git] / src / main / java / org / onap / clamp / clds / service / CldsService.java
index 3c9e470..14e6562 100644 (file)
@@ -2,7 +2,7 @@
  * ============LICENSE_START=======================================================
  * ONAP CLAMP
  * ================================================================================
- * Copyright (C) 2017 AT&T Intellectual Property. All rights
+ * Copyright (C) 2017-2018 AT&T Intellectual Property. All rights
  *                             reserved.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -23,7 +23,6 @@
 
 package org.onap.clamp.clds.service;
 
-import com.att.ajsc.common.AjscService;
 import com.att.eelf.configuration.EELFLogger;
 import com.att.eelf.configuration.EELFManager;
 import com.fasterxml.jackson.databind.JsonNode;
@@ -33,6 +32,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
 import java.io.IOException;
 import java.io.InputStream;
 import java.security.GeneralSecurityException;
+import java.util.ArrayList;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
@@ -56,72 +56,73 @@ import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.MediaType;
 import javax.xml.transform.TransformerException;
 
+import org.apache.camel.Produce;
 import org.apache.commons.codec.DecoderException;
 import org.apache.commons.lang3.StringUtils;
-import org.camunda.bpm.engine.RuntimeService;
-import org.camunda.bpm.engine.runtime.ProcessInstance;
 import org.json.simple.parser.ParseException;
+import org.onap.clamp.clds.camel.CamelProxy;
 import org.onap.clamp.clds.client.DcaeDispatcherServices;
 import org.onap.clamp.clds.client.DcaeInventoryServices;
 import org.onap.clamp.clds.client.req.sdc.SdcCatalogServices;
+import org.onap.clamp.clds.config.CldsReferenceProperties;
 import org.onap.clamp.clds.dao.CldsDao;
 import org.onap.clamp.clds.exception.CldsConfigException;
 import org.onap.clamp.clds.exception.SdcCommunicationException;
 import org.onap.clamp.clds.exception.policy.PolicyClientException;
+import org.onap.clamp.clds.model.CLDSMonitoringDetails;
 import org.onap.clamp.clds.model.CldsDBServiceCache;
 import org.onap.clamp.clds.model.CldsEvent;
 import org.onap.clamp.clds.model.CldsHealthCheck;
 import org.onap.clamp.clds.model.CldsInfo;
 import org.onap.clamp.clds.model.CldsModel;
 import org.onap.clamp.clds.model.CldsModelProp;
-import org.onap.clamp.clds.model.CldsSdcResource;
-import org.onap.clamp.clds.model.CldsSdcServiceDetail;
-import org.onap.clamp.clds.model.CldsSdcServiceInfo;
 import org.onap.clamp.clds.model.CldsServiceData;
 import org.onap.clamp.clds.model.CldsTemplate;
 import org.onap.clamp.clds.model.DcaeEvent;
 import org.onap.clamp.clds.model.ValueItem;
-import org.onap.clamp.clds.model.prop.AbstractModelElement;
-import org.onap.clamp.clds.model.prop.ModelProperties;
-import org.onap.clamp.clds.model.refprop.RefProp;
+import org.onap.clamp.clds.model.properties.AbstractModelElement;
+import org.onap.clamp.clds.model.properties.ModelProperties;
+import org.onap.clamp.clds.model.sdc.SdcResource;
+import org.onap.clamp.clds.model.sdc.SdcServiceDetail;
+import org.onap.clamp.clds.model.sdc.SdcServiceInfo;
 import org.onap.clamp.clds.transform.XslTransformer;
 import org.onap.clamp.clds.util.LoggingUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.ApplicationContext;
 import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Component;
 import org.springframework.web.client.HttpClientErrorException;
 
-import io.swagger.annotations.Api;
-import io.swagger.annotations.ApiOperation;
-
 /**
  * Service to save and retrieve the CLDS model attributes.
  */
-@AjscService
-@Api(value = "/clds")
+@Component
 @Path("/clds")
 public class CldsService extends SecureServiceBase {
+
+    @Produce(uri = "direct:processSubmit")
+    private CamelProxy camelProxy;
     protected static final EELFLogger securityLogger = EELFManager.getInstance().getSecurityLogger();
     @Autowired
-    private ApplicationContext        appContext;
-    private static final String       RESOURCE_NAME  = "clds-version.properties";
+    private ApplicationContext appContext;
+    private static final String RESOURCE_NAME = "clds-version.properties";
     @Value("${CLDS_PERMISSION_TYPE_CL:permission-type-cl}")
-    private String                    cldsPersmissionTypeCl;
+    private String cldsPersmissionTypeCl;
     @Value("${CLDS_PERMISSION_TYPE_CL_MANAGE:permission-type-cl-manage}")
-    private String                    cldsPermissionTypeClManage;
+    private String cldsPermissionTypeClManage;
     @Value("${CLDS_PERMISSION_TYPE_CL_EVENT:permission-type-cl-event}")
-    private String                    cldsPermissionTypeClEvent;
+    private String cldsPermissionTypeClEvent;
     @Value("${CLDS_PERMISSION_TYPE_FILTER_VF:permission-type-filter-vf}")
-    private String                    cldsPermissionTypeFilterVf;
+    private String cldsPermissionTypeFilterVf;
     @Value("${CLDS_PERMISSION_TYPE_TEMPLATE:permission-type-template}")
-    private String                    cldsPermissionTypeTemplate;
+    private String cldsPermissionTypeTemplate;
     @Value("${CLDS_PERMISSION_INSTANCE:dev}")
-    private String                    cldsPermissionInstance;
-    private SecureServicePermission   permissionReadCl;
-    private SecureServicePermission   permissionUpdateCl;
-    private SecureServicePermission   permissionReadTemplate;
-    private SecureServicePermission   permissionUpdateTemplate;
+    private String cldsPermissionInstance;
+    private SecureServicePermission permissionReadCl;
+    private SecureServicePermission permissionUpdateCl;
+    private SecureServicePermission permissionReadTemplate;
+    private SecureServicePermission permissionUpdateTemplate;
 
     @PostConstruct
     private final void afterConstruction() {
@@ -134,28 +135,49 @@ public class CldsService extends SecureServiceBase {
     }
 
     @Value("${org.onap.clamp.config.files.globalClds:'classpath:/clds/globalClds.properties'}")
-    private String                 globalClds;
-    private Properties             globalCldsProperties;
-    @Autowired
-    private CldsDao                cldsDao;
+    private String globalClds;
+    private Properties globalCldsProperties;
     @Autowired
-    private RuntimeService         runtimeService;
+    private CldsDao cldsDao;
     @Autowired
-    private XslTransformer         cldsBpmnTransformer;
+    private XslTransformer cldsBpmnTransformer;
     @Autowired
-    private RefProp                refProp;
+    private CldsReferenceProperties refProp;
     @Autowired
-    private SdcCatalogServices     sdcCatalogServices;
+    private SdcCatalogServices sdcCatalogServices;
     @Autowired
     private DcaeDispatcherServices dcaeDispatcherServices;
     @Autowired
-    private DcaeInventoryServices  dcaeInventoryServices;
+    private DcaeInventoryServices dcaeInventoryServices;
+
+    /*
+     * @return list of CLDS-Monitoring-Details: CLOSELOOP_NAME | Close loop name
+     * used in the CLDS application (prefix: ClosedLoop- + unique ClosedLoop ID)
+     * MODEL_NAME | Model Name in CLDS application SERVICE_TYPE_ID | TypeId
+     * returned from the DCAE application when the ClosedLoop is submitted
+     * (DCAEServiceTypeRequest generated in DCAE application). DEPLOYMENT_ID |
+     * Id generated when the ClosedLoop is deployed in DCAE. TEMPLATE_NAME |
+     * Template used to generate the ClosedLoop model. ACTION_CD | Current state
+     * of the ClosedLoop in CLDS application.
+     */
+    @GET
+    @Path("/cldsDetails")
+    @Produces(MediaType.APPLICATION_JSON)
+    public List<CLDSMonitoringDetails> getCLDSDetails() {
+        Date startTime = new Date();
+        LoggingUtils.setRequestContext("CldsService: GET model details", getPrincipalName());
+        List<CLDSMonitoringDetails> cldsMonitoringDetailsList = new ArrayList<CLDSMonitoringDetails>();
+        cldsMonitoringDetailsList = cldsDao.getCLDSMonitoringDetails();
+        // audit log
+        LoggingUtils.setTimeContext(startTime, new Date());
+        LoggingUtils.setResponseContext("0", "Get cldsDetails success", this.getClass().getName());
+        auditLogger.info("GET cldsDetails completed");
+        return cldsMonitoringDetailsList;
+    }
 
     /*
-     *
      * CLDS IFO service will return 3 things 1. User Name 2. CLDS code version
      * that is currently installed from pom.xml file 3. User permissions
-     *
      */
     @GET
     @Path("/cldsInfo")
@@ -229,7 +251,6 @@ public class CldsService extends SecureServiceBase {
      * @param modelName
      * @return bpmn xml text - content of bpmn given name
      */
-    @ApiOperation(value = "Retrieves BPMN for a CLDS model name from the database", notes = "This is only expected to be used for testing purposes, not by the UI", response = String.class)
     @GET
     @Path("/model/bpmn/{modelName}")
     @Produces(MediaType.TEXT_XML)
@@ -254,7 +275,6 @@ public class CldsService extends SecureServiceBase {
      * @param modelName
      * @return image xml text - content of image given name
      */
-    @ApiOperation(value = "Retrieves image for a CLDS model name from the database", notes = "This is only expected to be used for testing purposes, not by the UI", response = String.class)
     @GET
     @Path("/model/image/{modelName}")
     @Produces(MediaType.TEXT_XML)
@@ -277,7 +297,6 @@ public class CldsService extends SecureServiceBase {
      * @param modelName
      * @return clds model - clds model for the given model name
      */
-    @ApiOperation(value = "Retrieves a CLDS model by name from the database", notes = "", response = String.class)
     @GET
     @Path("/model/{modelName}")
     @Produces(MediaType.APPLICATION_JSON)
@@ -288,15 +307,10 @@ public class CldsService extends SecureServiceBase {
         logger.debug("GET model for  modelName={}", modelName);
         CldsModel cldsModel = CldsModel.retrieve(cldsDao, modelName, false);
         isAuthorizedForVf(cldsModel);
-        /**
-         * Checking condition whether our CLDS model can call INventory Method
-         */
+        // Checking condition whether our CLDS model can call Inventory Method
         if (cldsModel.canInventoryCall()) {
             try {
-                /*
-                 * Below is the method to for inventory call and DB insert for
-                 * event methods
-                 */
+                // Method to call dcae inventory and invoke insert event method
                 dcaeInventoryServices.setEventInventory(cldsModel, getUserId());
             } catch (Exception e) {
                 LoggingUtils.setErrorContext("900", "Set event inventory error");
@@ -315,7 +329,6 @@ public class CldsService extends SecureServiceBase {
      *
      * @param modelName
      */
-    @ApiOperation(value = "Saves a CLDS model by name in the database", notes = "", response = String.class)
     @PUT
     @Path("/model/{modelName}")
     @Consumes(MediaType.APPLICATION_JSON)
@@ -350,7 +363,6 @@ public class CldsService extends SecureServiceBase {
      *
      * @return model names in JSON
      */
-    @ApiOperation(value = "Retrieves a list of CLDS model names", notes = "", response = String.class)
     @GET
     @Path("/model-names")
     @Produces(MediaType.APPLICATION_JSON)
@@ -384,7 +396,6 @@ public class CldsService extends SecureServiceBase {
      * @throws DecoderException
      *             In case of issues with the Hex String decoding
      */
-    @ApiOperation(value = "Saves and processes an action for a CLDS model by name", notes = "", response = String.class)
     @PUT
     @Path("/action/{action}/{modelName}")
     @Consumes(MediaType.APPLICATION_JSON)
@@ -399,7 +410,7 @@ public class CldsService extends SecureServiceBase {
                 cldsPermissionInstance, actionCd);
         isAuthorized(permisionManage);
         isAuthorizedForVf(model);
-        String userid = getUserId();
+        String userId = getUserId();
         String actionStateCd = CldsEvent.ACTION_STATE_INITIATED;
         String processDefinitionKey = "clds-process-action-wf";
         logger.info("PUT actionCd={}", actionCd);
@@ -409,7 +420,7 @@ public class CldsService extends SecureServiceBase {
         logger.info("PUT test={}", test);
         logger.info("PUT bpmnText={}", model.getBpmnText());
         logger.info("PUT propText={}", model.getPropText());
-        logger.info("PUT userid={}", userid);
+        logger.info("PUT userId={}", userId);
         logger.info("PUT getTypeId={}", model.getTypeId());
         logger.info("PUT deploymentId={}", model.getDeploymentId());
         if (model.getTemplateName() != null) {
@@ -451,7 +462,7 @@ public class CldsService extends SecureServiceBase {
         logger.info("PUT isInsertTestEvent={}", isInsertTestEvent);
         // determine if requested action is permitted
         model.validateAction(actionCd);
-        // input variables to camunda process
+        // input variables for Camel process
         Map<String, Object> variables = new HashMap<>();
         variables.put("actionCd", actionCd);
         variables.put("modelProp", prop.getBytes());
@@ -460,24 +471,25 @@ public class CldsService extends SecureServiceBase {
         variables.put("controlName", controlName);
         variables.put("docText", docText.getBytes());
         variables.put("isTest", isTest);
-        variables.put("userid", userid);
+        variables.put("userid", userId);
         variables.put("isInsertTestEvent", isInsertTestEvent);
         logger.info("modelProp - " + prop);
         logger.info("docText - " + docText);
+        // ModelProperties modelProperties = new ModelProperties(modelName,
+        // controlName, actionCd, isTest, modelBpmnProp, modelProp);
         try {
-            // start camunda process
-            ProcessInstance pi = runtimeService.startProcessInstanceByKey(processDefinitionKey, variables);
-            // log process info
-            logger.info("Started processDefinitionId={}, processInstanceId={}", pi.getProcessDefinitionId(),
-                    pi.getProcessInstanceId());
+            String result = camelProxy.submit(actionCd, prop, bpmnJson, modelName, controlName, docText, isTest, userId,
+                    isInsertTestEvent);
+            logger.info("Starting Camel flow on request, result is: ", result);
         } catch (SdcCommunicationException | PolicyClientException | BadRequestException e) {
-            logger.error("Exception occured during invoking bpmn process", e);
+            logger.error("Exception occured during invoking Camel process", e);
             throw new CldsConfigException(e.getMessage(), e);
         }
         // refresh model info from db (get fresh event info)
         CldsModel retreivedModel = CldsModel.retrieve(cldsDao, modelName, false);
-        if (actionCd.equalsIgnoreCase(CldsEvent.ACTION_SUBMIT)
-                || actionCd.equalsIgnoreCase(CldsEvent.ACTION_RESUBMIT)) {
+        if (!isTest && (actionCd.equalsIgnoreCase(CldsEvent.ACTION_SUBMIT)
+                || actionCd.equalsIgnoreCase(CldsEvent.ACTION_RESUBMIT)
+                || actionCd.equalsIgnoreCase(CldsEvent.ACTION_SUBMITDCAE))) {
             // To verify inventory status and modify model status to distribute
             dcaeInventoryServices.setEventInventory(retreivedModel, getUserId());
             retreivedModel.save(cldsDao, getUserId());
@@ -495,7 +507,6 @@ public class CldsService extends SecureServiceBase {
      * @param test
      * @param dcaeEvent
      */
-    @ApiOperation(value = "Accepts events for a model", notes = "", response = String.class)
     @POST
     @Path("/dcae/event")
     @Consumes(MediaType.APPLICATION_JSON)
@@ -550,9 +561,7 @@ public class CldsService extends SecureServiceBase {
      *             In case of issue when decryting the SDC password
      * @throws DecoderException
      *             In case of issues with the decoding of the Hex String
-     *
      */
-    @ApiOperation(value = "Retrieves sdc services", notes = "", response = String.class)
     @GET
     @Path("/sdc/services")
     @Produces(MediaType.APPLICATION_JSON)
@@ -580,9 +589,7 @@ public class CldsService extends SecureServiceBase {
      * 
      * @throws IOException
      *             In case of issues
-     *
      */
-    @ApiOperation(value = "Retrieves total properties required by UI", notes = "", response = String.class)
     @GET
     @Path("/properties")
     @Produces(MediaType.APPLICATION_JSON)
@@ -598,41 +605,26 @@ public class CldsService extends SecureServiceBase {
      *             In case of issues with the decryting the encrypted password
      * @throws DecoderException
      *             In case of issues with the decoding of the Hex String
-     * 
+     * @throws IOException
+     *             In case of issue to convert CldsServiceCache to InputStream
      */
-    @ApiOperation(value = "Retrieves total properties by using invariantUUID based on refresh and non refresh", notes = "", response = String.class)
     @GET
     @Path("/properties/{serviceInvariantUUID}")
     @Produces(MediaType.APPLICATION_JSON)
     public String getSdcPropertiesByServiceUUIDForRefresh(
             @PathParam("serviceInvariantUUID") String serviceInvariantUUID,
-            @DefaultValue("false") @QueryParam("refresh") String refresh)
-            throws GeneralSecurityException, DecoderException {
+            @DefaultValue("false") @QueryParam("refresh") boolean refresh)
+            throws GeneralSecurityException, DecoderException, IOException {
         Date startTime = new Date();
         LoggingUtils.setRequestContext("CldsService: GET sdc properties by uuid", getPrincipalName());
         CldsServiceData cldsServiceData = new CldsServiceData();
         cldsServiceData.setServiceInvariantUUID(serviceInvariantUUID);
-        boolean isCldsSdcDataExpired = true;
-        // To getcldsService information from database cache using invariantUUID
-        // only when refresh = false
-        if (refresh != null && refresh.equalsIgnoreCase("false")) {
-            cldsServiceData = cldsServiceData.getCldsServiceCache(cldsDao, serviceInvariantUUID);
-            // If cldsService is available in database Cache , verify is data
-            // expired or not
-            if (cldsServiceData != null) {
-                isCldsSdcDataExpired = sdcCatalogServices.isCldsSdcCacheDataExpired(cldsServiceData);
-            }
+        if (!refresh) {
+            cldsServiceData = cldsDao.getCldsServiceCache(serviceInvariantUUID);
         }
-        // If user Requested for refresh or database cache expired , get all
-        // data from sdc api.
-        if ((refresh != null && refresh.equalsIgnoreCase("true")) || isCldsSdcDataExpired) {
+        if (sdcCatalogServices.isCldsSdcCacheDataExpired(cldsServiceData)) {
             cldsServiceData = sdcCatalogServices.getCldsServiceDataWithAlarmConditions(serviceInvariantUUID);
-            CldsDBServiceCache cldsDBServiceCache = sdcCatalogServices
-                    .getCldsDbServiceCacheUsingCldsServiceData(cldsServiceData);
-            if (cldsDBServiceCache != null && cldsDBServiceCache.getInvariantId() != null
-                    && cldsDBServiceCache.getServiceId() != null) {
-                cldsServiceData.setCldsServiceCache(cldsDao, cldsDBServiceCache);
-            }
+            cldsDao.setCldsServiceCache(new CldsDBServiceCache(cldsServiceData));
         }
         // filter out VFs the user is not authorized for
         cldsServiceData.filterVfs(this);
@@ -688,14 +680,14 @@ public class CldsService extends SecureServiceBase {
             return "";
         }
         ObjectMapper objectMapper = new ObjectMapper();
-        List<CldsSdcServiceInfo> rawList = objectMapper.readValue(responseStr,
-                objectMapper.getTypeFactory().constructCollectionType(List.class, CldsSdcServiceInfo.class));
+        List<SdcServiceInfo> rawList = objectMapper.readValue(responseStr,
+                objectMapper.getTypeFactory().constructCollectionType(List.class, SdcServiceInfo.class));
         ObjectNode invariantIdServiceNode = objectMapper.createObjectNode();
         ObjectNode serviceNode = objectMapper.createObjectNode();
         logger.info("value of cldsserviceiNfolist: {}", rawList);
         if (rawList != null && !rawList.isEmpty()) {
-            List<CldsSdcServiceInfo> cldsSdcServiceInfoList = sdcCatalogServices.removeDuplicateServices(rawList);
-            for (CldsSdcServiceInfo currCldsSdcServiceInfo : cldsSdcServiceInfoList) {
+            List<SdcServiceInfo> cldsSdcServiceInfoList = sdcCatalogServices.removeDuplicateServices(rawList);
+            for (SdcServiceInfo currCldsSdcServiceInfo : cldsSdcServiceInfoList) {
                 if (currCldsSdcServiceInfo != null) {
                     invariantIdServiceNode.put(currCldsSdcServiceInfo.getInvariantUUID(),
                             currCldsSdcServiceInfo.getName());
@@ -708,7 +700,7 @@ public class CldsService extends SecureServiceBase {
 
     private String createPropertiesObjectByUUID(String globalProps, String cldsResponseStr) throws IOException {
         ObjectMapper mapper = new ObjectMapper();
-        CldsSdcServiceDetail cldsSdcServiceDetail = mapper.readValue(cldsResponseStr, CldsSdcServiceDetail.class);
+        SdcServiceDetail cldsSdcServiceDetail = mapper.readValue(cldsResponseStr, SdcServiceDetail.class);
         ObjectNode globalPropsJson = null;
         if (cldsSdcServiceDetail != null && cldsSdcServiceDetail.getUuid() != null) {
             /**
@@ -753,18 +745,18 @@ public class CldsService extends SecureServiceBase {
     }
 
     private void createVfObjectNode(ObjectNode vfObjectNode2, ObjectMapper mapper,
-            List<CldsSdcResource> rawCldsSdcResourceList) {
+            List<SdcResource> rawCldsSdcResourceList) {
         ObjectNode vfNode = mapper.createObjectNode();
         vfNode.put("", "");
         // To remove repeated resource instance name from
         // resourceInstanceList
-        List<CldsSdcResource> cldsSdcResourceList = sdcCatalogServices
+        List<SdcResource> cldsSdcResourceList = sdcCatalogServices
                 .removeDuplicateSdcResourceInstances(rawCldsSdcResourceList);
         /**
          * Creating vf resource node using cldsSdcResource Object
          */
         if (cldsSdcResourceList != null && !cldsSdcResourceList.isEmpty()) {
-            for (CldsSdcResource cldsSdcResource : cldsSdcResourceList) {
+            for (SdcResource cldsSdcResource : cldsSdcResourceList) {
                 if (cldsSdcResource != null && "VF".equalsIgnoreCase(cldsSdcResource.getResoucreType())) {
                     vfNode.put(cldsSdcResource.getResourceUUID(), cldsSdcResource.getResourceName());
                 }
@@ -800,7 +792,7 @@ public class CldsService extends SecureServiceBase {
         vfObjectNode2.putPOJO("alarmCondition", alarmStringJsonNode);
     }
 
-    private ObjectNode createByVFCObjectNode(ObjectMapper mapper, List<CldsSdcResource> cldsSdcResourceList) {
+    private ObjectNode createByVFCObjectNode(ObjectMapper mapper, List<SdcResource> cldsSdcResourceList) {
         ObjectNode emptyObjectNode = mapper.createObjectNode();
         ObjectNode emptyvfcobjectNode = mapper.createObjectNode();
         ObjectNode vfCObjectNode = mapper.createObjectNode();
@@ -808,7 +800,7 @@ public class CldsService extends SecureServiceBase {
         ObjectNode subVfCObjectNode = mapper.createObjectNode();
         subVfCObjectNode.putPOJO("vfc", emptyObjectNode);
         if (cldsSdcResourceList != null && !cldsSdcResourceList.isEmpty()) {
-            for (CldsSdcResource cldsSdcResource : cldsSdcResourceList) {
+            for (SdcResource cldsSdcResource : cldsSdcResourceList) {
                 if (cldsSdcResource != null && "VF".equalsIgnoreCase(cldsSdcResource.getResoucreType())) {
                     vfCObjectNode.putPOJO(cldsSdcResource.getResourceUUID(), subVfCObjectNode);
                 }
@@ -950,10 +942,9 @@ public class CldsService extends SecureServiceBase {
                             && resourceVf.get(0).equalsIgnoreCase(currentVf.get(0))) {
                         throw new BadRequestException("Same Service/VF already exists in " + cldsModelProp.getName()
                                 + " model, please select different Service/VF.");
-
                     }
                 }
             }
         }
     }
-}
+}
\ No newline at end of file