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;
return buildErrorResponse(HttpStatus.BAD_REQUEST, exception);
}
+ @ExceptionHandler({AlreadyDefinedException.class, AlreadyDefinedExceptionBatch.class })
+ public static ResponseEntity<Object> handleAlreadyDefinedExceptions(final Exception exception) {
+ return buildErrorResponse(HttpStatus.CONFLICT, exception);
+ }
+
@ExceptionHandler({DataNodeNotFoundException.class})
public static ResponseEntity<Object> handleNotFoundExceptions(final CpsException exception) {
return buildErrorResponse(HttpStatus.NOT_FOUND, exception);
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
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.'() {
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)
}
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);
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;
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();
/**
* 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)
/**
* 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<CmHandleRegistrationResponse> createFailureResponses(final Collection<String> cmHandleIds,
+ public static List<CmHandleRegistrationResponse> createFailureResponses(final Collection<String> failedXpaths,
final RegistrationError registrationError) {
- return cmHandleIds.stream()
- .map(cmHandleId -> CmHandleRegistrationResponse.createFailureResponse(cmHandleId, registrationError))
- .collect(Collectors.toList());
+ final List<CmHandleRegistrationResponse> 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;
}
/**
public final String errorText;
}
-}
\ No newline at end of file
+}
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'
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'() {
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'() {
'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
+ }
+
+
+
+
}
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;
? HttpStatus.NOT_FOUND : HttpStatus.BAD_REQUEST, exception);
}
- @ExceptionHandler({DataInUseException.class, AlreadyDefinedException.class})
+ @ExceptionHandler({DataInUseException.class, AlreadyDefinedException.class, AlreadyDefinedExceptionBatch.class})
public static ResponseEntity<Object> handleDataInUseException(final Exception exception) {
return buildErrorResponse(HttpStatus.CONFLICT, exception);
}
@Override
public void addMultipleLists(final String dataspaceName, final String anchorName, final String parentNodeXpath,
final Collection<Collection<DataNode>> newLists) {
- final Collection<String> failedCmHandleIds = new HashSet<>();
+ final Collection<String> 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);
}
}
});
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);
}
private void retrySavingEachChildIndividually(final String dataspaceName, final String anchorName,
final String parentNodeXpath, final Collection<DataNode> newChildren) {
- newChildren.forEach(newChild -> addNewChildDataNode(dataspaceName, anchorName, parentNodeXpath, newChild));
+ 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);
+ }
}
@Override
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
}
@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])
return xpaths.collect { new DataNodeBuilder().withXpath(it).build() }
}
- static Collection<DataNode> 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<String, Object> getLeavesMap(FragmentEntity fragmentEntity) {
public class AlreadyDefinedExceptionBatch extends RuntimeException {
@Getter
- private final Collection<String> alreadyDefinedCmHandleIds;
+ private final Collection<String> alreadyDefinedXpaths;
- public AlreadyDefinedExceptionBatch(final Collection<String> alreadyDefinedCmHandleIds) {
- this.alreadyDefinedCmHandleIds = alreadyDefinedCmHandleIds;
+ public AlreadyDefinedExceptionBatch(final Collection<String> alreadyDefinedXPaths) {
+ this.alreadyDefinedXpaths = alreadyDefinedXPaths;
}
}