Merge "Reduce anchor lookups related to PrefixResolver (CPS-2417 #1)"
diff --git a/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java b/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java
index 6015e0e..f86073f 100755
--- a/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java
+++ b/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java
@@ -2,7 +2,7 @@
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2020-2022 Bell Canada.
  *  Modifications Copyright (C) 2021 Pantheon.tech
- *  Modifications Copyright (C) 2021-2023 Nordix Foundation
+ *  Modifications Copyright (C) 2021-2024 Nordix Foundation
  *  Modifications Copyright (C) 2022-2024 TechMahindra Ltd.
  *  Modifications Copyright (C) 2022 Deutsche Telekom AG
  *  ================================================================================
@@ -37,9 +37,11 @@
 import java.util.Map;
 import lombok.RequiredArgsConstructor;
 import org.apache.commons.lang3.StringUtils;
+import org.onap.cps.api.CpsAnchorService;
 import org.onap.cps.api.CpsDataService;
 import org.onap.cps.rest.api.CpsDataApi;
 import org.onap.cps.spi.FetchDescendantsOption;
+import org.onap.cps.spi.model.Anchor;
 import org.onap.cps.spi.model.DataNode;
 import org.onap.cps.spi.model.DeltaReport;
 import org.onap.cps.utils.ContentType;
@@ -63,6 +65,7 @@
     private static final DateTimeFormatter ISO_TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern(ISO_TIMESTAMP_FORMAT);
 
     private final CpsDataService cpsDataService;
+    private final CpsAnchorService cpsAnchorService;
     private final JsonObjectMapper jsonObjectMapper;
     private final PrefixResolver prefixResolver;
 
@@ -112,7 +115,8 @@
             ? FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS : FetchDescendantsOption.OMIT_DESCENDANTS;
         final DataNode dataNode = cpsDataService.getDataNodes(dataspaceName, anchorName, xpath,
             fetchDescendantsOption).iterator().next();
-        final String prefix = prefixResolver.getPrefix(dataspaceName, anchorName, dataNode.getXpath());
+        final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
+        final String prefix = prefixResolver.getPrefix(anchor, dataNode.getXpath());
         return new ResponseEntity<>(DataMapUtils.toDataMapWithIdentifier(dataNode, prefix), HttpStatus.OK);
     }
 
@@ -127,8 +131,9 @@
         final Collection<DataNode> dataNodes = cpsDataService.getDataNodes(dataspaceName, anchorName, xpath,
                 fetchDescendantsOption);
         final List<Map<String, Object>> dataMaps = new ArrayList<>(dataNodes.size());
+        final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
         for (final DataNode dataNode: dataNodes) {
-            final String prefix = prefixResolver.getPrefix(dataspaceName, anchorName, dataNode.getXpath());
+            final String prefix = prefixResolver.getPrefix(anchor, dataNode.getXpath());
             final Map<String, Object> dataMap = DataMapUtils.toDataMapWithIdentifier(dataNode, prefix);
             dataMaps.add(dataMap);
         }
diff --git a/cps-rest/src/main/java/org/onap/cps/rest/controller/QueryRestController.java b/cps-rest/src/main/java/org/onap/cps/rest/controller/QueryRestController.java
index 5334b48..547be66 100644
--- a/cps-rest/src/main/java/org/onap/cps/rest/controller/QueryRestController.java
+++ b/cps-rest/src/main/java/org/onap/cps/rest/controller/QueryRestController.java
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2022 Nordix Foundation
+ *  Copyright (C) 2021-2024 Nordix Foundation
  *  Modifications Copyright (C) 2022 Bell Canada.
  *  Modifications Copyright (C) 2022-2023 TechMahindra Ltd.
  *  ================================================================================
@@ -29,10 +29,12 @@
 import java.util.List;
 import java.util.Map;
 import lombok.RequiredArgsConstructor;
+import org.onap.cps.api.CpsAnchorService;
 import org.onap.cps.api.CpsQueryService;
 import org.onap.cps.rest.api.CpsQueryApi;
 import org.onap.cps.spi.FetchDescendantsOption;
 import org.onap.cps.spi.PaginationOption;
+import org.onap.cps.spi.model.Anchor;
 import org.onap.cps.spi.model.DataNode;
 import org.onap.cps.utils.DataMapUtils;
 import org.onap.cps.utils.JsonObjectMapper;
@@ -48,6 +50,7 @@
 public class QueryRestController implements CpsQueryApi {
 
     private final CpsQueryService cpsQueryService;
+    private final CpsAnchorService cpsAnchorService;
     private final JsonObjectMapper jsonObjectMapper;
     private final PrefixResolver prefixResolver;
 
@@ -87,14 +90,15 @@
                 cpsPath, fetchDescendantsOption, paginationOption);
         final List<Map<String, Object>> dataNodesAsListOfMaps = new ArrayList<>(dataNodes.size());
         String prefix = null;
