From ce3451d10315b54238b886491779b3975f326e02 Mon Sep 17 00:00:00 2001 From: "rajesh.kumar" Date: Fri, 21 Apr 2023 17:48:44 +0530 Subject: Fix issues related to query across all anchors CPS-1580: Query Across All Anchors Does NOT Filter on Dataspace CPS-1582: NullPointerException in queryDataNodesAcrossAnchors Issue-ID: CPS-1580 Change-ID: I73f97f986a817d423f93a8d922dcd9647b2104ab Signed-off-by: rajesh.kumar --- .../org/onap/cps/spi/entities/AnchorEntity.java | 4 ++ .../org/onap/cps/spi/entities/DataspaceEntity.java | 4 ++ .../org/onap/cps/spi/entities/FragmentEntity.java | 3 ++ .../cps/spi/entities/FragmentEntityArranger.java | 20 ++++++++ .../spi/impl/CpsDataPersistenceServiceImpl.java | 53 +++++++++++++++++----- .../cps/spi/repository/FragmentQueryBuilder.java | 8 ++-- .../cps/spi/repository/FragmentRepository.java | 10 ++-- .../repository/FragmentRepositoryCpsPathQuery.java | 2 +- .../FragmentRepositoryCpsPathQueryImpl.java | 4 +- .../CpsDataPersistenceQueryDataNodeSpec.groovy | 1 + .../spi/impl/CpsDataPersistenceServiceSpec.groovy | 30 ++++++------ 11 files changed, 103 insertions(+), 36 deletions(-) diff --git a/cps-ri/src/main/java/org/onap/cps/spi/entities/AnchorEntity.java b/cps-ri/src/main/java/org/onap/cps/spi/entities/AnchorEntity.java index b89342827..3b0e1834e 100644 --- a/cps-ri/src/main/java/org/onap/cps/spi/entities/AnchorEntity.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/entities/AnchorEntity.java @@ -1,6 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2021 Pantheon.tech + * Modifications Copyright (C) 2023 TechMahindra Ltd. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,6 +33,7 @@ import javax.persistence.Table; import javax.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -46,12 +48,14 @@ import lombok.Setter; @Builder @Entity @Table(name = "anchor") +@EqualsAndHashCode(onlyExplicitlyIncluded = true) public class AnchorEntity implements Serializable { private static final long serialVersionUID = -8049987915308262518L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @EqualsAndHashCode.Include private Integer id; @NotNull diff --git a/cps-ri/src/main/java/org/onap/cps/spi/entities/DataspaceEntity.java b/cps-ri/src/main/java/org/onap/cps/spi/entities/DataspaceEntity.java index 593746d94..30906ade3 100644 --- a/cps-ri/src/main/java/org/onap/cps/spi/entities/DataspaceEntity.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/entities/DataspaceEntity.java @@ -2,6 +2,7 @@ * ============LICENSE_START======================================================= * Copyright (C) 2020-2021 Nordix Foundation. * Modifications Copyright (C) 2020-2021 Pantheon.tech + * Modifications Copyright (C) 2023 TechMahindra Ltd. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,6 +31,7 @@ import javax.persistence.Id; import javax.persistence.Table; import javax.validation.constraints.NotNull; import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -44,12 +46,14 @@ import lombok.Setter; @AllArgsConstructor @NoArgsConstructor @Table(name = "dataspace") +@EqualsAndHashCode(onlyExplicitlyIncluded = true) public class DataspaceEntity implements Serializable { private static final long serialVersionUID = 8395254649813051882L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @EqualsAndHashCode.Include private Integer id; @NotNull diff --git a/cps-ri/src/main/java/org/onap/cps/spi/entities/FragmentEntity.java b/cps-ri/src/main/java/org/onap/cps/spi/entities/FragmentEntity.java index 82afc5a81..b90fb799a 100755 --- a/cps-ri/src/main/java/org/onap/cps/spi/entities/FragmentEntity.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/entities/FragmentEntity.java @@ -2,6 +2,7 @@ * ============LICENSE_START======================================================= * Copyright (C) 2020-2023 Nordix Foundation. * Modifications Copyright (C) 2021 Pantheon.tech + * Modifications Copyright (C) 2023 TechMahindra Ltd. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -84,10 +85,12 @@ public class FragmentEntity implements Serializable { @NotNull @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "dataspace_id") + @EqualsAndHashCode.Include private DataspaceEntity dataspace; @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "anchor_id") + @EqualsAndHashCode.Include private AnchorEntity anchor; @ToString.Exclude diff --git a/cps-ri/src/main/java/org/onap/cps/spi/entities/FragmentEntityArranger.java b/cps-ri/src/main/java/org/onap/cps/spi/entities/FragmentEntityArranger.java index 55d3c7e87..a33c8d055 100644 --- a/cps-ri/src/main/java/org/onap/cps/spi/entities/FragmentEntityArranger.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/entities/FragmentEntityArranger.java @@ -1,6 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2022 Nordix Foundation + * Modifications Copyright (C) 2023 TechMahindra Ltd. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,6 +48,25 @@ public class FragmentEntityArranger { return reuniteChildrenWithTheirParents(fragmentEntityPerId); } + /** + * Convert a collection of (related) FragmentExtracts into FragmentEntities (trees) with descendants. + * + * @param fragmentExtracts FragmentExtracts to convert. + * @param fragmentExtractAnchorMap Map of fragmentExtract with their anchor. + * @return a collection of FragmentEntities (trees) with descendants. + */ + public static Collection toFragmentEntityTreesAcrossAnchors( + final Collection fragmentExtracts, + final Map fragmentExtractAnchorMap) { + final Map fragmentEntityPerId = new HashMap<>(); + for (final FragmentExtract fragmentExtract : fragmentExtracts) { + final AnchorEntity anchorEntity = fragmentExtractAnchorMap.get(fragmentExtract.getId()); + final FragmentEntity fragmentEntity = toFragmentEntity(anchorEntity, fragmentExtract); + fragmentEntityPerId.put(fragmentEntity.getId(), fragmentEntity); + } + return reuniteChildrenWithTheirParents(fragmentEntityPerId); + } + private static FragmentEntity toFragmentEntity(final AnchorEntity anchorEntity, final FragmentExtract fragmentExtract) { final FragmentEntity fragmentEntity = new FragmentEntity(); 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 aa631d1b1..0a77de234 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 @@ -324,17 +324,20 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService } catch (final PathParsingException e) { throw new CpsPathException(e.getMessage()); } - + final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName); Collection fragmentEntities; if (canUseRegexQuickFind(fetchDescendantsOption, cpsPathQuery)) { - return getDataNodesUsingRegexQuickFind(fetchDescendantsOption, anchorEntity, cpsPathQuery); + return (anchorEntity == ALL_ANCHORS) ? getDataNodesUsingRegexQuickFindAcrossAnchors(fetchDescendantsOption, + dataspaceEntity, cpsPathQuery) : getDataNodesUsingRegexQuickFind(fetchDescendantsOption, + anchorEntity, cpsPathQuery); } - fragmentEntities = (anchorEntity == ALL_ANCHORS) ? fragmentRepository.findByCpsPath(cpsPathQuery) + fragmentEntities = (anchorEntity == ALL_ANCHORS) ? fragmentRepository + .findByDataspaceAndCpsPath(dataspaceEntity.getId(), cpsPathQuery) : fragmentRepository.findByAnchorAndCpsPath(anchorEntity.getId(), cpsPathQuery); if (cpsPathQuery.hasAncestorAxis()) { final Collection ancestorXpaths = processAncestorXpath(fragmentEntities, cpsPathQuery); - fragmentEntities = (anchorEntity == ALL_ANCHORS) ? getAncestorFragmentEntitiesAcrossAnchors(cpsPathQuery, - fragmentEntities) : getFragmentEntities(anchorEntity, ancestorXpaths, fetchDescendantsOption); + fragmentEntities = (anchorEntity == ALL_ANCHORS) ? getAncestorFragmentEntitiesAcrossAnchors(dataspaceEntity, + cpsPathQuery, fragmentEntities) : getFragmentEntities(anchorEntity, ancestorXpaths, fetchDescendantsOption); } return createDataNodesFromProxiedFragmentEntities(fetchDescendantsOption, anchorEntity, fragmentEntities); } @@ -357,22 +360,48 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService final CpsPathQuery cpsPathQuery) { Collection fragmentEntities; final String xpathRegex = FragmentQueryBuilder.getXpathSqlRegex(cpsPathQuery, true); - final List fragmentExtracts = (anchorEntity == ALL_ANCHORS) - ? fragmentRepository.quickFindWithDescendantsAcrossAnchor(xpathRegex) : + final List fragmentExtracts = fragmentRepository.quickFindWithDescendants(anchorEntity.getId(), xpathRegex); fragmentEntities = FragmentEntityArranger.toFragmentEntityTrees(anchorEntity, fragmentExtracts); if (cpsPathQuery.hasAncestorAxis()) { final Collection ancestorXpaths = processAncestorXpath(fragmentEntities, cpsPathQuery); - fragmentEntities = (anchorEntity == ALL_ANCHORS) ? getAncestorFragmentEntitiesAcrossAnchors(cpsPathQuery, - fragmentEntities) : getFragmentEntities(anchorEntity, ancestorXpaths, fetchDescendantsOption); + fragmentEntities = getFragmentEntities(anchorEntity, ancestorXpaths, fetchDescendantsOption); } return createDataNodesFromFragmentEntities(fetchDescendantsOption, fragmentEntities); } - private Collection getAncestorFragmentEntitiesAcrossAnchors(final CpsPathQuery cpsPathQuery, - final Collection fragmentEntities) { + private Collection getAncestorFragmentEntitiesAcrossAnchors(final DataspaceEntity dataspaceEntity, + final CpsPathQuery cpsPathQuery, final Collection fragmentEntities) { final Collection ancestorXpaths = processAncestorXpath(fragmentEntities, cpsPathQuery); - return ancestorXpaths.isEmpty() ? Collections.emptyList() : fragmentRepository.findAllByXpathIn(ancestorXpaths); + return ancestorXpaths.isEmpty() ? Collections.emptyList() : fragmentRepository + .findAllByXpathIn(dataspaceEntity, ancestorXpaths); + } + + private List getDataNodesUsingRegexQuickFindAcrossAnchors( + final FetchDescendantsOption fetchDescendantsOption, final DataspaceEntity dataspaceEntity, + final CpsPathQuery cpsPathQuery) { + Collection fragmentEntities; + final String xpathRegex = FragmentQueryBuilder.getXpathSqlRegex(cpsPathQuery, true); + final List fragmentExtracts = fragmentRepository + .quickFindWithDescendantsAcrossAnchor(dataspaceEntity.getId(), xpathRegex); + final Map fragmentExtractAnchorMap = getFragmentExtractAnchorMap(fragmentExtracts); + fragmentEntities = FragmentEntityArranger.toFragmentEntityTreesAcrossAnchors(fragmentExtracts, + fragmentExtractAnchorMap); + if (cpsPathQuery.hasAncestorAxis()) { + fragmentEntities = getAncestorFragmentEntitiesAcrossAnchors(dataspaceEntity, + cpsPathQuery, fragmentEntities); + } + return createDataNodesFromFragmentEntities(fetchDescendantsOption, fragmentEntities); + } + + private Map getFragmentExtractAnchorMap(final List fragmentExtracts) { + final Map fragmentEntityAnchorMap = new HashMap<>(); + fragmentExtracts.forEach(fragmentExtract -> { + final AnchorEntity anchorEntity = anchorRepository.getById(Math.toIntExact(fragmentExtract.getAnchorId())); + fragmentEntityAnchorMap.put(fragmentExtract.getId(), anchorEntity); + } + ); + return fragmentEntityAnchorMap; } private List createDataNodesFromProxiedFragmentEntities( diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentQueryBuilder.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentQueryBuilder.java index c23159593..5716304a1 100644 --- a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentQueryBuilder.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentQueryBuilder.java @@ -75,15 +75,17 @@ public class FragmentQueryBuilder { } /** - * Create a sql query to retrieve by cps path. + * Create a sql query to retrieve by dataspace id and cps path. * * @param cpsPathQuery the cps path query to be transformed into a sql query * @return a executable query object */ - public Query getQueryForCpsPath(final CpsPathQuery cpsPathQuery) { - final StringBuilder sqlStringBuilder = new StringBuilder("SELECT * FROM FRAGMENT WHERE xpath ~ :xpathRegex"); + public Query getQueryForDataspaceAndCpsPath(final int dataspaceId, final CpsPathQuery cpsPathQuery) { + final StringBuilder sqlStringBuilder = new StringBuilder("SELECT * FROM FRAGMENT WHERE dataspace_id = " + + ":dataspaceId AND xpath ~ :xpathRegex"); final Map queryParameters = new HashMap<>(); final String xpathRegex = getXpathSqlRegex(cpsPathQuery, false); + queryParameters.put("dataspaceId", dataspaceId); queryParameters.put("xpathRegex", xpathRegex); if (cpsPathQuery.hasLeafConditions()) { sqlStringBuilder.append(" AND attributes @> :leafDataAsJson\\:\\:jsonb"); 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 d486a39c7..0067cfe43 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 @@ -27,6 +27,7 @@ import java.util.Collection; import java.util.List; import java.util.Optional; import org.onap.cps.spi.entities.AnchorEntity; +import org.onap.cps.spi.entities.DataspaceEntity; import org.onap.cps.spi.entities.FragmentEntity; import org.onap.cps.spi.entities.FragmentExtract; import org.onap.cps.spi.exceptions.DataNodeNotFoundException; @@ -54,7 +55,9 @@ public interface FragmentRepository extends JpaRepository, List findAllByAnchorAndXpathIn(AnchorEntity anchorEntity, Collection xpath); - List findAllByXpathIn(Collection xpath); + @Query("SELECT f FROM FragmentEntity f WHERE dataspace = :dataspace AND xpath IN :xpaths") + List findAllByXpathIn(@Param("dataspace") DataspaceEntity dataspace, + @Param("xpaths") Collection xpaths); @Modifying @Query("DELETE FROM FragmentEntity WHERE anchor IN (:anchors)") @@ -109,7 +112,8 @@ public interface FragmentRepository extends JpaRepository, @Query(value = "SELECT id, anchor_id AS anchorId, xpath, parent_id AS parentId," + " CAST(attributes AS TEXT) AS attributes" - + " FROM FRAGMENT WHERE xpath ~ :xpathRegex", + + " FROM FRAGMENT WHERE dataspace_id = :dataspaceId AND xpath ~ :xpathRegex", nativeQuery = true) - List quickFindWithDescendantsAcrossAnchor(@Param("xpathRegex") String xpathRegex); + List quickFindWithDescendantsAcrossAnchor(@Param("dataspaceId") int dataspaceId, + @Param("xpathRegex") String xpathRegex); } diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryCpsPathQuery.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryCpsPathQuery.java index 32041e7d5..4195f62a6 100644 --- a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryCpsPathQuery.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryCpsPathQuery.java @@ -28,5 +28,5 @@ import org.onap.cps.spi.entities.FragmentEntity; public interface FragmentRepositoryCpsPathQuery { List findByAnchorAndCpsPath(int anchorId, CpsPathQuery cpsPathQuery); - List findByCpsPath(CpsPathQuery cpsPathQuery); + List findByDataspaceAndCpsPath(int dataspaceId, CpsPathQuery cpsPathQuery); } diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryCpsPathQueryImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryCpsPathQueryImpl.java index b95491cd3..3ea5cef83 100644 --- a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryCpsPathQueryImpl.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryCpsPathQueryImpl.java @@ -51,8 +51,8 @@ public class FragmentRepositoryCpsPathQueryImpl implements FragmentRepositoryCps @Override @Transactional - public List findByCpsPath(final CpsPathQuery cpsPathQuery) { - final Query query = fragmentQueryBuilder.getQueryForCpsPath(cpsPathQuery); + public List findByDataspaceAndCpsPath(final int dataspaceId, final CpsPathQuery cpsPathQuery) { + final Query query = fragmentQueryBuilder.getQueryForDataspaceAndCpsPath(dataspaceId, cpsPathQuery); final List fragmentEntities = query.getResultList(); log.debug("Fetched {} fragment entities by cps path across all anchors.", fragmentEntities.size()); return fragmentEntities; 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 60aaa8114..fae2c5aae 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 @@ -193,6 +193,7 @@ class CpsDataPersistenceQueryDataNodeSpec extends CpsPersistenceSpecBase { 'String and no descendants' | '/shops/shop[@id=1]/categories[@code=1]/book[@title="Dune"]' | OMIT_DESCENDANTS || 2 || ['ANCHOR-004', 'ANCHOR-005'] 'Integer and descendants' | '/shops/shop[@id=1]/categories[@code=1]/book[@price=5]' | INCLUDE_ALL_DESCENDANTS || 3 || ['ANCHOR-004', 'ANCHOR-005'] 'No condition no descendants' | '/shops/shop[@id=1]/categories' | OMIT_DESCENDANTS || 6 || ['ANCHOR-004', 'ANCHOR-005'] + 'top node and all descendants' | '/shops' | INCLUDE_ALL_DESCENDANTS || 2 || ['ANCHOR-004', 'ANCHOR-005'] 'multiple list-ancestors' | '//book/ancestor::categories' | INCLUDE_ALL_DESCENDANTS || 4 || ['ANCHOR-004', 'ANCHOR-005'] 'one ancestor with list value' | '//book/ancestor::categories[@code=1]' | INCLUDE_ALL_DESCENDANTS || 2 || ['ANCHOR-004', 'ANCHOR-005'] 'list with index value in the xpath prefix' | '//categories[@code=1]/book/ancestor::shop[@id=1]' | INCLUDE_ALL_DESCENDANTS || 2 || ['ANCHOR-004', 'ANCHOR-005'] 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 f02aa754f..fcd8f4637 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 @@ -212,32 +212,32 @@ class CpsDataPersistenceServiceSpec extends Specification { when: 'replace data node tree' objectUnderTest.updateDataNodesAndDescendants('dataspaceName', 'anchorName', dataNodes) then: 'call fragment repository save all method' - 1 * mockFragmentRepository.saveAll({fragmentEntities -> assert fragmentEntities as List == expectedFragmentEntities}) + 1 * mockFragmentRepository.saveAll({fragmentEntities -> fragmentEntities.containsAll(expectedFragmentEntities)}) where: 'the following Data Type is passed' scenario | dataNodes || expectedFragmentEntities 'empty data node list' | [] || [] - 'one data node in list' | [new DataNode(xpath: '/test/xpath', leaves: ['id': 'testId'], childDataNodes: [])] || [new FragmentEntity(xpath: '/test/xpath', attributes: '{"id":"testId"}', childFragments: [])] + 'one data node in list' | [new DataNode(xpath: '/test/xpath', leaves: ['id': 'testId'], childDataNodes: [])] || [new FragmentEntity(xpath: '/test/xpath', attributes: '{"id":"testId"}', childFragments: [], anchor: new AnchorEntity(id: 123), dataspace: new DataspaceEntity(id: 1))] } def 'update data nodes and descendants'() { given: 'the fragment repository returns fragment entities related to the xpath inputs' - mockFragmentRepository.findExtractsWithDescendants(123, ['/test/xpath1', '/test/xpath2'] as Set, _) >> [ + mockFragmentRepository.findExtractsWithDescendants(123, ['/test/xpath1', '/test/xpath2'] as Set, _) >> [ mockFragmentExtract(1, null, 123, '/test/xpath1', null), mockFragmentExtract(2, null, 123, '/test/xpath2', null) - ] + ] and: 'some data nodes with descendants' - def dataNode1 = new DataNode(xpath: '/test/xpath1', leaves: ['id': 'testId1'], childDataNodes: [new DataNode(xpath: '/test/xpath1/child', leaves: ['id': 'childTestId1'])]) - def dataNode2 = new DataNode(xpath: '/test/xpath2', leaves: ['id': 'testId2'], childDataNodes: [new DataNode(xpath: '/test/xpath2/child', leaves: ['id': 'childTestId2'])]) + def dataNode1 = new DataNode(xpath: '/test/xpath1', leaves: ['id': 'testId1'], childDataNodes: [new DataNode(xpath: '/test/xpath1/child', leaves: ['id': 'childTestId1'])]) + def dataNode2 = new DataNode(xpath: '/test/xpath2', leaves: ['id': 'testId2'], childDataNodes: [new DataNode(xpath: '/test/xpath2/child', leaves: ['id': 'childTestId2'])]) when: 'the fragment entities are update by the data nodes' - objectUnderTest.updateDataNodesAndDescendants('dataspaceName', 'anchorName', [dataNode1, dataNode2]) + objectUnderTest.updateDataNodesAndDescendants('dataspaceName', 'anchorName', [dataNode1, dataNode2]) then: 'call fragment repository save all method is called with the updated fragments' - 1 * mockFragmentRepository.saveAll({fragmentEntities -> { - fragmentEntities.containsAll([ - new FragmentEntity(xpath: '/test/xpath1', attributes: '{"id":"testId1"}', childFragments: [new FragmentEntity(xpath: '/test/xpath1/child', attributes: '{"id":"childTestId1"}', childFragments: [])]), - new FragmentEntity(xpath: '/test/xpath2', attributes: '{"id":"testId2"}', childFragments: [new FragmentEntity(xpath: '/test/xpath2/child', attributes: '{"id":"childTestId2"}', childFragments: [])]) - ]) - assert fragmentEntities.size() == 2 - }}) + 1 * mockFragmentRepository.saveAll({fragmentEntities -> { + fragmentEntities.containsAll([ + new FragmentEntity(xpath: '/test/xpath1', anchor: new AnchorEntity(id: 123), dataspace: new DataspaceEntity(id: 1), attributes: '{"id":"testId1"}', childFragments: [new FragmentEntity(xpath: '/test/xpath1/child', attributes: '{"id":"childTestId1"}', childFragments: [])]), + new FragmentEntity(xpath: '/test/xpath2', anchor: new AnchorEntity(id: 123), dataspace: new DataspaceEntity(id: 1), attributes: '{"id":"testId2"}', childFragments: [new FragmentEntity(xpath: '/test/xpath2/child', attributes: '{"id":"childTestId2"}', childFragments: [])]) + ]) + assert fragmentEntities.size() == 2 + }}) } def createDataNodeAndMockRepositoryMethodSupportingIt(xpath, scenario) { @@ -261,7 +261,7 @@ class CpsDataPersistenceServiceSpec extends Specification { dataNodes.add(dataNode) def fragmentExtract = mockFragmentExtract(fragmentId, null, null, xpath, null) fragmentExtracts.add(fragmentExtract) - def fragmentEntity = new FragmentEntity(id: fragmentId, xpath: xpath, childFragments: []) + def fragmentEntity = new FragmentEntity(id: fragmentId, xpath: xpath, childFragments: [], anchor: new AnchorEntity(id: 123), dataspace: new DataspaceEntity(id: 1)) mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, xpath) >> fragmentEntity if ('EXCEPTION' == scenario) { mockFragmentRepository.save(fragmentEntity) >> { throw new StaleStateException("concurrent updates") } -- cgit 1.2.3-korg