From: Sourabh Verma Date: Fri, 12 Dec 2025 11:46:30 +0000 (+0530) Subject: Fix Response code returned by Apply Delta Report API X-Git-Tag: 3.7.4~5^2 X-Git-Url: https://gerrit.onap.org/r/gitweb?a=commitdiff_plain;h=refs%2Fchanges%2F42%2F142742%2F12;p=cps.git Fix Response code returned by Apply Delta Report API -Changed response message when syntactically wrong json used. -Added Response Code and Exception messages for invalid xpath,invalid action,semantically wrong json and executing same request in delta report. Issue-ID: CPS-3098 Change-Id: Ic76004fdcf7e9f63cdf415f8da7641016ba17258 Signed-off-by: Sourabh Verma --- diff --git a/cps-service/src/main/java/org/onap/cps/impl/CpsDeltaServiceImpl.java b/cps-service/src/main/java/org/onap/cps/impl/CpsDeltaServiceImpl.java index 51e48e2062..e3be97aa1d 100644 --- a/cps-service/src/main/java/org/onap/cps/impl/CpsDeltaServiceImpl.java +++ b/cps-service/src/main/java/org/onap/cps/impl/CpsDeltaServiceImpl.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2023-2025 Deutsche Telekom AG + * Copyright (C) 2023-2026 Deutsche Telekom AG * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,6 +35,7 @@ import org.onap.cps.api.CpsAnchorService; import org.onap.cps.api.CpsDataService; import org.onap.cps.api.CpsDeltaService; import org.onap.cps.api.DataNodeFactory; +import org.onap.cps.api.exceptions.DataInUseException; import org.onap.cps.api.exceptions.DataValidationException; import org.onap.cps.api.model.Anchor; import org.onap.cps.api.model.DataNode; @@ -48,6 +49,7 @@ import org.onap.cps.utils.JsonObjectMapper; import org.onap.cps.utils.deltareport.DeltaReportExecutor; import org.onap.cps.utils.deltareport.DeltaReportGenerator; import org.onap.cps.utils.deltareport.GroupedDeltaReportGenerator; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Service; @Slf4j @@ -115,7 +117,12 @@ public class CpsDeltaServiceImpl implements CpsDeltaService { public void applyChangesInDeltaReport(final String dataspaceName, final String anchorName, final String deltaReportAsJsonString) { cpsValidator.validateNameCharacters(dataspaceName, anchorName); - deltaReportExecutor.applyChangesInDeltaReport(dataspaceName, anchorName, deltaReportAsJsonString); + try { + deltaReportExecutor.applyChangesInDeltaReport(dataspaceName, anchorName, deltaReportAsJsonString); + } catch (final DataIntegrityViolationException dataIntegrityViolationException) { + throw new DataInUseException("Duplicate key error", + dataIntegrityViolationException.getRootCause().getMessage()); + } } private List getDeltaReports(final Collection sourceDataNodes, diff --git a/cps-service/src/main/java/org/onap/cps/utils/JsonObjectMapper.java b/cps-service/src/main/java/org/onap/cps/utils/JsonObjectMapper.java index d52b6bde46..1262c0fd61 100644 --- a/cps-service/src/main/java/org/onap/cps/utils/JsonObjectMapper.java +++ b/cps-service/src/main/java/org/onap/cps/utils/JsonObjectMapper.java @@ -1,7 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2022 Nordix Foundation - * Modifications Copyright (C) 2025 Deutsche Telekom AG + * Modifications Copyright (C) 2025-2026 Deutsche Telekom AG * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -139,8 +139,8 @@ public class JsonObjectMapper { return objectMapper.readValue(jsonContent, collectionType); } catch (final JsonProcessingException e) { log.error("Parsing error occurred while converting JSON content to json array."); - throw new DataValidationException("Parsing error occurred while converting " - + "JSON content to specific class type.", e.getMessage()); + throw new DataValidationException(String.format("JSON parsing error at line: %d, column: %d", + e.getLocation().getLineNr(), e.getLocation().getColumnNr()), e.getOriginalMessage()); } } } diff --git a/cps-service/src/main/java/org/onap/cps/utils/deltareport/DeltaReportExecutor.java b/cps-service/src/main/java/org/onap/cps/utils/deltareport/DeltaReportExecutor.java index 86af4344c7..94c154d7e1 100644 --- a/cps-service/src/main/java/org/onap/cps/utils/deltareport/DeltaReportExecutor.java +++ b/cps-service/src/main/java/org/onap/cps/utils/deltareport/DeltaReportExecutor.java @@ -20,6 +20,9 @@ package org.onap.cps.utils.deltareport; +import static org.onap.cps.api.model.DeltaReport.CREATE_ACTION; +import static org.onap.cps.api.model.DeltaReport.REMOVE_ACTION; +import static org.onap.cps.api.model.DeltaReport.REPLACE_ACTION; import static org.onap.cps.cpspath.parser.CpsPathUtil.ROOT_NODE_XPATH; import static org.onap.cps.utils.ContentType.JSON; @@ -30,10 +33,12 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.onap.cps.api.CpsAnchorService; import org.onap.cps.api.DataNodeFactory; +import org.onap.cps.api.exceptions.DataValidationException; import org.onap.cps.api.model.Anchor; import org.onap.cps.api.model.DataNode; import org.onap.cps.api.model.DeltaReport; import org.onap.cps.cpspath.parser.CpsPathUtil; +import org.onap.cps.cpspath.parser.PathParsingException; import org.onap.cps.spi.CpsDataPersistenceService; import org.onap.cps.utils.JsonObjectMapper; import org.springframework.stereotype.Service; @@ -64,12 +69,12 @@ public class DeltaReportExecutor { final List deltaReports = jsonObjectMapper.convertToJsonArray(deltaReportAsJsonString, DeltaReport.class); for (final DeltaReport deltaReport: deltaReports) { - final String action = deltaReport.getAction(); - final String xpath = deltaReport.getXpath(); - if (action.equals(DeltaReport.REPLACE_ACTION)) { + final String xpath = validateXpath(deltaReport.getXpath()); + final String action = validateAction(deltaReport.getAction(), xpath); + if (REPLACE_ACTION.equals(action)) { final String dataForUpdate = jsonObjectMapper.asJsonString(deltaReport.getTargetData()); updateDataNodes(dataspaceName, anchorName, xpath, dataForUpdate); - } else if (action.equals(DeltaReport.REMOVE_ACTION)) { + } else if (REMOVE_ACTION.equals(action)) { final String dataForDelete = jsonObjectMapper.asJsonString(deltaReport.getSourceData()); deleteDataNodesUsingDelta(dataspaceName, anchorName, xpath, dataForDelete); } else { @@ -114,4 +119,24 @@ public class DeltaReportExecutor { final Anchor anchor = cpsAnchorService.getAnchor(datasapceName, anchorName); return dataNodeFactory.createDataNodesWithAnchorParentXpathAndNodeData(anchor, xpath, nodeData, JSON); } + + private String validateXpath(final String xpath) { + try { + return CpsPathUtil.getNormalizedXpath(xpath); + } catch (final PathParsingException pathParsingException) { + throw new DataValidationException(String.format("Error while parsing xpath expression '%s'.", xpath), + pathParsingException.getMessage()); + } + } + + private String validateAction(final String action, final String xpath) { + if (CREATE_ACTION.equals(action) || REMOVE_ACTION.equals(action) || REPLACE_ACTION.equals(action)) { + return action; + } + throw new DataValidationException("Invalid 'action' in delta report.", + String.format( + "Unsupported action '%s' at xpath: %s. Valid actions are: '%s', '%s' or '%s'.", + action, xpath, CREATE_ACTION, REMOVE_ACTION, REPLACE_ACTION + )); + } } diff --git a/cps-service/src/test/groovy/org/onap/cps/impl/CpsDeltaServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/impl/CpsDeltaServiceImplSpec.groovy index 076c4de2b1..8c24d7aabe 100644 --- a/cps-service/src/test/groovy/org/onap/cps/impl/CpsDeltaServiceImplSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/impl/CpsDeltaServiceImplSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2023-2025 Deutsche Telekom AG + * Copyright (C) 2023-2026 Deutsche Telekom AG * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,7 @@ import com.fasterxml.jackson.databind.ObjectMapper import org.onap.cps.TestUtils import org.onap.cps.api.CpsAnchorService import org.onap.cps.api.CpsDataService +import org.onap.cps.api.exceptions.DataInUseException import org.onap.cps.api.exceptions.DataValidationException import org.onap.cps.api.model.Anchor import org.onap.cps.api.model.DataNode @@ -46,6 +47,7 @@ import org.onap.cps.yang.YangTextSchemaSourceSet import org.onap.cps.yang.YangTextSchemaSourceSetBuilder import org.slf4j.LoggerFactory import org.springframework.context.annotation.AnnotationConfigApplicationContext +import org.springframework.dao.DataIntegrityViolationException import spock.lang.Shared import spock.lang.Specification @@ -357,6 +359,23 @@ class CpsDeltaServiceImplSpec extends Specification { 1 * mockDeltaReportExecutor.applyChangesInDeltaReport(dataspaceName, ANCHOR_NAME_1, deltaReportJson) } + def 'Apply changes from the same delta report to an anchor more than once'() { + given: 'delta report as JSON string and a DataIntegrityViolationException with a root cause' + def deltaReportJson = '[{"action":"create","xpath":"/bookstore"}]' + def rootCause = new RuntimeException('duplicate key') + def dataIntegrityViolationException = new DataIntegrityViolationException('error', rootCause) + and: 'mock to throw DataIntegrityViolationException on second call' + 1 * mockDeltaReportExecutor.applyChangesInDeltaReport(dataspaceName, ANCHOR_NAME_1, deltaReportJson) + 1 * mockDeltaReportExecutor.applyChangesInDeltaReport(dataspaceName, ANCHOR_NAME_1, deltaReportJson) >> { throw dataIntegrityViolationException } + when: 'first attempt to apply the delta report to the anchor' + objectUnderTest.applyChangesInDeltaReport(dataspaceName, ANCHOR_NAME_1, deltaReportJson) + and: 'second attempt to apply the same delta report to the anchor' + objectUnderTest.applyChangesInDeltaReport(dataspaceName, ANCHOR_NAME_1, deltaReportJson) + then: 'expected exception is thrown with message' + def thrownException = thrown(DataInUseException) + thrownException.message == 'Duplicate key error' + } + def setupSchemaSetMocks(yangResources) { def mockYangTextSchemaSourceSet = Mock(YangTextSchemaSourceSet) mockYangTextSchemaSourceSetCache.get(dataspaceName, schemaSetName) >> mockYangTextSchemaSourceSet diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/JsonObjectMapperSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/JsonObjectMapperSpec.groovy index e10a1c8f0d..1a750efea7 100644 --- a/cps-service/src/test/groovy/org/onap/cps/utils/JsonObjectMapperSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/utils/JsonObjectMapperSpec.groovy @@ -1,7 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2022 Nordix Foundation - * Modifications Copyright (C) 2025 Deutsche Telekom AG + * Modifications Copyright (C) 2025-2026 Deutsche Telekom AG * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -154,6 +154,6 @@ class JsonObjectMapperSpec extends Specification { jsonObjectMapper.convertToJsonArray(jsonContent, Map) then: 'a DataValidationException is thrown' def thrown = thrown(DataValidationException) - thrown.message.contains('Parsing error occurred while converting JSON content to specific class type.') + thrown.message.contains('JSON parsing error at line: 1, column: 19') } } diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/deltareport/DeltaReportExecutorSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/deltareport/DeltaReportExecutorSpec.groovy index ca7bd68cb5..eabccbf2b1 100644 --- a/cps-service/src/test/groovy/org/onap/cps/utils/deltareport/DeltaReportExecutorSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/utils/deltareport/DeltaReportExecutorSpec.groovy @@ -186,6 +186,28 @@ class DeltaReportExecutorSpec extends Specification { and: 'a DataValidationException is thrown' thrown(DataValidationException) } + def 'Apply delta report with an invalid xpath'() { + given: 'delta report as JSON string with an invalid xpath for #action action' + def deltaReportJson = '[{"action":"create","xpath":"/invalid[","targetData":{"data":[{"key":"value"}]}}]' + when: 'attempt to apply delta' + objectUnderTest.applyChangesInDeltaReport(dataspaceName, ANCHOR_NAME_1, deltaReportJson) + then: 'expected exception is thrown' + def thrownException = thrown(DataValidationException) + assert thrownException.message == 'Error while parsing xpath expression \'/invalid[\'.' + assert thrownException.details == 'failed to parse at line 1 due to no viable alternative at input \'[\'' + } + + def 'Apply delta report with an unsupported action'() { + given: 'delta report as JSON string' + def deltaReportJson = '[{"action":"invalidAction","xpath":"/bookstore","targetData":{"categories":[{"code":"1"}]}}]' + when: 'attempt to apply delta report' + objectUnderTest.applyChangesInDeltaReport(dataspaceName, ANCHOR_NAME_1, deltaReportJson) + then: 'expected exception is thrown with correct details' + def thrownException = thrown(DataValidationException) + assert thrownException.message == 'Invalid \'action\' in delta report.' + assert thrownException.details.contains('invalidAction') + assert thrownException.details.contains('/bookstore') + } def setupSchemaSetMocks(yangResources) { def mockYangTextSchemaSourceSet = Mock(YangTextSchemaSourceSet)