Optional clean-up
[netvirt.git] / vpnservice / cloud-servicechain / cloud-servicechain-impl / src / main / java / org / opendaylight / netvirt / cloudservicechain / utils / ElanServiceChainUtils.java
old mode 100644 (file)
new mode 100755 (executable)
index 57ee348..fb30d23
@@ -7,18 +7,30 @@
  */
 package org.opendaylight.netvirt.cloudservicechain.utils;
 
+import com.google.common.base.Optional;
 import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
-import java.util.HashSet;
+import java.util.Collections;
 import java.util.List;
-
+import java.util.stream.Collectors;
 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.genius.mdsalutil.ActionInfo;
 import org.opendaylight.genius.mdsalutil.MDSALUtil;
+import org.opendaylight.genius.mdsalutil.MatchFieldType;
+import org.opendaylight.genius.mdsalutil.MatchInfo;
 import org.opendaylight.genius.mdsalutil.MetaDataUtil;
 import org.opendaylight.genius.mdsalutil.NwConstants;
-import org.opendaylight.genius.itm.globals.ITMConstants;
+import org.opendaylight.genius.mdsalutil.actions.ActionRegLoad;
+import org.opendaylight.genius.mdsalutil.interfaces.IMdsalApiManager;
+import org.opendaylight.genius.utils.ServiceIndex;
 import org.opendaylight.netvirt.cloudservicechain.CloudServiceChainConstants;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowId;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.Flow;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.FlowBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.list.Instruction;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.cloud.servicechain.state.rev170511.ElanServiceChainState;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.cloud.servicechain.state.rev170511.elan.to.pseudo.port.data.list.ElanToPseudoPortData;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.cloud.servicechain.state.rev170511.elan.to.pseudo.port.data.list.ElanToPseudoPortDataBuilder;
