Introducing Antlr4 for cpsPath parsing 03/121503/9
authorToineSiebelink <toine.siebelink@est.tech>
Thu, 20 May 2021 15:44:21 +0000 (16:44 +0100)
committerToineSiebelink <toine.siebelink@est.tech>
Tue, 1 Jun 2021 09:12:55 +0000 (10:12 +0100)
-created new module for cpPathParser
-added antlr rule for cpsPathWithSingleLeafCondition
-added antlr rule for cpsPathWithDescendant (and with leaf conditions)
-added antlr rule for ancestor axis
-added unit test (copied from existing CpsPathQuerySpec)
-udpated cps-ri to use new cpPathQuery from parser module
-'imported' lexer rules from publix xPath grammar
-Re-used existing CpsPathException but conversion happens in cps-ri to prevent additional dependency in cps-path-parser module

Issue-ID: CPS-376

Change-Id: I2c5df98969402cbf69f6573c52705879450ce606
Signed-off-by: ToineSiebelink <toine.siebelink@est.tech>
18 files changed:
cps-dependencies/pom.xml
cps-parent/pom.xml
cps-path-parser/lombok.config [new file with mode: 0644]
cps-path-parser/pom.xml [new file with mode: 0644]
cps-path-parser/src/main/antlr4/org/onap/cps/cpspath/parser/antlr4/CpsPath.g4 [new file with mode: 0644]
cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathBuilder.java [new file with mode: 0644]
cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathQuery.java [new file with mode: 0644]
cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathQueryType.java [moved from cps-ri/src/main/java/org/onap/cps/spi/query/CpsPathQueryType.java with 94% similarity]
cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathQuerySpec.groovy [moved from cps-ri/src/test/groovy/org/onap/cps/spi/query/CpsPathQuerySpec.groovy with 59% similarity]
cps-rest/src/test/groovy/org/onap/cps/rest/exceptions/CpsRestExceptionHandlerSpec.groovy
cps-ri/pom.xml
cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java
cps-ri/src/main/java/org/onap/cps/spi/query/CpsPathQuery.java [deleted file]
cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceQueryDataNodeSpec.groovy
cps-service/src/main/java/org/onap/cps/spi/exceptions/CpsPathException.java
cps-service/src/test/groovy/org/onap/cps/spi/exceptions/CpsExceptionsSpec.groovy
pom.xml
spotbugs/src/main/resources/spotbugs-exclude.xml

index 73aa36a..16a6532 100755 (executable)
@@ -1,4 +1,21 @@
 <?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ============LICENSE_START=======================================================
+  Copyright (c) 2021 Linux 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.
+  ============LICENSE_END=========================================================
+-->
 <project xmlns="http://maven.apache.org/POM/4.0.0"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
@@ -13,6 +30,7 @@
     <description>This artifact contains dependencyManagement declarations of upstream versions.</description>
 
     <properties>
+        <antlr4-runtime.version>4.9.2</antlr4-runtime.version>
         <cglib-nodep.version>3.1</cglib-nodep.version>
         <commons-codec.version>1.15</commons-codec.version>
         <commons-lang3.version>3.11</commons-lang3.version>
                 <artifactId>hibernate-types-52</artifactId>
                 <version>${hibernate-types.version}</version>
             </dependency>
+            <dependency>
+                <groupId>org.antlr</groupId>
+                <artifactId>antlr4-runtime</artifactId>
+                <version>${antlr4-runtime.version}</version>
+            </dependency>
             <dependency>
                 <groupId>org.codehaus.groovy</groupId>
                 <artifactId>groovy</artifactId>