-        final Map<String, List<DataNode>> anchorDataNodeListMap = prepareDataNodesForAnchor(dataNodes);
-        for (final Map.Entry<String, List<DataNode>> anchorDataNodesMapEntry : anchorDataNodeListMap.entrySet()) {
+        final Map<String, List<DataNode>> dataNodesPerAnchor = groupDataNodesPerAnchor(dataNodes);
+        for (final Map.Entry<String, List<DataNode>> dataNodesPerAnchorEntry : dataNodesPerAnchor.entrySet()) {
+            final String anchorName = dataNodesPerAnchorEntry.getKey();
             if (prefix == null) {
-                prefix = prefixResolver.getPrefix(dataspaceName, anchorDataNodesMapEntry.getKey(),
-                        anchorDataNodesMapEntry.getValue().get(0).getXpath());
+                final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
+                prefix = prefixResolver.getPrefix(anchor, dataNodesPerAnchorEntry.getValue().get(0).getXpath());
             }
             final Map<String, Object> dataMap = DataMapUtils.toDataMapWithIdentifierAndAnchor(
-                    anchorDataNodesMapEntry.getValue(), anchorDataNodesMapEntry.getKey(), prefix);
+                    dataNodesPerAnchorEntry.getValue(), anchorName, prefix);
             dataNodesAsListOfMaps.add(dataMap);
         }
         final Integer totalPages = getTotalPages(dataspaceName, cpsPath, paginationOption);
@@ -112,7 +116,7 @@
                 : (int) Math.ceil((double) totalAnchors / paginationOption.getPageSize());
     }
 
