Simple implementation of attribute-axis 37/140137/1
authordanielhanrahan <daniel.hanrahan@est.tech>
Wed, 4 Dec 2024 23:17:36 +0000 (23:17 +0000)
committerdanielhanrahan <daniel.hanrahan@est.tech>
Tue, 4 Feb 2025 11:09:01 +0000 (11:09 +0000)
This minimally implements attribute-axis using existing
queryDataNodes API. Acceptance tests are un-ignored now.

Issue-ID: CPS-2416
Signed-off-by: danielhanrahan <daniel.hanrahan@est.tech>
Change-Id: Ia06be3dd85dfce261d9d78529784d54d84b71bcd

cps-ri/src/main/java/org/onap/cps/ri/CpsDataPersistenceServiceImpl.java
cps-service/src/main/java/org/onap/cps/impl/CpsQueryServiceImpl.java
cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java
cps-service/src/test/groovy/org/onap/cps/impl/CpsQueryServiceImplSpec.groovy
integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/QueryServiceIntegrationSpec.groovy

index 52fd7f2..ac6fe38 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2024 Nordix Foundation
+ *  Copyright (C) 2021-2025 Nordix Foundation
  *  Modifications Copyright (C) 2021 Pantheon.tech
  *  Modifications Copyright (C) 2020-2022 Bell Canada.
  *  Modifications Copyright (C) 2022-2023 TechMahindra Ltd.
@@ -37,6 +37,7 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.TreeMap;
 import java.util.stream.Collectors;
@@ -229,6 +230,27 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
         return createDataNodesFromFragmentEntities(fetchDescendantsOption, fragmentEntities);
     }
 
+    @Override
+    public <T> Set<T> queryDataLeaf(final String dataspaceName, final String anchorName, final String cpsPath,
+                                    final Class<T> targetClass) {
+        final CpsPathQuery cpsPathQuery = getCpsPathQuery(cpsPath);
+        if (!cpsPathQuery.hasAttributeAxis()) {
+            throw new IllegalArgumentException(
+                    "Only Cps Path Queries with attribute-axis are supported by queryDataLeaf");
+        }
+
+        final String attributeName = cpsPathQuery.getAttributeAxisAttributeName();
+        final List<DataNode> dataNodes = queryDataNodes(dataspaceName, anchorName, cpsPath,
+                FetchDescendantsOption.OMIT_DESCENDANTS);
+        return dataNodes.stream()
+                .map(dataNode -> {
+                    final Object attributeValue = dataNode.getLeaves().get(attributeName);
+                    return targetClass.isInstance(attributeValue) ? targetClass.cast(attributeValue) : null;
+                })
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+    }
+
     @Override
     @Timed(value = "cps.data.persistence.service.datanode.query.anchors",
             description = "Time taken to query data nodes across all anchors or list of anchors")
index 2687d8f..508a1b2 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2024 Nordix Foundation
+ *  Copyright (C) 2021-2025 Nordix Foundation
  *  Modifications Copyright (C) 2022-2023 TechMahindra Ltd.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