index 3552c28..18a9856 100755 (executable)
                         <configuration>
                             <excludes>
                                 <exclude>org/onap/cps/rest/model/*</exclude>
+                                <exclude>org/onap/cps/cpspath/parser/antlr4/*</exclude>
                             </excludes>
                             <dataFile>${project.build.directory}/code-coverage/jacoco-ut.exec</dataFile>
                             <rules>
diff --git a/cps-path-parser/lombok.config b/cps-path-parser/lombok.config
new file mode 100644 (file)
index 0000000..a23edb4
--- /dev/null
@@ -0,0 +1,2 @@
+config.stopBubbling = true
+lombok.addLombokGeneratedAnnotation = true
\ No newline at end of file
diff --git a/cps-path-parser/pom.xml b/cps-path-parser/pom.xml
new file mode 100644 (file)
index 0000000..38ba9ac
--- /dev/null
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ============LICENSE_START=======================================================
+  Copyright (c) 2021 Linux 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.
+  ============LICENSE_END=========================================================
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.onap.cps</groupId>
+        <artifactId>cps-parent</artifactId>
+        <version>1.1.0-SNAPSHOT</version>
+        <relativePath>../cps-parent/pom.xml</relativePath>
+    </parent>
+
+    <artifactId>cps-path-parser</artifactId>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.antlr</groupId>
+                <artifactId>antlr4-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>antlr4</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.antlr</groupId>
+            <artifactId>antlr4-runtime</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+        <!-- T E S T   D E P E N D E N C I E S -->
+        <dependency>
+            <groupId>org.codehaus.groovy</groupId>
+            <artifactId>groovy</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.spockframework</groupId>
+            <artifactId>spock-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.spockframework</groupId>
+            <artifactId>spock-spring</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>cglib</groupId>
+            <artifactId>cglib-nodep</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.junit.vintage</groupId>
+                    <artifactId>junit-vintage-engine</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+    </dependencies>
+
+</project>
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
new file mode 100644 (file)
index 0000000..8609545
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 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=========================================================
+ */
+
+grammar CpsPath ;
+
+cpsPath: (cpsPathWithSingleLeafCondition | cpsPathWithDescendant | cpsPathWithDescendantAndLeafConditions) ancestorAxis? ;
+
+ancestorAxis: SLASH KW_ANCESTOR COLONCOLON ancestorPath ;
+
+ancestorPath: yangElement (SLASH yangElement)* ;
+
+cpsPathWithSingleLeafCondition: prefix singleValueCondition ;
+
+/*
+No need to ditinguish between cpsPathWithDescendant | cpsPathWithDescendantAndLeafConditions really!
+See https://jira.onap.org/browse/CPS-436
+*/
+
+cpsPathWithDescendant: descendant ;
+
+cpsPathWithDescendantAndLeafConditions: descendant multipleValueConditions ;
+
+descendant: SLASH prefix ;
+
+prefix: (SLASH yangElement)* SLASH containerName ;
+
+yangElement: containerName listElementRef? ;
+
+containerName: QName ;
+
+listElementRef: multipleValueConditions ;
+
+singleValueCondition: '[' leafCondition ']' ;
+
+multipleValueConditions: '[' leafCondition (' and ' leafCondition)* ']' ;
+
+leafCondition: '@' leafName '=' (IntegerLiteral | StringLiteral ) ;
+
+//To Confirm: defintion of Lefname with external xPath grammar
+leafName: QName ;
+
+/*
+ * Lexer Rules
+ * Most of the lexer rules below are 'imporetd' from
+ * https://raw.githubusercontent.com/antlr/grammars-v4/master/xpath/xpath31/XPath31.g4
+ */
+
+SLASH : '/';
+COLONCOLON : '::' ;
+
+// KEYWORDS
+
+KW_ANCESTOR : 'ancestor' ;
+
+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 | ~['])*? '\'') ;
+fragment FragEscapeQuot : '""' ;
+fragment FragEscapeApos : '\'';
+fragment FragDigits : [0-9]+ ;
+
+QName  : FragQName ;
+NCName : FragmentNCName ;
+fragment FragQName : FragPrefixedName | FragUnprefixedName ;
+fragment FragPrefixedName : FragPrefix ':' FragLocalPart ;
+fragment FragUnprefixedName : FragLocalPart ;
+fragment FragPrefix : FragmentNCName ;
+fragment FragLocalPart : FragmentNCName ;
+fragment FragNCNameStartChar
+  :  'A'..'Z'
+  |  '_'
+  | 'a'..'z'
+  | '\u00C0'..'\u00D6'
+  | '\u00D8'..'\u00F6'
+  | '\u00F8'..'\u02FF'
+  | '\u0370'..'\u037D'
+  | '\u037F'..'\u1FFF'
+  | '\u200C'..'\u200D'
+  | '\u2070'..'\u218F'
+  | '\u2C00'..'\u2FEF'
+  | '\u3001'..'\uD7FF'
+  | '\uF900'..'\uFDCF'
+  | '\uFDF0'..'\uFFFD'
+  | '\u{10000}'..'\u{EFFFF}'
+  ;
+fragment FragNCNameChar
+  :  FragNCNameStartChar | '-' | '.' | '0'..'9'
+  |  '\u00B7' | '\u0300'..'\u036F'
+  |  '\u203F'..'\u2040'
+  ;
+fragment FragmentNCName : FragNCNameStartChar FragNCNameChar*  ;
+
+// https://www.w3.org/TR/REC-xml/#NT-Char
+
+fragment FragChar : '\u0009' | '\u000a' | '\u000d'
+  | '\u0020'..'\ud7ff'
+  | '\ue000'..'\ufffd'
+  | '\u{10000}'..'\u{10ffff}'
+ ;
+
+// Skip all Whitespace
+Whitespace : ('\u000d' | '\u000a' | '\u0020' | '\u0009')+ -> skip ;
+
+// handle characters which failed to match any other token (otherwise Antlr will ignore them)
+ErrorCharacter : . ;
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
new file mode 100644 (file)
index 0000000..83e076d
--- /dev/null
@@ -0,0 +1,107 @@
+package org.onap.cps.cpspath.parser;
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 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=========================================================
+ */
+
+import java.util.HashMap;
+import java.util.Map;
+import org.onap.cps.cpspath.parser.antlr4.CpsPathBaseListener;
+import org.onap.cps.cpspath.parser.antlr4.CpsPathParser.AncestorAxisContext;
+import org.onap.cps.cpspath.parser.antlr4.CpsPathParser.CpsPathWithDescendantAndLeafConditionsContext;
+import org.onap.cps.cpspath.parser.antlr4.CpsPathParser.CpsPathWithDescendantContext;
+import org.onap.cps.cpspath.parser.antlr4.CpsPathParser.CpsPathWithSingleLeafConditionContext;
+import org.onap.cps.cpspath.parser.antlr4.CpsPathParser.LeafConditionContext;
+import org.onap.cps.cpspath.parser.antlr4.CpsPathParser.MultipleValueConditionsContext;
+import org.onap.cps.cpspath.parser.antlr4.CpsPathParser.PrefixContext;
+import org.onap.cps.cpspath.parser.antlr4.CpsPathParser.SingleValueConditionContext;
+
+public class CpsPathBuilder extends CpsPathBaseListener {
+
+    final CpsPathQuery cpsPathQuery = new CpsPathQuery();
+
+    final Map<String, Object> leavesData = new HashMap<>();
+
+    @Override
+    public void exitPrefix(final PrefixContext ctx) {
+        cpsPathQuery.setXpathPrefix(ctx.getText());
+    }
+
+    @Override
+    public void exitLeafCondition(final LeafConditionContext ctx) {
+        Object comparisonValue = null;
+        if (ctx.IntegerLiteral() != null) {
+            comparisonValue = Integer.valueOf(ctx.IntegerLiteral().getText());
+        }
+        if (ctx.StringLiteral() != null) {
+            comparisonValue = stripFirstAndLastCharacter(ctx.StringLiteral().getText());
+        } else if (comparisonValue == null) {
+            throw new IllegalStateException("Unsupported comparison value encountered in expression" + ctx.getText());
+        }
+        leavesData.put(ctx.leafName().getText(), comparisonValue);
+    }
+
+    @Override
+    public void enterSingleValueCondition(final SingleValueConditionContext ctx) {
+        leavesData.clear();
+    }
+
+    @Override
+    public void enterMultipleValueConditions(final MultipleValueConditionsContext ctx) {
+        leavesData.clear();
+    }
+
+    @Override
+    public void exitSingleValueCondition(final SingleValueConditionContext ctx) {
+        final String leafName = ctx.leafCondition().leafName().getText();
+        cpsPathQuery.setLeafName(leafName);
+        cpsPathQuery.setLeafValue(leavesData.get(leafName));
+    }
+
+    @Override
+    public void exitCpsPathWithSingleLeafCondition(final CpsPathWithSingleLeafConditionContext ctx) {
+        cpsPathQuery.setCpsPathQueryType(CpsPathQueryType.XPATH_LEAF_VALUE);
+    }
+
+    @Override
+    public void exitCpsPathWithDescendant(final CpsPathWithDescendantContext ctx) {
+        cpsPathQuery.setCpsPathQueryType(CpsPathQueryType.XPATH_HAS_DESCENDANT_ANYWHERE);
+        cpsPathQuery.setDescendantName(cpsPathQuery.getXpathPrefix().substring(1));
+    }
+
+    @Override
+    public void exitCpsPathWithDescendantAndLeafConditions(
+        final CpsPathWithDescendantAndLeafConditionsContext ctx) {
+        cpsPathQuery.setCpsPathQueryType(CpsPathQueryType.XPATH_HAS_DESCENDANT_WITH_LEAF_VALUES);
+        cpsPathQuery.setDescendantName(cpsPathQuery.getXpathPrefix().substring(1));
+        cpsPathQuery.setLeavesData(leavesData);
+    }
+
+    @Override
+    public void exitAncestorAxis(final AncestorAxisContext ctx) {
+        cpsPathQuery.setAncestorSchemaNodeIdentifier(ctx.ancestorPath().getText());
+    }
+
+    CpsPathQuery build() {
+        return cpsPathQuery;
+    }
+
+    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/CpsPathQuery.java b/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathQuery.java
new file mode 100644 (file)
index 0000000..32fe0cb
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 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=========================================================
+ */
+
+package org.onap.cps.cpspath.parser;
+
+
+import java.util.Map;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.Setter;
+import org.antlr.v4.runtime.BaseErrorListener;
+import org.antlr.v4.runtime.CharStreams;
+import org.antlr.v4.runtime.CommonTokenStream;
+import org.antlr.v4.runtime.RecognitionException;
+import org.antlr.v4.runtime.Recognizer;
+import org.onap.cps.cpspath.parser.antlr4.CpsPathLexer;
+import org.onap.cps.cpspath.parser.antlr4.CpsPathParser;
+
+@Getter
+@Setter(AccessLevel.PACKAGE)
+public class CpsPathQuery {
+
+    private CpsPathQueryType cpsPathQueryType;
+    private String xpathPrefix;
+    private String leafName;
+    private Object leafValue;
+    private String descendantName;
+    private Map<String, Object> leavesData;
+    private String ancestorSchemaNodeIdentifier = "";
+
+    /**
+     * Returns a cps path query.
+     *
+     * @param cpsPathSource cps path
+     * @return a CpsPathQuery object.
+     */
+    public static CpsPathQuery createFrom(final String cpsPathSource) {
+        final var inputStream = CharStreams.fromString(cpsPathSource);
+        final var cpsPathLexer = new CpsPathLexer(inputStream);
+        final var cpsPathParser = new CpsPathParser(new CommonTokenStream(cpsPathLexer));
+        cpsPathParser.addErrorListener(new BaseErrorListener() {
+            @Override
+            public void syntaxError(final Recognizer<?, ?> recognizer, final Object offendingSymbol, final int line,
+                                    final int charPositionInLine, final String msg, final RecognitionException e) {
+                throw new IllegalStateException("failed to parse at line " + line + " due to " + msg, e);
+            }
+        });
+        final var cpsPathBuilder = new CpsPathBuilder();
+        cpsPathParser.addParseListener(cpsPathBuilder);
+        cpsPathParser.cpsPath();
+        return cpsPathBuilder.build();
+    }
+
+    /**
+     * Has ancestor axis been populated.
+     *
+     * @return boolean value.
+     */
+    public boolean hasAncestorAxis() {
+        return !(ancestorSchemaNodeIdentifier.isEmpty());
+    }
+
+}
@@ -18,7 +18,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.spi.query;
+package org.onap.cps.cpspath.parser;
 
 /**
  * The enum Cps path query type.
@@ -29,11 +29,11 @@ public enum CpsPathQueryType {
      */
     XPATH_HAS_DESCENDANT_ANYWHERE,
     /**
-     * Xpath descendant anywhere type e.g. //nodeName[@leafName=leafValue] .
+     * Xpath descendant anywhere type e.g. //nodeName[@leafName="value"] .
      */
     XPATH_HAS_DESCENDANT_WITH_LEAF_VALUES,
     /**
-     * Xpath leaf value cps path query type e.g. /cps-path[@leafName=leafValue] .
+     * Xpath leaf value cps path query type e.g. /cps-path[@leaf1="leafValue" and @leaf2=123] .
      */
     XPATH_LEAF_VALUE
 }