@@ -30,36 +42,26 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.elan.rev150602.elan
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.elan.rev150602.elan.dpn.interfaces.elan.dpn.interfaces.list.DpnInterfaces;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.elan.rev150602.elan.instances.ElanInstance;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.elan.rev150602.elan.instances.ElanInstanceKey;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.openflowjava.nx.match.rev140421.NxmNxReg2;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowId;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.Flow;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.FlowBuilder;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.list.Instruction;
-import org.opendaylight.genius.mdsalutil.interfaces.IMdsalApiManager;
-import org.opendaylight.genius.utils.ServiceIndex;
-import org.opendaylight.genius.mdsalutil.MatchFieldType;
-import org.opendaylight.genius.mdsalutil.MatchInfo;
-import java.util.Arrays;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.base.Optional;
-
 public class ElanServiceChainUtils {
 
-    private static final Logger logger = LoggerFactory.getLogger(ElanServiceChainUtils.class);
+    private static final Logger LOG = LoggerFactory.getLogger(ElanServiceChainUtils.class);
 
 
-    public static InstanceIdentifier<ElanInstance> getElanInstanceConfigurationDataPath(String elanInstanceName) {
-        return InstanceIdentifier.builder(ElanInstances.class).child(ElanInstance.class, new ElanInstanceKey(elanInstanceName)).build();
+    public static InstanceIdentifier<ElanInstance> getElanInstanceConfigDataPath(String elanInstanceName) {
+        return InstanceIdentifier.builder(ElanInstances.class).child(ElanInstance.class,
+                                                                     new ElanInstanceKey(elanInstanceName)).build();
     }
 
     public static Optional<ElanInstance> getElanInstanceByName(DataBroker broker, String elanName) {
-        return MDSALUtil.read(broker, LogicalDatastoreType.CONFIGURATION,
-                getElanInstanceConfigurationDataPath(elanName));
+        return MDSALUtil.read(broker, LogicalDatastoreType.CONFIGURATION, getElanInstanceConfigDataPath(elanName));
     }
 
-    public static Optional<Collection<BigInteger>> getElanDnsByName(DataBroker broker, String elanInstanceName) {
+    public static Collection<BigInteger> getElanDpnsByName(DataBroker broker, String elanInstanceName) {
         InstanceIdentifier<ElanDpnInterfacesList> elanDpnIfacesIid =
                 InstanceIdentifier.builder(ElanDpnInterfaces.class)
                                   .child(ElanDpnInterfacesList.class,new ElanDpnInterfacesListKey(elanInstanceName))
@@ -67,21 +69,16 @@ public class ElanServiceChainUtils {
         Optional<ElanDpnInterfacesList> elanDpnIfacesOpc =
                 MDSALUtil.read(broker, LogicalDatastoreType.OPERATIONAL, elanDpnIfacesIid);
         if (!elanDpnIfacesOpc.isPresent()) {
-            logger.warn("Could not find and DpnInterface for elan {}", elanInstanceName);
-            return Optional.<Collection<BigInteger>>absent();
+            LOG.warn("Could not find and DpnInterface for elan {}", elanInstanceName);
+            return Collections.emptySet();
         }
 
-        Collection<BigInteger> dpns = new HashSet<BigInteger>();
-        List<DpnInterfaces> elanDpnIfaces = elanDpnIfacesOpc.get().getDpnInterfaces();
-        for ( DpnInterfaces dpnIf : elanDpnIfaces) {
-            dpns.add(dpnIf.getDpId());
-        }
-
-        return Optional.of(dpns);
+        return elanDpnIfacesOpc.get().getDpnInterfaces().stream().map(DpnInterfaces::getDpId).collect(
+                Collectors.toSet());
     }
 
     public static BigInteger getElanMetadataLabel(long elanTag) {
-        return (BigInteger.valueOf(elanTag)).shiftLeft(24);
+        return BigInteger.valueOf(elanTag).shiftLeft(24);
     }
 
     /**
@@ -90,37 +87,38 @@ public class ElanServiceChainUtils {
      *  + Matches on lportTag=ElanPseudoLportTag + SI=1
      *  + Sets scfTag and sends to the DlSubsFilter table.
      *
-     * @param dpnId  Dpn Id where the LPortDispatcher table must be modified
-     * @param elanLportTag Dataplane identifier of the ElanPseudoPort
-     * @param elanTag Dataplane identifier of the ELAN
-     * @param addOrRemove  States if flows must be added or removed
+     * @param dpnId Dpn Id where the flow must be installed
+     * @param elanLportTag the Elan Pseudo Lport Id in Dataplane
+     * @param elanTag the Elan Id in the Dataplane
+     * @param addOrRemove States if the flow must be added or removed
      */
-    public static void programLPortDispatcherToScf(IMdsalApiManager mdsalManager, BigInteger dpnId, int elanTag,
-            int elanLportTag, short tableId, int scfTag, int addOrRemove) {
-        logger.info("L2-ServiceChaining: programLPortDispatcherToScf dpId={} elanLportTag={} scfTag={} addOrRemove={} ",
-                dpnId, elanLportTag, scfTag, addOrRemove);
+    public static void programLPortDispatcherToScf(IMdsalApiManager mdsalManager, BigInteger dpnId, long elanTag,
+            int elanLportTag, short tableId, long scfTag, int addOrRemove) {
+        LOG.info("L2-ServiceChaining: programLPortDispatcherToScf dpId={} elanLportTag={} scfTag={} addOrRemove={} ",
+                 dpnId, elanLportTag, scfTag, addOrRemove);
         String flowRef = buildLportDispToScfFlowRef(elanLportTag, scfTag);
         if (addOrRemove == NwConstants.ADD_FLOW) {
+            int instructionKey = 0;
+            List<Instruction> instructions = new ArrayList<>();
+            List<ActionInfo> actionsInfos = new ArrayList<>();
+            actionsInfos.add( new ActionRegLoad(NxmNxReg2.class, 0, 31, scfTag));
+            instructions.add(MDSALUtil.buildApplyActionsInstruction(MDSALUtil
+                    .buildActions(actionsInfos),instructionKey++));
+            instructions.add(MDSALUtil.buildAndGetGotoTableInstruction(tableId, instructionKey++));
             List<MatchInfo> matches = Arrays.asList(
                     new MatchInfo(MatchFieldType.metadata,
                             new BigInteger[] { MetaDataUtil.getMetaDataForLPortDispatcher(elanLportTag,
                                     ServiceIndex.getIndex(NwConstants.SCF_SERVICE_NAME, NwConstants.SCF_SERVICE_INDEX)),
                                     MetaDataUtil.getMetaDataMaskForLPortDispatcher() }));
-            int instructionKey = 0;
-            List<Instruction> instructions = Arrays.asList(
-                    MDSALUtil.buildAndGetWriteMetadaInstruction(VpnServiceChainUtils.getMetadataSCF(scfTag),
-                            CloudServiceChainConstants.METADATA_MASK_SCF_WRITE,
-                            instructionKey++),
-                    MDSALUtil.buildAndGetGotoTableInstruction(tableId, instructionKey++) );
 
             Flow flow = MDSALUtil.buildFlowNew(NwConstants.LPORT_DISPATCHER_TABLE, flowRef,
                     CloudServiceChainConstants.DEFAULT_SCF_FLOW_PRIORITY, flowRef,
-                    0, 0, ITMConstants.COOKIE_ITM_EXTERNAL.add(BigInteger.valueOf(elanTag)),
+                    0, 0, CloudServiceChainConstants.COOKIE_LPORT_DISPATCHER_BASE.add(BigInteger.valueOf(elanTag)),
                     matches, instructions);
             mdsalManager.installFlow(dpnId, flow);
         } else {
             Flow flow = new FlowBuilder().setTableId(NwConstants.LPORT_DISPATCHER_TABLE)
-                    .setId(new FlowId(flowRef)).build();
+                                         .setId(new FlowId(flowRef)).build();
             mdsalManager.removeFlow(dpnId, flow);
         }
     }
@@ -128,26 +126,28 @@ public class ElanServiceChainUtils {
     /**
      * This flow is in charge of handling packets coming from the SCF Pipeline
      * when there is no matching ServiceChain.
-     *
-     *  + Matches on ElanPseudoPortTag and SI=3 (ELAN)
-     *  + Sets elanTag and sends to DMAC table
-     *
-     * @param dpnId Dpn Id where the LPortDispatcher table must be modified
-     * @param elanLportTag Dataplane identifier of the ElanPseudoPort
-     * @param elanTag Dataplane identifier of the ELAN
-     * @param addOrRemove States if flows must be added or removed
+     * <ul>
+     *  <li> Matches on ElanPseudoPortTag and SI=3 (ELAN)</li>
+     *  <li> Sets elanTag and sends to DMAC table</li>
+     * </ul>
+     * @param dpnId Dpn Id where the flow must be installed
+     * @param elanLportTag the Elan Pseudo Lport Id to be used in the Dataplane
+     * @param elanTag the Elan Id to be used in the Dataplane
+     * @param addOrRemove States if the flow must be added or removed
      */
     public static void programLPortDispatcherFromScf(IMdsalApiManager mdsalManager, BigInteger dpnId,
-                                                     int elanLportTag, int elanTag, int addOrRemove) {
-        logger.info("L2-ServiceChaining: programLPortDispatcherFromScf dpId={} elanLportTag={} elanTag={} addOrRemove={} ",
-                dpnId, elanLportTag, elanTag, addOrRemove);
+                                                     int elanLportTag, long elanTag, int addOrRemove) {
+        LOG.info("L2-ServiceChaining: programLPortDispatcherFromScf dpId={} elanLportTag={} elanTag={} addOrRemove={} ",
+                 dpnId, elanLportTag, elanTag, addOrRemove);
         String flowRef = buildLportDispFromScfFlowRef(elanTag, elanLportTag );
         if (addOrRemove == NwConstants.ADD_FLOW) {
             List<MatchInfo> matches = Arrays.asList(
-                    new MatchInfo(MatchFieldType.metadata,
-                            new BigInteger[] { MetaDataUtil.getMetaDataForLPortDispatcher(elanLportTag,
-                                    ServiceIndex.getIndex(NwConstants.ELAN_SERVICE_NAME, NwConstants.ELAN_SERVICE_INDEX)),
-                                    MetaDataUtil.getMetaDataMaskForLPortDispatcher() }));
+                new MatchInfo(MatchFieldType.metadata,
+                              new BigInteger[] {
+                                  MetaDataUtil.getMetaDataForLPortDispatcher(elanLportTag,
+                                                                ServiceIndex.getIndex(NwConstants.ELAN_SERVICE_NAME,
+                                                                                      NwConstants.ELAN_SERVICE_INDEX)),
+                                  MetaDataUtil.getMetaDataMaskForLPortDispatcher() }));
             int instructionKey = 0;
             List<Instruction> instructions = Arrays.asList(
                     // BigInter.ONE is for setting also the Split-Horizon flag since it could have been cleared
@@ -160,8 +160,9 @@ public class ElanServiceChainUtils {
 
             Flow flow =
                     MDSALUtil.buildFlowNew(NwConstants.LPORT_DISPATCHER_TABLE, flowRef,
-                            CloudServiceChainConstants.DEFAULT_LPORT_DISPATCHER_FLOW_PRIORITY,
-                            flowRef, 0, 0, ITMConstants.COOKIE_ITM_EXTERNAL.add(BigInteger.valueOf(elanTag)),
+                            CloudServiceChainConstants.DEFAULT_SCF_FLOW_PRIORITY,
+                            flowRef, 0, 0,
+                            CloudServiceChainConstants.COOKIE_LPORT_DISPATCHER_BASE.add(BigInteger.valueOf(elanTag)),
                             matches, instructions);
             mdsalManager.installFlow(dpnId, flow);
         } else {
@@ -173,27 +174,27 @@ public class ElanServiceChainUtils {
 
 
     /**
-     *  This flow is in charge of receiving packets from the TOR and sending
+     * This flow is in charge of receiving packets from the TOR and sending
      * them to the SCF Pipeline by setting the LportTag of ElanPseudoPort.
      * Note that ELAN already has a flow in this table that redirects packets
      * to the ELAN Pipeline. However, the flow for the SCF Pipeline will have
      * higher priority, and will only be present when there is a ServiceChain
      * using this ElanPseudoPort.
-     *
-     *  + Matches on the VNI
-     *  + Sets SI=1 and ElanPseudoPort tag in the Metadata and sends to
+     * <ul>
+     *  <li> Matches on the VNI
+     *  <li> Sets SI=1 and ElanPseudoPort tag in the Metadata and sends to
      *    LPortDispatcher via table 80.
-     *
-     * @param dpnId Dpn Id where the ExtTunnel table must be modified
-     * @param elanLportTag Dataplane identifier of the ElanPseudoPort
-     * @param vni Virtual Network Identifier
-     * @param elanTag Dataplane identifier of the ELAN
-     * @param addOrRemove States if flows must be added or removed
+     * </ul>
+     * @param dpnId Dpn Id where the flow must be installed
+     * @param elanLportTag the Elan Pseudo Lport Id to be used in the Dataplane
+     * @param vni the VNI to which the Elan is related
+     * @param elanTag the Elan Id to be used in the Dataplane
+     * @param addOrRemove States if the flow must be added or removed
      */
     public static void programExternalTunnelTable(IMdsalApiManager mdsalManager, BigInteger dpnId, int elanLportTag,
-            Long vni, int elanTag, int addOrRemove) {
-        logger.info("L2-ServiceChaining: programExternalTunnelTable dpId={} vni={} elanLportTag={} addOrRemove={} ",
-                dpnId, vni, elanLportTag, addOrRemove);
+            long vni, int elanTag, int addOrRemove) {
+        LOG.info("L2-ServiceChaining: programExternalTunnelTable dpId={} vni={} elanLportTag={} addOrRemove={} ",
+                 dpnId, vni, elanLportTag, addOrRemove);
         String flowRef = buildExtTunnelTblToLportDispFlowRef(vni, elanLportTag);
         if (addOrRemove == NwConstants.ADD_FLOW) {
             List<MatchInfo> matches = Arrays.asList(new MatchInfo(MatchFieldType.tunnel_id,
@@ -201,26 +202,27 @@ public class ElanServiceChainUtils {
             List<Instruction> instructions = buildSetLportTagAndGotoLportDispInstructions(elanLportTag);
             Flow flow = MDSALUtil.buildFlowNew(NwConstants.EXTERNAL_TUNNEL_TABLE, flowRef,
                     CloudServiceChainConstants.DEFAULT_SCF_FLOW_PRIORITY, flowRef,
-                    0, 0, ITMConstants.COOKIE_ITM_EXTERNAL.add(BigInteger.valueOf(elanTag)),
+                    0, 0, NwConstants.TUNNEL_TABLE_COOKIE.add(BigInteger.valueOf(elanTag)),
                     matches, instructions);
             mdsalManager.installFlow(dpnId, flow);
         } else {
-            Flow flow = new FlowBuilder().setTableId(NwConstants.EXTERNAL_TUNNEL_TABLE).setId(new FlowId(flowRef)).build();
+            Flow flow =
+                new FlowBuilder().setTableId(NwConstants.EXTERNAL_TUNNEL_TABLE).setId(new FlowId(flowRef)).build();
             mdsalManager.removeFlow(dpnId, flow);
         }
     }
 
     /**
      * Builds a List of Instructions that set the ElanPseudoPort Tag in
-     * metadata and sends to LPortDispatcher table (via Table 80)
+     * metadata and sends to LPortDispatcher table (via Table 80).
      *
      * @param lportTag Dataplane identifier of the ElanPseudoPort
      *
      * @return the List of Instructions
      */
-    public static List<Instruction> buildSetLportTagAndGotoLportDispInstructions(long lportTag) {
+    public static List<Instruction> buildSetLportTagAndGotoLportDispInstructions(int lportTag) {
         int instructionKey = 0;
-        BigInteger metadata = MetaDataUtil.getMetaDataForLPortDispatcher((int) lportTag,
+        BigInteger metadata = MetaDataUtil.getMetaDataForLPortDispatcher(lportTag,
                 ServiceIndex.getIndex(NwConstants.SCF_SERVICE_NAME, NwConstants.SCF_SERVICE_INDEX));
         List<Instruction> result =
                 Arrays.asList(MDSALUtil.buildAndGetWriteMetadaInstruction(metadata,
@@ -230,23 +232,23 @@ public class ElanServiceChainUtils {
         return result;
     }
 
-    private static String buildExtTunnelTblToLportDispFlowRef(Long vni, int elanLportTag) {
+    private static String buildExtTunnelTblToLportDispFlowRef(long vni, int elanLportTag) {
         return CloudServiceChainConstants.L2_FLOWID_PREFIX + vni
                 + NwConstants.FLOWID_SEPARATOR + elanLportTag;
     }
 
-    private static String buildLportDispToScfFlowRef(int elanLportTag, int scfTag) {
+    private static String buildLportDispToScfFlowRef(int elanLportTag, long scfTag) {
         return CloudServiceChainConstants.ELAN_TO_SCF_L2_FLOWID_PREFIX + elanLportTag
                 + NwConstants.FLOWID_SEPARATOR + scfTag;
     }
 
-    private static String buildLportDispFromScfFlowRef(int elanTag, int elanLportTag) {
+    private static String buildLportDispFromScfFlowRef(long elanTag, int elanLportTag) {
         return CloudServiceChainConstants.SCF_TO_ELAN_L2_FLOWID_PREFIX + elanTag
                 + NwConstants.FLOWID_SEPARATOR + elanLportTag;
     }
 
     /**
-     * Stores the relation between elanInstanceName and ElanLport and scfTag.
+     * Stores the relation between ElanLport and scfTag.
      *
      * @param broker dataBroker service reference
      * @param elanInstanceName Name of the ELAN. Typically its UUID
@@ -255,17 +257,17 @@ public class ElanServiceChainUtils {
      * @param addOrRemove States if flows must be added or removed
      */
     public static void updateElanToLportTagMap(final DataBroker broker, final String elanInstanceName,
-                                                final int lportTag, final int scfTag, final int addOrRemove) {
-        ElanToPseudoPortDataKey key = new ElanToPseudoPortDataKey(elanInstanceName);
+                                               final int lportTag, final long scfTag, final int addOrRemove) {
+        ElanToPseudoPortDataKey key = new ElanToPseudoPortDataKey(new Long(lportTag), scfTag);
         InstanceIdentifier<ElanToPseudoPortData> path = InstanceIdentifier.builder(ElanInstances.class)
                 .child(ElanInstance.class, new ElanInstanceKey(elanInstanceName))
                 .augmentation(ElanServiceChainState.class)
-                .child(ElanToPseudoPortData.class, new ElanToPseudoPortDataKey(elanInstanceName)).build();
+                .child(ElanToPseudoPortData.class, new ElanToPseudoPortDataKey(key)).build();
 
         if ( addOrRemove == NwConstants.ADD_FLOW ) {
             ElanToPseudoPortData newValue =
-                    new ElanToPseudoPortDataBuilder().setKey(key).setElanInstanceName(elanInstanceName)
-                                                     .setElanLportTag((long) lportTag).setScfTag(scfTag).build();
+                    new ElanToPseudoPortDataBuilder().setKey(key).setElanLportTag(new Long(lportTag))
+                                                     .setScfTag(scfTag).build();
             MDSALUtil.syncWrite(broker, LogicalDatastoreType.CONFIGURATION, path, newValue);
         } else {
             MDSALUtil.syncDelete(broker, LogicalDatastoreType.CONFIGURATION, path);
@@ -276,25 +278,19 @@ public class ElanServiceChainUtils {
      * Read from ElanToLportTagMap the PsuedoLogicalPort related with a given elan.
      *
      * @param broker dataBroker service reference
-     * @param elanInstanceName
+     * @param elanInstanceName the name of the Elan
      * @return the ElanToPseudoPortData object or Optional.absent() if it
      *     cannot be found
      */
-    public static Optional<ElanToPseudoPortData> getElanToLportTagList(final DataBroker broker, final String elanInstanceName) {
-        ElanToPseudoPortDataKey key = new ElanToPseudoPortDataKey(elanInstanceName);
-        InstanceIdentifier<ElanToPseudoPortData> path = InstanceIdentifier.builder(ElanInstances.class)
+    public static Optional<ElanServiceChainState> getElanServiceChainState(final DataBroker broker,
+                                                                           final String elanInstanceName) {
+        InstanceIdentifier<ElanServiceChainState> path = InstanceIdentifier.builder(ElanInstances.class)
                 .child(ElanInstance.class, new ElanInstanceKey(elanInstanceName))
-                .augmentation(ElanServiceChainState.class)
-                .child(ElanToPseudoPortData.class, key).build();
-
-        Optional<ElanToPseudoPortData> elanLPortListOpc =
-                MDSALUtil.read(broker, LogicalDatastoreType.OPERATIONAL, path);
-        if (!elanLPortListOpc.isPresent()) {
-            logger.warn("Could not find and LPort for elan {}", elanInstanceName);
-            return Optional.absent();
-        }
+                .augmentation(ElanServiceChainState.class).build();
+        Optional<ElanServiceChainState> elanServiceChainStateOpc =
+            MDSALUtil.read(broker,LogicalDatastoreType.CONFIGURATION, path);
 
-        return elanLPortListOpc;
+        return elanServiceChainStateOpc;
 
     }
 }