aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--checkstyle/pom.xml2
-rwxr-xr-xcps-application/pom.xml2
-rw-r--r--cps-bom/pom.xml2
-rwxr-xr-xcps-dependencies/pom.xml2
-rw-r--r--cps-events/pom.xml2
-rw-r--r--cps-ncmp-events/pom.xml2
-rw-r--r--cps-ncmp-rest-stub/cps-ncmp-rest-stub-app/pom.xml2
-rw-r--r--cps-ncmp-rest-stub/cps-ncmp-rest-stub-service/pom.xml2
-rw-r--r--cps-ncmp-rest-stub/pom.xml2
-rw-r--r--cps-ncmp-rest/pom.xml2
-rw-r--r--cps-ncmp-service/pom.xml2
-rwxr-xr-xcps-parent/pom.xml2
-rw-r--r--cps-path-parser/pom.xml2
-rw-r--r--cps-path-parser/src/main/antlr4/org/onap/cps/cpspath/parser/antlr4/CpsPath.g414
-rw-r--r--cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathBooleanOperatorType.java51
-rw-r--r--cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathBuilder.java69
-rw-r--r--cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathComparativeOperator.java64
-rw-r--r--cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathQuery.java12
-rw-r--r--cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathQuerySpec.groovy71
-rwxr-xr-xcps-rest/pom.xml2
-rwxr-xr-xcps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy23
-rw-r--r--cps-ri/pom.xml2
-rw-r--r--cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentQueryBuilder.java21
-rw-r--r--cps-ri/src/main/java/org/onap/cps/spi/repository/TempTableCreator.java7
-rw-r--r--cps-ri/src/main/java/org/onap/cps/spi/utils/EscapeUtils.java8
-rw-r--r--cps-ri/src/main/resources/changelog/changelog-master.yaml2
-rw-r--r--cps-ri/src/main/resources/changelog/db/changes/21-escape-quotes-in-xpath-forward.sql19
-rw-r--r--cps-ri/src/main/resources/changelog/db/changes/21-escape-quotes-in-xpath-rollback.sql19
-rw-r--r--cps-ri/src/main/resources/changelog/db/changes/21-escape-quotes-in-xpath.yaml29
-rw-r--r--cps-ri/src/test/groovy/org/onap/cps/spi/utils/EscapeUtilsSpec.groovy7
-rw-r--r--cps-service/pom.xml2
-rwxr-xr-xcps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java12
-rw-r--r--cps-service/src/main/java/org/onap/cps/utils/YangUtils.java2
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy22
-rw-r--r--cps-service/src/test/resources/bookstore.json8
-rw-r--r--cps-service/src/test/resources/bookstore.yang28
-rw-r--r--dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-app/pom.xml4
-rw-r--r--dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/pom.xml2
-rw-r--r--dmi-plugin-demo-and-csit-stub/pom.xml2
-rw-r--r--docs/cps-path.rst3
-rwxr-xr-xdocs/release-notes.rst31
-rw-r--r--integration-test/pom.xml2
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataServiceIntegrationSpec.groovy31
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsQueryServiceIntegrationSpec.groovy131
-rw-r--r--integration-test/src/test/resources/data/bookstore/bookstore.yang28
-rw-r--r--integration-test/src/test/resources/data/bookstore/bookstoreData.json86
-rw-r--r--jacoco-report/pom.xml2
-rw-r--r--pom.xml2
-rw-r--r--releases/3.3.4-container.yaml8
-rw-r--r--releases/3.3.4.yaml4
-rw-r--r--spotbugs/pom.xml2
-rwxr-xr-xversion.properties2
52 files changed, 559 insertions, 301 deletions
diff --git a/checkstyle/pom.xml b/checkstyle/pom.xml
index a6fa570e5..34775c0dc 100644
--- a/checkstyle/pom.xml
+++ b/checkstyle/pom.xml
@@ -26,7 +26,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.onap.cps</groupId>
<artifactId>checkstyle</artifactId>
- <version>3.3.4-SNAPSHOT</version>
+ <version>3.3.5-SNAPSHOT</version>
<profiles>
<profile>
diff --git a/cps-application/pom.xml b/cps-application/pom.xml
index 4b28469ac..699ea4594 100755
--- a/cps-application/pom.xml
+++ b/cps-application/pom.xml
@@ -28,7 +28,7 @@
<parent>
<groupId>org.onap.cps</groupId>
<artifactId>cps-parent</artifactId>
- <version>3.3.4-SNAPSHOT</version>
+ <version>3.3.5-SNAPSHOT</version>
<relativePath>../cps-parent/pom.xml</relativePath>
</parent>
diff --git a/cps-bom/pom.xml b/cps-bom/pom.xml
index 7374b4c22..fcf98eee8 100644
--- a/cps-bom/pom.xml
+++ b/cps-bom/pom.xml
@@ -25,7 +25,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.onap.cps</groupId>
<artifactId>cps-bom</artifactId>
- <version>3.3.4-SNAPSHOT</version>
+ <version>3.3.5-SNAPSHOT</version>
<packaging>pom</packaging>
<description>This artifact contains dependencyManagement declarations of all published CPS components.</description>
diff --git a/cps-dependencies/pom.xml b/cps-dependencies/pom.xml
index 48e704455..05b7747c8 100755
--- a/cps-dependencies/pom.xml
+++ b/cps-dependencies/pom.xml
@@ -27,7 +27,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.onap.cps</groupId>
<artifactId>cps-dependencies</artifactId>
- <version>3.3.4-SNAPSHOT</version>
+ <version>3.3.5-SNAPSHOT</version>
<packaging>pom</packaging>
<name>${project.groupId}:${project.artifactId}</name>
diff --git a/cps-events/pom.xml b/cps-events/pom.xml
index 6eb8b50af..3ff539c6e 100644
--- a/cps-events/pom.xml
+++ b/cps-events/pom.xml
@@ -24,7 +24,7 @@
<parent>
<groupId>org.onap.cps</groupId>
<artifactId>cps-parent</artifactId>
- <version>3.3.4-SNAPSHOT</version>
+ <version>3.3.5-SNAPSHOT</version>
<relativePath>../cps-parent/pom.xml</relativePath>
</parent>
diff --git a/cps-ncmp-events/pom.xml b/cps-ncmp-events/pom.xml
index cd08e5613..e11ab11f3 100644
--- a/cps-ncmp-events/pom.xml
+++ b/cps-ncmp-events/pom.xml
@@ -23,7 +23,7 @@
<parent>
<groupId>org.onap.cps</groupId>
<artifactId>cps-parent</artifactId>
- <version>3.3.4-SNAPSHOT</version>
+ <version>3.3.5-SNAPSHOT</version>
<relativePath>../cps-parent/pom.xml</relativePath>
</parent>
diff --git a/cps-ncmp-rest-stub/cps-ncmp-rest-stub-app/pom.xml b/cps-ncmp-rest-stub/cps-ncmp-rest-stub-app/pom.xml
index 92540869d..3d5b5957e 100644
--- a/cps-ncmp-rest-stub/cps-ncmp-rest-stub-app/pom.xml
+++ b/cps-ncmp-rest-stub/cps-ncmp-rest-stub-app/pom.xml
@@ -22,7 +22,7 @@
<parent>
<groupId>org.onap.cps</groupId>
<artifactId>cps-ncmp-rest-stub</artifactId>
- <version>3.3.4-SNAPSHOT</version>
+ <version>3.3.5-SNAPSHOT</version>
</parent>
<artifactId>cps-ncmp-rest-stub-app</artifactId>
diff --git a/cps-ncmp-rest-stub/cps-ncmp-rest-stub-service/pom.xml b/cps-ncmp-rest-stub/cps-ncmp-rest-stub-service/pom.xml
index 9f3e9049f..c05f610ef 100644
--- a/cps-ncmp-rest-stub/cps-ncmp-rest-stub-service/pom.xml
+++ b/cps-ncmp-rest-stub/cps-ncmp-rest-stub-service/pom.xml
@@ -21,7 +21,7 @@
<parent>
<groupId>org.onap.cps</groupId>
<artifactId>cps-ncmp-rest-stub</artifactId>
- <version>3.3.4-SNAPSHOT</version>
+ <version>3.3.5-SNAPSHOT</version>
</parent>
<artifactId>cps-ncmp-rest-stub-service</artifactId>
diff --git a/cps-ncmp-rest-stub/pom.xml b/cps-ncmp-rest-stub/pom.xml
index 6d1cd5acb..84e7dac66 100644
--- a/cps-ncmp-rest-stub/pom.xml
+++ b/cps-ncmp-rest-stub/pom.xml
@@ -22,7 +22,7 @@
<parent>
<groupId>org.onap.cps</groupId>
<artifactId>cps-parent</artifactId>
- <version>3.3.4-SNAPSHOT</version>
+ <version>3.3.5-SNAPSHOT</version>
<relativePath>../cps-parent/pom.xml</relativePath>
</parent>
diff --git a/cps-ncmp-rest/pom.xml b/cps-ncmp-rest/pom.xml
index 744461959..c4c6e196f 100644
--- a/cps-ncmp-rest/pom.xml
+++ b/cps-ncmp-rest/pom.xml
@@ -27,7 +27,7 @@
<parent>
<groupId>org.onap.cps</groupId>
<artifactId>cps-parent</artifactId>
- <version>3.3.4-SNAPSHOT</version>
+ <version>3.3.5-SNAPSHOT</version>
<relativePath>../cps-parent/pom.xml</relativePath>
</parent>
diff --git a/cps-ncmp-service/pom.xml b/cps-ncmp-service/pom.xml
index 157303404..069893218 100644
--- a/cps-ncmp-service/pom.xml
+++ b/cps-ncmp-service/pom.xml
@@ -27,7 +27,7 @@
<parent>
<groupId>org.onap.cps</groupId>
<artifactId>cps-parent</artifactId>
- <version>3.3.4-SNAPSHOT</version>
+ <version>3.3.5-SNAPSHOT</version>
<relativePath>../cps-parent/pom.xml</relativePath>
</parent>
diff --git a/cps-parent/pom.xml b/cps-parent/pom.xml
index 913120dc6..49395e289 100755
--- a/cps-parent/pom.xml
+++ b/cps-parent/pom.xml
@@ -32,7 +32,7 @@
<groupId>org.onap.cps</groupId>
<artifactId>cps-parent</artifactId>
- <version>3.3.4-SNAPSHOT</version>
+ <version>3.3.5-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>
diff --git a/cps-path-parser/pom.xml b/cps-path-parser/pom.xml
index 15a271923..c1330abc3 100644
--- a/cps-path-parser/pom.xml
+++ b/cps-path-parser/pom.xml
@@ -23,7 +23,7 @@
<parent>
<groupId>org.onap.cps</groupId>
<artifactId>cps-parent</artifactId>
- <version>3.3.4-SNAPSHOT</version>
+ <version>3.3.5-SNAPSHOT</version>
<relativePath>../cps-parent/pom.xml</relativePath>
</parent>
diff --git a/cps-path-parser/src/main/antlr4/org/onap/cps/cpspath/parser/antlr4/CpsPath.g4 b/cps-path-parser/src/main/antlr4/org/onap/cps/cpspath/parser/antlr4/CpsPath.g4
index c88a82265..3aef120fe 100644
--- a/cps-path-parser/src/main/antlr4/org/onap/cps/cpspath/parser/antlr4/CpsPath.g4
+++ b/cps-path-parser/src/main/antlr4/org/onap/cps/cpspath/parser/antlr4/CpsPath.g4
@@ -1,6 +1,6 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2021-2022 Nordix Foundation
+ * Copyright (C) 2021-2023 Nordix Foundation
* Modifications Copyright (C) 2023 TechMahindra Ltd
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,6 +19,12 @@
* ============LICENSE_END=========================================================
*/
+/*
+ * Parser Rules
+ * Some of the parser rules below are inspired by
+ * https://github.com/antlr/grammars-v4/blob/master/xpath/xpath31/XPath31Parser.g4
+ */
+
grammar CpsPath ;
cpsPath : ( prefix | descendant | incorrectPrefix ) multipleLeafConditions? textFunctionCondition? containsFunctionCondition? ancestorAxis? invalidPostFix?;
@@ -60,7 +66,7 @@ invalidPostFix : (AT | CB | COLONCOLON | comparativeOperators ).+ ;
/*
* Lexer Rules
* Most of the lexer rules below are inspired by
- * https://raw.githubusercontent.com/antlr/grammars-v4/master/xpath/xpath31/XPath31.g4
+ * https://github.com/antlr/grammars-v4/blob/master/xpath/xpath31/XPath31Lexer.g4
*/
AT : '@' ;
@@ -89,9 +95,9 @@ IntegerLiteral : FragDigits ;
// Add below type definitions for leafvalue comparision in https://jira.onap.org/browse/CPS-440
DecimalLiteral : ('.' FragDigits) | (FragDigits '.' [0-9]*) ;
DoubleLiteral : (('.' FragDigits) | (FragDigits ('.' [0-9]*)?)) [eE] [+-]? FragDigits ;
-StringLiteral : ('"' (FragEscapeQuot | ~[^"])*? '"') | ('\'' (FragEscapeApos | ~['])*? '\'') ;
+StringLiteral : '"' (~["] | FragEscapeQuot)* '"' | '\'' (~['] | FragEscapeApos)* '\'' ;
fragment FragEscapeQuot : '""' ;
-fragment FragEscapeApos : '\'' ;
+fragment FragEscapeApos : '\'\'';
fragment FragDigits : [0-9]+ ;
QName : FragQName ;
diff --git a/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathBooleanOperatorType.java b/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathBooleanOperatorType.java
deleted file mode 100644
index b2f1dddb1..000000000
--- a/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathBooleanOperatorType.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * ============LICENSE_START=======================================================
- * Copyright (C) 2023 TechMahindra Ltd
- * ================================================================================
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * SPDX-License-Identifier: Apache-2.0
- * ============LICENSE_END=========================================================
- */
-
-package org.onap.cps.cpspath.parser;
-
-public enum CpsPathBooleanOperatorType {
- AND("and"),
- OR("or");
-
- private final String operatorValue;
-
- CpsPathBooleanOperatorType(final String operatorValue) {
- this.operatorValue = operatorValue;
- }
-
- public String getValues() {
- return this.operatorValue;
- }
-
- /**
- * Finds the value of the given enumeration.
- *
- * @param operatorValue value of the enum
- * @return a booleanOperatorType
- */
- public static CpsPathBooleanOperatorType fromString(final String operatorValue) {
- for (final CpsPathBooleanOperatorType booleanOperatorType : CpsPathBooleanOperatorType.values()) {
- if (booleanOperatorType.operatorValue.equalsIgnoreCase(operatorValue)) {
- return booleanOperatorType;
- }
- }
- return null;
- }
-}
diff --git a/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathBuilder.java b/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathBuilder.java
index 5c4712737..de261e64b 100644
--- a/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathBuilder.java
+++ b/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathBuilder.java
@@ -1,6 +1,6 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2021-2022 Nordix Foundation
+ * Copyright (C) 2021-2023 Nordix Foundation
* Modifications Copyright (C) 2023 TechMahindra Ltd
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -24,9 +24,7 @@ package org.onap.cps.cpspath.parser;
import static org.onap.cps.cpspath.parser.CpsPathPrefixType.DESCENDANT;
import java.util.ArrayList;
-import java.util.LinkedHashMap;
import java.util.List;
-import java.util.Map;
import org.onap.cps.cpspath.parser.antlr4.CpsPathBaseListener;
import org.onap.cps.cpspath.parser.antlr4.CpsPathParser;
import org.onap.cps.cpspath.parser.antlr4.CpsPathParser.AncestorAxisContext;
@@ -43,21 +41,21 @@ public class CpsPathBuilder extends CpsPathBaseListener {
private static final String CLOSE_BRACKET = "]";
- final CpsPathQuery cpsPathQuery = new CpsPathQuery();
+ private final CpsPathQuery cpsPathQuery = new CpsPathQuery();
- final Map<String, Object> leavesData = new LinkedHashMap<>();
+ private final List<CpsPathQuery.DataLeaf> leavesData = new ArrayList<>();
- final StringBuilder normalizedXpathBuilder = new StringBuilder();
+ private final StringBuilder normalizedXpathBuilder = new StringBuilder();
- final StringBuilder normalizedAncestorPathBuilder = new StringBuilder();
+ private final StringBuilder normalizedAncestorPathBuilder = new StringBuilder();
- boolean processingAncestorAxis = false;
+ private boolean processingAncestorAxis = false;
- private List<String> containerNames = new ArrayList<>();
+ private final List<String> containerNames = new ArrayList<>();
- final List<String> booleanOperators = new ArrayList<>();
+ private final List<String> booleanOperators = new ArrayList<>();
- final List<String> comparativeOperators = new ArrayList<>();
+ private final List<String> comparativeOperators = new ArrayList<>();
@Override
public void exitInvalidPostFix(final CpsPathParser.InvalidPostFixContext ctx) {
@@ -81,34 +79,25 @@ public class CpsPathBuilder extends CpsPathBaseListener {
@Override
public void exitLeafCondition(final LeafConditionContext ctx) {
- Object comparisonValue;
+ final Object comparisonValue;
if (ctx.IntegerLiteral() != null) {
comparisonValue = Integer.valueOf(ctx.IntegerLiteral().getText());
} else if (ctx.StringLiteral() != null) {
- final boolean wasWrappedInDoubleQuote = ctx.StringLiteral().getText().startsWith("\"");
- comparisonValue = stripFirstAndLastCharacter(ctx.StringLiteral().getText());
- if (wasWrappedInDoubleQuote) {
- comparisonValue = String.valueOf(comparisonValue).replace("'", "\\'");
- }
+ comparisonValue = unwrapQuotedString(ctx.StringLiteral().getText());
} else {
- throw new PathParsingException(
- "Unsupported comparison value encountered in expression" + ctx.getText());
+ throw new PathParsingException("Unsupported comparison value encountered in expression" + ctx.getText());
}
leafContext(ctx.leafName(), comparisonValue);
}
@Override
public void exitBooleanOperators(final CpsPathParser.BooleanOperatorsContext ctx) {
- final CpsPathBooleanOperatorType cpsPathBooleanOperatorType = CpsPathBooleanOperatorType.fromString(
- ctx.getText());
- booleanOperators.add(cpsPathBooleanOperatorType.getValues());
+ booleanOperators.add(ctx.getText());
}
@Override
public void exitComparativeOperators(final CpsPathParser.ComparativeOperatorsContext ctx) {
- final CpsPathComparativeOperator cpsPathComparativeOperator = CpsPathComparativeOperator.fromString(
- ctx.getText());
- comparativeOperators.add(cpsPathComparativeOperator.getLabel());
+ comparativeOperators.add(ctx.getText());
}
@Override
@@ -122,6 +111,8 @@ public class CpsPathBuilder extends CpsPathBaseListener {
public void enterMultipleLeafConditions(final MultipleLeafConditionsContext ctx) {
normalizedXpathBuilder.append(OPEN_BRACKET);
leavesData.clear();
+ booleanOperators.clear();
+ comparativeOperators.clear();
}
@Override
@@ -144,13 +135,13 @@ public class CpsPathBuilder extends CpsPathBaseListener {
@Override
public void exitTextFunctionCondition(final TextFunctionConditionContext ctx) {
cpsPathQuery.setTextFunctionConditionLeafName(ctx.leafName().getText());
- cpsPathQuery.setTextFunctionConditionValue(stripFirstAndLastCharacter(ctx.StringLiteral().getText()));
+ cpsPathQuery.setTextFunctionConditionValue(unwrapQuotedString(ctx.StringLiteral().getText()));
}
@Override
public void exitContainsFunctionCondition(final CpsPathParser.ContainsFunctionConditionContext ctx) {
cpsPathQuery.setContainsFunctionConditionLeafName(ctx.leafName().getText());
- cpsPathQuery.setContainsFunctionConditionValue(stripFirstAndLastCharacter(ctx.StringLiteral().getText()));
+ cpsPathQuery.setContainsFunctionConditionValue(unwrapQuotedString(ctx.StringLiteral().getText()));
}
@Override
@@ -177,10 +168,6 @@ public class CpsPathBuilder extends CpsPathBaseListener {
return cpsPathQuery;
}
- private static String stripFirstAndLastCharacter(final String wrappedString) {
- return wrappedString.substring(1, wrappedString.length() - 1);
- }
-
@Override
public void exitContainerName(final CpsPathParser.ContainerNameContext ctx) {
final String containerName = ctx.getText();
@@ -193,7 +180,7 @@ public class CpsPathBuilder extends CpsPathBaseListener {
}
private void leafContext(final CpsPathParser.LeafNameContext ctx, final Object comparisonValue) {
- leavesData.put(ctx.getText(), comparisonValue);
+ leavesData.add(new CpsPathQuery.DataLeaf(ctx.getText(), comparisonValue));
appendCondition(normalizedXpathBuilder, ctx.getText(), comparisonValue);
if (processingAncestorAxis) {
appendCondition(normalizedAncestorPathBuilder, ctx.getText(), comparisonValue);
@@ -211,11 +198,25 @@ public class CpsPathBuilder extends CpsPathBaseListener {
.append(name)
.append(getLastElement(comparativeOperators))
.append("'")
- .append(value)
+ .append(value.toString().replace("'", "''"))
.append("'");
}
- private String getLastElement(final List<String> listOfStrings) {
+ private static String getLastElement(final List<String> listOfStrings) {
return listOfStrings.get(listOfStrings.size() - 1);
}
+
+ private static String unwrapQuotedString(final String wrappedString) {
+ final boolean wasWrappedInSingleQuote = wrappedString.startsWith("'");
+ final String value = stripFirstAndLastCharacter(wrappedString);
+ if (wasWrappedInSingleQuote) {
+ return value.replace("''", "'");
+ } else {
+ return value.replace("\"\"", "\"");
+ }
+ }
+
+ private static String stripFirstAndLastCharacter(final String wrappedString) {
+ return wrappedString.substring(1, wrappedString.length() - 1);
+ }
}
diff --git a/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathComparativeOperator.java b/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathComparativeOperator.java
deleted file mode 100644
index c7ffd0d7e..000000000
--- a/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathComparativeOperator.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * ============LICENSE_START=======================================================
- * Copyright (C) 2023 Tech Mahindra Ltd
- * ================================================================================
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * SPDX-License-Identifier: Apache-2.0
- * ============LICENSE_END=========================================================
- */
-
-package org.onap.cps.cpspath.parser;
-
-import java.util.HashMap;
-import java.util.Map;
-
-public enum CpsPathComparativeOperator {
- EQ("="),
- GT(">"),
- LT("<"),
- GE(">="),
- LE("<=");
-
- private final String label;
-
- CpsPathComparativeOperator(final String label) {
- this.label = label;
- }
-
- public final String getLabel() {
- return this.label;
- }
-
- private static final Map<String, CpsPathComparativeOperator> cpsPathComparativeOperatorPerLabel = new HashMap<>();
-
- static {
- for (final CpsPathComparativeOperator cpsPathComparativeOperator : CpsPathComparativeOperator.values()) {
- cpsPathComparativeOperatorPerLabel.put(cpsPathComparativeOperator.label, cpsPathComparativeOperator);
- }
- }
-
- /**
- * Finds the value of the given enumeration.
- *
- * @param label value of the enum
- * @return a comparativeOperatorType
- */
- public static CpsPathComparativeOperator fromString(final String label) {
- if (!cpsPathComparativeOperatorPerLabel.containsKey(label)) {
- throw new PathParsingException("Incomplete leaf condition (no operator)");
- }
- return cpsPathComparativeOperatorPerLabel.get(label);
- }
-}
-
diff --git a/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathQuery.java b/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathQuery.java
index 3c3cbccf7..f98df05a2 100644
--- a/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathQuery.java
+++ b/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathQuery.java
@@ -24,8 +24,9 @@ package org.onap.cps.cpspath.parser;
import static org.onap.cps.cpspath.parser.CpsPathPrefixType.ABSOLUTE;
import java.util.List;
-import java.util.Map;
import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
@@ -39,7 +40,7 @@ public class CpsPathQuery {
private List<String> containerNames;
private CpsPathPrefixType cpsPathPrefixType = ABSOLUTE;
private String descendantName;
- private Map<String, Object> leavesData;
+ private List<DataLeaf> leavesData;
private String ancestorSchemaNodeIdentifier = "";
private String textFunctionConditionLeafName;
private String textFunctionConditionValue;
@@ -103,4 +104,11 @@ public class CpsPathQuery {
return cpsPathPrefixType == ABSOLUTE && hasLeafConditions();
}
+ @Getter
+ @EqualsAndHashCode
+ @AllArgsConstructor
+ public static class DataLeaf {
+ private final String name;
+ private final Object value;
+ }
}
diff --git a/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathQuerySpec.groovy b/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathQuerySpec.groovy
index 9ab5491b5..0017242ab 100644
--- a/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathQuerySpec.groovy
+++ b/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathQuerySpec.groovy
@@ -1,6 +1,6 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2021-2022 Nordix Foundation
+ * Copyright (C) 2021-2023 Nordix Foundation
* Modifications Copyright (C) 2023 TechMahindra Ltd
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -32,12 +32,12 @@ class CpsPathQuerySpec extends Specification {
when: 'the given cps path is parsed'
def result = CpsPathQuery.createFrom(cpsPath)
then: 'the query has the right xpath type'
- result.cpsPathPrefixType == ABSOLUTE
+ assert result.cpsPathPrefixType == ABSOLUTE
and: 'the right query parameters are set'
- result.xpathPrefix == expectedXpathPrefix
- result.hasLeafConditions()
- result.leavesData.containsKey(expectedLeafName)
- result.leavesData.get(expectedLeafName) == expectedLeafValue
+ assert result.xpathPrefix == expectedXpathPrefix
+ assert result.hasLeafConditions()
+ assert result.leavesData[0].name == expectedLeafName
+ assert result.leavesData[0].value == expectedLeafValue
where: 'the following data is used'
scenario | cpsPath || expectedXpathPrefix | expectedLeafName | expectedLeafValue
'leaf of type String' | '/parent/child[@common-leaf-name="common-leaf-value"]' || '/parent/child' | 'common-leaf-name' | 'common-leaf-value'
@@ -46,6 +46,10 @@ class CpsPathQuerySpec extends Specification {
'spaces around =' | '/parent/child[@common-leaf-name-int = 5]' || '/parent/child' | 'common-leaf-name-int' | 5
'key in top container' | '/parent[@common-leaf-name-int=5]' || '/parent' | 'common-leaf-name-int' | 5
'parent list' | '/shops/shop[@id=1]/categories[@id=1]/book[@title="Dune"]' || "/shops/shop[@id='1']/categories[@id='1']/book" | 'title' | 'Dune'
+ "' in double quote" | '/parent[@common-leaf-name="leaf\'value"]' || '/parent' | 'common-leaf-name' | "leaf'value"
+ "' in single quote" | "/parent[@common-leaf-name='leaf''value']" || '/parent' | 'common-leaf-name' | "leaf'value"
+ '" in double quote' | '/parent[@common-leaf-name="leaf""value"]' || '/parent' | 'common-leaf-name' | 'leaf"value'
+ '" in single quote' | '/parent[@common-leaf-name=\'leaf"value\']' || '/parent' | 'common-leaf-name' | 'leaf"value'
}
def 'Parse cps path of type ends with a #scenario.'() {
@@ -80,8 +84,8 @@ class CpsPathQuerySpec extends Specification {
'parent leaf of type Integer & child' | '/parent/child[@code=1]/child2' || "/parent/child[@code='1']/child2"
'parent leaf with double quotes' | '/parent/child[@code="1"]/child2' || "/parent/child[@code='1']/child2"
'parent leaf with double quotes inside single quotes' | '/parent/child[@code=\'"1"\']/child2' || "/parent/child[@code='\"1\"']/child2"
- 'parent leaf with single quotes inside double quotes' | '/parent/child[@code="\'1\'"]/child2' || "/parent/child[@code='\\\'1\\\'']/child2"
- 'leaf with single quotes inside double quotes' | '/parent/child[@code="\'1\'"]' || "/parent/child[@code='\\\'1\\\'']"
+ 'parent leaf with single quotes inside double quotes' | '/parent/child[@code="\'1\'"]/child2' || "/parent/child[@code='''1''']/child2"
+ 'leaf with single quotes inside double quotes' | '/parent/child[@code="\'1\'"]' || "/parent/child[@code='''1''']"
'leaf with more than one attribute' | '/parent/child[@key1=1 and @key2="abc"]' || "/parent/child[@key1='1' and @key2='abc']"
'parent & child with more than one attribute' | '/parent/child[@key1=1 and @key2="abc"]/child2' || "/parent/child[@key1='1' and @key2='abc']/child2"
'leaf with more than one attribute has OR operator' | '/parent/child[@key1=1 or @key2="abc"]' || "/parent/child[@key1='1' or @key2='abc']"
@@ -103,31 +107,24 @@ class CpsPathQuerySpec extends Specification {
'descendant anywhere' | '//xpath' || '//xpath'
}
- def 'Parse cps path that ends with a yang list containing #scenario.'() {
+ def 'Parse cps path that ends with a yang list containing multiple leaf conditions.'() {
when: 'the given cps path is parsed'
def result = CpsPathQuery.createFrom(cpsPath)
- then: 'the query has the right xpath type'
- result.cpsPathPrefixType == DESCENDANT
- and: 'the right parameters are set'
- result.descendantName == "child"
+ then: 'the expected number of leaves are returned'
result.leavesData.size() == expectedNumberOfLeaves
and: 'the given operator(s) returns in the correct order'
result.booleanOperators == expectedOperators
and: 'the given comparativeOperator(s) returns in the correct order'
result.comparativeOperators == expectedComparativeOperator
where: 'the following data is used'
- scenario | cpsPath || expectedNumberOfLeaves || expectedOperators || expectedComparativeOperator
- 'one attribute' | '//child[@common-leaf-name-int=5]' || 1 || [] || ['=']
- 'more than one attribute has AND operator' | '//child[@int-leaf=5 and @leaf-name="leaf value"]' || 2 || ['and'] || ['=', '=']
- 'more than one attribute has OR operator' | '//child[@int-leaf=5 or @leaf-name="leaf value"]' || 2 || ['or'] || ['=', '=']
- 'more than one attribute has combinations AND operators' | '//child[@int-leaf=5 and @common-leaf-name="leaf value" and @leaf-name="leaf value1" ]' || 3 || ['and', 'and'] || ['=', '=', '=']
- 'more than one attribute has combinations OR operators' | '//child[@int-leaf=5 or @common-leaf-name="leaf value" or @leaf-name="leaf value1" ]' || 3 || ['or', 'or'] || ['=', '=', '=']
- 'more than one attribute has combinations AND/OR combination' | '//child[@int-leaf=5 and @common-leaf-name="leaf value" or @leaf-name="leaf value1" ]' || 3 || ['and', 'or'] || ['=', '=', '=']
- 'more than one attribute has combinations OR/AND combination' | '//child[@int-leaf=5 or @common-leaf-name="leaf value" and @leaf-name="leaf value1" ]' || 3 || ['or', 'and'] || ['=', '=', '=']
- 'more than one attribute has AND/> operators' | '//child[@int-leaf>15 and @leaf-name="leaf value"]' || 2 || ['and'] || ['>', '=']
- 'more than one attribute has OR/< operators' | '//child[@int-leaf<5 or @leaf-name="leaf value"]' || 2 || ['or'] || ['<', '=']
- 'more than one attribute has combinations AND/>= operators' | '//child[@int-leaf>=18 and @common-leaf-name="leaf value" and @leaf-name="leaf value1" ]' || 3 || ['and', 'and'] || ['>=', '=', '=']
- 'more than one attribute has combinations OR/<= operators' | '//child[@int-leaf<=25 or @common-leaf-name="leaf value" or @leaf-name="leaf value1" ]' || 3 || ['or', 'or'] || ['<=', '=', '=']
+ cpsPath || expectedNumberOfLeaves || expectedOperators || expectedComparativeOperator
+ '/parent[@code=1]/child[@common-leaf-name-int=5]' || 1 || [] || ['=']
+ '//child[@int-leaf>15 and @leaf-name="leaf value"]' || 2 || ['and'] || ['>', '=']
+ '//child[@int-leaf<5 or @leaf-name="leaf value"]' || 2 || ['or'] || ['<', '=']
+ '//child[@int-leaf=5 and @common-leaf-name="leaf value" or @leaf-name="leaf value1" ]' || 3 || ['and', 'or'] || ['=', '=', '=']
+ '//child[@int-leaf=5 or @common-leaf-name="leaf value" and @leaf-name="leaf value1" ]' || 3 || ['or', 'and'] || ['=', '=', '=']
+ '//child[@int-leaf>=18 and @common-leaf-name="leaf value" and @leaf-name="leaf value1" ]' || 3 || ['and', 'and'] || ['>=', '=', '=']
+ '//child[@int-leaf<=25 or @common-leaf-name="leaf value" or @leaf-name="leaf value1" ]' || 3 || ['or', 'or'] || ['<=', '=', '=']
}
def 'Parse #scenario cps path with text function condition'() {
@@ -220,4 +217,28 @@ class CpsPathQuerySpec extends Specification {
'container with list-parent' | '//parent[@id=1]/child' || "parent[@id='1']/child" | false
'container with list-parent' | '//parent[@id=1]/child[@name="test"]' || "parent[@id='1']/child" | true
}
+
+ def 'Parse cps path with multiple conditions on same leaf.'() {
+ when: 'the given cps path is parsed using multiple conditions on same leaf'
+ def result = CpsPathQuery.createFrom('/test[@same-name="value1" or @same-name="value2"]')
+ then: 'two leaves are present with correct values'
+ assert result.leavesData.size() == 2
+ assert result.leavesData[0].name == "same-name"
+ assert result.leavesData[0].value == "value1"
+ assert result.leavesData[1].name == "same-name"
+ assert result.leavesData[1].value == "value2"
+ }
+
+ def 'Ordering of data leaves is preserved.'() {
+ when: 'the given cps path is parsed'
+ def result = CpsPathQuery.createFrom(cpsPath)
+ then: 'the order of the data leaves is preserved'
+ assert result.leavesData[0].name == expectedFirstLeafName
+ assert result.leavesData[1].name == expectedSecondLeafName
+ where: 'the following data is used'
+ cpsPath || expectedFirstLeafName | expectedSecondLeafName
+ '/test[@name1="value1" and @name2="value2"]' || 'name1' | 'name2'
+ '/test[@name2="value2" and @name1="value1"]' || 'name2' | 'name1'
+ }
+
}
diff --git a/cps-rest/pom.xml b/cps-rest/pom.xml
index e40aa91de..6eb1a92ae 100755
--- a/cps-rest/pom.xml
+++ b/cps-rest/pom.xml
@@ -28,7 +28,7 @@
<parent>
<groupId>org.onap.cps</groupId>
<artifactId>cps-parent</artifactId>
- <version>3.3.4-SNAPSHOT</version>
+ <version>3.3.5-SNAPSHOT</version>
<relativePath>../cps-parent/pom.xml</relativePath>
</parent>
diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy
index d88a9cdf0..81262c80c 100755
--- a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy
+++ b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy
@@ -179,6 +179,29 @@ class DataRestControllerSpec extends Specification {
'without observed-timestamp XML' | null | MediaType.APPLICATION_XML | requestBodyXml | expectedXmlData | ContentType.XML
}
+ def 'save list elements under root node #scenario.'() {
+ given: 'root node xpath '
+ def rootNodeXpath = '/'
+ when: 'list-node endpoint is invoked with post (create) operation'
+ def postRequestBuilder = post("$dataNodeBaseEndpointV1/anchors/$anchorName/list-nodes")
+ .contentType(MediaType.APPLICATION_JSON)
+ .param('xpath', rootNodeXpath )
+ .content(requestBodyJson)
+ if (observedTimestamp != null)
+ postRequestBuilder.param('observed-timestamp', observedTimestamp)
+ def response = mvc.perform(postRequestBuilder).andReturn().response
+ then: 'a created response is returned'
+ response.status == expectedHttpStatus.value()
+ then: 'the java API was called with the correct parameters'
+ expectedApiCount * mockCpsDataService.saveListElements(dataspaceName, anchorName, rootNodeXpath, expectedJsonData,
+ { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) })
+ where:
+ scenario | observedTimestamp || expectedApiCount | expectedHttpStatus
+ 'with observed-timestamp' | '2021-03-03T23:59:59.999-0400' || 1 | HttpStatus.CREATED
+ 'without observed-timestamp' | null || 1 | HttpStatus.CREATED
+ 'with invalid observed-timestamp' | 'invalid' || 0 | HttpStatus.BAD_REQUEST
+ }
+
def 'Save list elements #scenario.'() {
given: 'parent node xpath '
def parentNodeXpath = 'parent node xpath'
diff --git a/cps-ri/pom.xml b/cps-ri/pom.xml
index 1adca2f74..9ed8be57c 100644
--- a/cps-ri/pom.xml
+++ b/cps-ri/pom.xml
@@ -26,7 +26,7 @@
<parent>
<groupId>org.onap.cps</groupId>
<artifactId>cps-parent</artifactId>
- <version>3.3.4-SNAPSHOT</version>
+ <version>3.3.5-SNAPSHOT</version>
<relativePath>../cps-parent/pom.xml</relativePath>
</parent>
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentQueryBuilder.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentQueryBuilder.java
index 7b5c0c693..e371035ba 100644
--- a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentQueryBuilder.java
+++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentQueryBuilder.java
@@ -37,7 +37,6 @@ import org.onap.cps.spi.entities.DataspaceEntity;
import org.onap.cps.spi.entities.FragmentEntity;
import org.onap.cps.spi.exceptions.CpsPathException;
import org.onap.cps.spi.utils.EscapeUtils;
-import org.onap.cps.utils.JsonObjectMapper;
import org.springframework.stereotype.Component;
@RequiredArgsConstructor
@@ -49,8 +48,6 @@ public class FragmentQueryBuilder {
@PersistenceContext
private EntityManager entityManager;
- private final JsonObjectMapper jsonObjectMapper;
-
/**
* Create a sql query to retrieve by anchor(id) and cps path.
*
@@ -128,18 +125,18 @@ public class FragmentQueryBuilder {
sqlStringBuilder.append(" AND (");
final Queue<String> booleanOperatorsQueue = new LinkedList<>(cpsPathQuery.getBooleanOperators());
final Queue<String> comparativeOperatorQueue = new LinkedList<>(cpsPathQuery.getComparativeOperators());
- cpsPathQuery.getLeavesData().entrySet().forEach(entry -> {
+ cpsPathQuery.getLeavesData().forEach(leaf -> {
final String nextComparativeOperator = comparativeOperatorQueue.poll();
- if (entry.getValue() instanceof Integer) {
- sqlStringBuilder.append("(attributes ->> ");
- sqlStringBuilder.append("'").append(entry.getKey()).append("')\\:\\:int");
- sqlStringBuilder.append(" ").append(nextComparativeOperator).append(" ");
- sqlStringBuilder.append("'").append(jsonObjectMapper.asJsonString(entry.getValue())).append("'");
+ if (leaf.getValue() instanceof Integer) {
+ sqlStringBuilder.append("(attributes ->> '").append(leaf.getName()).append("')\\:\\:int");
+ sqlStringBuilder.append(nextComparativeOperator);
+ sqlStringBuilder.append(leaf.getValue());
} else {
if ("=".equals(nextComparativeOperator)) {
- sqlStringBuilder.append(" attributes @> ");
- sqlStringBuilder.append("'");
- sqlStringBuilder.append(jsonObjectMapper.asJsonString(entry));
+ final String leafValueAsText = leaf.getValue().toString();
+ sqlStringBuilder.append("attributes ->> '").append(leaf.getName()).append("'");
+ sqlStringBuilder.append(" = '");
+ sqlStringBuilder.append(EscapeUtils.escapeForSqlStringLiteral(leafValueAsText));
sqlStringBuilder.append("'");
} else {
throw new CpsPathException(" can use only " + nextComparativeOperator + " with integer ");
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/TempTableCreator.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/TempTableCreator.java
index 139a8b306..4c7971ead 100644
--- a/cps-ri/src/main/java/org/onap/cps/spi/repository/TempTableCreator.java
+++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/TempTableCreator.java
@@ -31,6 +31,7 @@ import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.spi.utils.EscapeUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@@ -86,7 +87,7 @@ public class TempTableCreator {
final Collection<String> sqlInserts = new HashSet<>(sqlData.size());
for (final Collection<String> rowValues : sqlData) {
final Collection<String> escapedValues =
- rowValues.stream().map(it -> escapeSingleQuotesByDoublingThem(it)).collect(Collectors.toList());
+ rowValues.stream().map(EscapeUtils::escapeForSqlStringLiteral).collect(Collectors.toList());
sqlInserts.add("('" + String.join("','", escapedValues) + "')");
}
sqlStringBuilder.append("INSERT INTO ");
@@ -98,8 +99,4 @@ public class TempTableCreator {
sqlStringBuilder.append(";");
}
- private static String escapeSingleQuotesByDoublingThem(final String value) {
- return value.replace("'", "''");
- }
-
}
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/utils/EscapeUtils.java b/cps-ri/src/main/java/org/onap/cps/spi/utils/EscapeUtils.java
index 3092b7905..2b61d3950 100644
--- a/cps-ri/src/main/java/org/onap/cps/spi/utils/EscapeUtils.java
+++ b/cps-ri/src/main/java/org/onap/cps/spi/utils/EscapeUtils.java
@@ -26,8 +26,12 @@ import lombok.NoArgsConstructor;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class EscapeUtils {
- public static String escapeForSqlLike(final String text) {
- return text.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_");
+ public static String escapeForSqlLike(final String value) {
+ return value.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_");
+ }
+
+ public static String escapeForSqlStringLiteral(final String value) {
+ return value.replace("'", "''");
}
}
diff --git a/cps-ri/src/main/resources/changelog/changelog-master.yaml b/cps-ri/src/main/resources/changelog/changelog-master.yaml
index 4e6986e71..f76c5ba3b 100644
--- a/cps-ri/src/main/resources/changelog/changelog-master.yaml
+++ b/cps-ri/src/main/resources/changelog/changelog-master.yaml
@@ -56,3 +56,5 @@ databaseChangeLog:
file: changelog/db/changes/19-delete-not-required-dataspace-id-from-fragment.yaml
- include:
file: changelog/db/changes/20-change-foreign-key-id-types-to-integer.yaml
+ - include:
+ file: changelog/db/changes/21-escape-quotes-in-xpath.yaml
diff --git a/cps-ri/src/main/resources/changelog/db/changes/21-escape-quotes-in-xpath-forward.sql b/cps-ri/src/main/resources/changelog/db/changes/21-escape-quotes-in-xpath-forward.sql
new file mode 100644
index 000000000..9bf7f9a74
--- /dev/null
+++ b/cps-ri/src/main/resources/changelog/db/changes/21-escape-quotes-in-xpath-forward.sql
@@ -0,0 +1,19 @@
+/*
+ ============LICENSE_START=======================================================
+ Copyright (C) 2023 Nordix Foundation.
+ ================================================================================
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ SPDX-License-Identifier: Apache-2.0
+ ============LICENSE_END=========================================================
+*/
+
+-- replace \' with '' and "" with "
+UPDATE fragment SET xpath = replace(replace(xpath, $$\'$$, $$''$$), '""', '"'); \ No newline at end of file
diff --git a/cps-ri/src/main/resources/changelog/db/changes/21-escape-quotes-in-xpath-rollback.sql b/cps-ri/src/main/resources/changelog/db/changes/21-escape-quotes-in-xpath-rollback.sql
new file mode 100644
index 000000000..0fd1633a5
--- /dev/null
+++ b/cps-ri/src/main/resources/changelog/db/changes/21-escape-quotes-in-xpath-rollback.sql
@@ -0,0 +1,19 @@
+/*
+ ============LICENSE_START=======================================================
+ Copyright (C) 2023 Nordix Foundation.
+ ================================================================================
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ SPDX-License-Identifier: Apache-2.0
+ ============LICENSE_END=========================================================
+*/
+
+-- replace '' with \' and " with ""
+UPDATE fragment SET xpath = replace(replace(xpath, $$''$$, $$\'$$), '"', '""'); \ No newline at end of file
diff --git a/cps-ri/src/main/resources/changelog/db/changes/21-escape-quotes-in-xpath.yaml b/cps-ri/src/main/resources/changelog/db/changes/21-escape-quotes-in-xpath.yaml
new file mode 100644
index 000000000..7b5b1dbd0
--- /dev/null
+++ b/cps-ri/src/main/resources/changelog/db/changes/21-escape-quotes-in-xpath.yaml
@@ -0,0 +1,29 @@
+# ============LICENSE_START=======================================================
+# Copyright (C) 2023 Nordix Foundation.
+# ================================================================================
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+# ============LICENSE_END=========================================================
+
+databaseChangeLog:
+
+ - changeSet:
+ id: 21
+ author: cps
+ changes:
+ - sqlFile:
+ path: changelog/db/changes/21-escape-quotes-in-xpath-forward.sql
+ rollback:
+ - sqlFile:
+ path: changelog/db/changes/21-escape-quotes-in-xpath-rollback.sql
diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/utils/EscapeUtilsSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/utils/EscapeUtilsSpec.groovy
index 7de9b97ba..52330e625 100644
--- a/cps-ri/src/test/groovy/org/onap/cps/spi/utils/EscapeUtilsSpec.groovy
+++ b/cps-ri/src/test/groovy/org/onap/cps/spi/utils/EscapeUtilsSpec.groovy
@@ -24,7 +24,7 @@ import spock.lang.Specification
class EscapeUtilsSpec extends Specification {
- def 'Escape text for using in SQL LIKE operation'() {
+ def 'Escape text for use in SQL LIKE operation.'() {
expect: 'SQL LIKE special characters to be escaped with forward-slash'
assert EscapeUtils.escapeForSqlLike(unescapedText) == escapedText
where:
@@ -33,4 +33,9 @@ class EscapeUtilsSpec extends Specification {
'Others (./?$) are not special' || 'Others (./?$) are not special'
}
+ def 'Escape text for use in SQL string literal.'() {
+ expect: 'single quotes to be doubled'
+ assert EscapeUtils.escapeForSqlStringLiteral("I'm escaping!") == "I''m escaping!"
+ }
+
}
diff --git a/cps-service/pom.xml b/cps-service/pom.xml
index dffc38da4..06d93d346 100644
--- a/cps-service/pom.xml
+++ b/cps-service/pom.xml
@@ -29,7 +29,7 @@
<parent>
<groupId>org.onap.cps</groupId>
<artifactId>cps-parent</artifactId>
- <version>3.3.4-SNAPSHOT</version>
+ <version>3.3.5-SNAPSHOT</version>
<relativePath>../cps-parent/pom.xml</relativePath>
</parent>
diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java
index 6e7c1649d..7db87e87e 100755
--- a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java
+++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java
@@ -116,8 +116,12 @@ public class CpsDataServiceImpl implements CpsDataService {
final Anchor anchor = cpsAdminService.getAnchor(dataspaceName, anchorName);
final Collection<DataNode> listElementDataNodeCollection =
buildDataNodes(anchor, parentNodeXpath, jsonData, ContentType.JSON);
- cpsDataPersistenceService.addListElements(dataspaceName, anchorName, parentNodeXpath,
- listElementDataNodeCollection);
+ if (isRootNodeXpath(parentNodeXpath)) {
+ cpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName, listElementDataNodeCollection);
+ } else {
+ cpsDataPersistenceService.addListElements(dataspaceName, anchorName, parentNodeXpath,
+ listElementDataNodeCollection);
+ }
processDataUpdatedEventAsync(anchor, parentNodeXpath, UPDATE, observedTimestamp);
}
@@ -391,6 +395,10 @@ public class CpsDataServiceImpl implements CpsDataService {
.get(anchor.getDataspaceName(), anchor.getSchemaSetName()).getSchemaContext();
}
+ private static boolean isRootNodeXpath(final String xpath) {
+ return ROOT_NODE_XPATH.equals(xpath);
+ }
+
private void processDataNodeUpdate(final Anchor anchor, final DataNode dataNodeUpdate) {
cpsDataPersistenceService.batchUpdateDataLeaves(anchor.getDataspaceName(), anchor.getName(),
Collections.singletonMap(dataNodeUpdate.getXpath(), dataNodeUpdate.getLeaves()));
diff --git a/cps-service/src/main/java/org/onap/cps/utils/YangUtils.java b/cps-service/src/main/java/org/onap/cps/utils/YangUtils.java
index 7da402415..f00f9442c 100644
--- a/cps-service/src/main/java/org/onap/cps/utils/YangUtils.java
+++ b/cps-service/src/main/java/org/onap/cps/utils/YangUtils.java
@@ -253,7 +253,7 @@ public class YangUtils {
final List<String> keyAttributes = nodeIdentifier.entrySet().stream().map(
entry -> {
final String name = entry.getKey().getLocalName();
- final String value = String.valueOf(entry.getValue()).replace("'", "\\'");
+ final String value = String.valueOf(entry.getValue()).replace("'", "''");
return String.format("@%s='%s'", name, value);
}
).collect(Collectors.toList());
diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy
index db8664042..ba438496f 100644
--- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy
@@ -110,6 +110,28 @@ class CpsDataServiceImplSpec extends Specification {
noExceptionThrown()
}
+ def 'Saving list element data fragment under Root node.'() {
+ given: 'schema set for given anchor and dataspace references bookstore model'
+ setupSchemaSetMocks('bookstore.yang')
+ when: 'save data method is invoked with list element json data'
+ def jsonData = '{"multiple-data-tree:invoice": [{"ProductID": "2","ProductName": "Banana","price": "100","stock": True}]}'
+ objectUnderTest.saveListElements(dataspaceName, anchorName, '/', jsonData, observedTimestamp)
+ then: 'the persistence service method is invoked with correct parameters'
+ 1 * mockCpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName,
+ { dataNodeCollection ->
+ {
+ assert dataNodeCollection.size() == 1
+ assert dataNodeCollection.collect { it.getXpath() }
+ .containsAll(['/invoice[@ProductID=\'2\']'])
+ }
+ }
+ )
+ and: 'the CpsValidator is called on the dataspaceName and AnchorName'
+ 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
+ and: 'data updated event is sent to notification service'
+ 1 * mockNotificationService.processDataUpdatedEvent(anchor, '/', Operation.UPDATE, observedTimestamp)
+ }
+
def 'Saving child data fragment under existing node.'() {
given: 'schema set for given anchor and dataspace references test-tree model'
setupSchemaSetMocks('test-tree.yang')
diff --git a/cps-service/src/test/resources/bookstore.json b/cps-service/src/test/resources/bookstore.json
index 459908bd6..4b8ed3dab 100644
--- a/cps-service/src/test/resources/bookstore.json
+++ b/cps-service/src/test/resources/bookstore.json
@@ -1,4 +1,12 @@
{
+ "multiple-data-tree:invoice": [
+ {
+ "ProductID": "1",
+ "ProductName": "Apple",
+ "price": "100",
+ "stock": false
+ }
+ ],
"test:bookstore":{
"bookstore-name": "Chapters/Easons",
"categories": [
diff --git a/cps-service/src/test/resources/bookstore.yang b/cps-service/src/test/resources/bookstore.yang
index 2179fb93d..b7a52e2c8 100644
--- a/cps-service/src/test/resources/bookstore.yang
+++ b/cps-service/src/test/resources/bookstore.yang
@@ -15,6 +15,34 @@ module stores {
}
}
+ list invoice {
+ key "ProductID";
+ leaf ProductID {
+ type uint64;
+ mandatory "true";
+ description
+ "Unique product ID. Example: 001";
+ }
+ leaf ProductName {
+ type string;
+ mandatory "true";
+ description
+ "Name of the Product";
+ }
+ leaf price {
+ type uint64;
+ mandatory "true";
+ description
+ "Price of book";
+ }
+ leaf stock {
+ type boolean;
+ default "false";
+ description
+ "Book in stock or not. Example value: true";
+ }
+ }
+
container bookstore {
leaf bookstore-name {
diff --git a/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-app/pom.xml b/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-app/pom.xml
index 71dcec880..534729e49 100644
--- a/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-app/pom.xml
+++ b/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-app/pom.xml
@@ -22,7 +22,7 @@
<parent>
<groupId>org.onap.cps</groupId>
<artifactId>dmi-plugin-demo-and-csit-stub</artifactId>
- <version>3.3.4-SNAPSHOT</version>
+ <version>3.3.5-SNAPSHOT</version>
</parent>
<artifactId>dmi-plugin-demo-and-csit-stub-app</artifactId>
@@ -30,7 +30,7 @@
<properties>
<app>org.onap.cps.ncmp.dmi.rest.stub.DmiDemoApplication</app>
<maven.build.timestamp.format>yyyyMMdd'T'HHmmss'Z'</maven.build.timestamp.format>
- <base.image>${docker.pull.registry}/onap/integration-java11:8.0.0</base.image>
+ <base.image>${docker.pull.registry}/onap/integration-java17:12.0.0</base.image>
<image.tag>${project.version}-${maven.build.timestamp}</image.tag>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
diff --git a/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/pom.xml b/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/pom.xml
index a9e382768..d5bc8a8f7 100644
--- a/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/pom.xml
+++ b/dmi-plugin-demo-and-csit-stub/dmi-plugin-demo-and-csit-stub-service/pom.xml
@@ -21,7 +21,7 @@
<parent>
<groupId>org.onap.cps</groupId>
<artifactId>dmi-plugin-demo-and-csit-stub</artifactId>
- <version>3.3.4-SNAPSHOT</version>
+ <version>3.3.5-SNAPSHOT</version>
</parent>
<artifactId>dmi-plugin-demo-and-csit-stub-service</artifactId>
diff --git a/dmi-plugin-demo-and-csit-stub/pom.xml b/dmi-plugin-demo-and-csit-stub/pom.xml
index e8dd4c021..e32911c72 100644
--- a/dmi-plugin-demo-and-csit-stub/pom.xml
+++ b/dmi-plugin-demo-and-csit-stub/pom.xml
@@ -22,7 +22,7 @@
<parent>
<groupId>org.onap.cps</groupId>
<artifactId>cps-parent</artifactId>
- <version>3.3.4-SNAPSHOT</version>
+ <version>3.3.5-SNAPSHOT</version>
<relativePath>../cps-parent/pom.xml</relativePath>
</parent>
diff --git a/docs/cps-path.rst b/docs/cps-path.rst
index 796eb7f42..661178954 100644
--- a/docs/cps-path.rst
+++ b/docs/cps-path.rst
@@ -177,6 +177,7 @@ General Notes
=============
- String values must be wrapped in quotation marks ``"`` (U+0022) or apostrophes ``'`` (U+0027).
+- Quotations marks and apostrophes can be escaped by doubling them in their respective quotes, for example ``'CPS ''Path'' Query' -> CPS 'Path' Query``
- String comparisons are case sensitive.
Query Syntax
@@ -247,7 +248,6 @@ leaf-conditions
- The key should be supplied with correct data type for it to be queried from DB. In the last example above the attribute code is of type
Integer so the cps query will not work if the value is passed as string.
eg: ``//categories[@code="1"]`` or ``//categories[@code='1']`` will not work because the key attribute code is treated a string.
- - Having '[' token in any index in any list will have a negative impact on this function.
**Notes**
- For performance reasons it does not make sense to query using key leaf as attribute. If the key value is known it is better to execute a get request with the complete xpath.
@@ -272,7 +272,6 @@ The text()-condition can be added to any CPS path query.
- Only string and integer values are supported, boolean and float values are not supported.
- Since CPS cannot return individual leaves it will always return the container with all its leaves. Ancestor-axis can be used to specify a parent higher up the tree.
- When querying a leaf value (instead of leaf-list) it is better, more performant to use a text value condition use @<leaf-name> as described above.
- - Having '[' token in any index in any list will have a negative impact on this function.
contains()-condition
--------------------
diff --git a/docs/release-notes.rst b/docs/release-notes.rst
index d9033a0ea..25f6d22ac 100755
--- a/docs/release-notes.rst
+++ b/docs/release-notes.rst
@@ -16,6 +16,34 @@ CPS Release Notes
.. * * * MONTREAL * * *
.. ========================
+Version: 3.3.5
+==============
+
+Release Data
+------------
+
++--------------------------------------+--------------------------------------------------------+
+| **CPS Project** | |
+| | |
++--------------------------------------+--------------------------------------------------------+
+| **Docker images** | onap/cps-and-ncmp:3.3.5 |
+| | |
++--------------------------------------+--------------------------------------------------------+
+| **Release designation** | 3.3.5 Montreal |
+| | |
++--------------------------------------+--------------------------------------------------------+
+| **Release date** | Not yet released |
+| | |
++--------------------------------------+--------------------------------------------------------+
+
+Bug Fixes
+---------
+3.3.5
+
+Features
+--------
+ - `CPS-1760 <https://jira.onap.org/browse/CPS-1760>`_ Improve handling of special characters in Cps Paths
+
Version: 3.3.4
==============
@@ -32,7 +60,7 @@ Release Data
| **Release designation** | 3.3.4 Montreal |
| | |
+--------------------------------------+--------------------------------------------------------+
-| **Release date** | Not yet released |
+| **Release date** | 2023 July 19 |
| | |
+--------------------------------------+--------------------------------------------------------+
@@ -42,6 +70,7 @@ Bug Fixes
Features
--------
+ - `CPS-1767 <https://jira.onap.org/browse/CPS-1767>`_ Upgrade CPS to java 17
Version: 3.3.3
==============
diff --git a/integration-test/pom.xml b/integration-test/pom.xml
index 04ce7cca1..18b660fe3 100644
--- a/integration-test/pom.xml
+++ b/integration-test/pom.xml
@@ -23,7 +23,7 @@
<parent>
<groupId>org.onap.cps</groupId>
<artifactId>cps-parent</artifactId>
- <version>3.3.4-SNAPSHOT</version>
+ <version>3.3.5-SNAPSHOT</version>
<relativePath>../cps-parent/pom.xml</relativePath>
</parent>
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataServiceIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataServiceIntegrationSpec.groovy
index 351f3106f..a3f14397c 100644
--- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataServiceIntegrationSpec.groovy
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataServiceIntegrationSpec.groovy
@@ -43,11 +43,13 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase {
CpsDataService objectUnderTest
def originalCountBookstoreChildNodes
+ def originalCountBookstoreTopLevelListNodes
def now = OffsetDateTime.now()
def setup() {
objectUnderTest = cpsDataService
originalCountBookstoreChildNodes = countDataNodesInBookstore()
+ originalCountBookstoreTopLevelListNodes = countTopLevelListDataNodesInBookstore()
}
def 'Read bookstore top-level container(s) using #fetchDescendantsOption.'() {
@@ -64,18 +66,18 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase {
where: 'the following option is used'
fetchDescendantsOption || expectNumberOfDataNodes
OMIT_DESCENDANTS || 1
- DIRECT_CHILDREN_ONLY || 6
- INCLUDE_ALL_DESCENDANTS || 17
- new FetchDescendantsOption(2) || 17
+ DIRECT_CHILDREN_ONLY || 7
+ INCLUDE_ALL_DESCENDANTS || 28
+ new FetchDescendantsOption(2) || 28
}
def 'Read bookstore top-level container(s) using "root" path variations.'() {
when: 'get data nodes for bookstore container'
def result = objectUnderTest.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, root, OMIT_DESCENDANTS)
then: 'the tree consist ouf of one data node'
- assert countDataNodesInTree(result) == 1
+ assert countDataNodesInTree(result) == 2
and: 'the top level data node has the expected attribute and value'
- assert result.leaves['bookstore-name'] == ['Easons']
+ assert result.leaves.size() == 2
where: 'the following variations of "root" are used'
root << [ '/', '' ]
}
@@ -179,6 +181,21 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase {
thrown(DataNodeNotFoundExceptionBatch)
}
+ def 'Add and Delete top-level list (element) data nodes with root node.'() {
+ given: 'a new (multiple-data-tree:invoice) datanodes'
+ def json = '{"multiple-data-tree:invoice": [{"ProductID": "2","ProductName": "Mango","price": "150","stock": true}]}'
+ when: 'the new list elements are saved'
+ objectUnderTest.saveListElements(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/', json, now)
+ then: 'they can be retrieved by their xpaths'
+ objectUnderTest.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/invoice[@ProductID ="2"]', INCLUDE_ALL_DESCENDANTS)
+ and: 'there is one extra datanode'
+ assert originalCountBookstoreTopLevelListNodes + 1 == countTopLevelListDataNodesInBookstore()
+ when: 'the new elements are deleted'
+ objectUnderTest.deleteDataNode(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/invoice[@ProductID ="2"]', now)
+ then: 'the original number of datanodes is restored'
+ assert originalCountBookstoreTopLevelListNodes == countTopLevelListDataNodesInBookstore()
+ }
+
def 'Add and Delete list (element) data nodes.'() {
given: 'two new (categories) data nodes'
def json = '{"categories": [ {"code":"new1"}, {"code":"new2" } ] }'
@@ -368,4 +385,8 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase {
def countDataNodesInBookstore() {
return countDataNodesInTree(objectUnderTest.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '/bookstore', INCLUDE_ALL_DESCENDANTS))
}
+
+ def countTopLevelListDataNodesInBookstore() {
+ return countDataNodesInTree(objectUnderTest.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '/', INCLUDE_ALL_DESCENDANTS))
+ }
}
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsQueryServiceIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsQueryServiceIntegrationSpec.groovy
index a736ab0c0..74496d301 100644
--- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsQueryServiceIntegrationSpec.groovy
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsQueryServiceIntegrationSpec.groovy
@@ -54,52 +54,30 @@ class CpsQueryServiceIntegrationSpec extends FunctionalSpecBase {
'the AND is used where result does not exist' | '//books[@lang="English" and @price=1000]' || 0 | []
}
- def 'Cps Path query using combinations of OR operator #scenario.'() {
+ def 'Cps Path query using comparative and boolean operators.'() {
+ given: 'a cps path query in the discount category'
+ def cpsPath = "/bookstore/categories[@code='5']/books" + leafCondition
when: 'a query is executed to get response by the given cps path'
- def result = objectUnderTest.queryDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, cpsPath, OMIT_DESCENDANTS)
- then: 'the result contains expected number of nodes'
- assert result.size() == expectedResultSize
- and: 'the cps-path of queryDataNodes has the expectedLeaves'
- assert result.leaves.sort() == expectedLeaves.sort()
- where: 'the following data is used'
- scenario | cpsPath || expectedResultSize | expectedLeaves
- 'the "OR" condition' | '//books[@lang="English" or @price=15]' || 6 | [[lang: "English", price: 15, title: "Annihilation", authors: ["Jeff VanderMeer"], editions: [2014]],
- [lang: "English", price: 15, title: "The Gruffalo", authors: ["Julia Donaldson"], editions: [1999]],
- [lang: "English", price: 14, title: "The Light Fantastic", authors: ["Terry Pratchett"], editions: [1986]],
- [lang: "English", price: 13, title: "Good Omens", authors: ["Terry Pratchett", "Neil Gaiman"], editions: [2006]],
- [lang: "English", price: 12, title: "The Colour of Magic", authors: ["Terry Pratchett"], editions: [1983]],
- [lang: "English", price: 10, title: "Matilda", authors: ["Roald Dahl"], editions: [1988, 2000]]]
- 'the "OR" condition with non-json data' | '//books[@title="xyz" or @price=15]' || 2 | [[lang: "English", price: 15, title: "Annihilation", authors: ["Jeff VanderMeer"], editions: [2014]],
- [lang: "English", price: 15, title: "The Gruffalo", authors: ["Julia Donaldson"], editions: [1999]]]
- 'combination of multiple AND' | '//books[@lang="English" and @price=15 and @edition=1983]' || 0 | []
- 'combination of multiple OR' | '//books[ @title="Matilda" or @price=15 or @edition=1983]' || 3 | [[lang: "English", price: 15, title: "Annihilation", authors: ["Jeff VanderMeer"], editions: [2014]],
- [lang: "English", price: 10, title: "Matilda", authors: ["Roald Dahl"], editions: [1988, 2000]],
- [lang: "English", price: 15, title: "The Gruffalo", authors: ["Julia Donaldson"], editions: [1999]]]
- 'combination of AND/OR' | '//books[@edition=1983 and @price=15 or @title="Good Omens"]' || 1 | [[lang: "English", price: 13, title: "Good Omens", authors: ["Terry Pratchett", "Neil Gaiman"], editions: [2006]]]
- 'combination of OR/AND' | '//books[@title="Annihilation" or @price=39 and @lang="arabic"]' || 1 | [[lang: "English", price: 15, title: "Annihilation", authors: ["Jeff VanderMeer"], editions: [2014]]]
- }
-
- def 'cps-path query using combinations of Comparative Operators #scenario.'() {
- when: 'a query is executed to get response by the given cpsPath'
- def result = objectUnderTest.queryDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, cpsPath, OMIT_DESCENDANTS)
- then: 'the result contains expected number of nodes'
- assert result.size() == expectedResultSize
- and: 'xpaths of the retrieved data nodes are as expected'
- def bookTitles = result.collect { it.getLeaves().get('title') }
- assert bookTitles.sort() == expectedBookTitles.sort()
+ def result = objectUnderTest.queryDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1,
+ cpsPath, OMIT_DESCENDANTS)
+ then: 'the cps-path of queryDataNodes has the expectedLeaves'
+ def bookPrices = result.collect { it.getLeaves().get('price') }
+ assert bookPrices.sort() == expectedBookPrices.sort()
where: 'the following data is used'
- scenario | cpsPath || expectedResultSize | expectedBookTitles
- 'the ">" condition' | '//books[@price>13 ]' || 5 | ['A Book with No Language', 'Annihilation', 'Debian GNU/Linux', 'The Gruffalo', 'The Light Fantastic']
- 'the "<" condition ' | '//books[@price<15]' || 5 | ['Good Omens', 'Logarithm tables', 'Matilda', 'The Colour of Magic', 'The Light Fantastic']
- 'the "<=" condition' | '//books[@price<=15]' || 7 | ['Annihilation', 'Good Omens', 'Logarithm tables', 'Matilda', 'The Colour of Magic', 'The Gruffalo', 'The Light Fantastic']
- 'the ">=" condition' | '//books[@price>=20]' || 2 | ['A Book with No Language', 'Debian GNU/Linux']
- 'the "<" condition where result does not exist' | '//books[@price<5]' || 0 | []
- 'the ">" condition where result does not exist' | '//books[@price>1000]' || 0 | []
- 'the ">" condition with AND condition' | '//books[@price>13 and @title="A Book with No Language"]' || 1 | ['A Book with No Language']
- 'the "<" condition with OR condition' | '//books[@price<10 or @lang="German"]' || 1 | ['Debian GNU/Linux']
- 'the "<=" condition with AND/OR condition' | '//books[@price<=15 and @title="Annihilation" or @lang="Spanish"]' || 1 | ['Annihilation']
- 'the ">=" condition with OR/AND condition' | '//books[@price>=13 or @lang="Spanish" and @title="Good Omens"]' || 6 | ['A Book with No Language', 'Annihilation', 'Good Omens', 'Debian GNU/Linux', 'The Gruffalo', 'The Light Fantastic']
- 'Mix of integer and string condition ' | '//books[@lang="German" and @price>38]' || 1 | ['Debian GNU/Linux']
+ leafCondition || expectedBookPrices
+ '[@price = 5]' || [5]
+ '[@price < 5]' || [1, 2, 3, 4]
+ '[@price > 5]' || [6, 7, 8, 9, 10]
+ '[@price <= 5]' || [1, 2, 3, 4, 5]
+ '[@price >= 5]' || [5, 6, 7, 8, 9, 10]
+ '[@price > 10]' || []
+ '[@price = 3 or @price = 7]' || [3, 7]
+ '[@price = 3 and @price = 7]' || []
+ '[@price > 3 and @price <= 6]' || [4, 5, 6]
+ '[@price < 3 or @price > 8]' || [1, 2, 9, 10]
+ '[@price = 1 or @price = 3 or @price = 5]' || [1, 3, 5]
+ '[@price = 1 or @price >= 8 and @price < 10]' || [1, 8, 9]
+ '[@price >= 3 and @price <= 5 or @price > 9]' || [3, 4, 5, 10]
}
def 'Cps Path query for leaf value(s) with #scenario.'() {
@@ -113,9 +91,9 @@ class CpsQueryServiceIntegrationSpec extends FunctionalSpecBase {
scenario | cpsPath | fetchDescendantsOption || expectedNumberOfParentNodes | expectedTotalNumberOfNodes
'string and no descendants' | '/bookstore/categories[@code="1"]/books[@title="Matilda"]' | OMIT_DESCENDANTS || 1 | 1
'integer and descendants' | '/bookstore/categories[@code="1"]/books[@price=15]' | INCLUDE_ALL_DESCENDANTS || 1 | 1
- 'no condition and no descendants' | '/bookstore/categories' | OMIT_DESCENDANTS || 4 | 4
- 'no condition and level 1 descendants' | '/bookstore' | new FetchDescendantsOption(1) || 1 | 6
- 'no condition and level 2 descendants' | '/bookstore' | new FetchDescendantsOption(2) || 1 | 17
+ 'no condition and no descendants' | '/bookstore/categories' | OMIT_DESCENDANTS || 5 | 5
+ 'no condition and level 1 descendants' | '/bookstore' | new FetchDescendantsOption(1) || 1 | 7
+ 'no condition and level 2 descendants' | '/bookstore' | new FetchDescendantsOption(2) || 1 | 28
}
def 'Query for attribute by cps path with cps paths that return no data because of #scenario.'() {
@@ -146,7 +124,7 @@ class CpsQueryServiceIntegrationSpec extends FunctionalSpecBase {
when: 'a query is executed to get all books'
def result = objectUnderTest.queryDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '//books', OMIT_DESCENDANTS)
then: 'the expected number of books are returned'
- assert result.size() == 9
+ assert result.size() == 19
}
def 'Cps Path query using descendant anywhere with #scenario.'() {
@@ -160,7 +138,7 @@ class CpsQueryServiceIntegrationSpec extends FunctionalSpecBase {
'string leaf condition' | '//books[@title="Matilda"]' || ["Matilda"]
'text condition on leaf' | '//books/title[text()="Matilda"]' || ["Matilda"]
'text condition case mismatch' | '//books/title[text()="matilda"]' || []
- 'text condition on int leaf' | '//books/price[text()="10"]' || ["Matilda"]
+ 'text condition on int leaf' | '//books/price[text()="20"]' || ["A Book with No Language", "Matilda"]
'text condition on leaf-list' | '//books/authors[text()="Terry Pratchett"]' || ["Good Omens", "The Colour of Magic", "The Light Fantastic"]
'text condition partial match' | '//books/authors[text()="Terry"]' || []
'text condition (existing) empty string' | '//books/lang[text()=""]' || ["A Book with No Language"]
@@ -182,7 +160,13 @@ class CpsQueryServiceIntegrationSpec extends FunctionalSpecBase {
'contains condition with leaf' | '//books[contains(@title,"Mat")]' || ["Matilda"]
'contains condition with case-sensitive' | '//books[contains(@title,"Ti")]' || []
'contains condition with Integer Value' | '//books[contains(@price,"15")]' || ["Annihilation", "The Gruffalo"]
- 'contains condition with No-value' | '//books[contains(@title,"")]' || ["A Book with No Language", "Annihilation", "Debian GNU/Linux", "Good Omens", "Logarithm tables", "Matilda", "The Colour of Magic", "The Gruffalo", "The Light Fantastic"]
+ }
+
+ def 'Query for attribute by cps path using contains condition with no value.'() {
+ when: 'a query is executed to get response by the given cps path'
+ def result = objectUnderTest.queryDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '//books[contains(@title,"")]', OMIT_DESCENDANTS)
+ then: 'all books are returned'
+ assert result.size() == 19
}
def 'Cps Path query using descendant anywhere with #scenario condition for a container element.'() {
@@ -194,7 +178,7 @@ class CpsQueryServiceIntegrationSpec extends FunctionalSpecBase {
where: 'the following data is used'
scenario | cpsPath || expectedBookTitles
'one leaf' | '//books[@price=14]' || ['The Light Fantastic']
- 'one leaf with ">" condition' | '//books[@price>14]' || ['A Book with No Language', 'Annihilation', 'Debian GNU/Linux', 'The Gruffalo']
+ 'one leaf with ">" condition' | '//books[@price>14]' || ['A Book with No Language', 'Annihilation', 'Debian GNU/Linux', 'Matilda', 'The Gruffalo']
'one text' | '//books/authors[text()="Terry Pratchett"]' || ['Good Omens', 'The Colour of Magic', 'The Light Fantastic']
'more than one leaf' | '//books[@price=12 and @lang="English"]' || ['The Colour of Magic']
'more than one leaf has "OR" condition' | '//books[@lang="English" or @price=15]' || ['Annihilation', 'Good Omens', 'Matilda', 'The Colour of Magic', 'The Gruffalo', 'The Light Fantastic']
@@ -228,11 +212,11 @@ class CpsQueryServiceIntegrationSpec extends FunctionalSpecBase {
assert result.xpath.sort() == expectedXPaths.sort()
where: 'the following data is used'
scenario | cpsPath || expectedXPaths
- 'multiple list-ancestors' | '//books/ancestor::categories' || ["/bookstore/categories[@code='1']", "/bookstore/categories[@code='2']", "/bookstore/categories[@code='3']", "/bookstore/categories[@code='4']"]
+ 'multiple list-ancestors' | '//books/ancestor::categories' || ["/bookstore/categories[@code='1']", "/bookstore/categories[@code='2']", "/bookstore/categories[@code='3']", "/bookstore/categories[@code='4']", "/bookstore/categories[@code='5']"]
'one ancestor with list value' | '//books/ancestor::categories[@code="1"]' || ["/bookstore/categories[@code='1']"]
'top ancestor' | '//books/ancestor::bookstore' || ["/bookstore"]
'list with index value in the xpath prefix' | '//categories[@code="1"]/books/ancestor::bookstore' || ["/bookstore"]
- 'ancestor with parent list' | '//books/ancestor::bookstore/categories' || ["/bookstore/categories[@code='1']", "/bookstore/categories[@code='2']", "/bookstore/categories[@code='3']", "/bookstore/categories[@code='4']"]
+ 'ancestor with parent list' | '//books/ancestor::bookstore/categories' || ["/bookstore/categories[@code='1']", "/bookstore/categories[@code='2']", "/bookstore/categories[@code='3']", "/bookstore/categories[@code='4']", "/bookstore/categories[@code='5']"]
'ancestor with parent' | '//books/ancestor::bookstore/categories[@code="2"]' || ["/bookstore/categories[@code='2']"]
'ancestor combined with text condition' | '//books/title[text()="Matilda"]/ancestor::bookstore' || ["/bookstore"]
'ancestor with parent that does not exist' | '//books/ancestor::parentDoesNoExist/categories' || []
@@ -248,8 +232,8 @@ class CpsQueryServiceIntegrationSpec extends FunctionalSpecBase {
where: 'the following data is used'
scenario | fetchDescendantsOption || expectedNumberOfNodes
'no' | OMIT_DESCENDANTS || 1
- 'direct' | DIRECT_CHILDREN_ONLY || 6
- 'all' | INCLUDE_ALL_DESCENDANTS || 17
+ 'direct' | DIRECT_CHILDREN_ONLY || 7
+ 'all' | INCLUDE_ALL_DESCENDANTS || 28
}
def 'Cps Path query with #scenario throws a CPS Path Exception.'() {
@@ -277,13 +261,13 @@ class CpsQueryServiceIntegrationSpec extends FunctionalSpecBase {
where: 'the following data is used'
scenario | cpsPath || expectedXpathsPerAnchor
'container node' | '/bookstore' || ["/bookstore"]
- 'list node' | '/bookstore/categories' || ["/bookstore/categories[@code='1']", "/bookstore/categories[@code='2']", "/bookstore/categories[@code='3']", "/bookstore/categories[@code='4']"]
+ 'list node' | '/bookstore/categories' || ["/bookstore/categories[@code='1']", "/bookstore/categories[@code='2']", "/bookstore/categories[@code='3']", "/bookstore/categories[@code='4']", "/bookstore/categories[@code='5']"]
'string leaf-condition' | '/bookstore[@bookstore-name="Easons"]' || ["/bookstore"]
'integer leaf-condition' | '/bookstore/categories[@code="1"]/books[@price=15]' || ["/bookstore/categories[@code='1']/books[@title='The Gruffalo']"]
- 'multiple list-ancestors' | '//books/ancestor::categories' || ["/bookstore/categories[@code='1']", "/bookstore/categories[@code='2']", "/bookstore/categories[@code='3']", "/bookstore/categories[@code='4']"]
+ 'multiple list-ancestors' | '//books/ancestor::categories' || ["/bookstore/categories[@code='1']", "/bookstore/categories[@code='2']", "/bookstore/categories[@code='3']", "/bookstore/categories[@code='4']", "/bookstore/categories[@code='5']"]
'one ancestor with list value' | '//books/ancestor::categories[@code="1"]' || ["/bookstore/categories[@code='1']"]
'list with index value in the xpath prefix' | '//categories[@code="1"]/books/ancestor::bookstore' || ["/bookstore"]
- 'ancestor with parent list' | '//books/ancestor::bookstore/categories' || ["/bookstore/categories[@code='1']", "/bookstore/categories[@code='2']", "/bookstore/categories[@code='3']", "/bookstore/categories[@code='4']"]
+ 'ancestor with parent list' | '//books/ancestor::bookstore/categories' || ["/bookstore/categories[@code='1']", "/bookstore/categories[@code='2']", "/bookstore/categories[@code='3']", "/bookstore/categories[@code='4']", "/bookstore/categories[@code='5']"]
'ancestor with parent list element' | '//books/ancestor::bookstore/categories[@code="2"]' || ["/bookstore/categories[@code='2']"]
'ancestor combined with text condition' | '//books/title[text()="Matilda"]/ancestor::bookstore' || ["/bookstore"]
}
@@ -298,8 +282,8 @@ class CpsQueryServiceIntegrationSpec extends FunctionalSpecBase {
where: 'the following data is used'
scenario | fetchDescendantsOption || expectedNumberOfNodesPerAnchor
'no' | OMIT_DESCENDANTS || 1
- 'direct' | DIRECT_CHILDREN_ONLY || 6
- 'all' | INCLUDE_ALL_DESCENDANTS || 17
+ 'direct' | DIRECT_CHILDREN_ONLY || 7
+ 'all' | INCLUDE_ALL_DESCENDANTS || 28
}
def 'Cps Path query across anchors with ancestors and #scenario descendants.'() {
@@ -312,8 +296,8 @@ class CpsQueryServiceIntegrationSpec extends FunctionalSpecBase {
where: 'the following data is used'
scenario | fetchDescendantsOption || expectedNumberOfNodesPerAnchor
'no' | OMIT_DESCENDANTS || 1
- 'direct' | DIRECT_CHILDREN_ONLY || 6
- 'all' | INCLUDE_ALL_DESCENDANTS || 17
+ 'direct' | DIRECT_CHILDREN_ONLY || 7
+ 'all' | INCLUDE_ALL_DESCENDANTS || 28
}
def 'Cps Path query across anchors with syntax error throws a CPS Path Exception.'() {
@@ -330,10 +314,10 @@ class CpsQueryServiceIntegrationSpec extends FunctionalSpecBase {
assert countDataNodesInTree(result) == expectedNumberOfDataNodes
where:
scenario | cpsPath || expectedNumberOfDataNodes
- 'absolute path all list entries' | '/bookstore/categories' || 13
+ 'absolute path all list entries' | '/bookstore/categories' || 24
'absolute path 1 list entry by key' | '/bookstore/categories[@code="3"]' || 5
'absolute path 1 list entry by name' | '/bookstore/categories[@name="Comedy"]' || 5
- 'relative path all list entries' | '//categories' || 13
+ 'relative path all list entries' | '//categories' || 24
'relative path 1 list entry by key' | '//categories[@code="3"]' || 5
'relative path 1 list entry by leaf' | '//categories[@name="Comedy"]' || 5
'incomplete absolute path' | '/categories' || 0
@@ -372,4 +356,23 @@ class CpsQueryServiceIntegrationSpec extends FunctionalSpecBase {
'text-condition' || "/bookstore/categories[@code='1']/books/title[text()='[@hello=world]']"
'contains-condition' || "/bookstore/categories[@code='1']/books[contains(@title, '[@hello=world]')]"
}
+
+ def 'Cps Path get and query can handle apostrophe inside #quotes.'() {
+ given: 'a book with special characters in title'
+ cpsDataService.saveData(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, "/bookstore/categories[@code='1']",
+ '{"books": [ {"title":"I\'m escaping"} ] }', OffsetDateTime.now())
+ when: 'a query is executed'
+ def result = objectUnderTest.queryDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, cpsPath, OMIT_DESCENDANTS)
+ then: 'the node is returned'
+ assert result.size() == 1
+ assert result[0].xpath == "/bookstore/categories[@code='1']/books[@title='I''m escaping']"
+ cleanup: 'the new datanode'
+ cpsDataService.deleteDataNode(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, "/bookstore/categories[@code='1']/books[@title='I''m escaping']", OffsetDateTime.now())
+ where:
+ quotes || cpsPath
+ 'single quotes' || "/bookstore/categories[@code='1']/books[@title='I''m escaping']"
+ 'double quotes' || '/bookstore/categories[@code="1"]/books[@title="I\'m escaping"]'
+ 'text-condition' || "/bookstore/categories[@code='1']/books/title[text()='I''m escaping']"
+ 'contains-condition' || "/bookstore/categories[@code='1']/books[contains(@title, 'I''m escaping')]"
+ }
}
diff --git a/integration-test/src/test/resources/data/bookstore/bookstore.yang b/integration-test/src/test/resources/data/bookstore/bookstore.yang
index e592a9c5c..ab384de1c 100644
--- a/integration-test/src/test/resources/data/bookstore/bookstore.yang
+++ b/integration-test/src/test/resources/data/bookstore/bookstore.yang
@@ -15,6 +15,34 @@ module stores {
}
}
+ list invoice {
+ key "ProductID";
+ leaf ProductID {
+ type uint64;
+ mandatory "true";
+ description
+ "Unique product ID. Example: 001";
+ }
+ leaf ProductName {
+ type string;
+ mandatory "true";
+ description
+ "Name of the Product";
+ }
+ leaf price {
+ type uint64;
+ mandatory "true";
+ description
+ "Price of book";
+ }
+ leaf stock {
+ type boolean;
+ default "false";
+ description
+ "Book in stock or not. Example value: true";
+ }
+ }
+
container bookstore {
leaf bookstore-name {
diff --git a/integration-test/src/test/resources/data/bookstore/bookstoreData.json b/integration-test/src/test/resources/data/bookstore/bookstoreData.json
index 12df20e55..5f66a1d00 100644
--- a/integration-test/src/test/resources/data/bookstore/bookstoreData.json
+++ b/integration-test/src/test/resources/data/bookstore/bookstoreData.json
@@ -1,4 +1,12 @@
{
+ "multiple-data-tree:invoice": [
+ {
+ "ProductID": "1",
+ "ProductName": "Apple",
+ "price": "100",
+ "stock": false
+ }
+ ],
"bookstore": {
"bookstore-name": "Easons",
"premises": {
@@ -27,7 +35,7 @@
"lang": "English",
"authors": ["Roald Dahl"],
"editions": [1988, 2000],
- "price": 10
+ "price": 20
},
{
"title": "The Gruffalo",
@@ -104,6 +112,82 @@
"price": 11
}
]
+ },
+ {
+ "code": 5,
+ "name": "Discount books",
+ "books" : [
+ {
+ "title": "Book 1",
+ "lang": "blah",
+ "authors": [],
+ "editions": [],
+ "price": 1
+ },
+ {
+ "title": "Book 2",
+ "lang": "blah",
+ "authors": [],
+ "editions": [],
+ "price": 2
+ },
+ {
+ "title": "Book 3",
+ "lang": "blah",
+ "authors": [],
+ "editions": [],
+ "price": 3
+ },
+ {
+ "title": "Book 4",
+ "lang": "blah",
+ "authors": [],
+ "editions": [],
+ "price": 4
+ },
+ {
+ "title": "Book 5",
+ "lang": "blah",
+ "authors": [],
+ "editions": [],
+ "price": 5
+ },
+ {
+ "title": "Book 6",
+ "lang": "blah",
+ "authors": [],
+ "editions": [],
+ "price": 6
+ },
+ {
+ "title": "Book 7",
+ "lang": "blah",
+ "authors": [],
+ "editions": [],
+ "price": 7
+ },
+ {
+ "title": "Book 8",
+ "lang": "blah",
+ "authors": [],
+ "editions": [],
+ "price": 8
+ },
+ {
+ "title": "Book 9",
+ "lang": "blah",
+ "authors": [],
+ "editions": [],
+ "price": 9
+ },
+ {
+ "title": "Book 10",
+ "lang": "blah",
+ "authors": [],
+ "editions": [],
+ "price": 10
+ }
+ ]
}
]
}
diff --git a/jacoco-report/pom.xml b/jacoco-report/pom.xml
index 623f2a071..7bd6c9b52 100644
--- a/jacoco-report/pom.xml
+++ b/jacoco-report/pom.xml
@@ -5,7 +5,7 @@
<parent>
<groupId>org.onap.cps</groupId>
<artifactId>cps-parent</artifactId>
- <version>3.3.4-SNAPSHOT</version>
+ <version>3.3.5-SNAPSHOT</version>
<relativePath>../cps-parent/pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/pom.xml b/pom.xml
index 910afa2ac..119b14b78 100644
--- a/pom.xml
+++ b/pom.xml
@@ -32,7 +32,7 @@
<groupId>org.onap.cps</groupId>
<artifactId>cps-aggregator</artifactId>
- <version>3.3.4-SNAPSHOT</version>
+ <version>3.3.5-SNAPSHOT</version>
<packaging>pom</packaging>
<name>cps</name>
diff --git a/releases/3.3.4-container.yaml b/releases/3.3.4-container.yaml
new file mode 100644
index 000000000..ee2a0d4b8
--- /dev/null
+++ b/releases/3.3.4-container.yaml
@@ -0,0 +1,8 @@
+distribution_type: container
+container_release_tag: 3.3.4
+project: cps
+log_dir: cps-maven-docker-stage-master/923/
+ref: 6b31279b2122ff9add6696b5eacfbeea8bb31cef
+containers:
+ - name: 'cps-and-ncmp'
+ version: '3.3.4-20230718T101218Z'
diff --git a/releases/3.3.4.yaml b/releases/3.3.4.yaml
new file mode 100644
index 000000000..073bd426e
--- /dev/null
+++ b/releases/3.3.4.yaml
@@ -0,0 +1,4 @@
+distribution_type: maven
+log_dir: cps-maven-stage-master/931/
+project: cps
+version: 3.3.4 \ No newline at end of file
diff --git a/spotbugs/pom.xml b/spotbugs/pom.xml
index 20a10d23f..6e84c3f78 100644
--- a/spotbugs/pom.xml
+++ b/spotbugs/pom.xml
@@ -25,7 +25,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.onap.cps</groupId>
<artifactId>spotbugs</artifactId>
- <version>3.3.4-SNAPSHOT</version>
+ <version>3.3.5-SNAPSHOT</version>
<properties>
<nexusproxy>https://nexus.onap.org</nexusproxy>
diff --git a/version.properties b/version.properties
index 9456209eb..5445d9978 100755
--- a/version.properties
+++ b/version.properties
@@ -22,7 +22,7 @@
major=3
minor=3
-patch=4
+patch=5
base_version=${major}.${minor}.${patch}