From ae5a47388dae38c51b437e448ae6a23fa9a77591 Mon Sep 17 00:00:00 2001 From: ToineSiebelink Date: Tue, 13 Sep 2022 12:51:21 +0100 Subject: [PATCH] Handle partial failure (improvements) - catching of failures on retry of individual nodes - extract cm handle id from xpaths (can only report xpaths in cps core) - add test for same Issue-ID: CPS-1232 Issue-ID: CPS-1126 Signed-off-by: ToineSiebelink Change-Id: Ice2032c8b15fea97ae0aaa4d1ed642b3499228fa --- .../NetworkCmProxyRestExceptionHandler.java | 7 ++++ .../NetworkCmProxyRestExceptionHandlerSpec.groovy | 20 ++++++----- .../api/impl/NetworkCmProxyDataServiceImpl.java | 2 +- .../api/models/CmHandleRegistrationResponse.java | 40 +++++++++++++++------- ...rkCmProxyDataServiceImplRegistrationSpec.groovy | 11 +++--- .../models/CmHandleRegistrationResponseSpec.groovy | 23 +++++++++++++ .../rest/exceptions/CpsRestExceptionHandler.java | 3 +- .../spi/impl/CpsDataPersistenceServiceImpl.java | 24 +++++++++---- ...CpsDataPersistenceServiceIntegrationSpec.groovy | 40 +++++++++++++--------- .../exceptions/AlreadyDefinedExceptionBatch.java | 6 ++-- 10 files changed, 122 insertions(+), 54 deletions(-) diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandler.java index f12a1c5f2..58a60d2e1 100755 --- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandler.java +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandler.java @@ -32,6 +32,8 @@ import org.onap.cps.ncmp.rest.controller.NetworkCmProxyInventoryController; import org.onap.cps.ncmp.rest.model.DmiErrorMessage; import org.onap.cps.ncmp.rest.model.DmiErrorMessageDmiresponse; import org.onap.cps.ncmp.rest.model.ErrorMessage; +import org.onap.cps.spi.exceptions.AlreadyDefinedException; +import org.onap.cps.spi.exceptions.AlreadyDefinedExceptionBatch; import org.onap.cps.spi.exceptions.CpsException; import org.onap.cps.spi.exceptions.DataNodeNotFoundException; import org.onap.cps.spi.exceptions.DataValidationException; @@ -80,6 +82,11 @@ public class NetworkCmProxyRestExceptionHandler { return buildErrorResponse(HttpStatus.BAD_REQUEST, exception); } + @ExceptionHandler({AlreadyDefinedException.class, AlreadyDefinedExceptionBatch.class }) + public static ResponseEntity handleAlreadyDefinedExceptions(final Exception exception) { + return buildErrorResponse(HttpStatus.CONFLICT, exception); + } + @ExceptionHandler({DataNodeNotFoundException.class}) public static ResponseEntity handleNotFoundExceptions(final CpsException exception) { return buildErrorResponse(HttpStatus.NOT_FOUND, exception); diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandlerSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandlerSpec.groovy index b8b7fe3bc..9d1077fd2 100644 --- a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandlerSpec.groovy +++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandlerSpec.groovy @@ -33,6 +33,8 @@ import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastoreResourceRequestHa import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor import org.onap.cps.ncmp.rest.mapper.CmHandleStateMapper import org.onap.cps.ncmp.rest.util.DeprecationHelper +import org.onap.cps.spi.exceptions.AlreadyDefinedException +import org.onap.cps.spi.exceptions.AlreadyDefinedExceptionBatch import org.onap.cps.spi.exceptions.CpsException import org.onap.cps.spi.exceptions.DataNodeNotFoundException import org.onap.cps.spi.exceptions.DataValidationException @@ -106,13 +108,15 @@ class NetworkCmProxyRestExceptionHandlerSpec extends Specification { then: 'an HTTP response is returned with correct message and details' assertTestResponse(response, expectedErrorCode, expectedErrorMessage, expectedErrorDetails) where: - scenario | exception || expectedErrorDetails | expectedErrorMessage | expectedErrorCode - 'CPS' | new CpsException(sampleErrorMessage, sampleErrorDetails) || sampleErrorDetails | sampleErrorMessage | INTERNAL_SERVER_ERROR - 'NCMP-server' | new ServerNcmpException(sampleErrorMessage, sampleErrorDetails) || null | sampleErrorMessage | INTERNAL_SERVER_ERROR - 'NCMP-client' | new DmiRequestException(sampleErrorMessage, sampleErrorDetails) || null | sampleErrorMessage | BAD_REQUEST - 'DataNode Validation' | new DataNodeNotFoundException('myDataspaceName', 'myAnchorName') || null | 'DataNode not found' | NOT_FOUND - 'other' | new IllegalStateException(sampleErrorMessage) || null | sampleErrorMessage | INTERNAL_SERVER_ERROR - 'Data Node Not Found' | new DataNodeNotFoundException('myDataspaceName', 'myAnchorName') || 'DataNode not found' | 'DataNode not found' | NOT_FOUND + scenario | exception || expectedErrorDetails | expectedErrorMessage | expectedErrorCode + 'CPS' | new CpsException(sampleErrorMessage, sampleErrorDetails) || sampleErrorDetails | sampleErrorMessage | INTERNAL_SERVER_ERROR + 'NCMP-server' | new ServerNcmpException(sampleErrorMessage, sampleErrorDetails) || null | sampleErrorMessage | INTERNAL_SERVER_ERROR + 'NCMP-client' | new DmiRequestException(sampleErrorMessage, sampleErrorDetails) || null | sampleErrorMessage | BAD_REQUEST + 'DataNode Validation' | new DataNodeNotFoundException('myDataspaceName', 'myAnchorName') || null | 'DataNode not found' | NOT_FOUND + 'other' | new IllegalStateException(sampleErrorMessage) || null | sampleErrorMessage | INTERNAL_SERVER_ERROR + 'Data Node Not Found' | new DataNodeNotFoundException('myDataspaceName', 'myAnchorName') || 'DataNode not found' | 'DataNode not found' | NOT_FOUND + 'Existing entry' | new AlreadyDefinedException('name',null) || 'name already exists' | 'Already defined exception' | CONFLICT + 'Existing entries' | new AlreadyDefinedExceptionBatch(["x[@id='abc']"]) || 'Check logs for details' | null | CONFLICT } def 'Post request with exception returns correct HTTP Status.'() { @@ -156,7 +160,7 @@ class NetworkCmProxyRestExceptionHandlerSpec extends Specification { assert response.status == expectedStatus.value() def content = new JsonSlurper().parseText(response.contentAsString) assert content['status'].toString().contains(expectedStatus.toString()) - assert content['message'].toString().contains(expectedErrorMessage) + assert expectedErrorMessage == null || content['message'].toString().contains(expectedErrorMessage) assert expectedErrorDetails == null || content['details'].toString().contains(expectedErrorDetails) } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java index 0eb275cf0..3f440d65b 100755 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java @@ -367,7 +367,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService return CmHandleRegistrationResponse.createSuccessResponses(cmHandleIds); } catch (final AlreadyDefinedExceptionBatch alreadyDefinedExceptionBatch) { return CmHandleRegistrationResponse.createFailureResponses( - alreadyDefinedExceptionBatch.getAlreadyDefinedCmHandleIds(), + alreadyDefinedExceptionBatch.getAlreadyDefinedXpaths(), RegistrationError.CM_HANDLE_ALREADY_EXIST); } catch (final Exception exception) { return CmHandleRegistrationResponse.createFailureResponses(cmHandleIds, exception); diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponse.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponse.java index 9f8021842..d5b27b61f 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponse.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponse.java @@ -21,15 +21,20 @@ package org.onap.cps.ncmp.api.models; +import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.stream.Collectors; import lombok.Builder; import lombok.Data; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; @Data @Builder +@Slf4j public class CmHandleRegistrationResponse { private final String cmHandle; @@ -37,16 +42,19 @@ public class CmHandleRegistrationResponse { private RegistrationError registrationError; private String errorText; + private static final Pattern cmHandleIdInXpathPattern = Pattern.compile("\\[@id='(.*?)']"); + /** * Creates a failure response based on exception. * - * @param cmHandle cmHandle + * @param cmHandleId cmHandleId * @param exception exception * @return CmHandleRegistrationResponse */ - public static CmHandleRegistrationResponse createFailureResponse(final String cmHandle, final Exception exception) { + public static CmHandleRegistrationResponse createFailureResponse(final String cmHandleId, + final Exception exception) { return CmHandleRegistrationResponse.builder() - .cmHandle(cmHandle) + .cmHandle(cmHandleId) .status(Status.FAILURE) .registrationError(RegistrationError.UNKNOWN_ERROR) .errorText(exception.getMessage()).build(); @@ -55,13 +63,13 @@ public class CmHandleRegistrationResponse { /** * Creates a failure response based on registration error. * - * @param cmHandle cmHandle + * @param cmHandleId cmHandleId * @param registrationError registrationError * @return CmHandleRegistrationResponse */ - public static CmHandleRegistrationResponse createFailureResponse(final String cmHandle, + public static CmHandleRegistrationResponse createFailureResponse(final String cmHandleId, final RegistrationError registrationError) { - return CmHandleRegistrationResponse.builder().cmHandle(cmHandle) + return CmHandleRegistrationResponse.builder().cmHandle(cmHandleId) .status(Status.FAILURE) .registrationError(registrationError) .errorText(registrationError.errorText) @@ -71,15 +79,23 @@ public class CmHandleRegistrationResponse { /** * Creates a failure response based on registration error. * - * @param cmHandleIds list of failed cmHandleIds + * @param failedXpaths list of failed Xpaths * @param registrationError enum describing the type of registration error * @return CmHandleRegistrationResponse */ - public static List createFailureResponses(final Collection cmHandleIds, + public static List createFailureResponses(final Collection failedXpaths, final RegistrationError registrationError) { - return cmHandleIds.stream() - .map(cmHandleId -> CmHandleRegistrationResponse.createFailureResponse(cmHandleId, registrationError)) - .collect(Collectors.toList()); + final List cmHandleRegistrationResponses = new ArrayList<>(failedXpaths.size()); + for (final String xpath : failedXpaths) { + final Matcher matcher = cmHandleIdInXpathPattern.matcher(xpath); + if (matcher.find()) { + cmHandleRegistrationResponses.add( + CmHandleRegistrationResponse.createFailureResponse(matcher.group(1), registrationError)); + } else { + log.warn("Unexpected xpath {}", xpath); + } + } + return cmHandleRegistrationResponses; } /** @@ -121,4 +137,4 @@ public class CmHandleRegistrationResponse { public final String errorText; } -} \ No newline at end of file +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy index 2fe521c03..0b58d4419 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy @@ -184,7 +184,8 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification { new NcmpServiceCmHandle(cmHandleId: 'cmhandle2'), new NcmpServiceCmHandle(cmHandleId: 'cmhandle3')]) and: 'cm-handle creation is successful for 1st and 3rd; failed for 2nd' - mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(*_) >> { throw new AlreadyDefinedExceptionBatch(['cmhandle2']) } + def xpath = "somePathWithId[@id='cmhandle2']" + mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(*_) >> { throw new AlreadyDefinedExceptionBatch([xpath]) } when: 'registration is updated to create cm-handles' def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) then: 'a response is received for all cm-handles' @@ -215,10 +216,10 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification { assert it.errorText == expectedErrorText } where: - scenario | cmHandleId | exception || expectedError | expectedErrorText - 'cm-handle already exist' | 'cmhandle' | new AlreadyDefinedExceptionBatch([cmHandleId]) || CM_HANDLE_ALREADY_EXIST | 'cm-handle already exists' - 'cm-handle has invalid name' | 'cm handle with space' | new DataValidationException("", "") || CM_HANDLE_INVALID_ID | 'cm-handle has an invalid character(s) in id' - 'unknown exception while registering cm-handle' | 'cmhandle' | new RuntimeException('Failed') || UNKNOWN_ERROR | 'Failed' + scenario | cmHandleId | exception || expectedError | expectedErrorText + 'cm-handle already exist' | 'cmhandle' | new AlreadyDefinedExceptionBatch(["path[@id='${cmHandleId}']".toString()]) || CM_HANDLE_ALREADY_EXIST | 'cm-handle already exists' + 'cm-handle has invalid name' | 'cm handle with space' | new DataValidationException("", "") || CM_HANDLE_INVALID_ID | 'cm-handle has an invalid character(s) in id' + 'unknown exception while registering cm-handle' | 'cmhandle' | new RuntimeException('Failed') || UNKNOWN_ERROR | 'Failed' } def 'Update CM-Handle: Update Operation Response is added to the response'() { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponseSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponseSpec.groovy index 4476998d8..dba29343e 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponseSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponseSpec.groovy @@ -24,6 +24,8 @@ import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationErr import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.Status import spock.lang.Specification +import java.util.stream.Collectors + class CmHandleRegistrationResponseSpec extends Specification { def 'Successful cm-handle Registration Response'() { @@ -68,4 +70,25 @@ class CmHandleRegistrationResponseSpec extends Specification { 'cm-handle id is invalid' | 'cm handle' | RegistrationError.CM_HANDLE_INVALID_ID } + def 'Failed cm-handle Registration with multiple responses.'() { + when: 'cm-handle failure response is created for 2 xpaths' + def cmHandleRegistrationResponses = + CmHandleRegistrationResponse.createFailureResponses(["somePathWithId[@id='123']","somePathWithId[@id='456']"], RegistrationError.CM_HANDLE_ALREADY_EXIST) + then: 'the response has the correct cm handle ids' + assert cmHandleRegistrationResponses.size() == 2 + assert cmHandleRegistrationResponses.stream().map(it -> it.cmHandle).collect(Collectors.toList()) + .containsAll(['123','456']) + } + + def 'Failed cm-handle Registration with multiple responses with an unexpected xpath.'() { + when: 'cm-handle failure response is created for one valid and one unexpected xpath' + def cmHandleRegistrationResponses = + CmHandleRegistrationResponse.createFailureResponses(["somePathWithId[@id='123']","valid/xpath/without-id[@key='123']"], RegistrationError.CM_HANDLE_ALREADY_EXIST) + then: 'the response has only one entry' + assert cmHandleRegistrationResponses.size() == 1 + } + + + + } diff --git a/cps-rest/src/main/java/org/onap/cps/rest/exceptions/CpsRestExceptionHandler.java b/cps-rest/src/main/java/org/onap/cps/rest/exceptions/CpsRestExceptionHandler.java index 9495b3d9e..93233d9c4 100755 --- a/cps-rest/src/main/java/org/onap/cps/rest/exceptions/CpsRestExceptionHandler.java +++ b/cps-rest/src/main/java/org/onap/cps/rest/exceptions/CpsRestExceptionHandler.java @@ -31,6 +31,7 @@ import org.onap.cps.rest.controller.DataRestController; import org.onap.cps.rest.controller.QueryRestController; import org.onap.cps.rest.model.ErrorMessage; import org.onap.cps.spi.exceptions.AlreadyDefinedException; +import org.onap.cps.spi.exceptions.AlreadyDefinedExceptionBatch; import org.onap.cps.spi.exceptions.CpsAdminException; import org.onap.cps.spi.exceptions.CpsException; import org.onap.cps.spi.exceptions.CpsPathException; @@ -75,7 +76,7 @@ public class CpsRestExceptionHandler { ? HttpStatus.NOT_FOUND : HttpStatus.BAD_REQUEST, exception); } - @ExceptionHandler({DataInUseException.class, AlreadyDefinedException.class}) + @ExceptionHandler({DataInUseException.class, AlreadyDefinedException.class, AlreadyDefinedExceptionBatch.class}) public static ResponseEntity handleDataInUseException(final Exception exception) { return buildErrorResponse(HttpStatus.CONFLICT, exception); } 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 e02fb7355..d62421c5a 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 @@ -103,17 +103,17 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService @Override public void addMultipleLists(final String dataspaceName, final String anchorName, final String parentNodeXpath, final Collection> newLists) { - final Collection failedCmHandleIds = new HashSet<>(); + final Collection failedXpaths = new HashSet<>(); newLists.forEach(newList -> { try { addChildrenDataNodes(dataspaceName, anchorName, parentNodeXpath, newList); - } catch (final AlreadyDefinedException e) { - newList.forEach(listElement -> failedCmHandleIds.add((String) listElement.getLeaves().get("id"))); + } catch (final AlreadyDefinedExceptionBatch e) { + failedXpaths.addAll(e.getAlreadyDefinedXpaths()); } }); - if (!failedCmHandleIds.isEmpty()) { - throw new AlreadyDefinedExceptionBatch(failedCmHandleIds); + if (!failedXpaths.isEmpty()) { + throw new AlreadyDefinedExceptionBatch(failedXpaths); } } @@ -147,7 +147,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService }); fragmentRepository.saveAll(fragmentEntities); } catch (final DataIntegrityViolationException e) { - log.warn("Exception occurred : {} , Batch with size : {} will be retried using individual save operations", + log.warn("Exception occurred : {} , While saving : {} children, retrying using individual save operations", e, fragmentEntities.size()); retrySavingEachChildIndividually(dataspaceName, anchorName, parentNodeXpath, newChildren); } @@ -155,7 +155,17 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService private void retrySavingEachChildIndividually(final String dataspaceName, final String anchorName, final String parentNodeXpath, final Collection newChildren) { - newChildren.forEach(newChild -> addNewChildDataNode(dataspaceName, anchorName, parentNodeXpath, newChild)); + final Collection 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); + } } @Override 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 ba9bd6f95..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 @@ -49,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 @@ -180,33 +181,41 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase { } @Sql([CLEAR_DATA, SET_DATA]) - def 'Add already existing list elements'() { + def 'Add multiple list with a mix of existing and new elements'() { given: 'two new child list elements for an existing parent' - def listElementXpaths1 = ['/parent-100', '/parent-100/child-001'] - def listElementXpaths2 = ['/parent-200', '/parent-200/child-201'] - def listElements1 = toDataNodesWithId(listElementXpaths1, 'cmhandle1') - def listElements2 = toDataNodesWithId(listElementXpaths2, 'cmhandle2') + 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, '/', [listElements1,listElements2]) + objectUnderTest.addMultipleLists(DATASPACE_NAME, ANCHOR_NAME3, '/', [dataNodeList1,dataNodeList2]) then: 'already defined batch exception is thrown' - def e = thrown(AlreadyDefinedExceptionBatch) - and: 'it contains both cmhandle ids' - assert e.alreadyDefinedCmHandleIds.size() == 2 - assert e.alreadyDefinedCmHandleIds.containsAll(['cmhandle1', 'cmhandle2']) + 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]) @@ -576,12 +585,9 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase { return xpaths.collect { new DataNodeBuilder().withXpath(it).build() } } - static Collection toDataNodesWithId(xpaths, id) { - return xpaths.collect { new DataNodeBuilder().withXpath(it).withLeaves(['id': id]).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 getLeavesMap(FragmentEntity fragmentEntity) { diff --git a/cps-service/src/main/java/org/onap/cps/spi/exceptions/AlreadyDefinedExceptionBatch.java b/cps-service/src/main/java/org/onap/cps/spi/exceptions/AlreadyDefinedExceptionBatch.java index a5ce6fde2..0ba656aa1 100644 --- a/cps-service/src/main/java/org/onap/cps/spi/exceptions/AlreadyDefinedExceptionBatch.java +++ b/cps-service/src/main/java/org/onap/cps/spi/exceptions/AlreadyDefinedExceptionBatch.java @@ -26,9 +26,9 @@ import lombok.Getter; public class AlreadyDefinedExceptionBatch extends RuntimeException { @Getter - private final Collection alreadyDefinedCmHandleIds; + private final Collection alreadyDefinedXpaths; - public AlreadyDefinedExceptionBatch(final Collection alreadyDefinedCmHandleIds) { - this.alreadyDefinedCmHandleIds = alreadyDefinedCmHandleIds; + public AlreadyDefinedExceptionBatch(final Collection alreadyDefinedXPaths) { + this.alreadyDefinedXpaths = alreadyDefinedXPaths; } } -- 2.16.6