-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 <sv001010507@techmahindra.com>
/*
* ============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.
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;
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
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<DeltaReport> getDeltaReports(final Collection<DataNode> sourceDataNodes,
/*
* ============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.
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());
}
}
}
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;
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;
final List<DeltaReport> 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 {
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
+ ));
+ }
}
/*
* ============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.
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
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
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
/*
* ============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.
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')
}
}
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)