@@ -1,7 +1,6 @@
 /*
  * ============LICENSE_START=======================================================
  *  Copyright (C) 2021 Nordix Foundation
- *  Modifications Copyright (C) 2020-2021 Bell Canada.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.spi.query
+package org.onap.cps.cpspath.parser
 
-import org.onap.cps.spi.exceptions.CpsPathException
 import spock.lang.Specification
 
 class CpsPathQuerySpec extends Specification {
 
-    def objectUnderTest = new CpsPathQuery()
-
     def 'Parse cps path with valid cps path and a filter with #scenario.'() {
         when: 'the given cps path is parsed'
-            def result = objectUnderTest.createFrom(cpsPath)
+            def result = CpsPathQuery.createFrom(cpsPath)
         then: 'the query has the right type'
             result.cpsPathQueryType == CpsPathQueryType.XPATH_LEAF_VALUE
         and: 'the right query parameters are set'
@@ -37,29 +33,31 @@ class CpsPathQuerySpec extends Specification {
             result.leafName == expectedLeafName
             result.leafValue == 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'
-            'leaf of type Integer' | '/parent/child[@common-leaf-name-int=5]'                 || '/parent/child'     | 'common-leaf-name-int' | 5
-            '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
+            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'
+            'leaf of type String'  | '/parent/child[@common-leaf-name=\'common-leaf-value\']'   || '/parent/child'                             | 'common-leaf-name'     | 'common-leaf-value'
+            'leaf of type Integer' | '/parent/child[@common-leaf-name-int=5]'                   || '/parent/child'                             | 'common-leaf-name-int' | 5
+            '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'
     }
 
     def 'Parse cps path of type ends with a #scenario.'() {
         when: 'the given cps path is parsed'
-            def result = objectUnderTest.createFrom(cpsPath)
+            def result = CpsPathQuery.createFrom(cpsPath)
         then: 'the query has the right type'
             result.cpsPathQueryType == CpsPathQueryType.XPATH_HAS_DESCENDANT_ANYWHERE
         and: 'the right ends with parameters are set'
-            result.descendantName == expectedEndsWithValue
+            result.descendantName == expectedDescendantName
         where: 'the following data is used'
-            scenario         | cpsPath          || expectedEndsWithValue
+            scenario         | cpsPath          || expectedDescendantName
             'yang container' | '//cps-path'     || 'cps-path'
             'parent & child' | '//parent/child' || 'parent/child'
     }
 
     def 'Parse cps path that ends with a yang list containing #scenario.'() {
         when: 'the given cps path is parsed'
-            def result = objectUnderTest.createFrom(cpsPath)
+            def result = CpsPathQuery.createFrom(cpsPath)
         then: 'the query has the right type'
             result.cpsPathQueryType == CpsPathQueryType.XPATH_HAS_DESCENDANT_WITH_LEAF_VALUES
         and: 'the right ends with parameters are set'
@@ -71,11 +69,11 @@ class CpsPathQuerySpec extends Specification {
             'more than one attribute' | '//child[@int-leaf=5 and @leaf-name="leaf value"]' || 2
     }
 
-    def 'Parse cps path with #scenario.'() {
+    def 'Parse cps path with error: #scenario.'() {
         when: 'the given cps path is parsed'
-            objectUnderTest.createFrom(cpsPath)
+            CpsPathQuery.createFrom(cpsPath)
         then: 'a CpsPathException is thrown'
-            thrown(CpsPathException)
+            thrown(IllegalStateException)
         where: 'the following data is used'
             scenario                                                            | cpsPath
             'no / at the start'                                                 | 'invalid-cps-path/child'
@@ -83,34 +81,44 @@ class CpsPathQuerySpec extends Specification {
             'float value'                                                       | '/parent/child[@someFloat=5.0]'
             'unmatched quotes, double quote first '                             | '/parent/child[@someString="value with unmatched quotes\']'
             'unmatched quotes, single quote first'                              | '/parent/child[@someString=\'value with unmatched quotes"]'
-            'too many containers'                                               | '/1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/31/32/33/34/35/36/37/38/39/40/41/42/43/44/45/46/47/48/49/50/51/52/53/54/55/56/57/58/59/60/61/62/63/64/65/66/67/68/69/70/71/72/73/74/75/76/77/78/79/80/81/82/83/84/85/86/87/88/89/90/91/92/93/94/95/96/97/98/99/100[@a=1]'
             'end with descendant and more than one attribute separated by "or"' | '//child[@int-leaf=5 or @leaf-name="leaf value"]'
             'missing attribute value'                                           | '//child[@int-leaf=5 and @name]'
             'incomplete ancestor value'                                         | '//books/ancestor::'
     }
 
-    def 'Convert cps leaf value to valid type with leaf of type #scenario.'() {
-        when: 'the given leaf value is converted'
-            def result = objectUnderTest.convertLeafValueToCorrectType(leafValueInputString, 'source xPath (for error message only)')
-        then: 'the leaf value returned is of the right type'
-            result == expectedLeafOutputValue
-        where: "the following data is used"
-            scenario                         | leafValueInputString         || expectedLeafOutputValue
-            'Integer'                        | "5"                          || 5
-            'String with single quotes'      | '\'value in single quotes\'' || 'value in single quotes'
-            'String with double quotes'      | '"value in double quotes"'   || 'value in double quotes'
-            'String containing single quote' | '"value with \'"'            || 'value with \''
-            'String containing double quote' | '\'value with "\''           || 'value with "'
-    }
-
-    def 'Parse cps path using ancestor by schema node identifier.'() {
+    def 'Parse cps path using ancestor by schema node identifier with a #scenario.'() {
         when: 'the given cps path is parsed'
-            def result = objectUnderTest.createFrom('//someXpath/ancestor::someAncestor')
+            def result = CpsPathQuery.createFrom('//descendant/ancestor::' + ancestorPath)
         then: 'the query has the right type'
             result.cpsPathQueryType == CpsPathQueryType.XPATH_HAS_DESCENDANT_ANYWHERE
+        and: 'the result has ancestor axis'
+            result.hasAncestorAxis()
         and: 'the correct ancestor schema node identifier is set'
-            result.ancestorSchemaNodeIdentifier == 'someAncestor'
+            result.ancestorSchemaNodeIdentifier == ancestorPath
+        where:
+            scenario                  | ancestorPath
+            'basic container'         | 'someContainer'
+            'container with parent'   | 'parent/child'
+            'ancestor that is a list' | 'categories[@code=1]'
+            'parent that is a list'   | 'parent[@id=1]/child'
+    }
+
+    def 'Combinations #scenario.'() {
+        when: 'the given cps path is parsed'
+            def result = CpsPathQuery.createFrom(cpsPath + '/ancestor::someAncestor')
+        then: 'the query has the right type'
+            result.cpsPathQueryType == expectedCpsPathQueryType
         and: 'the result has ancestor axis'
             result.hasAncestorAxis()
+        and: 'the correct ancestor schema node identifier is set'
+            result.ancestorSchemaNodeIdentifier == 'someAncestor'
+            result.descendantName == expectedDescendantName
+        where:
+            scenario                     | cpsPath                               || expectedDescendantName | expectedCpsPathQueryType
+            'basic container'            | '//someContainer'                     || 'someContainer'        | CpsPathQueryType.XPATH_HAS_DESCENDANT_ANYWHERE
+            'container with parent'      | '//parent/child'                      || 'parent/child'         | CpsPathQueryType.XPATH_HAS_DESCENDANT_ANYWHERE
+            'container with list-parent' | '//parent[@id=1]/child'               || 'parent[@id=1]/child'  | CpsPathQueryType.XPATH_HAS_DESCENDANT_ANYWHERE
+            'container with list-parent' | '//parent[@id=1]/child[@name="test"]' || 'parent[@id=1]/child'  | CpsPathQueryType.XPATH_HAS_DESCENDANT_WITH_LEAF_VALUES
     }
+
 }
index b487290..7da3df2 100644 (file)
 
 package org.onap.cps.rest.exceptions
 
-import static org.springframework.http.HttpStatus.BAD_REQUEST
-import static org.springframework.http.HttpStatus.CONFLICT
-import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR
-import static org.springframework.http.HttpStatus.NOT_FOUND
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
-
 import groovy.json.JsonSlurper
 import org.modelmapper.ModelMapper
 import org.onap.cps.api.CpsAdminService
@@ -52,6 +45,13 @@ import org.springframework.test.web.servlet.MockMvc
 import spock.lang.Shared
 import spock.lang.Specification
 
+import static org.springframework.http.HttpStatus.BAD_REQUEST
+import static org.springframework.http.HttpStatus.CONFLICT
+import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR
+import static org.springframework.http.HttpStatus.NOT_FOUND
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
+
 @WebMvcTest
 class CpsRestExceptionHandlerSpec extends Specification {
 
@@ -128,11 +128,12 @@ class CpsRestExceptionHandlerSpec extends Specification {
             setupTestException(exceptionThrown)
             def response = performTestRequest()
         then: 'an HTTP Bad Request response is returned with correct message and details'
-            assertTestResponse(response, BAD_REQUEST, errorMessage, errorDetails)
+            assertTestResponse(response, BAD_REQUEST, expectedErrorMessage, expectedErrorDetails)
         where: 'the following exceptions are thrown'
-            exceptionThrown << [new ModelValidationException(errorMessage, errorDetails, null),
-                                new DataValidationException(errorMessage, errorDetails, null),
-                                new CpsPathException(errorMessage, errorDetails)]
+            exceptionThrown                                                || expectedErrorMessage           | expectedErrorDetails
+            new ModelValidationException(errorMessage, errorDetails, null) || errorMessage                   | errorDetails
+            new DataValidationException(errorMessage, errorDetails, null)  || errorMessage                   | errorDetails
+            new CpsPathException(errorDetails)                             || CpsPathException.ERROR_MESSAGE | errorDetails
     }
 
     def 'Delete request with a #exceptionThrown.class.simpleName returns HTTP Status Conflict'() {
index 94d2fa1..14e720e 100644 (file)
             <groupId>${project.groupId}</groupId>\r
             <artifactId>cps-service</artifactId>\r
         </dependency>\r
+        <dependency>\r
+            <groupId>${project.groupId}</groupId>\r
+            <artifactId>cps-path-parser</artifactId>\r
+            <version>${project.version}</version>\r
+        </dependency>\r
         <dependency>\r
             <groupId>org.springframework.boot</groupId>\r
             <artifactId>spring-boot-starter-data-jpa</artifactId>\r
index 1044092..af1eca3 100644 (file)
@@ -36,16 +36,17 @@ import java.util.Set;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 import javax.transaction.Transactional;
+import org.onap.cps.cpspath.parser.CpsPathQuery;
+import org.onap.cps.cpspath.parser.CpsPathQueryType;
 import org.onap.cps.spi.CpsDataPersistenceService;
 import org.onap.cps.spi.FetchDescendantsOption;
 import org.onap.cps.spi.entities.AnchorEntity;
 import org.onap.cps.spi.entities.DataspaceEntity;
 import org.onap.cps.spi.entities.FragmentEntity;
 import org.onap.cps.spi.exceptions.AlreadyDefinedException;
+import org.onap.cps.spi.exceptions.CpsPathException;
 import org.onap.cps.spi.model.DataNode;
 import org.onap.cps.spi.model.DataNodeBuilder;
-import org.onap.cps.spi.query.CpsPathQuery;
-import org.onap.cps.spi.query.CpsPathQueryType;
 import org.onap.cps.spi.repository.AnchorRepository;
 import org.onap.cps.spi.repository.DataspaceRepository;
 import org.onap.cps.spi.repository.FragmentRepository;
@@ -171,7 +172,12 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
         final FetchDescendantsOption fetchDescendantsOption) {
         final var dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
         final var anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName);
-        final var cpsPathQuery = CpsPathQuery.createFrom(cpsPath);
+        final CpsPathQuery cpsPathQuery;
+        try {
+            cpsPathQuery = CpsPathQuery.createFrom(cpsPath);
+        } catch (final IllegalStateException e) {
+            throw new CpsPathException(e.getMessage());
+        }
         List<FragmentEntity> fragmentEntities;
         if (CpsPathQueryType.XPATH_LEAF_VALUE.equals(cpsPathQuery.getCpsPathQueryType())) {
             fragmentEntities = fragmentRepository
@@ -283,7 +289,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
         fragmentRepository.save(fragmentEntity);
     }
 
-    private boolean isRootXpath(final String xpath) {
+    private static boolean isRootXpath(final String xpath) {
         return "/".equals(xpath) || "".equals(xpath);
     }
 }
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/query/CpsPathQuery.java b/cps-ri/src/main/java/org/onap/cps/spi/query/CpsPathQuery.java
deleted file mode 100644 (file)
index 401667e..0000000
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * ============LICENSE_START=======================================================
- *  Copyright (C) 2021 Nordix Foundation
- *  Modifications Copyright (C) 2021 Bell Canada.
- *  ================================================================================
- *  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.spi.query;
-
-import static org.springframework.util.StringUtils.isEmpty;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import lombok.AccessLevel;
-import lombok.Getter;
-import lombok.Setter;
-import org.onap.cps.spi.exceptions.CpsPathException;
-
-@Getter
-@Setter(AccessLevel.PRIVATE)
-public class CpsPathQuery {
-
-    private CpsPathQueryType cpsPathQueryType;
-    private String xpathPrefix;
-    private String leafName;
-    private Object leafValue;
-    private String descendantName;
-    private Map<String, Object> leavesData;
-    private String ancestorSchemaNodeIdentifier;
-
-    private static final String NON_CAPTURING_GROUP_1_TO_99_YANG_CONTAINERS = "((?:\\/[^\\/]+){1,99})";
-
-    private static final String YANG_LEAF_VALUE_EQUALS_CONDITION =
-        "\\[\\s{0,9}@(\\S+?)\\s{0,9}=\\s{0,9}(.*?)\\s{0,9}\\]";
-
-    private static final Pattern QUERY_CPS_PATH_WITH_SINGLE_LEAF_PATTERN =
-        Pattern.compile(NON_CAPTURING_GROUP_1_TO_99_YANG_CONTAINERS + YANG_LEAF_VALUE_EQUALS_CONDITION);
-
-    private static final Pattern DESCENDANT_ANYWHERE_PATTERN = Pattern.compile("\\/\\/([^\\/][^:]+)");
-
-    private static final Pattern LEAF_INTEGER_VALUE_PATTERN = Pattern.compile("[-+]?\\d+");
-
-    private static final Pattern LEAF_STRING_VALUE_IN_SINGLE_QUOTES_PATTERN = Pattern.compile("'(.*)'");
-    private static final Pattern LEAF_STRING_VALUE_IN_DOUBLE_QUOTES_PATTERN = Pattern.compile("\"(.*)\"");
-
-    private static final String YANG_MULTIPLE_LEAF_VALUE_EQUALS_CONDITION = "\\[(.*?)\\s{0,9}]";
-
-    private static final Pattern DESCENDANT_ANYWHERE_PATTERN_WITH_MULTIPLE_LEAF_PATTERN =
-        Pattern.compile(DESCENDANT_ANYWHERE_PATTERN + YANG_MULTIPLE_LEAF_VALUE_EQUALS_CONDITION);
-
-    private static final String INDIVIDUAL_LEAF_DETAIL_PATTERN = ("\\s{1,9}and\\s{1,9}");
-
-    private static final Pattern LEAF_VALUE_PATTERN = Pattern.compile("@(?>(\\S+?)=(.*))");
-
-    private static final Pattern ANCESTOR_AXIS_PATTERN = Pattern.compile("(?>(\\S+)\\/ancestor::\\/?(\\S+))");
-
-    /**
-     * Returns a cps path query.
-     *
-     * @param cpsPathSource cps path
-     * @return a CpsPath object.
-     */
-    public static CpsPathQuery createFrom(final String cpsPathSource) {
-        var cpsPath = cpsPathSource;
-        final var cpsPathQuery = new CpsPathQuery();
-        var matcher = ANCESTOR_AXIS_PATTERN.matcher(cpsPath);
-        if (matcher.matches()) {
-            cpsPath = matcher.group(1);
-            cpsPathQuery.setAncestorSchemaNodeIdentifier(matcher.group(2));
-        }
-        matcher = QUERY_CPS_PATH_WITH_SINGLE_LEAF_PATTERN.matcher(cpsPath);
-        if (matcher.matches()) {
-            cpsPathQuery.setParametersForSingleLeafValue(cpsPath, matcher);
-            return cpsPathQuery;
-        }
-        matcher = DESCENDANT_ANYWHERE_PATTERN_WITH_MULTIPLE_LEAF_PATTERN.matcher(cpsPath);
-        if (matcher.matches()) {
-            cpsPathQuery.setParametersForDescendantWithLeafValues(cpsPath, matcher);
-            return cpsPathQuery;
-        }
-        matcher = DESCENDANT_ANYWHERE_PATTERN.matcher(cpsPath);
-        if (matcher.matches()) {
-            cpsPathQuery.setParametersForDescendantAnywhere(matcher);
-            return cpsPathQuery;
-        }
-        throw new CpsPathException("Invalid cps path.",
-            String.format("Cannot interpret or parse cps path '%s'.", cpsPath));
-    }
-
-    /**
-     * Has ancestor axis been populated.
-     *
-     * @return boolean value.
-     */
-    public boolean hasAncestorAxis() {
-        return !(isEmpty(ancestorSchemaNodeIdentifier));
-    }
-
-    private void setParametersForSingleLeafValue(final String cpsPath, final Matcher matcher) {
-        setCpsPathQueryType(CpsPathQueryType.XPATH_LEAF_VALUE);
-        setXpathPrefix(matcher.group(1));
-        setLeafName(matcher.group(2));
-        setLeafValue(convertLeafValueToCorrectType(matcher.group(3), cpsPath));
-    }
-
-    private void setParametersForDescendantWithLeafValues(final String cpsPath, final Matcher matcher) {
-        setCpsPathQueryType(CpsPathQueryType.XPATH_HAS_DESCENDANT_WITH_LEAF_VALUES);
-        setDescendantName(matcher.group(1));
-        final Map<String, Object> leafData = new HashMap<>();
-        for (final String leafValuePair : matcher.group(2).split(INDIVIDUAL_LEAF_DETAIL_PATTERN)) {
-            final var descendentMatcher = LEAF_VALUE_PATTERN.matcher(leafValuePair);
-            if (descendentMatcher.matches()) {
-                leafData.put(descendentMatcher.group(1),
-                    convertLeafValueToCorrectType(descendentMatcher.group(2), cpsPath));
-            } else {
-                throw new CpsPathException("Invalid cps path.",
-                    String.format("Cannot interpret or parse attributes in cps path '%s'.", cpsPath));
-            }
-        }
-        setLeavesData(leafData);
-    }
-
-    private void setParametersForDescendantAnywhere(final Matcher matcher) {
-        setCpsPathQueryType(CpsPathQueryType.XPATH_HAS_DESCENDANT_ANYWHERE);
-        setDescendantName(matcher.group(1));
-    }
-
-    private static Object convertLeafValueToCorrectType(final String leafValueString, final String cpsPath) {
-        final var stringValueWithSingleQuotesMatcher =
-                LEAF_STRING_VALUE_IN_SINGLE_QUOTES_PATTERN.matcher(leafValueString);
-        if (stringValueWithSingleQuotesMatcher.matches()) {
-            return stringValueWithSingleQuotesMatcher.group(1);
-        }
-        final var stringValueWithDoubleQuotesMatcher =
-                LEAF_STRING_VALUE_IN_DOUBLE_QUOTES_PATTERN.matcher(leafValueString);
-        if (stringValueWithDoubleQuotesMatcher.matches()) {
-            return stringValueWithDoubleQuotesMatcher.group(1);
-        }
-        final var integerValueMatcher = LEAF_INTEGER_VALUE_PATTERN.matcher(leafValueString);
-        if (integerValueMatcher.matches()) {
-            return Integer.valueOf(leafValueString);
-        }
-        throw new CpsPathException("Unsupported leaf value.",
-            String.format("Unsupported leaf value '%s' in cps path '%s'.", leafValueString, cpsPath));
-    }
-}
index f64a22c..b2477d4 100644 (file)
@@ -128,18 +128,6 @@ class CpsDataPersistenceQueryDataNodeSpec extends CpsPersistenceSpecBase {
             'mix of partial key and non key leaf' | '//author[@FirstName="Joe" and @title="Dune"]' || ['/shops/shop[@id=1]/categories[@code=1]/book/author[@FirstName="Joe" and @Surname="Bloggs"]']
     }
 
