/*
* ============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.
import javax.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
+import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
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
* ============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.
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
+import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
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
* ============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.
@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
/*
* ============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.
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<FragmentEntity> toFragmentEntityTreesAcrossAnchors(
+ final Collection<FragmentExtract> fragmentExtracts,
+ final Map<Long, AnchorEntity> fragmentExtractAnchorMap) {
+ final Map<Long, FragmentEntity> 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();
} catch (final PathParsingException e) {
throw new CpsPathException(e.getMessage());
}
-
+ final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
Collection<FragmentEntity> 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<String> 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);
}
final CpsPathQuery cpsPathQuery) {
Collection<FragmentEntity> fragmentEntities;
final String xpathRegex = FragmentQueryBuilder.getXpathSqlRegex(cpsPathQuery, true);
- final List<FragmentExtract> fragmentExtracts = (anchorEntity == ALL_ANCHORS)
- ? fragmentRepository.quickFindWithDescendantsAcrossAnchor(xpathRegex) :
+ final List<FragmentExtract> fragmentExtracts =
fragmentRepository.quickFindWithDescendants(anchorEntity.getId(), xpathRegex);
fragmentEntities = FragmentEntityArranger.toFragmentEntityTrees(anchorEntity, fragmentExtracts);
if (cpsPathQuery.hasAncestorAxis()) {
final Collection<String> 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<FragmentEntity> getAncestorFragmentEntitiesAcrossAnchors(final CpsPathQuery cpsPathQuery,
- final Collection<FragmentEntity> fragmentEntities) {
+ private Collection<FragmentEntity> getAncestorFragmentEntitiesAcrossAnchors(final DataspaceEntity dataspaceEntity,
+ final CpsPathQuery cpsPathQuery, final Collection<FragmentEntity> fragmentEntities) {
final Collection<String> ancestorXpaths = processAncestorXpath(fragmentEntities, cpsPathQuery);
- return ancestorXpaths.isEmpty() ? Collections.emptyList() : fragmentRepository.findAllByXpathIn(ancestorXpaths);
+ return ancestorXpaths.isEmpty() ? Collections.emptyList() : fragmentRepository
+ .findAllByXpathIn(dataspaceEntity, ancestorXpaths);
+ }
+
+ private List<DataNode> getDataNodesUsingRegexQuickFindAcrossAnchors(
+ final FetchDescendantsOption fetchDescendantsOption, final DataspaceEntity dataspaceEntity,
+ final CpsPathQuery cpsPathQuery) {
+ Collection<FragmentEntity> fragmentEntities;
+ final String xpathRegex = FragmentQueryBuilder.getXpathSqlRegex(cpsPathQuery, true);
+ final List<FragmentExtract> fragmentExtracts = fragmentRepository
+ .quickFindWithDescendantsAcrossAnchor(dataspaceEntity.getId(), xpathRegex);
+ final Map<Long, AnchorEntity> fragmentExtractAnchorMap = getFragmentExtractAnchorMap(fragmentExtracts);
+ fragmentEntities = FragmentEntityArranger.toFragmentEntityTreesAcrossAnchors(fragmentExtracts,
+ fragmentExtractAnchorMap);
+ if (cpsPathQuery.hasAncestorAxis()) {
+ fragmentEntities = getAncestorFragmentEntitiesAcrossAnchors(dataspaceEntity,
+ cpsPathQuery, fragmentEntities);
+ }
+ return createDataNodesFromFragmentEntities(fetchDescendantsOption, fragmentEntities);
+ }
+
+ private Map<Long, AnchorEntity> getFragmentExtractAnchorMap(final List<FragmentExtract> fragmentExtracts) {
+ final Map<Long, AnchorEntity> fragmentEntityAnchorMap = new HashMap<>();
+ fragmentExtracts.forEach(fragmentExtract -> {
+ final AnchorEntity anchorEntity = anchorRepository.getById(Math.toIntExact(fragmentExtract.getAnchorId()));
+ fragmentEntityAnchorMap.put(fragmentExtract.getId(), anchorEntity);
+ }
+ );
+ return fragmentEntityAnchorMap;
}
private List<DataNode> createDataNodesFromProxiedFragmentEntities(
}
/**
- * 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<String, Object> 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");
import java.util.List;\r
import java.util.Optional;\r
import org.onap.cps.spi.entities.AnchorEntity;\r
+import org.onap.cps.spi.entities.DataspaceEntity;\r
import org.onap.cps.spi.entities.FragmentEntity;\r
import org.onap.cps.spi.entities.FragmentExtract;\r
import org.onap.cps.spi.exceptions.DataNodeNotFoundException;\r
\r
List<FragmentEntity> findAllByAnchorAndXpathIn(AnchorEntity anchorEntity, Collection<String> xpath);\r
\r
- List<FragmentEntity> findAllByXpathIn(Collection<String> xpath);\r
+ @Query("SELECT f FROM FragmentEntity f WHERE dataspace = :dataspace AND xpath IN :xpaths")\r
+ List<FragmentEntity> findAllByXpathIn(@Param("dataspace") DataspaceEntity dataspace,\r
+ @Param("xpaths") Collection<String> xpaths);\r
\r
@Modifying\r
@Query("DELETE FROM FragmentEntity WHERE anchor IN (:anchors)")\r
\r
@Query(value = "SELECT id, anchor_id AS anchorId, xpath, parent_id AS parentId,"\r
+ " CAST(attributes AS TEXT) AS attributes"\r
- + " FROM FRAGMENT WHERE xpath ~ :xpathRegex",\r
+ + " FROM FRAGMENT WHERE dataspace_id = :dataspaceId AND xpath ~ :xpathRegex",\r
nativeQuery = true)\r
- List<FragmentExtract> quickFindWithDescendantsAcrossAnchor(@Param("xpathRegex") String xpathRegex);\r
+ List<FragmentExtract> quickFindWithDescendantsAcrossAnchor(@Param("dataspaceId") int dataspaceId,\r
+ @Param("xpathRegex") String xpathRegex);\r
}\r
public interface FragmentRepositoryCpsPathQuery {
List<FragmentEntity> findByAnchorAndCpsPath(int anchorId, CpsPathQuery cpsPathQuery);
- List<FragmentEntity> findByCpsPath(CpsPathQuery cpsPathQuery);
+ List<FragmentEntity> findByDataspaceAndCpsPath(int dataspaceId, CpsPathQuery cpsPathQuery);
}
@Override
@Transactional
- public List<FragmentEntity> findByCpsPath(final CpsPathQuery cpsPathQuery) {
- final Query query = fragmentQueryBuilder.getQueryForCpsPath(cpsPathQuery);
+ public List<FragmentEntity> findByDataspaceAndCpsPath(final int dataspaceId, final CpsPathQuery cpsPathQuery) {
+ final Query query = fragmentQueryBuilder.getQueryForDataspaceAndCpsPath(dataspaceId, cpsPathQuery);
final List<FragmentEntity> fragmentEntities = query.getResultList();
log.debug("Fetched {} fragment entities by cps path across all anchors.", fragmentEntities.size());
return fragmentEntities;
'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']
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) {
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") }