summaryrefslogtreecommitdiffstats
path: root/cps-ri
diff options
context:
space:
mode:
Diffstat (limited to 'cps-ri')
-rw-r--r--cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java63
-rwxr-xr-xcps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepository.java6
-rw-r--r--cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryMultiPathQuery.java31
-rw-r--r--cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryMultiPathQueryImpl.java74
-rw-r--r--cps-ri/src/main/java/org/onap/cps/spi/repository/TempTableCreator.java11
-rw-r--r--cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceQueryDataNodeSpec.groovy31
-rwxr-xr-xcps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy35
-rw-r--r--cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy45
-rw-r--r--cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsPersistencePerfSpecBase.groovy74
-rw-r--r--cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsDataPersistenceServiceDeletePerfTest.groovy154
-rw-r--r--cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsDataPersistenceServicePerfTest.groovy (renamed from cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsToDataNodePerfTest.groovy)88
11 files changed, 525 insertions, 87 deletions
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java
index 3bd299430..06ee8ecad 100644
--- a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java
+++ b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java
@@ -1,6 +1,6 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2021-2022 Nordix Foundation
+ * Copyright (C) 2021-2023 Nordix Foundation
* Modifications Copyright (C) 2021 Pantheon.tech
* Modifications Copyright (C) 2020-2022 Bell Canada.
* Modifications Copyright (C) 2022 TechMahindra Ltd.
@@ -256,6 +256,27 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
return toDataNode(fragmentEntity, fetchDescendantsOption);
}
+ @Override
+ public Collection<DataNode> getDataNodes(final String dataspaceName, final String anchorName,
+ final Collection<String> xpaths,
+ final FetchDescendantsOption fetchDescendantsOption) {
+ final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
+ final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName);
+
+ final Set<String> normalizedXpaths = new HashSet<>(xpaths.size());
+ for (final String xpath : xpaths) {
+ try {
+ normalizedXpaths.add(CpsPathUtil.getNormalizedXpath(xpath));
+ } catch (final PathParsingException e) {
+ log.warn("Error parsing xpath \"{}\" in getDataNodes: {}", xpath, e.getMessage());
+ }
+ }
+
+ final List<FragmentEntity> fragmentEntities =
+ fragmentRepository.findByAnchorAndMultipleCpsPaths(anchorEntity.getId(), normalizedXpaths);
+ return toDataNodes(fragmentEntities, fetchDescendantsOption);
+ }
+
private FragmentEntity getFragmentWithoutDescendantsByXpath(final String dataspaceName,
final String anchorName,
final String xpath) {
@@ -317,7 +338,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
}
fragmentEntities = fragmentRepository.findByAnchorAndCpsPath(anchorEntity.getId(), cpsPathQuery);
if (cpsPathQuery.hasAncestorAxis()) {
- fragmentEntities = getAncestorFragmentEntities(anchorEntity, cpsPathQuery, fragmentEntities);
+ fragmentEntities = getAncestorFragmentEntities(anchorEntity.getId(), cpsPathQuery, fragmentEntities);
}
return createDataNodesFromProxiedFragmentEntities(fetchDescendantsOption, anchorEntity, fragmentEntities);
}
@@ -338,18 +359,17 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
fragmentRepository.quickFindWithDescendants(anchorEntity.getId(), xpathRegex);
fragmentEntities = FragmentEntityArranger.toFragmentEntityTrees(anchorEntity, fragmentExtracts);
if (cpsPathQuery.hasAncestorAxis()) {
- fragmentEntities = getAncestorFragmentEntities(anchorEntity, cpsPathQuery, fragmentEntities);
+ fragmentEntities = getAncestorFragmentEntities(anchorEntity.getId(), cpsPathQuery, fragmentEntities);
}
return createDataNodesFromFragmentEntities(fetchDescendantsOption, fragmentEntities);
}
- private Collection<FragmentEntity> getAncestorFragmentEntities(final AnchorEntity anchorEntity,
+ private Collection<FragmentEntity> getAncestorFragmentEntities(final int anchorId,
final CpsPathQuery cpsPathQuery,
- Collection<FragmentEntity> fragmentEntities) {
- final Set<String> ancestorXpaths = processAncestorXpath(fragmentEntities, cpsPathQuery);
- fragmentEntities = ancestorXpaths.isEmpty() ? Collections.emptyList()
- : fragmentRepository.findAllByAnchorAndXpathIn(anchorEntity, ancestorXpaths);
- return fragmentEntities;
+ final Collection<FragmentEntity> fragmentEntities) {
+ final Collection<String> ancestorXpaths = processAncestorXpath(fragmentEntities, cpsPathQuery);
+ return ancestorXpaths.isEmpty() ? Collections.emptyList()
+ : fragmentRepository.findByAnchorAndMultipleCpsPaths(anchorId, ancestorXpaths);
}
private List<DataNode> createDataNodesFromProxiedFragmentEntities(
@@ -435,6 +455,15 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
.withChildDataNodes(childDataNodes).build();
}
+ private Collection<DataNode> toDataNodes(final Collection<FragmentEntity> fragmentEntities,
+ final FetchDescendantsOption fetchDescendantsOption) {
+ final Collection<DataNode> dataNodes = new ArrayList<>(fragmentEntities.size());
+ for (final FragmentEntity fragmentEntity : fragmentEntities) {
+ dataNodes.add(toDataNode(fragmentEntity, fetchDescendantsOption));
+ }
+ return dataNodes;
+ }
+
private List<DataNode> getChildDataNodes(final FragmentEntity fragmentEntity,
final FetchDescendantsOption fetchDescendantsOption) {
if (fetchDescendantsOption.hasNext()) {
@@ -447,9 +476,11 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
@Override
public void updateDataLeaves(final String dataspaceName, final String anchorName, final String xpath,
- final Map<String, Serializable> leaves) {
+ final Map<String, Serializable> updateLeaves) {
final FragmentEntity fragmentEntity = getFragmentWithoutDescendantsByXpath(dataspaceName, anchorName, xpath);
- fragmentEntity.setAttributes(jsonObjectMapper.asJsonString(leaves));
+ final String currentLeavesAsString = fragmentEntity.getAttributes();
+ final String mergedLeaves = mergeLeaves(updateLeaves, currentLeavesAsString);
+ fragmentEntity.setAttributes(mergedLeaves);
fragmentRepository.save(fragmentEntity);
}
@@ -694,4 +725,14 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
private static boolean isRootXpath(final String xpath) {
return "/".equals(xpath) || "".equals(xpath);
}
+
+ private String mergeLeaves(final Map<String, Serializable> updateLeaves, final String currentLeavesAsString) {
+ final Map<String, Serializable> currentLeavesAsMap = currentLeavesAsString.isEmpty()
+ ? new HashMap<>() : jsonObjectMapper.convertJsonString(currentLeavesAsString, Map.class);
+ currentLeavesAsMap.putAll(updateLeaves);
+ if (currentLeavesAsMap.isEmpty()) {
+ return "";
+ }
+ return jsonObjectMapper.asJsonString(currentLeavesAsMap);
+ }
}
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepository.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepository.java
index c9461bf06..4b42b2da8 100755
--- a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepository.java
+++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepository.java
@@ -39,7 +39,8 @@ import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
@Repository
-public interface FragmentRepository extends JpaRepository<FragmentEntity, Long>, FragmentRepositoryCpsPathQuery {
+public interface FragmentRepository extends JpaRepository<FragmentEntity, Long>, FragmentRepositoryCpsPathQuery,
+ FragmentRepositoryMultiPathQuery {
Optional<FragmentEntity> findByDataspaceAndAnchorAndXpath(@NonNull DataspaceEntity dataspaceEntity,
@NonNull AnchorEntity anchorEntity,
@@ -80,9 +81,6 @@ public interface FragmentRepository extends JpaRepository<FragmentEntity, Long>,
return fragmentExtracts;
}
- List<FragmentEntity> findAllByAnchorAndXpathIn(@NonNull AnchorEntity anchorEntity,
- @NonNull Collection<String> xpath);
-
@Modifying
@Query("DELETE FROM FragmentEntity fe WHERE fe.anchor IN (:anchors)")
void deleteByAnchorIn(@NotNull @Param("anchors") Collection<AnchorEntity> anchorEntities);
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryMultiPathQuery.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryMultiPathQuery.java
new file mode 100644
index 000000000..9c34a459e
--- /dev/null
+++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryMultiPathQuery.java
@@ -0,0 +1,31 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2022 Nordix Foundation.
+ * ================================================================================
+ * 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.spi.repository;
+
+import java.util.Collection;
+import java.util.List;
+import org.onap.cps.spi.entities.FragmentEntity;
+
+public interface FragmentRepositoryMultiPathQuery {
+
+ List<FragmentEntity> findByAnchorAndMultipleCpsPaths(Integer anchorId, Collection<String> cpsPathQuery);
+
+}
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryMultiPathQueryImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryMultiPathQueryImpl.java
new file mode 100644
index 000000000..8c357bbb3
--- /dev/null
+++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryMultiPathQueryImpl.java
@@ -0,0 +1,74 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2022-2023 Nordix Foundation.
+ * ================================================================================
+ * 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.spi.repository;
+
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import javax.transaction.Transactional;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.spi.entities.FragmentEntity;
+
+
+@Slf4j
+@AllArgsConstructor
+public class FragmentRepositoryMultiPathQueryImpl implements FragmentRepositoryMultiPathQuery {
+
+ @PersistenceContext
+ private EntityManager entityManager;
+
+ private TempTableCreator tempTableCreator;
+
+ @Override
+ @Transactional
+ public List<FragmentEntity> findByAnchorAndMultipleCpsPaths(final Integer anchorId,
+ final Collection<String> cpsPathQueryList) {
+ if (cpsPathQueryList.isEmpty()) {
+ return Collections.emptyList();
+ }
+ final Collection<List<String>> sqlData = new HashSet<>(cpsPathQueryList.size());
+ for (final String query : cpsPathQueryList) {
+ final List<String> row = new ArrayList<>(1);
+ row.add(query);
+ sqlData.add(row);
+ }
+
+ final String tempTableName = tempTableCreator.createTemporaryTable(
+ "xpathTemporaryTable", sqlData, "xpath");
+ return selectMatchingFragments(anchorId, tempTableName);
+ }
+
+ private List<FragmentEntity> selectMatchingFragments(final Integer anchorId, final String tempTableName) {
+ final String sql = String.format(
+ "SELECT * FROM FRAGMENT WHERE anchor_id = %d AND xpath IN (select xpath FROM %s);",
+ anchorId, tempTableName);
+ final List<FragmentEntity> fragmentEntities = entityManager.createNativeQuery(sql, FragmentEntity.class)
+ .getResultList();
+ log.debug("Fetched {} fragment entities by anchor and cps path.", fragmentEntities.size());
+ return fragmentEntities;
+ }
+}
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/TempTableCreator.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/TempTableCreator.java
index 8cad9f5e4..d713746e4 100644
--- a/cps-ri/src/main/java/org/onap/cps/spi/repository/TempTableCreator.java
+++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/TempTableCreator.java
@@ -26,6 +26,7 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
+import java.util.stream.Collectors;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import lombok.AllArgsConstructor;
@@ -82,8 +83,10 @@ public class TempTableCreator {
final String[] columnNames,
final Collection<List<String>> sqlData) {
final Collection<String> sqlInserts = new HashSet<>(sqlData.size());
- for (final Collection<String> row : sqlData) {
- sqlInserts.add("('" + String.join("','", row) + "')");
+ for (final Collection<String> rowValues : sqlData) {
+ final Collection<String> escapedValues =
+ rowValues.stream().map(it -> escapeSingleQuotesByDoublingThem(it)).collect(Collectors.toList());
+ sqlInserts.add("('" + String.join("','", escapedValues) + "')");
}
sqlStringBuilder.append("INSERT INTO ");
sqlStringBuilder.append(tempTableName);
@@ -94,4 +97,8 @@ public class TempTableCreator {
sqlStringBuilder.append(";");
}
+ private static String escapeSingleQuotesByDoublingThem(final String value) {
+ return value.replace("'", "''");
+ }
+
}
diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceQueryDataNodeSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceQueryDataNodeSpec.groovy
index 59bbfd854..ba8425fef 100644
--- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceQueryDataNodeSpec.groovy
+++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceQueryDataNodeSpec.groovy
@@ -28,6 +28,8 @@ import org.onap.cps.spi.exceptions.CpsPathException
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.test.context.jdbc.Sql
+import java.util.stream.Collectors
+
import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
@@ -151,27 +153,30 @@ class CpsDataPersistenceQueryDataNodeSpec extends CpsPersistenceSpecBase {
def result = objectUnderTest.queryDataNodes(DATASPACE_NAME, ANCHOR_FOR_SHOP_EXAMPLE, cpsPath, INCLUDE_ALL_DESCENDANTS)
then: 'the xpaths of the retrieved data nodes are as expected'
result.size() == expectedXPaths.size()
- for (int i = 0; i < result.size(); i++) {
- assert result[i].getXpath() == expectedXPaths[i]
- assert result[i].childDataNodes.size() == expectedNumberOfChildren[i]
+ if (result.size() > 0) {
+ def resultXpaths = result.stream().map(it -> it.xpath).collect(Collectors.toSet())
+ resultXpaths.containsAll(expectedXPaths)
+ result.each {
+ assert it.childDataNodes.size() == expectedNumberOfChildren
+ }
}
where: 'the following data is used'
scenario | cpsPath || expectedXPaths || expectedNumberOfChildren
- 'multiple list-ancestors' | '//book/ancestor::categories' || ["/shops/shop[@id='1']/categories[@code='1']", "/shops/shop[@id='1']/categories[@code='2']"] || [1, 1]
- 'one ancestor with list value' | '//book/ancestor::categories[@code=1]' || ["/shops/shop[@id='1']/categories[@code='1']"] || [1]
- 'top ancestor' | '//shop[@id=1]/ancestor::shops' || ['/shops'] || [5]
- 'list with index value in the xpath prefix' | '//categories[@code=1]/book/ancestor::shop[@id=1]' || ["/shops/shop[@id='1']"] || [3]
- 'ancestor with parent list' | '//book/ancestor::shop[@id=1]/categories[@code=2]' || ["/shops/shop[@id='1']/categories[@code='2']"] || [1]
- 'ancestor with parent' | '//phonenumbers[@type="mob"]/ancestor::info/contact' || ["/shops/shop[@id='3']/info/contact"] || [3]
- 'ancestor combined with text condition' | '//book/title[text()="Dune"]/ancestor::shop' || ["/shops/shop[@id='1']"] || [3]
- 'ancestor with parent that does not exist' | '//book/ancestor::parentDoesNoExist/categories' || [] || []
- 'ancestor does not exist' | '//book/ancestor::ancestorDoesNotExist' || [] || []
+ 'multiple list-ancestors' | '//book/ancestor::categories' || ["/shops/shop[@id='1']/categories[@code='2']", "/shops/shop[@id='1']/categories[@code='1']"] || 1
+ 'one ancestor with list value' | '//book/ancestor::categories[@code=1]' || ["/shops/shop[@id='1']/categories[@code='1']"] || 1
+ 'top ancestor' | '//shop[@id=1]/ancestor::shops' || ['/shops'] || 5
+ 'list with index value in the xpath prefix' | '//categories[@code=1]/book/ancestor::shop[@id=1]' || ["/shops/shop[@id='1']"] || 3
+ 'ancestor with parent list' | '//book/ancestor::shop[@id=1]/categories[@code=2]' || ["/shops/shop[@id='1']/categories[@code='2']"] || 1
+ 'ancestor with parent' | '//phonenumbers[@type="mob"]/ancestor::info/contact' || ["/shops/shop[@id='3']/info/contact"] || 3
+ 'ancestor combined with text condition' | '//book/title[text()="Dune"]/ancestor::shop' || ["/shops/shop[@id='1']"] || 3
+ 'ancestor with parent that does not exist' | '//book/ancestor::parentDoesNoExist/categories' || [] || null
+ 'ancestor does not exist' | '//book/ancestor::ancestorDoesNotExist' || [] || null
}
def 'Cps Path query with syntax error throws a CPS Path Exception.'() {
when: 'trying to execute a query with a syntax (parsing) error'
objectUnderTest.queryDataNodes(DATASPACE_NAME, ANCHOR_FOR_SHOP_EXAMPLE, 'cpsPath that cannot be parsed' , OMIT_DESCENDANTS)
- then: 'exception is thrown'
+ then: 'a cps path exception is thrown'
thrown(CpsPathException)
}
diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy
index cc2369d50..6252fff56 100755
--- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy
+++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy
@@ -1,6 +1,6 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2021-2022 Nordix Foundation
+ * Copyright (C) 2021-2023 Nordix Foundation
* Modifications Copyright (C) 2021 Pantheon.tech
* Modifications Copyright (C) 2021-2022 Bell Canada.
* Modifications Copyright (C) 2022 TechMahindra Ltd.
@@ -296,6 +296,39 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
}
@Sql([CLEAR_DATA, SET_DATA])
+ def 'Get multiple data nodes by xpath.'() {
+ when: 'fetch #scenario.'
+ def results = objectUnderTest.getDataNodes(DATASPACE_NAME, ANCHOR_NAME3, inputXpaths, OMIT_DESCENDANTS)
+ then: 'the expected number of data nodes are returned'
+ assert results.size() == expectedResultSize
+ where: 'following parameters were used'
+ scenario | inputXpaths || expectedResultSize
+ '1 node' | ["/parent-200"] || 1
+ '2 unique nodes' | ["/parent-200", "/parent-201"] || 2
+ '3 unique nodes' | ["/parent-200", "/parent-201", "/parent-202"] || 3
+ '1 unique node with duplicate xpath' | ["/parent-200", "/parent-200"] || 1
+ '2 unique nodes with duplicate xpath' | ["/parent-200", "/parent-202", "/parent-200"] || 2
+ 'list element with key (single quote)' | ["/parent-201/child-204[@key='A']"] || 1
+ 'list element with key (double quote)' | ['/parent-201/child-204[@key="A"]'] || 1
+ 'non-existing xpath' | ["/NO-XPATH"] || 0
+ 'existing and non-existing xpaths' | ["/parent-200", "/NO-XPATH", "/parent-201"] || 2
+ 'invalid xpath' | ["INVALID XPATH"] || 0
+ 'valid and invalid xpaths' | ["/parent-200", "INVALID XPATH", "/parent-201"] || 2
+ }
+
+ @Sql([CLEAR_DATA, SET_DATA])
+ def 'Get multiple data nodes error scenario: #scenario.'() {
+ when: 'attempt to get data nodes with #scenario'
+ objectUnderTest.getDataNodes(dataspaceName, anchorName, ['/not-relevant'], OMIT_DESCENDANTS)
+ then: 'a #expectedException is thrown'
+ thrown(expectedException)
+ where: 'the following data is used'
+ scenario | dataspaceName | anchorName || expectedException
+ 'non-existing dataspace' | 'NO DATASPACE' | 'not relevant' || DataspaceNotFoundException
+ 'non-existing anchor' | DATASPACE_NAME | 'NO ANCHOR' || AnchorNotFoundException
+ }
+
+ @Sql([CLEAR_DATA, SET_DATA])
def 'Update data node leaves.'() {
when: 'update is performed for leaves'
objectUnderTest.updateDataLeaves(DATASPACE_NAME, ANCHOR_FOR_DATA_NODES_WITH_LEAVES,
diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy
index 255e8e52f..87e59c60d 100644
--- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy
+++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy
@@ -1,8 +1,8 @@
/*
* ============LICENSE_START=======================================================
* Copyright (c) 2021 Bell Canada.
- * Modifications Copyright (C) 2021-2022 Nordix Foundation
- * Modifications Copyright (C) 2022 TechMahindra Ltd.
+ * Modifications Copyright (C) 2021-2023 Nordix Foundation
+ * Modifications Copyright (C) 2022 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -107,7 +107,6 @@ class CpsDataPersistenceServiceSpec extends Specification {
assert thrown.details.contains('/node3')
}
-
def 'Retrieving a data node with a property JSON value of #scenario'() {
given: 'the db has a fragment with an attribute property JSON value of #scenario'
mockFragmentWithJson("{\"some attribute\": ${dataString}}")
@@ -142,6 +141,20 @@ class CpsDataPersistenceServiceSpec extends Specification {
thrown(DataValidationException)
}
+ def 'Retrieving multiple data nodes.'() {
+ given: 'db contains an anchor'
+ def anchorEntity = new AnchorEntity(id:123)
+ mockAnchorRepository.getByDataspaceAndName(*_) >> anchorEntity
+ and: 'fragment repository returns a collection of fragments'
+ def fragmentEntity1 = new FragmentEntity(xpath: '/xpath1', childFragments: [])
+ def fragmentEntity2 = new FragmentEntity(xpath: '/xpath2', childFragments: [])
+ mockFragmentRepository.findByAnchorAndMultipleCpsPaths(123, ['/xpath1', '/xpath2'] as Set<String>) >> [fragmentEntity1, fragmentEntity2]
+ when: 'getting data nodes for 2 xpaths'
+ def result = objectUnderTest.getDataNodes('some-dataspace', 'some-anchor', ['/xpath1', '/xpath2'], FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS)
+ then: '2 data nodes are returned'
+ assert result.size() == 2
+ }
+
def 'start session'() {
when: 'start session'
objectUnderTest.startSession()
@@ -165,6 +178,25 @@ class CpsDataPersistenceServiceSpec extends Specification {
1 * mockSessionManager.lockAnchor('mySessionId', 'myDataspaceName', 'myAnchorName', 123L)
}
+ def 'update data node leaves: #scenario'(){
+ given: 'A node exists for the given xpath'
+ mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, '/some/xpath') >> new FragmentEntity(xpath: '/some/xpath', attributes: existingAttributes)
+ when: 'the node leaves are updated'
+ objectUnderTest.updateDataLeaves('some-dataspace', 'some-anchor', '/some/xpath', newAttributes as Map<String, Serializable>)
+ then: 'the fragment entity saved has the original and new attributes'
+ 1 * mockFragmentRepository.save({fragmentEntity -> {
+ assert fragmentEntity.getXpath() == '/some/xpath'
+ assert fragmentEntity.getAttributes() == mergedAttributes
+ }})
+ where: 'the following attributes combinations are used'
+ scenario | existingAttributes | newAttributes | mergedAttributes
+ 'add new leaf' | '{"existing":"value"}' | ["new":"value"] | '{"existing":"value","new":"value"}'
+ 'update existing leaf' | '{"existing":"value"}' | ["existing":"value2"] | '{"existing":"value2"}'
+ 'update nothing with nothing' | '' | [] | ''
+ 'update with nothing' | '{"existing":"value"}' | [] | '{"existing":"value"}'
+ 'update with same value' | '{"existing":"value"}' | ["existing":"value"] | '{"existing":"value"}'
+ }
+
def 'update data node and descendants: #scenario'(){
given: 'mocked responses'
mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, '/test/xpath') >> new FragmentEntity(xpath: '/test/xpath', childFragments: [])
@@ -208,11 +240,8 @@ class CpsDataPersistenceServiceSpec extends Specification {
}
def mockFragmentWithJson(json) {
- def anchorName = 'some anchor'
- def mockAnchor = Mock(AnchorEntity)
- mockAnchor.getId() >> 123
- mockAnchor.getName() >> anchorName
- mockAnchorRepository.getByDataspaceAndName(*_) >> mockAnchor
+ def anchorEntity = new AnchorEntity(id:123)
+ mockAnchorRepository.getByDataspaceAndName(*_) >> anchorEntity
def mockFragmentExtract = Mock(FragmentExtract)
mockFragmentExtract.getId() >> 456
mockFragmentExtract.getAttributes() >> json
diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsPersistencePerfSpecBase.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsPersistencePerfSpecBase.groovy
new file mode 100644
index 000000000..3bbae2d08
--- /dev/null
+++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsPersistencePerfSpecBase.groovy
@@ -0,0 +1,74 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2023 Nordix Foundation
+ * ================================================================================
+ * 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.spi.impl
+
+import org.onap.cps.spi.model.DataNode
+import org.onap.cps.spi.model.DataNodeBuilder
+
+class CpsPersistencePerfSpecBase extends CpsPersistenceSpecBase {
+
+ static final String PERF_TEST_DATA = '/data/perf-test.sql'
+ static final String PERF_DATASPACE = 'PERF-DATASPACE'
+ static final String PERF_ANCHOR = 'PERF-ANCHOR'
+ static final String PERF_TEST_PARENT = '/perf-parent-1'
+
+ static def xpathsToAllGrandChildren = []
+
+ def createLineage(cpsDataPersistenceService, numberOfChildren, numberOfGrandChildren, createLists) {
+ xpathsToAllGrandChildren = []
+ (1..numberOfChildren).each {
+ if (createLists) {
+ def xpathFormat = "${PERF_TEST_PARENT}/perf-test-list-${it}[@key='%d']"
+ def listElements = goForthAndMultiply(xpathFormat, numberOfGrandChildren)
+ cpsDataPersistenceService.addListElements(PERF_DATASPACE, PERF_ANCHOR, PERF_TEST_PARENT, listElements)
+ } else {
+ def xpathFormat = "${PERF_TEST_PARENT}/perf-test-child-${it}/perf-test-grand-child-%d"
+ def grandChildren = goForthAndMultiply(xpathFormat, numberOfGrandChildren)
+ def child = new DataNodeBuilder()
+ .withXpath("${PERF_TEST_PARENT}/perf-test-child-${it}")
+ .withChildDataNodes(grandChildren)
+ .build()
+ cpsDataPersistenceService.addChildDataNode(PERF_DATASPACE, PERF_ANCHOR, PERF_TEST_PARENT, child)
+ }
+ }
+ }
+
+ def goForthAndMultiply(xpathFormat, numberOfGrandChildren) {
+ def grandChildren = []
+ (1..numberOfGrandChildren).each {
+ def xpath = String.format(xpathFormat as String, it)
+ def grandChild = new DataNodeBuilder().withXpath(xpath).build()
+ xpathsToAllGrandChildren.add(grandChild.xpath)
+ grandChildren.add(grandChild)
+ }
+ return grandChildren
+ }
+
+ def countDataNodes(dataNodes) {
+ int nodeCount = 1
+ for (DataNode parent : dataNodes) {
+ for (DataNode child : parent.childDataNodes) {
+ nodeCount = nodeCount + (countDataNodes(child))
+ }
+ }
+ return nodeCount
+ }
+}
diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsDataPersistenceServiceDeletePerfTest.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsDataPersistenceServiceDeletePerfTest.groovy
new file mode 100644
index 000000000..5aae285d7
--- /dev/null
+++ b/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsDataPersistenceServiceDeletePerfTest.groovy
@@ -0,0 +1,154 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2023 Nordix Foundation
+ * ================================================================================
+ * 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.spi.performance
+
+import org.onap.cps.spi.CpsDataPersistenceService
+import org.onap.cps.spi.impl.CpsPersistencePerfSpecBase
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.test.context.jdbc.Sql
+import org.springframework.util.StopWatch
+
+import java.util.concurrent.TimeUnit
+
+class CpsDataPersistenceServiceDeletePerfTest extends CpsPersistencePerfSpecBase {
+
+ @Autowired
+ CpsDataPersistenceService objectUnderTest
+
+ static def NUMBER_OF_CHILDREN = 100
+ static def NUMBER_OF_GRAND_CHILDREN = 50
+ static def NUMBER_OF_LISTS = 100
+ static def NUMBER_OF_LIST_ELEMENTS = 50
+ static def ALLOWED_SETUP_TIME_MS = TimeUnit.SECONDS.toMillis(10)
+
+ def stopWatch = new StopWatch()
+
+ @Sql([CLEAR_DATA, PERF_TEST_DATA])
+ def 'Create a node with many descendants (please note, subsequent tests depend on this running first).'() {
+ given: 'a node with a large number of descendants is created'
+ stopWatch.start()
+ createLineage(objectUnderTest, NUMBER_OF_CHILDREN, NUMBER_OF_GRAND_CHILDREN, false)
+ stopWatch.stop()
+ def setupDurationInMillis = stopWatch.getTotalTimeMillis()
+ and: 'setup duration is under #ALLOWED_SETUP_TIME_MS milliseconds'
+ assert setupDurationInMillis < ALLOWED_SETUP_TIME_MS
+ }
+
+ def 'Delete 5 children with grandchildren'() {
+ when: 'child nodes are deleted'
+ stopWatch.start()
+ (1..5).each {
+ def childPath = "${PERF_TEST_PARENT}/perf-test-child-${it}".toString();
+ objectUnderTest.deleteDataNode(PERF_DATASPACE, PERF_ANCHOR, childPath)
+ }
+ stopWatch.stop()
+ def deleteDurationInMillis = stopWatch.getTotalTimeMillis()
+ then: 'delete duration is under 6000 milliseconds'
+ assert deleteDurationInMillis < 6000
+ }
+
+ def 'Delete 50 grandchildren (that have no descendants)'() {
+ when: 'target nodes are deleted'
+ stopWatch.start()
+ (1..50).each {
+ def grandchildPath = "${PERF_TEST_PARENT}/perf-test-child-6/perf-test-grand-child-${it}".toString();
+ objectUnderTest.deleteDataNode(PERF_DATASPACE, PERF_ANCHOR, grandchildPath)
+ }
+ stopWatch.stop()
+ def deleteDurationInMillis = stopWatch.getTotalTimeMillis()
+ then: 'delete duration is under 500 milliseconds'
+ assert deleteDurationInMillis < 500
+ }
+
+ def 'Delete 1 large data node with many descendants'() {
+ when: 'parent node is deleted'
+ stopWatch.start()
+ objectUnderTest.deleteDataNode(PERF_DATASPACE, PERF_ANCHOR, PERF_TEST_PARENT)
+ stopWatch.stop()
+ def deleteDurationInMillis = stopWatch.getTotalTimeMillis()
+ then: 'delete duration is under 2500 milliseconds'
+ assert deleteDurationInMillis < 2500
+ }
+
+ @Sql([CLEAR_DATA, PERF_TEST_DATA])
+ def 'Create a node with many list elements (please note, subsequent tests depend on this running first).'() {
+ given: 'a node with a large number of descendants is created'
+ stopWatch.start()
+ createLineage(objectUnderTest, NUMBER_OF_LISTS, NUMBER_OF_LIST_ELEMENTS, true)
+ stopWatch.stop()
+ def setupDurationInMillis = stopWatch.getTotalTimeMillis()
+ and: 'setup duration is under #ALLOWED_SETUP_TIME_MS milliseconds'
+ assert setupDurationInMillis < ALLOWED_SETUP_TIME_MS
+ }
+
+ def 'Delete 5 whole lists with many elements'() {
+ when: 'list nodes are deleted'
+ stopWatch.start()
+ (1..5).each {
+ def childPath = "${PERF_TEST_PARENT}/perf-test-list-${it}".toString();
+ objectUnderTest.deleteListDataNode(PERF_DATASPACE, PERF_ANCHOR, childPath)
+ }
+ stopWatch.stop()
+ def deleteDurationInMillis = stopWatch.getTotalTimeMillis()
+ then: 'delete duration is under 4000 milliseconds'
+ assert deleteDurationInMillis < 4000
+ }
+
+ def 'Delete 10 list elements with keys'() {
+ when: 'list elements are deleted'
+ stopWatch.start()
+ (1..10).each {
+ def key = it.toString()
+ def grandchildPath = "${PERF_TEST_PARENT}/perf-test-list-6[@key='${key}']"
+ objectUnderTest.deleteListDataNode(PERF_DATASPACE, PERF_ANCHOR, grandchildPath)
+ }
+ stopWatch.stop()
+ def deleteDurationInMillis = stopWatch.getTotalTimeMillis()
+ then: 'delete duration is under 6000 milliseconds'
+ assert deleteDurationInMillis < 6000
+ }
+
+ @Sql([CLEAR_DATA, PERF_TEST_DATA])
+ def 'Delete root node with many descendants'() {
+ given: 'a node with a large number of descendants is created'
+ createLineage(objectUnderTest, NUMBER_OF_CHILDREN, NUMBER_OF_GRAND_CHILDREN, false)
+ when: 'root node is deleted'
+ stopWatch.start()
+ objectUnderTest.deleteDataNode(PERF_DATASPACE, PERF_ANCHOR, '/')
+ stopWatch.stop()
+ def deleteDurationInMillis = stopWatch.getTotalTimeMillis()
+ then: 'delete duration is under 250 milliseconds'
+ assert deleteDurationInMillis < 250
+ }
+
+ @Sql([CLEAR_DATA, PERF_TEST_DATA])
+ def 'Delete data nodes for an anchor'() {
+ given: 'a node with a large number of descendants is created'
+ createLineage(objectUnderTest, NUMBER_OF_CHILDREN, NUMBER_OF_GRAND_CHILDREN, false)
+ when: 'data nodes are deleted'
+ stopWatch.start()
+ objectUnderTest.deleteDataNodes(PERF_DATASPACE, PERF_ANCHOR)
+ stopWatch.stop()
+ def deleteDurationInMillis = stopWatch.getTotalTimeMillis()
+ then: 'delete duration is under 250 milliseconds'
+ assert deleteDurationInMillis < 250
+ }
+}
diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsToDataNodePerfTest.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsDataPersistenceServicePerfTest.groovy
index 265c5fc5f..2346239df 100644
--- a/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsToDataNodePerfTest.groovy
+++ b/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsDataPersistenceServicePerfTest.groovy
@@ -1,6 +1,6 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2022 Nordix Foundation
+ * Copyright (C) 2022-2023 Nordix Foundation
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,11 +20,12 @@
package org.onap.cps.spi.performance
+import org.onap.cps.spi.impl.CpsPersistencePerfSpecBase
import org.springframework.util.StopWatch
import org.onap.cps.spi.CpsDataPersistenceService
-import org.onap.cps.spi.impl.CpsPersistenceSpecBase
-import org.onap.cps.spi.model.DataNode
-import org.onap.cps.spi.model.DataNodeBuilder
+import org.onap.cps.spi.repository.AnchorRepository
+import org.onap.cps.spi.repository.DataspaceRepository
+import org.onap.cps.spi.repository.FragmentRepository
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.test.context.jdbc.Sql
@@ -33,41 +34,46 @@ import java.util.concurrent.TimeUnit
import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
-class CpsToDataNodePerfTest extends CpsPersistenceSpecBase {
-
- static final String PERF_TEST_DATA = '/data/perf-test.sql'
+class CpsDataPersistenceServicePerfTest extends CpsPersistencePerfSpecBase {
@Autowired
CpsDataPersistenceService objectUnderTest
- static def PERF_TEST_PARENT = '/perf-parent-1'
+ @Autowired
+ DataspaceRepository dataspaceRepository
+
+ @Autowired
+ AnchorRepository anchorRepository
+
+ @Autowired
+ FragmentRepository fragmentRepository
+
static def NUMBER_OF_CHILDREN = 200
static def NUMBER_OF_GRAND_CHILDREN = 50
static def TOTAL_NUMBER_OF_NODES = 1 + NUMBER_OF_CHILDREN + (NUMBER_OF_CHILDREN * NUMBER_OF_GRAND_CHILDREN) // Parent + Children + Grand-children
- static def ALLOWED_SETUP_TIME_MS = TimeUnit.SECONDS.toMillis(10)
- static def ALLOWED_READ_TIME_AL_NODES_MS = 500
def stopWatch = new StopWatch()
+ def readStopWatch = new StopWatch()
@Sql([CLEAR_DATA, PERF_TEST_DATA])
def 'Create a node with many descendants (please note, subsequent tests depend on this running first).'() {
given: 'a node with a large number of descendants is created'
stopWatch.start()
- createLineage()
+ createLineage(objectUnderTest, NUMBER_OF_CHILDREN, NUMBER_OF_GRAND_CHILDREN, false)
stopWatch.stop()
def setupDurationInMillis = stopWatch.getTotalTimeMillis()
- and: 'setup duration is under #ALLOWED_SETUP_TIME_MS milliseconds'
- assert setupDurationInMillis < ALLOWED_SETUP_TIME_MS
+ and: 'setup duration is under 10 seconds'
+ assert setupDurationInMillis < 10000
}
def 'Get data node with many descendants by xpath #scenario'() {
when: 'get parent is executed with all descendants'
stopWatch.start()
- def result = objectUnderTest.getDataNode('PERF-DATASPACE', 'PERF-ANCHOR', xpath, INCLUDE_ALL_DESCENDANTS)
+ def result = objectUnderTest.getDataNode(PERF_DATASPACE, PERF_ANCHOR, xpath, INCLUDE_ALL_DESCENDANTS)
stopWatch.stop()
def readDurationInMillis = stopWatch.getTotalTimeMillis()
then: 'read duration is under 500 milliseconds'
- assert readDurationInMillis < ALLOWED_READ_TIME_AL_NODES_MS
+ assert readDurationInMillis < 500
and: 'data node is returned with all the descendants populated'
assert countDataNodes(result) == TOTAL_NUMBER_OF_NODES
where: 'the following xPaths are used'
@@ -79,55 +85,41 @@ class CpsToDataNodePerfTest extends CpsPersistenceSpecBase {
def 'Query parent data node with many descendants by cps-path'() {
when: 'query is executed with all descendants'
stopWatch.start()
- def result = objectUnderTest.queryDataNodes('PERF-DATASPACE', 'PERF-ANCHOR', '//perf-parent-1' , INCLUDE_ALL_DESCENDANTS)
+ def result = objectUnderTest.queryDataNodes(PERF_DATASPACE, PERF_ANCHOR, '//perf-parent-1' , INCLUDE_ALL_DESCENDANTS)
stopWatch.stop()
def readDurationInMillis = stopWatch.getTotalTimeMillis()
then: 'read duration is under 500 milliseconds'
- assert readDurationInMillis < ALLOWED_READ_TIME_AL_NODES_MS
+ assert readDurationInMillis < 500
and: 'data node is returned with all the descendants populated'
assert countDataNodes(result) == TOTAL_NUMBER_OF_NODES
}
+ def 'Performance of finding multiple xpaths'() {
+ when: 'we query for all grandchildren (except 1 for fun) with the new native method'
+ xpathsToAllGrandChildren.remove(0)
+ readStopWatch.start()
+ def result = objectUnderTest.getDataNodes(PERF_DATASPACE, PERF_ANCHOR, xpathsToAllGrandChildren, INCLUDE_ALL_DESCENDANTS)
+ readStopWatch.stop()
+ def readDurationInMillis = readStopWatch.getTotalTimeMillis()
+ then: 'the returned number of entities equal to the number of children * number of grandchildren'
+ assert result.size() == xpathsToAllGrandChildren.size()
+ and: 'it took less then 4000ms'
+ assert readDurationInMillis < 4000
+ }
+
def 'Query many descendants by cps-path with #scenario'() {
when: 'query is executed with all descendants'
stopWatch.start()
- def result = objectUnderTest.queryDataNodes('PERF-DATASPACE', 'PERF-ANCHOR', '//perf-test-grand-child-1', descendantsOption)
+ def result = objectUnderTest.queryDataNodes(PERF_DATASPACE, PERF_ANCHOR, '//perf-test-grand-child-1', descendantsOption)
stopWatch.stop()
def readDurationInMillis = stopWatch.getTotalTimeMillis()
- then: 'read duration is under 500 milliseconds'
- assert readDurationInMillis < alowedDuration
+ then: 'read duration is under #allowedDuration milliseconds'
+ assert readDurationInMillis < allowedDuration
and: 'data node is returned with all the descendants populated'
assert result.size() == NUMBER_OF_CHILDREN
where: 'the following options are used'
- scenario | descendantsOption || alowedDuration
+ scenario | descendantsOption || allowedDuration
'omit descendants ' | OMIT_DESCENDANTS || 150
'include descendants (although there are none)' | INCLUDE_ALL_DESCENDANTS || 150
}
-
- def createLineage() {
- (1..NUMBER_OF_CHILDREN).each {
- def childName = "perf-test-child-${it}".toString()
- def child = goForthAndMultiply(PERF_TEST_PARENT, childName)
- objectUnderTest.addChildDataNode('PERF-DATASPACE', 'PERF-ANCHOR', PERF_TEST_PARENT, child)
- }
- }
-
- def goForthAndMultiply(parentXpath, childName) {
- def grandChildren = []
- (1..NUMBER_OF_GRAND_CHILDREN).each {
- def grandChild = new DataNodeBuilder().withXpath("${parentXpath}/${childName}/perf-test-grand-child-${it}").build()
- grandChildren.add(grandChild)
- }
- return new DataNodeBuilder().withXpath("${parentXpath}/${childName}").withChildDataNodes(grandChildren).build()
- }
-
- def countDataNodes(dataNodes) {
- int nodeCount = 1
- for (DataNode parent : dataNodes) {
- for (DataNode child : parent.childDataNodes) {
- nodeCount = nodeCount + (countDataNodes(child))
- }
- }
- return nodeCount
- }
}