-    @Sql([CLEAR_DATA, SET_DATA])
-    def 'Cps Path query error scenario using descendant anywhere ends with yang list containing %scenario '() {
-        when: 'a query is executed to get a data node by the given cps path'
-            objectUnderTest.queryDataNodes(DATASPACE_NAME, ANCHOR_FOR_SHOP_EXAMPLE, cpsPath, OMIT_DESCENDANTS)
-        then: 'exception is thrown'
-            thrown(CpsPathException)
-        where: 'the following data is used'
-            scenario                             | cpsPath
-            'one of the leaf without value'      | '//categories[@code=1 and @name=]'
-            'more than one leaf separated by or' | '//categories[@code=1 or @name="SciFi"]'
-    }
-
     @Sql([CLEAR_DATA, SET_DATA])
     def 'Query for attribute by cps path of type ancestor with #scenario.'() {
         when: 'the given cps path is parsed'
@@ -157,7 +145,15 @@ class CpsDataPersistenceQueryDataNodeSpec extends CpsPersistenceSpecBase {
             'list with index value in the xpath prefix' | '//categories[@code=1]/book/ancestor::shop[@id=1]'   || ['/shops/shop[@id=1]']
             'ancestor with parent list'                 | '//book/ancestor::shop[@id=1]/categories[@code=2]'   || ['/shops/shop[@id=1]/categories[@code=2]']
             'ancestor with parent'                      | '//phonenumbers[@type="mob"]/ancestor::info/contact' || ['/shops/shop[@id=3]/info/contact']
-            'ancestor with parent that does not exist'  | '//book/ancestor::/parentDoesNoExist/categories'     || []
+            'ancestor with parent that does not exist'  | '//book/ancestor::parentDoesNoExist/categories'      || []
             'ancestor does not exist'                   | '//book/ancestor::ancestorDoesNotExist'              || []
     }
+
+    def 'Cps Path query with syntax error throws a CPS Path Exception.'() {
+        when: 'trying to execute a query with a syntax (parsing) error'
+            objectUnderTest.queryDataNodes(DATASPACE_NAME, ANCHOR_FOR_SHOP_EXAMPLE, 'cpsPath that cannot be parsed' , OMIT_DESCENDANTS)
+        then: 'exception is thrown'
+            thrown(CpsPathException)
+    }
+
 }
