From c30d5a455b0016241ef6eb0ebbabdb3f0070f80c Mon Sep 17 00:00:00 2001 From: ToineSiebelink Date: Wed, 19 Nov 2025 08:46:01 +0000 Subject: [PATCH] Fix bug related to incorrect exception check when saving data nodes Issue-ID: CPS-3053 Change-Id: I32c14a64f3422f0d088752821519a4cc592e4509 Signed-off-by: ToineSiebelink --- .../onap/cps/ri/CpsDataPersistenceServiceImpl.java | 8 +++---- .../ri/CpsDataPersistenceServiceImplSpec.groovy | 27 +++++++++++----------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/cps-ri/src/main/java/org/onap/cps/ri/CpsDataPersistenceServiceImpl.java b/cps-ri/src/main/java/org/onap/cps/ri/CpsDataPersistenceServiceImpl.java index 3e80e30c2a..7c065f8112 100644 --- a/cps-ri/src/main/java/org/onap/cps/ri/CpsDataPersistenceServiceImpl.java +++ b/cps-ri/src/main/java/org/onap/cps/ri/CpsDataPersistenceServiceImpl.java @@ -43,7 +43,6 @@ import java.util.TreeMap; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.hibernate.StaleStateException; import org.onap.cps.api.exceptions.AlreadyDefinedException; import org.onap.cps.api.exceptions.ConcurrencyException; import org.onap.cps.api.exceptions.CpsAdminException; @@ -67,6 +66,7 @@ import org.onap.cps.ri.utils.SessionManager; import org.onap.cps.spi.CpsDataPersistenceService; import org.onap.cps.utils.JsonObjectMapper; import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.orm.ObjectOptimisticLockingFailureException; import org.springframework.stereotype.Service; @Service @@ -145,7 +145,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService try { fragmentRepository.saveAll(fragmentEntities); - } catch (final StaleStateException staleStateException) { + } catch (final ObjectOptimisticLockingFailureException objectOptimisticLockingFailureException) { retryUpdateDataNodesIndividually(anchorEntity, fragmentEntities); } } @@ -173,7 +173,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService try { fragmentRepository.saveAll(existingFragmentEntities); - } catch (final StaleStateException staleStateException) { + } catch (final ObjectOptimisticLockingFailureException objectOptimisticLockingFailureException) { retryUpdateDataNodesIndividually(anchorEntity, existingFragmentEntities); } } @@ -184,7 +184,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService for (final FragmentEntity dataNodeFragment : fragmentEntities) { try { fragmentRepository.save(dataNodeFragment); - } catch (final StaleStateException staleStateException) { + } catch (final ObjectOptimisticLockingFailureException objectOptimisticLockingFailureException) { failedXpaths.add(dataNodeFragment.getXpath()); } } diff --git a/cps-ri/src/test/groovy/org/onap/cps/ri/CpsDataPersistenceServiceImplSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/ri/CpsDataPersistenceServiceImplSpec.groovy index 7fa9b2e755..665201e520 100644 --- a/cps-ri/src/test/groovy/org/onap/cps/ri/CpsDataPersistenceServiceImplSpec.groovy +++ b/cps-ri/src/test/groovy/org/onap/cps/ri/CpsDataPersistenceServiceImplSpec.groovy @@ -1,7 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (c) 2021 Bell Canada. - * Modifications Copyright (C) 2021-2023 Nordix Foundation + * Modifications Copyright (C) 2021-2025 OpenInfra Foundation Europe. * Modifications Copyright (C) 2022-2023 TechMahindra Ltd. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,8 +21,12 @@ package org.onap.cps.ri import com.fasterxml.jackson.databind.ObjectMapper -import org.hibernate.StaleStateException +import org.onap.cps.api.exceptions.ConcurrencyException import org.onap.cps.api.exceptions.DataNodeNotFoundExceptionBatch +import org.onap.cps.api.exceptions.DataValidationException +import org.onap.cps.api.model.DataNode +import org.onap.cps.api.parameters.FetchDescendantsOption +import org.onap.cps.impl.DataNodeBuilder import org.onap.cps.ri.models.AnchorEntity import org.onap.cps.ri.models.DataspaceEntity import org.onap.cps.ri.models.FragmentEntity @@ -30,13 +34,9 @@ import org.onap.cps.ri.repository.AnchorRepository import org.onap.cps.ri.repository.DataspaceRepository import org.onap.cps.ri.repository.FragmentRepository import org.onap.cps.ri.utils.SessionManager -import org.onap.cps.api.parameters.FetchDescendantsOption -import org.onap.cps.api.exceptions.ConcurrencyException -import org.onap.cps.api.exceptions.DataValidationException -import org.onap.cps.api.model.DataNode -import org.onap.cps.impl.DataNodeBuilder import org.onap.cps.utils.JsonObjectMapper import org.springframework.dao.DataIntegrityViolationException +import org.springframework.orm.ObjectOptimisticLockingFailureException import spock.lang.Specification import java.util.stream.Collectors @@ -48,6 +48,7 @@ class CpsDataPersistenceServiceImplSpec extends Specification { def mockFragmentRepository = Mock(FragmentRepository) def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper()) def mockSessionManager = Mock(SessionManager) + def someCause = Mock(Throwable) def objectUnderTest = Spy(new CpsDataPersistenceServiceImpl(mockDataspaceRepository, mockAnchorRepository, mockFragmentRepository, jsonObjectMapper, mockSessionManager)) @@ -72,7 +73,7 @@ class CpsDataPersistenceServiceImplSpec extends Specification { 2 * mockFragmentRepository.save(_) } - def 'Handling of StaleStateException (caused by concurrent updates) during patch operation for data nodes.'() { + def 'Handling of ObjectOptimisticLockingFailureException (caused by concurrent updates) during patch operation for data nodes.'() { given: 'the system can update one datanode and has two more datanodes that throw an exception while updating' def dataNodes = createDataNodesAndMockRepositoryMethodSupportingThem([ '/node1': 'OK', @@ -81,7 +82,7 @@ class CpsDataPersistenceServiceImplSpec extends Specification { def updatedLeavesPerXPath = dataNodes.stream() .collect(Collectors.toMap(DataNode::getXpath, DataNode::getLeaves)) and: 'the batch update will therefore also fail' - mockFragmentRepository.saveAll(*_) >> { throw new StaleStateException("concurrent updates") } + mockFragmentRepository.saveAll(*_) >> { throw new ObjectOptimisticLockingFailureException('concurrent updates', someCause) } when: 'attempt batch update data nodes' objectUnderTest.batchUpdateDataLeaves('some-dataspace', 'some-anchor', updatedLeavesPerXPath) then: 'concurrency exception is thrown' @@ -131,14 +132,14 @@ class CpsDataPersistenceServiceImplSpec extends Specification { assert thrown.message == 'DataNode not found' } - def 'Handling of StaleStateException (caused by concurrent updates) during update data nodes and descendants.'() { + def 'Handling of ObjectOptimisticLockingFailureException (caused by concurrent updates) during update data nodes and descendants.'() { given: 'the system can update one datanode and has two more datanodes that throw an exception while updating' def dataNodes = createDataNodesAndMockRepositoryMethodSupportingThem([ '/node1': 'OK', '/node2': 'EXCEPTION', '/node3': 'EXCEPTION']) and: 'the batch update will therefore also fail' - mockFragmentRepository.saveAll(*_) >> { throw new StaleStateException("concurrent updates") } + mockFragmentRepository.saveAll(*_) >> { throw new ObjectOptimisticLockingFailureException('concurrent updates', someCause) } when: 'attempt batch update data nodes' objectUnderTest.updateDataNodesAndDescendants('some-dataspace', 'some-anchor', dataNodes) then: 'concurrency exception is thrown' @@ -261,7 +262,7 @@ class CpsDataPersistenceServiceImplSpec extends Specification { def fragmentEntity = new FragmentEntity(xpath: xpath, childFragments: []) mockFragmentRepository.findByAnchorIdAndXpath(_, xpath) >> fragmentEntity if ('EXCEPTION' == scenario) { - mockFragmentRepository.save(fragmentEntity) >> { throw new StaleStateException("concurrent updates") } + mockFragmentRepository.save(fragmentEntity) >> { throw new ObjectOptimisticLockingFailureException('concurrent updates', someCause) } } return dataNode } @@ -278,7 +279,7 @@ class CpsDataPersistenceServiceImplSpec extends Specification { def fragmentEntity = new FragmentEntity(id: fragmentId, anchor: anchorEntity, xpath: xpath, childFragments: []) fragmentEntities.add(fragmentEntity) if ('EXCEPTION' == scenario) { - mockFragmentRepository.save(fragmentEntity) >> { throw new StaleStateException("concurrent updates") } + mockFragmentRepository.save(fragmentEntity) >> { throw new ObjectOptimisticLockingFailureException('concurrent updates', someCause) } } fragmentId++ } -- 2.16.6