@@ -54,7 +54,7 @@ public class CpsQueryServiceImpl implements CpsQueryService {
     public <T> Set<T> queryDataLeaf(final String dataspaceName, final String anchorName, final String cpsPath,
                                     final Class<T> targetClass) {
         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
-        throw new UnsupportedOperationException("Query by attribute-axis not implemented yet!");
+        return cpsDataPersistenceService.queryDataLeaf(dataspaceName, anchorName, cpsPath, targetClass);
     }
 
     @Override
index 5be5b1e..a4f05cd 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2020-2024 Nordix Foundation.
+ *  Copyright (C) 2020-2025 Nordix Foundation.
  *  Modifications Copyright (C) 2021 Pantheon.tech
  *  Modifications Copyright (C) 2022 Bell Canada
  *  Modifications Copyright (C) 2022-2023 TechMahindra Ltd.
@@ -27,6 +27,7 @@ import java.io.Serializable;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import org.onap.cps.api.model.DataNode;
 import org.onap.cps.api.parameters.FetchDescendantsOption;
 import org.onap.cps.api.parameters.PaginationOption;
@@ -184,6 +185,17 @@ public interface CpsDataPersistenceService {
     List<DataNode> queryDataNodes(String dataspaceName, String anchorName,
                                   String cpsPath, FetchDescendantsOption fetchDescendantsOption);
 
+    /**
+     * Get data leaf for the given dataspace and anchor by cps path.
+     *
+     * @param dataspaceName          dataspace name
+     * @param anchorName             anchor name
+     * @param cpsPath                cps path
+     * @param targetClass            class of the expected data type
+     * @return a collection of data objects of expected type
+     */
+    <T> Set<T> queryDataLeaf(String dataspaceName, String anchorName, String cpsPath, Class<T> targetClass);
+
     /**
      * Get a datanode by dataspace name and cps path across all anchors.
      *
index 237e4e4..b15ee72 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2023 Nordix Foundation
+ *  Copyright (C) 2021-2025 Nordix Foundation
  *  Modifications Copyright (C) 2023 TechMahindra Ltd.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
@@ -73,11 +73,10 @@ class CpsQueryServiceImplSpec extends Specification {
             1 * mockCpsDataPersistenceService.countAnchorsForDataspaceAndCpsPath("some-dataspace", "/cps-path")
     }
 
-    // TODO will be implemented in CPS-2416
     def 'Query data leaf.'() {
         when: 'a query for a specific leaf is executed'
             objectUnderTest.queryDataLeaf('some-dataspace', 'some-anchor', '/cps-path/@id', Object.class)
         then: 'solution is not implemented yet'
-            thrown(UnsupportedOperationException)
+            1 * mockCpsDataPersistenceService.queryDataLeaf('some-dataspace', 'some-anchor', '/cps-path/@id', Object.class)
     }
 }
index e4d75aa..d1b445f 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2023-2024 Nordix Foundation
+ *  Copyright (C) 2023-2025 Nordix Foundation
  *  Modifications Copyright (C) 2023-2025 TechMahindra Ltd
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the 'License');
@@ -27,7 +27,6 @@ import org.onap.cps.integration.base.FunctionalSpecBase
 import org.onap.cps.api.parameters.FetchDescendantsOption
 import org.onap.cps.api.parameters.PaginationOption
 import org.onap.cps.api.exceptions.CpsPathException
-import spock.lang.Ignore
 
 import static org.onap.cps.api.parameters.FetchDescendantsOption.DIRECT_CHILDREN_ONLY
 import static org.onap.cps.api.parameters.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
@@ -57,7 +56,6 @@ class QueryServiceIntegrationSpec extends FunctionalSpecBase {
             'the AND is used where result does not exist' | '//books[@lang="English" and @price=1000]' || 0                  | []
     }
 
-    @Ignore  // TODO will be implemented in CPS-2416
     def 'Query data leaf using CPS path for #scenario.'() {
         when: 'query data leaf for bookstore container'
             def result = objectUnderTest.queryDataLeaf(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, cpsPath, Object.class)
@@ -70,7 +68,6 @@ class QueryServiceIntegrationSpec extends FunctionalSpecBase {
             'non-existing path'       | '/non-existing/@title'                        || 0
     }
 
-    @Ignore
     def 'Query data leaf with type #leafType using CPS path.'() {
         given: 'a cps path query for two books, returning only #leafName'
             def cpsPath = '//books[@title="Matilda" or @title="Good Omens"]/@' + leafName
@@ -85,7 +82,6 @@ class QueryServiceIntegrationSpec extends FunctionalSpecBase {
             'editions'  | List.class    || [[1988, 2000], [2006]]
     }
 
-    @Ignore
     def 'Query data leaf using CPS path with ancestor axis.'() {
         given: 'a cps path query that will return the names of the categories of two books'
             def cpsPath = '//books[@title="Matilda" or @title="Good Omens"]/ancestor::categories/@name'