index fde5566..f1e8dc5 100644 (file)
@@ -23,13 +23,14 @@ public class CpsPathException extends CpsException {
 
     private static final long serialVersionUID = 1006899957127327791L;
 
+    private static final String ERROR_MESSAGE = "Error while parsing cpsPath expression";
+
     /**
      * Constructor.
      *
-     * @param message the error message
      * @param details the error details
      */
-    public CpsPathException(final String message, final String details) {
-        super(message, details);
+    public CpsPathException(final String details) {
+        super(ERROR_MESSAGE, details);
     }
 }
index 8592af9..b0bd629 100755 (executable)
@@ -115,8 +115,8 @@ class CpsExceptionsSpec extends Specification {
     def 'Creating an exception that the schema set being used and cannot be deleted.'() {
         expect: 'the exception details contains the correct message with dataspace and schema set names'
             (new SchemaSetInUseException(dataspaceName, schemaSetName)).details
-                    == ("Schema Set with name ${schemaSetName} in dataspace ${dataspaceName} is having "
-                    + "Anchor records associated.")
+                    == ("Schema Set with name ${schemaSetName} in dataspace ${dataspaceName} is having" +
+                Anchor records associated.")
     }
 
     def 'Creating a exception that a datanode with a specified xpath does not exist.'() {
@@ -157,10 +157,8 @@ class CpsExceptionsSpec extends Specification {
 
     def 'Creating a cps path exception.'() {
         given: 'a cps path exception is created'
-            def exception = new CpsPathException(providedMessage, providedDetails)
-        expect: 'the exception has the provided message'
-            exception.message == providedMessage
-        and: 'the exception has the provided details'
+            def exception = new CpsPathException(providedDetails)
+        expect: 'the exception has the provided details'
             exception.details == providedDetails
     }
-}
\ No newline at end of file
+}
diff --git a/pom.xml b/pom.xml
index f196cba..b0f549b 100644 (file)
--- a/pom.xml
+++ b/pom.xml
@@ -54,6 +54,7 @@
         <module>cps-rest</module>\r
         <module>cps-ncmp-service</module>\r
         <module>cps-ncmp-rest</module>\r
+        <module>cps-path-parser</module>\r
         <module>cps-ri</module>\r
         <module>checkstyle</module>\r
         <module>spotbugs</module>\r
index c46270c..7fdda73 100644 (file)
@@ -1,4 +1,8 @@
 <FindBugsFilter>
+  <Match>
+    <!-- Ignore generated code from Antlr4 -->
+    <Package name="org.onap.cps.cpspath.parser.antlr4" />
+  </Match>
   <Match>
     <Or>
       <!-- Anonymous inner classes are very common. -->