-    private Map<String, List<DataNode>> prepareDataNodesForAnchor(final Collection<DataNode> dataNodes) {
+    private Map<String, List<DataNode>> groupDataNodesPerAnchor(final Collection<DataNode> dataNodes) {
         final Map<String, List<DataNode>> dataNodesMapForAnchor = new HashMap<>();
         for (final DataNode dataNode : dataNodes) {
             List<DataNode> dataNodesInAnchor = dataNodesMapForAnchor.get(dataNode.getAnchorName());
@@ -130,10 +134,11 @@
         final Collection<DataNode> dataNodes =
             cpsQueryService.queryDataNodes(dataspaceName, anchorName, cpsPath, fetchDescendantsOption);
         final List<Map<String, Object>> dataNodesAsListOfMaps = new ArrayList<>(dataNodes.size());
+        final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
         String prefix = null;
         for (final DataNode dataNode : dataNodes) {
             if (prefix == null) {
-                prefix = prefixResolver.getPrefix(dataspaceName, anchorName, dataNode.getXpath());
+                prefix = prefixResolver.getPrefix(anchor, dataNode.getXpath());
             }
             final Map<String, Object> dataMap = DataMapUtils.toDataMapWithIdentifier(dataNode, prefix);
             dataNodesAsListOfMaps.add(dataMap);
diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy
index 5241f61..e101ea6 100755
--- a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy
+++ b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2022 Nordix Foundation
+ *  Copyright (C) 2021-2024 Nordix Foundation
  *  Modifications Copyright (C) 2021 Pantheon.tech
  *  Modifications Copyright (C) 2021-2022 Bell Canada.
  *  Modifications Copyright (C) 2022 Deutsche Telekom AG
@@ -26,6 +26,7 @@
 
 import com.fasterxml.jackson.databind.ObjectMapper
 import groovy.json.JsonSlurper
+import org.onap.cps.api.CpsAnchorService
 import org.onap.cps.api.CpsDataService
 import org.onap.cps.spi.FetchDescendantsOption
 import org.onap.cps.spi.model.DataNode
@@ -63,6 +64,9 @@
     CpsDataService mockCpsDataService = Mock()
 
     @SpringBean
+    CpsAnchorService mockCpsAnchorService = Mock()
+
+    @SpringBean
     JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
 
     @SpringBean
diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/QueryRestControllerSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/QueryRestControllerSpec.groovy
index c30a63f..80b287c 100644
--- a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/QueryRestControllerSpec.groovy
+++ b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/QueryRestControllerSpec.groovy
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2022 Nordix Foundation
+ *  Copyright (C) 2021-2024 Nordix Foundation
  *  Modifications Copyright (C) 2021-2022 Bell Canada.
  *  Modifications Copyright (C) 2021 Pantheon.tech
  *  Modifications Copyright (C) 2022-2023 TechMahindra Ltd.
@@ -23,18 +23,13 @@
 
 package org.onap.cps.rest.controller
 
-import org.onap.cps.spi.PaginationOption
-import org.onap.cps.utils.PrefixResolver
-
-import static org.onap.cps.spi.FetchDescendantsOption.DIRECT_CHILDREN_ONLY
-import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
-import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
-
 import com.fasterxml.jackson.databind.ObjectMapper
-import org.onap.cps.utils.JsonObjectMapper
+import org.onap.cps.api.CpsAnchorService
 import org.onap.cps.api.CpsQueryService
+import org.onap.cps.spi.PaginationOption
 import org.onap.cps.spi.model.DataNodeBuilder
+import org.onap.cps.utils.JsonObjectMapper
+import org.onap.cps.utils.PrefixResolver
 import org.spockframework.spring.SpringBean
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.beans.factory.annotation.Value
@@ -43,6 +38,11 @@
 import org.springframework.test.web.servlet.MockMvc
 import spock.lang.Specification
 
+import static org.onap.cps.spi.FetchDescendantsOption.DIRECT_CHILDREN_ONLY
+import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
+import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
+
 @WebMvcTest(QueryRestController)
 class QueryRestControllerSpec extends Specification {
 
@@ -50,6 +50,9 @@
     CpsQueryService mockCpsQueryService = Mock()
 
     @SpringBean
+    CpsAnchorService mockCpsAnchorService = Mock()
+
+    @SpringBean
     JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
 
     @SpringBean
diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java
index 951770b..eed4f09 100644
--- a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java
+++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java
@@ -400,8 +400,7 @@
     private List<Map<String, Object>> prefixResolver(final Anchor anchor, final Collection<DataNode> dataNodes) {
         final List<Map<String, Object>> prefixToDataNodes = new ArrayList<>(dataNodes.size());
         for (final DataNode dataNode: dataNodes) {
-            final String prefix = prefixResolver
-                    .getPrefix(anchor.getDataspaceName(), anchor.getName(), dataNode.getXpath());
+            final String prefix = prefixResolver.getPrefix(anchor, dataNode.getXpath());
             final Map<String, Object> prefixToDataNode = DataMapUtils.toDataMapWithIdentifier(dataNode, prefix);
             prefixToDataNodes.add(prefixToDataNode);
         }
diff --git a/cps-service/src/main/java/org/onap/cps/utils/PrefixResolver.java b/cps-service/src/main/java/org/onap/cps/utils/PrefixResolver.java
index 35dc734..93fb728 100644
--- a/cps-service/src/main/java/org/onap/cps/utils/PrefixResolver.java
+++ b/cps-service/src/main/java/org/onap/cps/utils/PrefixResolver.java
@@ -55,19 +55,6 @@
     private final IMap<String, AnchorDataCacheEntry> anchorDataCache;
 
     /**
-     * Get the module prefix for the given xpath for a dataspace and anchor name.
-     *
-     * @param dataspaceName the name of the dataspace
-     * @param anchorName the name of the anchor the xpath belongs to
-     * @param xpath the xpath to prefix a prefix for
-     * @return the prefix of the module the top level element of given xpath
-     */
-    public String getPrefix(final String dataspaceName, final String anchorName, final String xpath) {
-        final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
-        return getPrefix(anchor, xpath);
-    }
-
-    /**
      * Get the module prefix for the given xpath under the given anchor.
      *
      * @param anchor the anchor the xpath belong to
diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/PrefixResolverSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/PrefixResolverSpec.groovy
index b975de6..5ef584a 100644
--- a/cps-service/src/test/groovy/org/onap/cps/utils/PrefixResolverSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/utils/PrefixResolverSpec.groovy
@@ -48,10 +48,10 @@
 
     def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext()
 
+    def anchor = new Anchor(dataspaceName: 'testDataspace', name: 'testAnchor')
+
     def setup() {
-        given: 'an anchor for the test-tree model'
-            def anchor = new Anchor(dataspaceName: 'testDataspace', name: 'testAnchor')
-        and: 'the system can get this anchor'
+        given: 'the system can get the anchor'
             mockCpsAnchorService.getAnchor('testDataspace', 'testAnchor') >> anchor
         and: 'the schema source cache contains the schema context for the test-tree module'
             mockYangTextSchemaSourceSet.getSchemaContext() >> schemaContext
@@ -59,7 +59,7 @@
 
     def 'get xpath prefix using node schema context'() {
         when: 'the prefix of the yang module is retrieved'
-            def result = objectUnderTest.getPrefix('testDataspace', 'testAnchor', xpath)
+            def result = objectUnderTest.getPrefix(anchor, xpath)
         then: 'the expected prefix is returned'
             result == expectedPrefix
         and: 'the cache is updated for the given anchor with a map of prefixes per top level container (just one one this case)'
@@ -90,7 +90,7 @@
             mockAnchorDataCache.containsKey('testAnchor') >> true
             mockAnchorDataCache.get('testAnchor') >> anchorDataCacheEntry
         when: 'the prefix of the yang module is retrieved'
-            def result = objectUnderTest.getPrefix('testDataspace', 'testAnchor', '/test-tree')
+            def result = objectUnderTest.getPrefix(anchor, '/test-tree')
         then: 'the expected prefix is returned'
             result == expectedPrefix
         and: 'schema source cache is not used (i.e. no need to build schema context)'
@@ -108,7 +108,7 @@
             mockAnchorDataCache.containsKey('testAnchor') >> true
             mockAnchorDataCache.get('testAnchor') >> anchorDataCacheEntry
         when: 'the prefix of the yang module is retrieved'
-            def result = objectUnderTest.getPrefix('testDataspace', 'testAnchor', '/test-tree')
+            def result = objectUnderTest.getPrefix(anchor, '/test-tree')
         then: 'the expected prefix is returned'
             result == 'tree'
         and: 'schema source cache is used (i.e. need to build schema context)'