aboutsummaryrefslogtreecommitdiffstats
path: root/cps-ri/src
diff options
context:
space:
mode:
Diffstat (limited to 'cps-ri/src')
-rw-r--r--cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java110
-rwxr-xr-xcps-ri/src/main/java/org/onap/cps/spi/impl/CpsModulePersistenceServiceImpl.java8
-rw-r--r--cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryCpsPathQueryImpl.java29
-rw-r--r--cps-ri/src/main/java/org/onap/cps/spi/repository/YangResourceNativeRepositoryImpl.java19
-rw-r--r--cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceQueryDataNodeSpec.groovy25
-rwxr-xr-xcps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy37
-rw-r--r--cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy36
-rw-r--r--cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsModulePersistenceServiceIntegrationSpec.groovy19
-rw-r--r--cps-ri/src/test/resources/data/cps-path-query.sql4
9 files changed, 206 insertions, 81 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 61e1d5b56..c13422dc4 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
@@ -52,6 +52,7 @@ import org.onap.cps.spi.entities.FragmentEntity;
import org.onap.cps.spi.entities.SchemaSetEntity;
import org.onap.cps.spi.entities.YangResourceEntity;
import org.onap.cps.spi.exceptions.AlreadyDefinedException;
+import org.onap.cps.spi.exceptions.AlreadyDefinedExceptionBatch;
import org.onap.cps.spi.exceptions.ConcurrencyException;
import org.onap.cps.spi.exceptions.CpsAdminException;
import org.onap.cps.spi.exceptions.CpsPathException;
@@ -88,48 +89,82 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
Pattern.compile("\\[(\\@([^\\/]{0,9999}))\\]$");
@Override
- @Transactional
public void addChildDataNode(final String dataspaceName, final String anchorName, final String parentNodeXpath,
final DataNode newChildDataNode) {
- addChildDataNodes(dataspaceName, anchorName, parentNodeXpath, Collections.singleton(newChildDataNode));
+ addNewChildDataNode(dataspaceName, anchorName, parentNodeXpath, newChildDataNode);
}
@Override
- @Transactional
public void addListElements(final String dataspaceName, final String anchorName, final String parentNodeXpath,
final Collection<DataNode> newListElements) {
- addChildDataNodes(dataspaceName, anchorName, parentNodeXpath, newListElements);
+ addChildrenDataNodes(dataspaceName, anchorName, parentNodeXpath, newListElements);
}
@Override
- @Transactional
- public void addListElementsBatch(final String dataspaceName, final String anchorName, final String parentNodeXpath,
- final Collection<Collection<DataNode>> newListsElements) {
+ public void addMultipleLists(final String dataspaceName, final String anchorName, final String parentNodeXpath,
+ final Collection<Collection<DataNode>> newLists) {
+ final Collection<String> failedXpaths = new HashSet<>();
+ newLists.forEach(newList -> {
+ try {
+ addChildrenDataNodes(dataspaceName, anchorName, parentNodeXpath, newList);
+ } catch (final AlreadyDefinedExceptionBatch e) {
+ failedXpaths.addAll(e.getAlreadyDefinedXpaths());
+ }
+ });
- newListsElements.forEach(
- newListElement -> addListElements(dataspaceName, anchorName, parentNodeXpath, newListElement));
+ if (!failedXpaths.isEmpty()) {
+ throw new AlreadyDefinedExceptionBatch(failedXpaths);
+ }
}
- private void addChildDataNodes(final String dataspaceName, final String anchorName, final String parentNodeXpath,
- final Collection<DataNode> newChildren) {
+ private void addNewChildDataNode(final String dataspaceName, final String anchorName,
+ final String parentNodeXpath, final DataNode newChild) {
final FragmentEntity parentFragmentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath);
+ final FragmentEntity newChildAsFragmentEntity =
+ convertToFragmentWithAllDescendants(parentFragmentEntity.getDataspace(),
+ parentFragmentEntity.getAnchor(), newChild);
+ newChildAsFragmentEntity.setParentId(parentFragmentEntity.getId());
+ try {
+ fragmentRepository.save(newChildAsFragmentEntity);
+ } catch (final DataIntegrityViolationException e) {
+ throw AlreadyDefinedException.forDataNode(newChild.getXpath(), anchorName, e);
+ }
+
+ }
+
+ private void addChildrenDataNodes(final String dataspaceName, final String anchorName, final String parentNodeXpath,
+ final Collection<DataNode> newChildren) {
+ final FragmentEntity parentFragmentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath);
+ final List<FragmentEntity> fragmentEntities = new ArrayList<>(newChildren.size());
try {
- final List<FragmentEntity> fragmentEntities = new ArrayList<>();
newChildren.forEach(newChildAsDataNode -> {
- final FragmentEntity newChildAsFragmentEntity = convertToFragmentWithAllDescendants(
- parentFragmentEntity.getDataspace(),
- parentFragmentEntity.getAnchor(),
- newChildAsDataNode);
+ final FragmentEntity newChildAsFragmentEntity =
+ convertToFragmentWithAllDescendants(parentFragmentEntity.getDataspace(),
+ parentFragmentEntity.getAnchor(), newChildAsDataNode);
newChildAsFragmentEntity.setParentId(parentFragmentEntity.getId());
fragmentEntities.add(newChildAsFragmentEntity);
});
fragmentRepository.saveAll(fragmentEntities);
- } catch (final DataIntegrityViolationException exception) {
- final List<String> conflictXpaths = newChildren.stream()
- .map(DataNode::getXpath)
- .collect(Collectors.toList());
- throw AlreadyDefinedException.forDataNodes(conflictXpaths, anchorName, exception);
+ } catch (final DataIntegrityViolationException e) {
+ log.warn("Exception occurred : {} , While saving : {} children, retrying using individual save operations",
+ e, fragmentEntities.size());
+ retrySavingEachChildIndividually(dataspaceName, anchorName, parentNodeXpath, newChildren);
+ }
+ }
+
+ private void retrySavingEachChildIndividually(final String dataspaceName, final String anchorName,
+ final String parentNodeXpath, final Collection<DataNode> newChildren) {
+ final Collection<String> failedXpaths = new HashSet<>();
+ for (final DataNode newChild : newChildren) {
+ try {
+ addNewChildDataNode(dataspaceName, anchorName, parentNodeXpath, newChild);
+ } catch (final AlreadyDefinedException e) {
+ failedXpaths.add(newChild.getXpath());
+ }
+ }
+ if (!failedXpaths.isEmpty()) {
+ throw new AlreadyDefinedExceptionBatch(failedXpaths);
}
}
@@ -199,8 +234,8 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
} catch (final PathParsingException e) {
throw new CpsPathException(e.getMessage());
}
- return fragmentRepository.getByDataspaceAndAnchorAndXpath(dataspaceEntity, anchorEntity,
- normalizedXpath);
+
+ return fragmentRepository.getByDataspaceAndAnchorAndXpath(dataspaceEntity, anchorEntity, normalizedXpath);
}
}
@@ -310,8 +345,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
} catch (final StaleStateException staleStateException) {
throw new ConcurrencyException("Concurrent Transactions",
String.format("dataspace :'%s', Anchor : '%s' and xpath: '%s' is updated by another transaction.",
- dataspaceName, anchorName, dataNode.getXpath()),
- staleStateException);
+ dataspaceName, anchorName, dataNode.getXpath()));
}
}
@@ -319,6 +353,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
public void updateDataNodesAndDescendants(final String dataspaceName,
final String anchorName,
final List<DataNode> dataNodes) {
+
final Map<DataNode, FragmentEntity> dataNodeFragmentEntityMap = dataNodes.stream()
.collect(Collectors.toMap(
dataNode -> dataNode, dataNode -> getFragmentByXpath(dataspaceName, anchorName, dataNode.getXpath())));
@@ -327,10 +362,27 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
try {
fragmentRepository.saveAll(dataNodeFragmentEntityMap.values());
} catch (final StaleStateException staleStateException) {
- throw new ConcurrencyException("Concurrent Transactions",
- String.format("A data node in dataspace :'%s' with Anchor : '%s' is updated by another transaction.",
- dataspaceName, anchorName),
- staleStateException);
+ retryUpdateDataNodesIndividually(dataspaceName, anchorName, dataNodeFragmentEntityMap.values());
+ }
+ }
+
+ private void retryUpdateDataNodesIndividually(final String dataspaceName, final String anchorName,
+ final Collection<FragmentEntity> fragmentEntities) {
+ final Collection<String> failedXpaths = new HashSet<>();
+
+ fragmentEntities.forEach(dataNodeFragment -> {
+ try {
+ fragmentRepository.save(dataNodeFragment);
+ } catch (final StaleStateException e) {
+ failedXpaths.add(dataNodeFragment.getXpath());
+ }
+ });
+
+ if (!failedXpaths.isEmpty()) {
+ final String failedXpathsConcatenated = String.join(",", failedXpaths);
+ throw new ConcurrencyException("Concurrent Transactions", String.format(
+ "DataNodes : %s in Dataspace :'%s' with Anchor : '%s' are updated by another transaction.",
+ failedXpathsConcatenated, dataspaceName, anchorName));
}
}
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsModulePersistenceServiceImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsModulePersistenceServiceImpl.java
index e9e945a49..400e9b3e8 100755
--- a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsModulePersistenceServiceImpl.java
+++ b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsModulePersistenceServiceImpl.java
@@ -162,14 +162,14 @@ public class CpsModulePersistenceServiceImpl implements CpsModulePersistenceServ
@Backoff(random = true, delay = 200, maxDelay = 2000, multiplier = 2))
public void storeSchemaSetFromModules(final String dataspaceName, final String schemaSetName,
final Map<String, String> newModuleNameToContentMap,
- final Collection<ModuleReference> moduleReferences) {
+ final Collection<ModuleReference> allModuleReferences) {
storeSchemaSet(dataspaceName, schemaSetName, newModuleNameToContentMap);
final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
final SchemaSetEntity schemaSetEntity =
schemaSetRepository.getByDataspaceAndName(dataspaceEntity, schemaSetName);
- final List<Long> listOfYangResourceIds =
- yangResourceRepository.getResourceIdsByModuleReferences(moduleReferences);
- yangResourceRepository.insertSchemaSetIdYangResourceId(schemaSetEntity.getId(), listOfYangResourceIds);
+ final List<Long> allYangResourceIds =
+ yangResourceRepository.getResourceIdsByModuleReferences(allModuleReferences);
+ yangResourceRepository.insertSchemaSetIdYangResourceId(schemaSetEntity.getId(), allYangResourceIds);
}
@Override
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 f07f7f8d5..654c1c085 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
@@ -20,7 +20,6 @@
package org.onap.cps.spi.repository;
-import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -29,16 +28,18 @@ import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.transaction.Transactional;
import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
import org.onap.cps.cpspath.parser.CpsPathPrefixType;
import org.onap.cps.cpspath.parser.CpsPathQuery;
import org.onap.cps.spi.entities.FragmentEntity;
import org.onap.cps.utils.JsonObjectMapper;
@RequiredArgsConstructor
+@Slf4j
public class FragmentRepositoryCpsPathQueryImpl implements FragmentRepositoryCpsPathQuery {
- public static final String SIMILAR_TO_ABSOLUTE_PATH_PREFIX = "%/";
- public static final String SIMILAR_TO_OPTIONAL_LIST_INDEX_POSTFIX = "(\\[[^/]*])?";
+ public static final String REGEX_ABSOLUTE_PATH_PREFIX = ".*\\/";
+ public static final String REGEX_OPTIONAL_LIST_INDEX_POSTFIX = "(\\[@(?!.*\\[).*?])?$";
@PersistenceContext
private EntityManager entityManager;
@@ -50,8 +51,8 @@ public class FragmentRepositoryCpsPathQueryImpl implements FragmentRepositoryCps
final StringBuilder sqlStringBuilder = new StringBuilder("SELECT * FROM FRAGMENT WHERE anchor_id = :anchorId");
final Map<String, Object> queryParameters = new HashMap<>();
queryParameters.put("anchorId", anchorId);
- sqlStringBuilder.append(" AND xpath SIMILAR TO :xpathRegex");
- final String xpathRegex = getSimilarToXpathSqlRegex(cpsPathQuery);
+ sqlStringBuilder.append(" AND xpath ~ :xpathRegex");
+ final String xpathRegex = getXpathSqlRegex(cpsPathQuery);
queryParameters.put("xpathRegex", xpathRegex);
if (cpsPathQuery.hasLeafConditions()) {
sqlStringBuilder.append(" AND attributes @> :leafDataAsJson\\:\\:jsonb");
@@ -62,28 +63,20 @@ public class FragmentRepositoryCpsPathQueryImpl implements FragmentRepositoryCps
addTextFunctionCondition(cpsPathQuery, sqlStringBuilder, queryParameters);
final Query query = entityManager.createNativeQuery(sqlStringBuilder.toString(), FragmentEntity.class);
setQueryParameters(query, queryParameters);
- return getFragmentEntitiesAsStream(query);
- }
-
- private List<FragmentEntity> getFragmentEntitiesAsStream(final Query query) {
- final List<FragmentEntity> fragmentEntities = new ArrayList<>();
- query.getResultStream().forEach(fragmentEntity -> {
- fragmentEntities.add((FragmentEntity) fragmentEntity);
- entityManager.detach(fragmentEntity);
- });
-
+ final List<FragmentEntity> fragmentEntities = query.getResultList();
+ log.debug("Fetched {} fragment entities by anchor and cps path.", fragmentEntities.size());
return fragmentEntities;
}
- private static String getSimilarToXpathSqlRegex(final CpsPathQuery cpsPathQuery) {
+ private static String getXpathSqlRegex(final CpsPathQuery cpsPathQuery) {
final StringBuilder xpathRegexBuilder = new StringBuilder();
if (CpsPathPrefixType.ABSOLUTE.equals(cpsPathQuery.getCpsPathPrefixType())) {
xpathRegexBuilder.append(escapeXpath(cpsPathQuery.getXpathPrefix()));
} else {
- xpathRegexBuilder.append(SIMILAR_TO_ABSOLUTE_PATH_PREFIX);
+ xpathRegexBuilder.append(REGEX_ABSOLUTE_PATH_PREFIX);
xpathRegexBuilder.append(escapeXpath(cpsPathQuery.getDescendantName()));
}
- xpathRegexBuilder.append(SIMILAR_TO_OPTIONAL_LIST_INDEX_POSTFIX);
+ xpathRegexBuilder.append(REGEX_OPTIONAL_LIST_INDEX_POSTFIX);
return xpathRegexBuilder.toString();
}
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/YangResourceNativeRepositoryImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/YangResourceNativeRepositoryImpl.java
index e21fecb2b..850b274c9 100644
--- a/cps-ri/src/main/java/org/onap/cps/spi/repository/YangResourceNativeRepositoryImpl.java
+++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/YangResourceNativeRepositoryImpl.java
@@ -21,16 +21,19 @@
package org.onap.cps.spi.repository;
import java.util.Collection;
+import java.util.Collections;
import java.util.List;
import java.util.StringJoiner;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
+import lombok.extern.slf4j.Slf4j;
import org.hibernate.type.StandardBasicTypes;
import org.onap.cps.spi.model.ModuleReference;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
+@Slf4j
@Repository
public class YangResourceNativeRepositoryImpl implements YangResourceNativeRepository {
@@ -40,19 +43,27 @@ public class YangResourceNativeRepositoryImpl implements YangResourceNativeRepos
@Override
@Transactional
public List<Long> getResourceIdsByModuleReferences(final Collection<ModuleReference> moduleReferences) {
+ if (moduleReferences.isEmpty()) {
+ return Collections.emptyList();
+ }
final Query query = entityManager.createNativeQuery(getCombinedSelectSqlQuery(moduleReferences))
.unwrap(org.hibernate.query.NativeQuery.class)
.addScalar("id", StandardBasicTypes.LONG);
- return query.getResultList();
+ final List<Long> yangResourceIds = query.getResultList();
+ if (yangResourceIds.size() != moduleReferences.size()) {
+ log.warn("ModuleReferences size : {} and QueryResult size : {}", moduleReferences.size(),
+ yangResourceIds.size());
+ }
+ return yangResourceIds;
}
private String getCombinedSelectSqlQuery(final Collection<ModuleReference> moduleReferences) {
final StringJoiner sqlQueryJoiner = new StringJoiner(" UNION ALL ");
- moduleReferences.stream().forEach(moduleReference -> {
+ moduleReferences.forEach(moduleReference ->
sqlQueryJoiner.add(String.format("SELECT id FROM yang_resource WHERE module_name='%s' and revision='%s'",
moduleReference.getModuleName(),
- moduleReference.getRevision()));
- });
+ moduleReference.getRevision()))
+ );
return sqlQueryJoiner.toString();
}
}
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 36b378a77..be2f8febf 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
@@ -50,7 +50,7 @@ class CpsDataPersistenceQueryDataNodeSpec extends CpsPersistenceSpecBase {
scenario | cpsPath | includeDescendantsOption || expectedNumberOfParentNodes | expectedNumberOfChildNodes
'String and no descendants' | '/shops/shop[@id=1]/categories[@code=1]/book[@title="Dune"]' | OMIT_DESCENDANTS || 1 | 0
'Integer and descendants' | '/shops/shop[@id=1]/categories[@code=1]/book[@price=5]' | INCLUDE_ALL_DESCENDANTS || 1 | 1
- 'No condition no descendants' | '/shops/shop[@id=1]/categories' | OMIT_DESCENDANTS || 2 | 0
+ 'No condition no descendants' | '/shops/shop[@id=1]/categories' | OMIT_DESCENDANTS || 3 | 0
}
@Sql([CLEAR_DATA, SET_DATA])
@@ -91,16 +91,19 @@ class CpsDataPersistenceQueryDataNodeSpec extends CpsPersistenceSpecBase {
assert result[i].getXpath() == expectedXPaths[i]
}
where: 'the following data is used'
- scenario | cpsPath || expectedXPaths
- 'fully unique descendant name' | '//categories[@code=2]' || ["/shops/shop[@id='1']/categories[@code='2']", "/shops/shop[@id='2']/categories[@code='1']", "/shops/shop[@id='2']/categories[@code='2']"]
- 'descendant name match end of other node' | '//book' || ["/shops/shop[@id='1']/categories[@code='1']/book", "/shops/shop[@id='1']/categories[@code='2']/book"]
- 'descendant with text condition on leaf' | '//book/title[text()="Chapters"]' || ["/shops/shop[@id='1']/categories[@code='2']/book"]
- 'descendant with text condition case mismatch' | '//book/title[text()="chapters"]' || []
- 'descendant with text condition on int leaf' | '//book/price[text()="5"]' || ["/shops/shop[@id='1']/categories[@code='1']/book"]
- 'descendant with text condition on leaf-list' | '//book/labels[text()="special offer"]' || ["/shops/shop[@id='1']/categories[@code='1']/book"]
- 'descendant with text condition partial match' | '//book/labels[text()="special"]' || []
- 'descendant with text condition (existing) empty string' | '//book/labels[text()=""]' || ["/shops/shop[@id='1']/categories[@code='1']/book"]
- 'descendant with text condition on int leaf-list' | '//book/editions[text()="2000"]' || ["/shops/shop[@id='1']/categories[@code='2']/book"]
+ scenario | cpsPath || expectedXPaths
+ 'fully unique descendant name' | '//categories[@code=2]' || ["/shops/shop[@id='1']/categories[@code='2']", "/shops/shop[@id='2']/categories[@code='1']", "/shops/shop[@id='2']/categories[@code='2']"]
+ 'descendant name match end of other node' | '//book' || ["/shops/shop[@id='1']/categories[@code='1']/book", "/shops/shop[@id='1']/categories[@code='2']/book"]
+ 'descendant with text condition on leaf' | '//book/title[text()="Chapters"]' || ["/shops/shop[@id='1']/categories[@code='2']/book"]
+ 'descendant with text condition case mismatch' | '//book/title[text()="chapters"]' || []
+ 'descendant with text condition on int leaf' | '//book/price[text()="5"]' || ["/shops/shop[@id='1']/categories[@code='1']/book"]
+ 'descendant with text condition on leaf-list' | '//book/labels[text()="special offer"]' || ["/shops/shop[@id='1']/categories[@code='1']/book"]
+ 'descendant with text condition partial match' | '//book/labels[text()="special"]' || []
+ 'descendant with text condition (existing) empty string' | '//book/labels[text()=""]' || ["/shops/shop[@id='1']/categories[@code='1']/book"]
+ 'descendant with text condition on int leaf-list' | '//book/editions[text()="2000"]' || ["/shops/shop[@id='1']/categories[@code='2']/book"]
+ 'descendant name match of leaf containing /' | '//categories/type[text()="text/with/slash"]' || ["/shops/shop[@id='1']/categories[@code='string/with/slash/']"]
+ 'descendant with text condition on leaf containing /' | '//categories[@code=\'string/with/slash\']' || ["/shops/shop[@id='1']/categories[@code='string/with/slash/']"]
+ 'descendant with text condition on leaf containing [' | '//book/author[@Address="String[with]square[bracket]"]'|| []
}
@Sql([CLEAR_DATA, SET_DATA])
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 acc243b5b..5e15ca795 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
@@ -27,6 +27,7 @@ import org.onap.cps.cpspath.parser.PathParsingException
import org.onap.cps.spi.CpsDataPersistenceService
import org.onap.cps.spi.entities.FragmentEntity
import org.onap.cps.spi.exceptions.AlreadyDefinedException
+import org.onap.cps.spi.exceptions.AlreadyDefinedExceptionBatch
import org.onap.cps.spi.exceptions.AnchorNotFoundException
import org.onap.cps.spi.exceptions.CpsAdminException
import org.onap.cps.spi.exceptions.CpsPathException
@@ -48,6 +49,7 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
CpsDataPersistenceService objectUnderTest
static final JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
+ static final DataNodeBuilder dataNodeBuilder = new DataNodeBuilder()
static final String SET_DATA = '/data/fragment.sql'
static final int DATASPACE_1001_ID = 1001L
@@ -165,7 +167,7 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
def grandChild = buildDataNode('/parent-201/child-204[@key="NEW1"]/grand-child-204[@key2="NEW1-CHILD"]', [leave:'value'], [])
listElements[0].childDataNodes = [grandChild]
when: 'the new data node (list elements) are added to an existing parent node'
- objectUnderTest.addListElementsBatch(DATASPACE_NAME, ANCHOR_NAME3, '/parent-201', [listElements])
+ objectUnderTest.addMultipleLists(DATASPACE_NAME, ANCHOR_NAME3, '/parent-201', [listElements])
then: 'new entries are successfully persisted, parent node now contains 5 children (2 new + 3 existing before)'
def parentFragment = fragmentRepository.getById(LIST_DATA_NODE_PARENT201_FRAGMENT_ID)
def allChildXpaths = parentFragment.childFragments.collect { it.xpath }
@@ -179,17 +181,41 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
}
@Sql([CLEAR_DATA, SET_DATA])
+ def 'Add multiple list with a mix of existing and new elements'() {
+ given: 'two new child list elements for an existing parent'
+ def existingDataNode = dataNodeBuilder.withXpath('/parent-100/child-001').withLeaves(['id': '001']).build()
+ def newDataNode1 = dataNodeBuilder.withXpath('/parent-100/child-new1').withLeaves(['id': 'new1']).build()
+ def newDataNode2 = dataNodeBuilder.withXpath('/parent-200/child-new2').withLeaves(['id': 'new2']).build()
+ def dataNodeList1 = [existingDataNode, newDataNode1]
+ def dataNodeList2 = [newDataNode2]
+ when: 'duplicate data node is requested to be added'
+ objectUnderTest.addMultipleLists(DATASPACE_NAME, ANCHOR_NAME3, '/', [dataNodeList1,dataNodeList2])
+ then: 'already defined batch exception is thrown'
+ def thrown = thrown(AlreadyDefinedExceptionBatch)
+ and: 'it only contains the xpath(s) of the duplicated elements'
+ assert thrown.alreadyDefinedXpaths.size() == 1
+ assert thrown.alreadyDefinedXpaths.contains('/parent-100/child-001')
+ and: 'it does NOT contains the xpaths of the new element that were not combined with existing elements'
+ assert !thrown.alreadyDefinedXpaths.contains('/parent-100/child-new1')
+ assert !thrown.alreadyDefinedXpaths.contains('/parent-100/child-new1')
+ and: 'the new entity is inserted correctly'
+ def dataspaceEntity = dataspaceRepository.getByName(DATASPACE_NAME)
+ def anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, ANCHOR_NAME3)
+ fragmentRepository.findByDataspaceAndAnchorAndXpath(dataspaceEntity, anchorEntity, '/parent-200/child-new2').isPresent()
+ }
+
+ @Sql([CLEAR_DATA, SET_DATA])
def 'Add list element error scenario: #scenario.'() {
given: 'list element as a collection of data nodes'
- def listElementCollection = toDataNodes(listElementXpaths)
+ def listElements = toDataNodes(listElementXpaths)
when: 'attempt to add list elements to parent node'
- objectUnderTest.addListElements(DATASPACE_NAME, ANCHOR_NAME3, parentNodeXpath, listElementCollection)
+ objectUnderTest.addListElements(DATASPACE_NAME, ANCHOR_NAME3, parentNodeXpath, listElements)
then: 'a #expectedException is thrown'
thrown(expectedException)
where: 'following parameters were used'
scenario | parentNodeXpath | listElementXpaths || expectedException
'parent node does not exist' | '/unknown' | ['irrelevant'] || DataNodeNotFoundException
- 'data fragment already exists' | '/parent-201' | ["/parent-201/child-204[@key='A']"] || AlreadyDefinedException
+ 'data fragment already exists' | '/parent-201' | ["/parent-201/child-204[@key='A']"] || AlreadyDefinedExceptionBatch
}
@Sql([CLEAR_DATA, SET_DATA])
@@ -559,8 +585,9 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
return xpaths.collect { new DataNodeBuilder().withXpath(it).build() }
}
+
static DataNode buildDataNode(xpath, leaves, childDataNodes) {
- return new DataNodeBuilder().withXpath(xpath).withLeaves(leaves).withChildDataNodes(childDataNodes).build()
+ return dataNodeBuilder.withXpath(xpath).withLeaves(leaves).withChildDataNodes(childDataNodes).build()
}
static Map<String, Object> getLeavesMap(FragmentEntity fragmentEntity) {
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 1bbf358e5..470b03afd 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
@@ -86,17 +86,25 @@ class CpsDataPersistenceServiceSpec extends Specification {
assert concurrencyException.getDetails().contains('/some/xpath')
}
- def 'Handling of StaleStateException (caused by concurrent updates) during update data nodes and descendants.'() {
- given: 'the fragment repository returns a list of fragment entities'
- mockFragmentRepository.getByDataspaceAndAnchorAndXpath(*_) >> new FragmentEntity()
- and: 'a data node is concurrently updated by another transaction'
+ def 'Handling of StaleStateException (caused by concurrent updates) during update data nodes and descendants.'() {
+ given: 'the system contains and can update one datanode'
+ def dataNode1 = mockDataNodeAndFragmentEntity('/node1', 'OK')
+ and: 'the system contains two more datanodes that throw an exception while updating'
+ def dataNode2 = mockDataNodeAndFragmentEntity('/node2', 'EXCEPTION')
+ def dataNode3 = mockDataNodeAndFragmentEntity('/node3', 'EXCEPTION')
+ and: 'the batch update will therefore also fail'
mockFragmentRepository.saveAll(*_) >> { throw new StaleStateException("concurrent updates") }
- when: 'attempt to update data node with submitted data nodes'
- objectUnderTest.updateDataNodesAndDescendants('some-dataspace', 'some-anchor', [])
+ when: 'attempt batch update data nodes'
+ objectUnderTest.updateDataNodesAndDescendants('some-dataspace', 'some-anchor', [dataNode1, dataNode2, dataNode3])
then: 'concurrency exception is thrown'
- def concurrencyException = thrown(ConcurrencyException)
- assert concurrencyException.getDetails().contains('some-dataspace')
- assert concurrencyException.getDetails().contains('some-anchor')
+ def thrown = thrown(ConcurrencyException)
+ assert thrown.message == 'Concurrent Transactions'
+ and: 'it does not contain the successfull datanode'
+ assert !thrown.details.contains('/node1')
+ and: 'it contains the failed datanodes'
+ assert thrown.details.contains('/node2')
+ assert thrown.details.contains('/node3')
+
}
def 'Retrieving a data node with a property JSON value of #scenario'() {
@@ -193,4 +201,14 @@ class CpsDataPersistenceServiceSpec extends Specification {
assert fragmentEntities.size() == 2
}})
}
+
+ def mockDataNodeAndFragmentEntity(xpath, scenario) {
+ def dataNode = new DataNodeBuilder().withXpath(xpath).build()
+ def fragmentEntity = new FragmentEntity(xpath: xpath, childFragments: [])
+ mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, xpath) >> fragmentEntity
+ if ('EXCEPTION' == scenario) {
+ mockFragmentRepository.save(fragmentEntity) >> { throw new StaleStateException("concurrent updates") }
+ }
+ return dataNode
+ }
} \ No newline at end of file
diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsModulePersistenceServiceIntegrationSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsModulePersistenceServiceIntegrationSpec.groovy
index eac28873e..f9ebc52f1 100644
--- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsModulePersistenceServiceIntegrationSpec.groovy
+++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsModulePersistenceServiceIntegrationSpec.groovy
@@ -31,8 +31,10 @@ import org.onap.cps.spi.model.ModuleReference
import org.onap.cps.spi.repository.AnchorRepository
import org.onap.cps.spi.repository.SchemaSetRepository
import org.onap.cps.spi.repository.SchemaSetYangResourceRepositoryImpl
+import org.onap.cps.spi.repository.YangResourceRepository
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.test.context.jdbc.Sql
+import spock.lang.Ignore
class CpsModulePersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
@@ -48,6 +50,9 @@ class CpsModulePersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase
@Autowired
CpsAdminPersistenceService cpsAdminPersistenceService
+ @Autowired
+ YangResourceRepository yangResourceRepository
+
final static String SET_DATA = '/data/schemaset.sql'
def static EXISTING_SCHEMA_SET_NAME = SCHEMA_SET_NAME1
@@ -77,6 +82,20 @@ class CpsModulePersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase
}
@Sql([CLEAR_DATA, SET_DATA])
+ def 'Getting yang resource ids from module references'() {
+ when: 'getting yang resources for #scenario'
+ def result = yangResourceRepository.getResourceIdsByModuleReferences(moduleReferences)
+ then: 'the result contains the expected number entries'
+ assert result.size() == expectedResultSize
+ where: 'the following module references are provided'
+ scenario | moduleReferences || expectedResultSize
+ '2 valid module references' | [ new ModuleReference('MODULE-NAME-002','REVISION-002'), new ModuleReference('MODULE-NAME-003','REVISION-002') ] || 2
+ '1 invalid module reference' | [ new ModuleReference('NOT EXIST','IRRELEVANT') ] || 0
+ '1 valid and 1 invalid module reference' | [ new ModuleReference('MODULE-NAME-002','REVISION-002'), new ModuleReference('NOT EXIST','IRRELEVANT') ] || 1
+ 'no module references' | [] || 0
+ }
+
+ @Sql([CLEAR_DATA, SET_DATA])
def 'Store schema set error scenario: #scenario.'() {
when: 'attempt to store schema set #schemaSetName in dataspace #dataspaceName'
objectUnderTest.storeSchemaSet(dataspaceName, schemaSetName, newYangResourcesNameToContentMap)
diff --git a/cps-ri/src/test/resources/data/cps-path-query.sql b/cps-ri/src/test/resources/data/cps-path-query.sql
index b6000cffb..fa711cbbf 100644
--- a/cps-ri/src/test/resources/data/cps-path-query.sql
+++ b/cps-ri/src/test/resources/data/cps-path-query.sql
@@ -55,10 +55,12 @@ INSERT INTO FRAGMENT (ID, DATASPACE_ID, ANCHOR_ID, PARENT_ID, XPATH, ATTRIBUTES)
(2, 1001, 1003, 1, '/shops/shop[@id=''1'']', '{"id" : 1, "type" : "bookstore"}'),
(3, 1001, 1003, 2, '/shops/shop[@id=''1'']/categories[@code=''1'']', '{"code" : 1, "type" : "bookstore", "name": "SciFi"}'),
(4, 1001, 1003, 2, '/shops/shop[@id=''1'']/categories[@code=''2'']', '{"code" : 2, "type" : "bookstore", "name": "Fiction"}'),
+ (31, 1001, 1003, 2, '/shops/shop[@id=''1'']/categories[@code=''string/with/slash/'']', '{"code" : "string/with/slash", "type" : "text/with/slash", "name": "Fiction"}'),
(5, 1001, 1003, 3, '/shops/shop[@id=''1'']/categories[@code=''1'']/book', '{"price" : 5, "title" : "Dune", "labels" : ["special offer","classics",""]}'),
(6, 1001, 1003, 4, '/shops/shop[@id=''1'']/categories[@code=''2'']/book', '{"price" : 15, "title" : "Chapters", "editions" : [2000,2010,2020]}'),
(7, 1001, 1003, 5, '/shops/shop[@id=''1'']/categories[@code=''1'']/book/author[@FirstName=''Joe'' and @Surname=''Bloggs'']', '{"FirstName" : "Joe", "Surname": "Bloggs","title": "Dune"}'),
- (8, 1001, 1003, 6, '/shops/shop[@id=''1'']/categories[@code=''2'']/book/author[@FirstName=''Joe'' and @Surname=''Smith'']', '{"FirstName" : "Joe", "Surname": "Smith","title": "Chapters"}');
+ (8, 1001, 1003, 6, '/shops/shop[@id=''1'']/categories[@code=''2'']/book/author[@FirstName=''Joe'' and @Surname=''Smith'']', '{"FirstName" : "Joe", "Surname": "Smith","title": "Chapters"}'),
+ (32, 1001, 1003, 6, '/shops/shop[@id=''1'']/categories[@code=''2'']/book/author[@FirstName=''Joe'' and @Address=''string[with]square[brackets]'']', '{"FirstName" : "Joe", "Address": "string[with]square[brackets]","title": "Chapters"}');
INSERT INTO FRAGMENT (ID, DATASPACE_ID, ANCHOR_ID, PARENT_ID, XPATH, ATTRIBUTES) VALUES
(9, 1001, 1003, 1, '/shops/shop[@id=''2'']', '{"type" : "bookstore"}'),