Use a Hazelcast map of alternate ids to Cm handle ids 64/140664/9
authoremaclee <lee.anjella.macabuhay@est.tech>
Tue, 8 Apr 2025 11:48:48 +0000 (12:48 +0100)
committerLee Anjella Macabuhay <lee.anjella.macabuhay@est.tech>
Mon, 14 Apr 2025 18:51:14 +0000 (18:51 +0000)
Issue-ID: CPS-2753
Change-Id: I6f3657bf14d78e412f650ee536e79beefb601c09
Signed-off-by: emaclee <lee.anjella.macabuhay@est.tech>
16 files changed:
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cache/AlternateIdCacheConfig.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/AlternateIdChecker.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationService.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServicePropertyHandler.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistence.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImpl.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdog.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/AlternateIdMatcher.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/AlternateIdCacheDataLoader.java [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/AlternateIdCheckerSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServicePropertyHandlerSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServiceSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/utils/AlternateIdMatcherSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/AlternateIdCacheDataLoaderSpec.groovy [new file with mode: 0644]
docs/deployment.rst

diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cache/AlternateIdCacheConfig.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cache/AlternateIdCacheConfig.java
new file mode 100644 (file)
index 0000000..14936c8
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2025 OpenInfra Foundation Europe. All rights reserved.
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.ncmp.impl.cache;
+
+import com.hazelcast.config.MapConfig;
+import com.hazelcast.map.IMap;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class AlternateIdCacheConfig extends HazelcastCacheConfig {
+
+    private static final MapConfig cmHandleIdPerAlternateIdMapConfig =
+            createMapConfig("cmHandleIdPerAlternateIdMapConfig");
+
+    /**
+     * Distributed instance used for mapping alternate id to cm handle id.
+     *
+     * @return configured map of cm handle id by alternate id
+     */
+    @Bean
+    public IMap<String, String> cmHandleIdPerAlternateId() {
+        return getOrCreateHazelcastInstance(cmHandleIdPerAlternateIdMapConfig).getMap("cmHandleIdPerAlternateId");
+    }
+
+}
index a0ca44c..aa7e261 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * ============LICENSE_START========================================================
- * Copyright (c) 2024 Nordix Foundation.
+ * Copyright (c) 2024-2025 OpenInfra Foundation Europe. All rights reserved.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
 
 package org.onap.cps.ncmp.impl.inventory;
 
+import com.hazelcast.map.IMap;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashSet;
 import java.util.Set;
 import java.util.stream.Collectors;
 import lombok.RequiredArgsConstructor;
@@ -29,7 +31,7 @@ import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.onap.cps.api.exceptions.DataNodeNotFoundException;
 import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle;
-import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle;
+import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.stereotype.Service;
 
 @Service
@@ -42,6 +44,8 @@ public class AlternateIdChecker {
     }
 
     private final InventoryPersistence inventoryPersistence;
+    @Qualifier("cmHandleIdPerAlternateId")
+    private final IMap<String, String> cmHandleIdPerAlternateId;
 
     private static final String NO_CURRENT_ALTERNATE_ID = "";
 
@@ -54,9 +58,9 @@ public class AlternateIdChecker {
      * @return collection of cm handles ids which are acceptable
      */
     public Collection<String> getIdsOfCmHandlesWithRejectedAlternateId(
-                                    final Collection<NcmpServiceCmHandle> newNcmpServiceCmHandles,
-                                    final Operation operation) {
-        final Set<String> assignedAlternateIds = getAlternateIdsAlreadyInDb(newNcmpServiceCmHandles);
+            final Collection<NcmpServiceCmHandle> newNcmpServiceCmHandles,
+            final Operation operation) {
+        final Set<String> assignedAlternateIds = new HashSet<>(getAlternateIdsAlreadyInDb(newNcmpServiceCmHandles));
         final Collection<String> rejectedCmHandleIds = new ArrayList<>();
         for (final NcmpServiceCmHandle ncmpServiceCmHandle : newNcmpServiceCmHandles) {
             final String cmHandleId = ncmpServiceCmHandle.getCmHandleId();
@@ -97,9 +101,7 @@ public class AlternateIdChecker {
                 .map(NcmpServiceCmHandle::getAlternateId)
                 .filter(StringUtils::isNotBlank)
                 .collect(Collectors.toSet());
-        return inventoryPersistence.getYangModelCmHandleByAlternateIds(alternateIdsToCheck).stream()
-                .map(YangModelCmHandle::getAlternateId)
-                .collect(Collectors.toSet());
+        return cmHandleIdPerAlternateId.getAll(alternateIdsToCheck).keySet();
     }
 
     private String getCurrentAlternateId(final Operation operation, final String cmHandleId) {
index ea8c3ca..6153fd6 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2025 Nordix Foundation
+ *  Copyright (C) 2021-2025 OpenInfra Foundation Europe. All rights reserved.
  *  Modifications Copyright (C) 2021 Pantheon.tech
  *  Modifications Copyright (C) 2021-2022 Bell Canada
  *  Modifications Copyright (C) 2023 TechMahindra Ltd.
@@ -64,6 +64,7 @@ import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle;
 import org.onap.cps.ncmp.impl.inventory.sync.ModuleOperationsUtils;
 import org.onap.cps.ncmp.impl.inventory.sync.lcm.LcmEventsCmHandleStateHandler;
 import org.onap.cps.ncmp.impl.inventory.trustlevel.TrustLevelManager;
+import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.stereotype.Service;
 
 @Slf4j
@@ -80,6 +81,8 @@ public class CmHandleRegistrationService {
     private final IMap<String, Object> moduleSyncStartedOnCmHandles;
     private final TrustLevelManager trustLevelManager;
     private final AlternateIdChecker alternateIdChecker;
+    @Qualifier("cmHandleIdPerAlternateId")
+    private final IMap<String, String> cmHandleIdPerAlternateId;
 
     /**
      * Registration of Created, Removed, Updated or Upgraded CM Handles.
@@ -135,6 +138,27 @@ public class CmHandleRegistrationService {
         }
     }
 
+    /**
+     * Method to add alternate ids to cache by passing in yang model cm handle.
+     * Note: If alternate id does not exist for given cm handle,
+     * then the map is populated with cm handle id as both key and value.
+     *
+     * @param yangModelCmHandles collection of yang model cm handles
+     */
+    public void addAlternateIdsToCache(final Collection<YangModelCmHandle> yangModelCmHandles) {
+        final Map<String, String> cmHandleIdPerAlternateIdToRegister = new HashMap<>(yangModelCmHandles.size());
+        for (final YangModelCmHandle yangModelCmHandle: yangModelCmHandles) {
+            final String cmHandleId = yangModelCmHandle.getId();
+            final String alternateId = yangModelCmHandle.getAlternateId();
+            if (StringUtils.isNotBlank(alternateId)) {
+                cmHandleIdPerAlternateIdToRegister.put(alternateId, cmHandleId);
+            } else {
+                cmHandleIdPerAlternateIdToRegister.put(cmHandleId, cmHandleId);
+            }
+        }
+        cmHandleIdPerAlternateId.putAll(cmHandleIdPerAlternateIdToRegister);
+    }
+
     protected void processRemovedCmHandles(final DmiPluginRegistration dmiPluginRegistration,
                                            final DmiPluginRegistrationResponse dmiPluginRegistrationResponse) {
         final List<String> toBeRemovedCmHandleIds = dmiPluginRegistration.getRemovedCmHandles();
@@ -168,6 +192,7 @@ public class CmHandleRegistrationService {
         }
         yangModelCmHandles.removeIf(yangModelCmHandle -> notDeletedCmHandles.contains(yangModelCmHandle.getId()));
         updateCmHandleStateBatch(yangModelCmHandles, CmHandleState.DELETED);
+        removeAlternateIdsFromCache(yangModelCmHandles);
         dmiPluginRegistrationResponse.setRemovedCmHandles(cmHandleRegistrationResponses);
     }
 
@@ -186,9 +211,11 @@ public class CmHandleRegistrationService {
             processTrustLevels(ncmpServiceCmHandles, succeededCmHandleIds);
 
         } catch (final AlreadyDefinedException alreadyDefinedException) {
+            log.error("Error while creating CM handles", alreadyDefinedException);
             failedCmHandleRegistrationResponses.addAll(CmHandleRegistrationResponse.createFailureResponsesFromXpaths(
                 alreadyDefinedException.getAlreadyDefinedObjectNames(), CM_HANDLE_ALREADY_EXIST));
         } catch (final Exception exception) {
+            log.error("Error while creating CM handles", exception);
             final Collection<String> cmHandleIds =
                 ncmpServiceCmHandles.stream().map(NcmpServiceCmHandle::getCmHandleId).collect(Collectors.toList());
             failedCmHandleRegistrationResponses.addAll(CmHandleRegistrationResponse
@@ -367,6 +394,7 @@ public class CmHandleRegistrationService {
             }
         }
         lcmEventsCmHandleStateHandler.initiateStateAdvised(yangModelCmHandlesToRegister);
+        addAlternateIdsToCache(yangModelCmHandlesToRegister);
         dmiPluginRegistrationResponse.setCreatedCmHandles(cmHandleRegistrationResponses);
         return succeededCmHandleIds;
     }
@@ -383,4 +411,16 @@ public class CmHandleRegistrationService {
             ncmpServiceCmHandle.getDataProducerIdentifier());
     }
 
+    private void removeAlternateIdsFromCache(final Collection<YangModelCmHandle> yangModelCmHandles) {
+        for (final YangModelCmHandle yangModelCmHandle: yangModelCmHandles) {
+            final String cmHandleId = yangModelCmHandle.getId();
+            final String alternateId = yangModelCmHandle.getAlternateId();
+            if (StringUtils.isNotBlank(alternateId)) {
+                cmHandleIdPerAlternateId.delete(alternateId);
+            } else {
+                cmHandleIdPerAlternateId.delete(cmHandleId);
+            }
+        }
+    }
+
 }
index 7a6cb14..47d03c6 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2022-2025 Nordix Foundation
+ *  Copyright (C) 2022-2025 OpenInfra Foundation Europe. All rights reserved.
  *  Modifications Copyright (C) 2022 Bell Canada
  *  Modifications Copyright (C) 2024 TechMahindra Ltd.
  *  ================================================================================
@@ -33,6 +33,7 @@ import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY
 import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT;
 
 import com.google.common.collect.ImmutableMap;
+import com.hazelcast.map.IMap;
 import java.time.OffsetDateTime;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -57,6 +58,7 @@ import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle;
 import org.onap.cps.ncmp.impl.utils.YangDataConverter;
 import org.onap.cps.utils.ContentType;
 import org.onap.cps.utils.JsonObjectMapper;
+import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.stereotype.Service;
 
 @Slf4j
@@ -70,6 +72,8 @@ public class CmHandleRegistrationServicePropertyHandler {
     private final CpsDataService cpsDataService;
     private final JsonObjectMapper jsonObjectMapper;
     private final AlternateIdChecker alternateIdChecker;
+    @Qualifier("cmHandleIdPerAlternateId")
+    private final IMap<String, String> cmHandleIdPerAlternateId;
 
     /**
      * Iterates over incoming updatedNcmpServiceCmHandles and update the dataNodes based on the updated attributes.
@@ -125,9 +129,12 @@ public class CmHandleRegistrationServicePropertyHandler {
     }
 
     private void updateAlternateId(final NcmpServiceCmHandle ncmpServiceCmHandle) {
+        final String cmHandleId = ncmpServiceCmHandle.getCmHandleId();
         final String newAlternateId = ncmpServiceCmHandle.getAlternateId();
         if (StringUtils.isNotBlank(newAlternateId)) {
             setAndUpdateCmHandleField(ncmpServiceCmHandle.getCmHandleId(), "alternate-id", newAlternateId);
+            cmHandleIdPerAlternateId.delete(cmHandleId);
+            cmHandleIdPerAlternateId.set(newAlternateId, cmHandleId);
         }
     }
 
index aeeb865..29eaf2e 100644 (file)
@@ -124,22 +124,6 @@ public interface InventoryPersistence extends NcmpPersistence {
     Collection<DataNode> getCmHandleDataNodeByCmHandleId(String cmHandleId,
                                                          FetchDescendantsOption fetchDescendantsOption);
 
-    /**
-     * Get yang model cm handle with the given alternate id.
-     *
-     * @param alternateId alternate ID
-     * @return yang model cm handle
-     */
-    YangModelCmHandle getYangModelCmHandleByAlternateId(String alternateId);
-
-    /**
-     * Get yang model cm handles for the given batch of alternate ids.
-     *
-     * @param alternateIds alternate IDs
-     * @return yang model cm handles
-     */
-    Collection<YangModelCmHandle> getYangModelCmHandleByAlternateIds(Collection<String> alternateIds);
-
     /**
      * Get collection of data nodes of given cm handles.
      *
index 8832290..9bbc8b8 100644 (file)
@@ -45,7 +45,6 @@ import org.onap.cps.api.model.DataNode;
 import org.onap.cps.api.model.ModuleDefinition;
 import org.onap.cps.api.model.ModuleReference;
 import org.onap.cps.api.parameters.FetchDescendantsOption;
-import org.onap.cps.ncmp.api.exceptions.CmHandleNotFoundException;
 import org.onap.cps.ncmp.api.inventory.models.CompositeState;
 import org.onap.cps.ncmp.api.inventory.models.CompositeStateBuilder;
 import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle;
@@ -63,7 +62,6 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv
 
     private final CpsModuleService cpsModuleService;
     private final CpsValidator cpsValidator;
-    private final CmHandleQueryService cmHandleQueryService;
 
     /**
      * initialize an inventory persistence object.
@@ -73,21 +71,17 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv
      * @param cpsAnchorService     cps anchor service instance
      * @param cpsModuleService     cps module service instance
      * @param cpsDataService       cps data service instance
-     * @param cmHandleQueryService cm handle query service instance
      */
     public InventoryPersistenceImpl(final CpsValidator cpsValidator,
                                     final JsonObjectMapper jsonObjectMapper,
                                     final CpsAnchorService cpsAnchorService,
                                     final CpsModuleService cpsModuleService,
-                                    final CpsDataService cpsDataService,
-                                    final CmHandleQueryService cmHandleQueryService) {
+                                    final CpsDataService cpsDataService) {
         super(jsonObjectMapper, cpsAnchorService, cpsDataService);
         this.cpsModuleService = cpsModuleService;
         this.cpsValidator = cpsValidator;
-        this.cmHandleQueryService = cmHandleQueryService;
     }
 
-
     @Override
     public CompositeState getCmHandleState(final String cmHandleId) {
         final DataNode stateAsDataNode = cpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
@@ -178,28 +172,6 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv
         return this.getDataNode(getXPathForCmHandleById(cmHandleId), fetchDescendantsOption);
     }
 
-    @Override
-    public YangModelCmHandle getYangModelCmHandleByAlternateId(final String alternateId) {
-        final String cpsPathForCmHandleByAlternateId = getCpsPathForCmHandleByAlternateId(alternateId);
-        final Collection<DataNode> dataNodes = cmHandleQueryService
-            .queryNcmpRegistryByCpsPath(cpsPathForCmHandleByAlternateId, OMIT_DESCENDANTS, 1);
-        if (dataNodes.isEmpty()) {
-            throw new CmHandleNotFoundException(alternateId);
-        }
-        return YangDataConverter.toYangModelCmHandle(dataNodes.iterator().next());
-    }
-
-    @Override
-    public Collection<YangModelCmHandle> getYangModelCmHandleByAlternateIds(final Collection<String> alternateIds) {
-        if (alternateIds.isEmpty()) {
-            return Collections.emptyList();
-        }
-        final String cpsPathForCmHandlesByAlternateIds = getCpsPathForCmHandlesByAlternateIds(alternateIds);
-        final Collection<DataNode> dataNodes = cmHandleQueryService.queryNcmpRegistryByCpsPath(
-            cpsPathForCmHandlesByAlternateIds, INCLUDE_ALL_DESCENDANTS, alternateIds.size());
-        return YangDataConverter.toYangModelCmHandles(dataNodes);
-    }
-
     @Override
     public Collection<DataNode> getCmHandleDataNodes(final Collection<String> cmHandleIds,
                                                      final FetchDescendantsOption fetchDescendantsOption) {
@@ -232,15 +204,6 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv
         return NCMP_DMI_REGISTRY_PARENT + "/cm-handles[@id='" + cmHandleId + "']";
     }
 
-    private static String getCpsPathForCmHandleByAlternateId(final String alternateId) {
-        return NCMP_DMI_REGISTRY_PARENT + "/cm-handles[@alternate-id='" + alternateId + "']";
-    }
-
-    private static String getCpsPathForCmHandlesByAlternateIds(final Collection<String> alternateIds) {
-        return alternateIds.stream().collect(Collectors.joining("' or @alternate-id='",
-                NCMP_DMI_REGISTRY_PARENT + "/cm-handles[@alternate-id='", "']"));
-    }
-
     private static String createStateJsonData(final String state) {
         return "{\"state\":" + state + "}";
     }
@@ -249,7 +212,6 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv
         return "{\"cm-handles\":" + jsonObjectMapper.asJsonString(yangModelCmHandles) + "}";
     }
 
-
     private Collection<String> getAlternateIdsForCmHandleIds(final Collection<String> cmHandleIds) {
         final Collection<DataNode> dataNodes = getCmHandleDataNodes(cmHandleIds, OMIT_DESCENDANTS);
         return dataNodes.stream()
index 62eb514..12e013f 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2022-2025 Nordix Foundation
+ *  Copyright (C) 2022-2025 OpenInfra Foundation Europe. All rights reserved.
  *  Modifications Copyright (C) 2022 Bell Canada
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
@@ -30,6 +30,7 @@ import java.util.concurrent.BlockingQueue;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle;
+import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Service;
 
@@ -42,6 +43,7 @@ public class ModuleSyncWatchdog {
     private final BlockingQueue<String> moduleSyncWorkQueue;
     private final IMap<String, Object> moduleSyncStartedOnCmHandles;
     private final ModuleSyncTasks moduleSyncTasks;
+    @Qualifier("cpsAndNcmpLock")
     private final IMap<String, String> cpsAndNcmpLock;
 
     private static final int MODULE_SYNC_BATCH_SIZE = 300;
index b8e4e7f..3a0201e 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2024-2025 Nordix Foundation
+ *  Copyright (C) 2024-2025 OpenInfra Foundation Europe. All rights reserved.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
 
 package org.onap.cps.ncmp.impl.utils;
 
+import com.hazelcast.map.IMap;
 import java.util.Map;
 import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
+import org.onap.cps.ncmp.api.exceptions.CmHandleNotFoundException;
 import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle;
 import org.onap.cps.ncmp.exceptions.NoAlternateIdMatchFoundException;
-import org.onap.cps.ncmp.impl.inventory.InventoryPersistence;
-import org.onap.cps.utils.CpsValidator;
+import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.stereotype.Service;
 
+@Slf4j
 @Service
 @RequiredArgsConstructor
 public class AlternateIdMatcher {
 
-    private final InventoryPersistence inventoryPersistence;
-    private final CpsValidator cpsValidator;
+    @Qualifier("cmHandleIdPerAlternateId")
+    private final IMap<String, String> cmHandleIdPerAlternateId;
 
     /**
      * Get cm handle that matches longest alternate id by removing elements
@@ -68,22 +71,15 @@ public class AlternateIdMatcher {
      * @return cm handle id string
      */
     public String getCmHandleId(final String cmHandleReference) {
-        if (cpsValidator.isValidName(cmHandleReference)) {
-            return getCmHandleIdTryingStandardIdFirst(cmHandleReference);
-        }
-        return getCmHandleIdByAlternateId(cmHandleReference);
-    }
-
-    private String getCmHandleIdByAlternateId(final String cmHandleReference) {
-        // Please note: because of cm handle id validation rules this case does NOT need to try by (standard) id
-        return inventoryPersistence.getYangModelCmHandleByAlternateId(cmHandleReference).getId();
-    }
-
-    private String getCmHandleIdTryingStandardIdFirst(final String cmHandleReference) {
-        if (inventoryPersistence.isExistingCmHandleId(cmHandleReference)) {
-            return cmHandleReference;
+        final String cmHandleId = cmHandleIdPerAlternateId.get(cmHandleReference);
+        if (cmHandleId == null) {
+            if (cmHandleIdPerAlternateId.containsValue(cmHandleReference)) {
+                return cmHandleReference;
+            } else {
+                throw new CmHandleNotFoundException(cmHandleReference);
+            }
         }
-        return inventoryPersistence.getYangModelCmHandleByAlternateId(cmHandleReference).getId();
+        return cmHandleId;
     }
 
     private String getParentPath(final String path, final String separator) {
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/AlternateIdCacheDataLoader.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/AlternateIdCacheDataLoader.java
new file mode 100644 (file)
index 0000000..0629c51
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2025 OpenInfra Foundation Europe. All rights reserved.
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.ncmp.init;
+
+import static org.onap.cps.api.parameters.FetchDescendantsOption.DIRECT_CHILDREN_ONLY;
+import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT;
+
+import com.hazelcast.map.IMap;
+import java.util.Collection;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.api.model.DataNode;
+import org.onap.cps.ncmp.impl.inventory.CmHandleRegistrationService;
+import org.onap.cps.ncmp.impl.inventory.InventoryPersistence;
+import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle;
+import org.onap.cps.ncmp.impl.utils.YangDataConverter;
+import org.onap.cps.ncmp.utils.events.NcmpInventoryModelOnboardingFinishedEvent;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.context.event.EventListener;
+import org.springframework.stereotype.Service;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class AlternateIdCacheDataLoader {
+
+    private final InventoryPersistence inventoryPersistence;
+    private final CmHandleRegistrationService cmHandleRegistrationService;
+
+    @Qualifier("cmHandleIdPerAlternateId")
+    private final IMap<String, String> cmHandleIdPerAlternateId;
+
+    /**
+     * Method to initialise the Alternate ID Cache by querying the current inventory.
+     * This method is triggered by NcmpInventoryModelOnboardingFinishedEvent.
+     *
+     * @param event the event that triggers the initialization
+     */
+    @EventListener
+    public void populateCmHandleIdPerAlternateIdMap(final NcmpInventoryModelOnboardingFinishedEvent event) {
+        if (cmHandleIdPerAlternateId.isEmpty()) {
+            log.info("Populating Alternate ID map from inventory");
+            final Collection<DataNode> dataNodes = inventoryPersistence.getDataNode(
+                    NCMP_DMI_REGISTRY_PARENT, DIRECT_CHILDREN_ONLY).iterator().next().getChildDataNodes();
+            final Collection<YangModelCmHandle> yangModelCmHandles = dataNodes.stream()
+                    .map(YangDataConverter::toYangModelCmHandle).toList();
+            addAlternateIdsToCache(yangModelCmHandles);
+        }
+        log.info("Alternate ID map has {} entries", cmHandleIdPerAlternateId.size());
+    }
+
+
+    public void addAlternateIdsToCache(final Collection<YangModelCmHandle> yangModelCmHandles) {
+        cmHandleRegistrationService.addAlternateIdsToCache(yangModelCmHandles);
+    }
+
+}
index aba9bf9..d1ced0d 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * ============LICENSE_START========================================================
- * Copyright (c) 2024 Nordix Foundation.
+ * Copyright (c) 2024-2025 OpenInfra Foundation Europe. All rights reserved.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@
 
 package org.onap.cps.ncmp.impl.inventory
 
+import com.hazelcast.map.IMap
 import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle
 import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle
 import org.onap.cps.api.exceptions.DataNodeNotFoundException
@@ -28,53 +29,52 @@ import spock.lang.Specification
 class AlternateIdCheckerSpec extends Specification {
 
     def mockInventoryPersistenceService = Mock(InventoryPersistence)
+    def mockCmHandleIdPerAlternateId = Mock(IMap)
 
-    def objectUnderTest = new AlternateIdChecker(mockInventoryPersistenceService)
+    def objectUnderTest = new AlternateIdChecker(mockInventoryPersistenceService, mockCmHandleIdPerAlternateId)
+
+    def setup() {
+        mockCmHandleIdPerAlternateId.getAll(_) >> [fdnInCache1:'ch-1',fdnInCache2:'ch-2']
+    }
 
     def 'Check a batch of created cm handles with #scenario.'() {
         given: 'a batch of 2 new cm handles with alternate ids #alt1 and #alt2'
             def batch = [new NcmpServiceCmHandle(cmHandleId: 'ch-1', alternateId: alt1),
                          new NcmpServiceCmHandle(cmHandleId: 'ch-2', alternateId: alt2)]
-        and: 'the database already contains cm handle(s) with these alternate ids: #altAlreadyInDb'
-            mockInventoryPersistenceService.getYangModelCmHandleByAlternateIds(_ as Collection<String>) >>
-                { args -> args[0].stream().filter(altId -> altAlreadyInDb.contains(altId)).map(altId -> new YangModelCmHandle(alternateId: altId)).toList() }
         when: 'the batch of new cm handles is checked'
             def result = objectUnderTest.getIdsOfCmHandlesWithRejectedAlternateId(batch, AlternateIdChecker.Operation.CREATE)
         then: 'the result contains ids of the rejected cm handles'
             assert result == expectedRejectedCmHandleIds
         where: 'the following alternate ids are used'
-            scenario                          | alt1   | alt2   | altAlreadyInDb   || expectedRejectedCmHandleIds
-            'blank alternate ids'             | ''     | ''     | ['dont matter']  || []
-            'null alternate ids'              | null   | null   | ['dont matter']  || []
-            'new alternate ids'               | 'fdn1' | 'fdn2' | ['other fdn']    || []
-            'one already used alternate id'   | 'fdn1' | 'fdn2' | ['fdn1']         || ['ch-1']
-            'two already used alternate ids'  | 'fdn1' | 'fdn2' | ['fdn1', 'fdn2'] || ['ch-1', 'ch-2']
-            'duplicate alternate id in batch' | 'fdn1' | 'fdn1' | ['dont matter']  || ['ch-2']
+            scenario                          | alt1         | alt2         || expectedRejectedCmHandleIds
+            'blank alternate ids'             | ''           | ''           || []
+            'null alternate ids'              | null         | null         || []
+            'new alternate ids'               | 'newFdn1'    | 'newFdn2'    || []
+            'one already used alternate id'   | 'fdnInCache1'| 'newFdn'     || ['ch-1']
+            'two already used alternate ids'  | 'fdnInCache1'| 'fdnInCache2'|| ['ch-1', 'ch-2']
+            'duplicate alternate id in batch' | 'newFdn1'    | 'newFdn1'    || ['ch-2']
     }
 
     def 'Check a batch of updates to existing cm handles with #scenario.'() {
         given: 'a batch of 1 existing cm handle to update alternate id to #proposedAlt'
             def batch = [new NcmpServiceCmHandle(cmHandleId: 'ch-1', alternateId: proposedAlt)]
         and: 'the database already contains a cm handle with alternate id: #altAlreadyInDb'
-            mockInventoryPersistenceService.getYangModelCmHandleByAlternateIds(_ as Collection<String>) >>
-                { args -> args[0].stream().filter(altId -> altAlreadyInDb == altId).map(altId -> new YangModelCmHandle(alternateId: altId)).toList() }
             mockInventoryPersistenceService.getYangModelCmHandle(_) >> new YangModelCmHandle(alternateId: altAlreadyInDb)
         when: 'the batch of cm handle updates is checked'
             def result = objectUnderTest.getIdsOfCmHandlesWithRejectedAlternateId(batch, AlternateIdChecker.Operation.UPDATE)
         then: 'the result contains ids of the rejected cm handles'
             assert result == expectedRejectedCmHandleIds
         where: 'the following parameters are used'
-            scenario                      | proposedAlt | altAlreadyInDb || expectedRejectedCmHandleIds
-            'no alternate id'             | 'fdn1'      | ''             || []
-            'used the same alternate id'  | 'fdn1'      | 'fdn1'         || []
-            'used different alternate id' | 'otherFdn'  | 'fdn1'         || ['ch-1']
+            scenario                      | proposedAlt  | altAlreadyInDb|| expectedRejectedCmHandleIds
+            'no alternate id'             | 'newFdn1'    | ''            || []
+            'used the same alternate id'  | 'fdnInCache1'| 'fdnInCache1' || []
+            'used different alternate id' | 'otherFdn'   | 'fdnInCache1' || ['ch-1']
     }
 
     def 'Check update of non-existing cm handle.'() {
         given: 'a batch of 1 non-existing cm handle to update alternate id'
             def batch = [new NcmpServiceCmHandle(cmHandleId: 'non-existing', alternateId: 'altId')]
         and: 'the database does not contain any cm handles'
-            mockInventoryPersistenceService.getYangModelCmHandleByAlternateIds(_) >> []
             mockInventoryPersistenceService.getYangModelCmHandle(_) >> { throwDataNodeNotFoundException() }
         when: 'the batch of cm handle updates is checked'
             def result = objectUnderTest.getIdsOfCmHandlesWithRejectedAlternateId(batch, AlternateIdChecker.Operation.UPDATE)
index 31610ed..cec3acb 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * ============LICENSE_START=======================================================
- * Copyright (C) 2022-2025 Nordix Foundation
+ * Copyright (C) 2022-2025 OpenInfra Foundation Europe. All rights reserved.
  * Modifications Copyright (C) 2022 Bell Canada
  * Modifications Copyright (C) 2024 TechMahindra Ltd.
  * ================================================================================
@@ -27,6 +27,7 @@ import ch.qos.logback.classic.Logger
 import ch.qos.logback.classic.spi.ILoggingEvent
 import ch.qos.logback.core.read.ListAppender
 import com.fasterxml.jackson.databind.ObjectMapper
+import com.hazelcast.map.IMap
 import org.onap.cps.api.CpsDataService
 import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle
 import org.onap.cps.api.exceptions.DataNodeNotFoundException
@@ -52,8 +53,9 @@ class CmHandleRegistrationServicePropertyHandlerSpec extends Specification {
     def mockCpsDataService = Mock(CpsDataService)
     def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
     def mockAlternateIdChecker = Mock(AlternateIdChecker)
+    def mockCmHandleIdPerAlternateId = Mock(IMap)
 
-    def objectUnderTest = new CmHandleRegistrationServicePropertyHandler(mockInventoryPersistence, mockCpsDataService, jsonObjectMapper, mockAlternateIdChecker)
+    def objectUnderTest = new CmHandleRegistrationServicePropertyHandler(mockInventoryPersistence, mockCpsDataService, jsonObjectMapper, mockAlternateIdChecker, mockCmHandleIdPerAlternateId)
     def logger = Spy(ListAppender<ILoggingEvent>)
 
     void setup() {
@@ -203,6 +205,8 @@ class CmHandleRegistrationServicePropertyHandlerSpec extends Specification {
     def 'Update alternate id of existing CM Handle.'() {
         given: 'cm handles request'
             def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleId: cmHandleId, alternateId: 'alt-1')]
+        and: 'the cm handle per alternate id cache returns a value'
+            mockCmHandleIdPerAlternateId.get(_) >> 'someId'
         and: 'a data node found'
             def dataNode = new DataNode(xpath: cmHandleXpath, leaves: ['id': cmHandleId, 'alternate-id': 'alt-1'])
             mockInventoryPersistence.getCmHandleDataNodeByCmHandleId(cmHandleId, INCLUDE_ALL_DESCENDANTS) >> [dataNode]
index 953e1c7..f99fe2d 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2025 Nordix Foundation
+ *  Copyright (C) 2021-2025 OpenInfra Foundation Europe. All rights reserved.
  *  Modifications Copyright (C) 2022 Bell Canada
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
@@ -59,10 +59,11 @@ class CmHandleRegistrationServiceSpec extends Specification {
     def mockModuleSyncStartedOnCmHandles = Mock(IMap<String, Object>)
     def mockTrustLevelManager = Mock(TrustLevelManager)
     def mockAlternateIdChecker = Mock(AlternateIdChecker)
+    def mockCmHandleIdPerAlternateId = Mock(IMap)
 
     def objectUnderTest = Spy(new CmHandleRegistrationService(
         mockNetworkCmProxyDataServicePropertyHandler, mockInventoryPersistence, mockCpsDataService, mockLcmEventsCmHandleStateHandler,
-        mockModuleSyncStartedOnCmHandles as IMap<String, Object>, mockTrustLevelManager, mockAlternateIdChecker))
+        mockModuleSyncStartedOnCmHandles, mockTrustLevelManager, mockAlternateIdChecker, mockCmHandleIdPerAlternateId))
 
     def setup() {
         // always accept all cm handles
index bc21360..0755554 100644 (file)
@@ -35,11 +35,9 @@ import org.onap.cps.api.model.DataNode
 import org.onap.cps.api.model.ModuleDefinition
 import org.onap.cps.api.model.ModuleReference
 import org.onap.cps.utils.CpsValidator
-import org.onap.cps.ncmp.api.exceptions.CmHandleNotFoundException
 import org.onap.cps.ncmp.api.inventory.models.CompositeState
 import org.onap.cps.ncmp.api.inventory.models.CmHandleState
 import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle
-import org.onap.cps.ncmp.impl.utils.YangDataConverter
 import org.onap.cps.utils.ContentType
 import org.onap.cps.utils.JsonObjectMapper
 import spock.lang.Shared
@@ -65,11 +63,7 @@ class InventoryPersistenceImplSpec extends Specification {
 
     def mockCpsValidator = Mock(CpsValidator)
 
-    def mockCmHandleQueries = Mock(CmHandleQueryService)
-
-    def mockYangDataConverter = Mock(YangDataConverter)
-
-    def objectUnderTest = new InventoryPersistenceImpl(mockCpsValidator, spiedJsonObjectMapper, mockCpsAnchorService, mockCpsModuleService, mockCpsDataService, mockCmHandleQueries)
+    def objectUnderTest = new InventoryPersistenceImpl(mockCpsValidator, spiedJsonObjectMapper, mockCpsAnchorService, mockCpsModuleService, mockCpsDataService)
 
     def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ")
             .format(OffsetDateTime.of(2022, 12, 31, 20, 30, 40, 1, ZoneOffset.UTC))
@@ -295,39 +289,6 @@ class InventoryPersistenceImplSpec extends Specification {
             1 * mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, expectedXPath, INCLUDE_ALL_DESCENDANTS)
     }
 
-    def 'Get yang model cm handle by alternate id'() {
-        given: 'expected xPath to get cmHandle data node'
-            def expectedXPath = '/dmi-registry/cm-handles[@alternate-id=\'alternate id\']'
-            def expectedDataNode = new DataNode(xpath: expectedXPath, leaves: [id: 'id', alternateId: 'alternate id'])
-        and: 'query service is invoked with expected xpath'
-            mockCmHandleQueries.queryNcmpRegistryByCpsPath(expectedXPath, OMIT_DESCENDANTS, _) >> [expectedDataNode]
-            mockYangDataConverter.toYangModelCmHandle(expectedDataNode) >> new YangModelCmHandle(id: 'id')
-        expect: 'getting the yang model cm handle'
-            assert objectUnderTest.getYangModelCmHandleByAlternateId('alternate id') == new YangModelCmHandle(id: 'id')
-    }
-
-    def 'Attempt to get non existing yang model cm handle by alternate id'() {
-        given: 'query service is invoked and returns empty collection of data nodes'
-            mockCmHandleQueries.queryNcmpRegistryByCpsPath(*_) >> []
-        when: 'getting the yang model cm handle'
-            objectUnderTest.getYangModelCmHandleByAlternateId('alternate id')
-        then: 'no data found exception thrown'
-            def thrownException = thrown(CmHandleNotFoundException)
-            assert thrownException.getMessage().contains('Cm handle not found')
-            assert thrownException.getDetails().contains('No cm handles found with reference alternate id')
-    }
-
-    def 'Get multiple yang model cm handles by alternate ids #scenario'() {
-        when: 'getting the yang model cm handle with a empty/populated collection of alternate Ids'
-            objectUnderTest.getYangModelCmHandleByAlternateIds(alternateIdCollection)
-        then: 'query service invoked when needed'
-            expectedInvocations * mockCmHandleQueries.queryNcmpRegistryByCpsPath(*_) >> [dataNode]
-        where: 'collections are either empty or populated with alternate ids'
-            scenario               | alternateIdCollection || expectedInvocations
-            'empty collection'     | []                    || 0
-            'populated collection' | ['alt']               || 1
-    }
-
     def 'Get CM handle ids for CM Handles that has given module names'() {
         when: 'the method to get cm handles is called'
             objectUnderTest.getCmHandleReferencesWithGivenModules(['sample-module-name'], false)
index 098e88a..984d45e 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2024-2025 Nordix Foundation
+ *  Copyright (C) 2024-2025 OpenInfra Foundation Europe. All rights reserved.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the 'License');
  *  you may not use this file except in compliance with the License.
 
 package org.onap.cps.ncmp.impl.utils
 
+import com.hazelcast.map.IMap
+import org.onap.cps.ncmp.api.exceptions.CmHandleNotFoundException
 import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle
 import org.onap.cps.ncmp.exceptions.NoAlternateIdMatchFoundException
-import org.onap.cps.ncmp.impl.inventory.InventoryPersistence
-import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle
-import org.onap.cps.utils.CpsValidatorImpl
-import org.springframework.boot.test.context.SpringBootTest
-import org.springframework.test.context.ContextConfiguration
 import spock.lang.Specification
 
-@SpringBootTest
-@ContextConfiguration(classes = [InventoryPersistence])
 class AlternateIdMatcherSpec extends Specification {
 
-    def mockInventoryPersistence = Mock(InventoryPersistence)
+    def mockCmHandleIdPerAlternateId = Mock(IMap)
 
-    def objectUnderTest = new AlternateIdMatcher(mockInventoryPersistence, new CpsValidatorImpl())
+    def objectUnderTest = new AlternateIdMatcher(mockCmHandleIdPerAlternateId)
 
     def 'Finding longest alternate id matches.'() {
         given: 'a cm handle with alternate id /a/b in the cached map of all cm handles'
@@ -44,15 +39,15 @@ class AlternateIdMatcherSpec extends Specification {
         expect: 'querying for alternate id a matching result found'
             assert objectUnderTest.getCmHandleByLongestMatchingAlternateId(targetAlternateId, '/', cmHandlePerAlternateId) != null
         where: 'the following parameters are used'
-            scenario                                | targetAlternateId
-            'exact match'                           | '/a/b'
-            'parent match'                          | '/a/b/c'
-            'grand parent match'                    | '/a/b/c/d'
-            'trailing separator match'              | '/a/b/'
-            'trailing hash'                         | '/a/b#q'
-            'trailing hash parent match'            | '/a/b/c#q'
-            'trailing hash grand parent match'      | '/a/b/c/d#q'
-            'trailing separator then hash match'    | '/a/b/#q'
+            scenario                             | targetAlternateId
+            'exact match'                        | '/a/b'
+            'parent match'                       | '/a/b/c'
+            'grand parent match'                 | '/a/b/c/d'
+            'trailing separator match'           | '/a/b/'
+            'trailing hash'                      | '/a/b#q'
+            'trailing hash parent match'         | '/a/b/c#q'
+            'trailing hash grand parent match'   | '/a/b/c/d#q'
+            'trailing separator then hash match' | '/a/b/#q'
     }
 
     def 'Attempt to find longest alternate id match without any matches.'() {
@@ -71,21 +66,26 @@ class AlternateIdMatcherSpec extends Specification {
     }
 
     def 'Get cm handle id from a cm handle reference that is a #scenario id.' () {
-        given: 'inventory persistence service confirms the reference exists as an id or not (#isExistingCmHandleId)'
-            mockInventoryPersistence.isExistingCmHandleId(cmHandleReference) >> isExistingCmHandleId
+        given: 'cmHandleIdPerAlternateId cache contains the given reference'
+            mockCmHandleIdPerAlternateId.get(cmHandleReference) >> returnedCacheValue
+            mockCmHandleIdPerAlternateId.containsValue(cmHandleReference) >> true
         when: 'getting a cm handle id from the reference'
             def result = objectUnderTest.getCmHandleId(cmHandleReference)
-        then: 'a call to find the cm handle by alternate id is only made when needed'
-            if (isExistingCmHandleId) {
-                0 * mockInventoryPersistence.getYangModelCmHandleByAlternateId(*_)
-            } else {
-                1 * mockInventoryPersistence.getYangModelCmHandleByAlternateId(cmHandleReference) >> new YangModelCmHandle(id: 'ch-id-2')
-            }
-        and: 'the expected cm handle id is returned'
-            assert result == expectedCmHandleId
+        then: 'the expected cm handle id is returned'
+            assert result == expectedResult
         where: 'the following parameters are used'
-            scenario    | cmHandleReference | isExistingCmHandleId || expectedCmHandleId
-            'standard'  | 'ch-id-1'         | true                 || 'ch-id-1'
-            'alternate' | 'alt-id=1'        | false                || 'ch-id-2'
+            scenario    | cmHandleReference| returnedCacheValue|| expectedResult
+            'standard'  | 'ch-id-1'        | null              || 'ch-id-1'
+            'alternate' | 'alt-id=1'       | 'ch-id-2'         || 'ch-id-2'
     }
-}
+
+    def 'Get cm handle id when given reference DOES NOT exist in cache.'() {
+        given: 'cmHandleIdPerAlternateId cache returns null'
+            mockCmHandleIdPerAlternateId.get('nonExistingId') >> null
+        when: 'getting a cm handle id from the reference'
+            objectUnderTest.getCmHandleId('nonExistingId')
+        then: 'an exception is thrown'
+            def thrownException = thrown(CmHandleNotFoundException)
+            assert thrownException.getMessage().contains('Cm handle not found')
+    }
+}
\ No newline at end of file
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/AlternateIdCacheDataLoaderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/AlternateIdCacheDataLoaderSpec.groovy
new file mode 100644 (file)
index 0000000..8941c4c
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2025 OpenInfra Foundation Europe. All rights reserved.
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.ncmp.init
+
+import com.hazelcast.map.IMap
+import org.onap.cps.api.model.DataNode
+import org.onap.cps.ncmp.impl.inventory.CmHandleRegistrationService
+import org.onap.cps.ncmp.impl.inventory.InventoryPersistence
+import org.onap.cps.ncmp.utils.events.NcmpInventoryModelOnboardingFinishedEvent
+import spock.lang.Specification
+
+class AlternateIdCacheDataLoaderSpec extends Specification {
+
+    def mockInventoryPersistence = Mock(InventoryPersistence)
+    def mockCmHandleRegistrationService = Mock(CmHandleRegistrationService)
+    def mockCmHandleIdPerAlternateId = Mock(IMap)
+
+    def objectUnderTest = new AlternateIdCacheDataLoader(mockInventoryPersistence, mockCmHandleRegistrationService, mockCmHandleIdPerAlternateId)
+
+    def 'Populate cm handle id per alternate id cache.'() {
+        given: 'cache is empty'
+            mockCmHandleIdPerAlternateId.isEmpty() >> true
+        and: 'inventory persistence returns some data nodes'
+            def childDataNodes = [new DataNode(xpath: "", leaves: ['id': 'ch-1', 'alternate-id': 'alt-1'])]
+            mockInventoryPersistence.getDataNode(_, _) >> [new DataNode(childDataNodes:childDataNodes, leaves: ['id':''])]
+        when: 'the method to populate the cache is invoked by the ncmp model onboarding event'
+            objectUnderTest.populateCmHandleIdPerAlternateIdMap(Mock(NcmpInventoryModelOnboardingFinishedEvent))
+        then: 'the cm handle registration service is called once to add ids to cache'
+            1 * mockCmHandleRegistrationService.addAlternateIdsToCache(_)
+    }
+}
index b3a279f..990fea1 100644 (file)
@@ -410,5 +410,7 @@ Below are the list of distributed datastructures that we have.
 +--------------+------------------------------------+-----------------------------------------------------------+
 | cps-ncmp     | cpsAndNcmpLock                     | Cps and NCMP distributed lock for various use cases.      |
 +--------------+------------------------------------+-----------------------------------------------------------+
+| cps-ncmp     | cmHandleIdPerAlternateId           | Stores cm handle ids per alternate ids.                   |
++--------------+------------------------------------+-----------------------------------------------------------+
 
-Total number of caches : 7
+Total number of caches : 8