Ensure Leaf value retains Integer type 82/123682/9
authorlukegleeson <luke.gleeson@est.tech>
Mon, 30 Aug 2021 09:24:30 +0000 (10:24 +0100)
committerlukegleeson <luke.gleeson@est.tech>
Fri, 3 Sep 2021 08:42:02 +0000 (09:42 +0100)
BUG
GSON.fromJson() is unable to parse numerical values and defaults values to Doubles
Added a datatype conversion which forces Double values which can be Integers to being Integers

Issue-ID: CPS-591
Signed-off-by: lukegleeson <luke.gleeson@est.tech>
Change-Id: I72d54ad06823a8755ee407f39104f3edf9a8cc75

cps-ri/pom.xml
cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java
cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy

index 273cb2e..9072a52 100644 (file)
             <groupId>org.apache.commons</groupId>\r
             <artifactId>commons-lang3</artifactId>\r
         </dependency>\r
+        <dependency>\r
+            <groupId>com.fasterxml.jackson.core</groupId>\r
+            <artifactId>jackson-databind</artifactId>\r
+        </dependency>\r
         <!-- T E S T   D E P E N D E N C I E S -->\r
         <dependency>\r
             <groupId>org.codehaus.groovy</groupId>\r
         </dependency>\r
     </dependencies>\r
 \r
+\r
     <build>\r
         <plugins>\r
             <plugin>\r
index af010f4..7b3b726 100644 (file)
@@ -24,12 +24,15 @@ package org.onap.cps.spi.impl;
 
 import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS;
 
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSet.Builder;
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -50,6 +53,7 @@ import org.onap.cps.spi.exceptions.AlreadyDefinedException;
 import org.onap.cps.spi.exceptions.ConcurrencyException;
 import org.onap.cps.spi.exceptions.CpsPathException;
 import org.onap.cps.spi.exceptions.DataNodeNotFoundException;
+import org.onap.cps.spi.exceptions.DataValidationException;
 import org.onap.cps.spi.model.DataNode;
 import org.onap.cps.spi.model.DataNodeBuilder;
 import org.onap.cps.spi.repository.AnchorRepository;
@@ -68,6 +72,8 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
 
     private FragmentRepository fragmentRepository;
 
+    private final ObjectMapper objectMapper;
+
     /**
      * Constructor.
      *
@@ -80,6 +86,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
         this.dataspaceRepository = dataspaceRepository;
         this.anchorRepository = anchorRepository;
         this.fragmentRepository = fragmentRepository;
+        this.objectMapper = new ObjectMapper();
     }
 
     private static final Gson GSON = new GsonBuilder().create();
@@ -236,17 +243,27 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
         return ancestorXpath;
     }
 
-    private static DataNode toDataNode(final FragmentEntity fragmentEntity,
+    private DataNode toDataNode(final FragmentEntity fragmentEntity,
         final FetchDescendantsOption fetchDescendantsOption) {
-        final Map<String, Object> leaves = GSON.fromJson(fragmentEntity.getAttributes(), Map.class);
         final List<DataNode> childDataNodes = getChildDataNodes(fragmentEntity, fetchDescendantsOption);
+        Map<String, Object> leaves = new HashMap<>();
+        if (fragmentEntity.getAttributes() != null) {
+            try {
+                leaves = objectMapper.readValue(fragmentEntity.getAttributes(), Map.class);
+            } catch (final JsonProcessingException jsonProcessingException) {
+                final String message = "Parsing error occurred while processing fragmentEntity attributes.";
+                log.error(message);
+                throw new DataValidationException(message,
+                    jsonProcessingException.getMessage(), jsonProcessingException);
+            }
+        }
         return new DataNodeBuilder()
             .withXpath(fragmentEntity.getXpath())
             .withLeaves(leaves)
             .withChildDataNodes(childDataNodes).build();
     }
 
-    private static List<DataNode> getChildDataNodes(final FragmentEntity fragmentEntity,
+    private List<DataNode> getChildDataNodes(final FragmentEntity fragmentEntity,
         final FetchDescendantsOption fetchDescendantsOption) {
         if (fetchDescendantsOption == INCLUDE_ALL_DESCENDANTS) {
             return fragmentEntity.getChildFragments().stream()
index 5ed3ae3..9fcd550 100644 (file)
@@ -19,6 +19,7 @@
 package org.onap.cps.spi.impl
 
 import org.hibernate.StaleStateException
+import org.onap.cps.spi.FetchDescendantsOption
 import org.onap.cps.spi.entities.FragmentEntity
 import org.onap.cps.spi.exceptions.ConcurrencyException
 import org.onap.cps.spi.model.DataNodeBuilder
@@ -68,5 +69,30 @@ class CpsDataPersistenceServiceSpec extends Specification {
             assert concurrencyException.getDetails().contains(parentXpath)
     }
 
-
+    def 'Retrieving a data node with a property JSON value of #scenario'() {
+        given: 'a fragment with a property JSON value of #scenario'
+            mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, _) >> {
+                new FragmentEntity(childFragments: Collections.emptySet(),
+                        attributes: "{\"some attribute\": ${dataString}}")
+            }
+        when: 'getting the data node represented by this fragment'
+            def dataNode = objectUnderTest.getDataNode('my-dataspace', 'my-anchor',
+                    'parent-01', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS)
+        then: 'the leaf is of the correct value and data type'
+            def attributeValue = dataNode.leaves.get('some attribute')
+            assert attributeValue == expectedValue
+            assert attributeValue.class == expectedDataClass
+        where: 'the following Data Type is passed'
+            scenario                              | dataString            || expectedValue     | expectedDataClass
+            'just numbers'                        | '15174'               || 15174             | Integer
+            'number with dot'                     | '15174.32'            || 15174.32          | Double
+            'number with 0 value after dot'       | '15174.0'             || 15174.0           | Double
+            'number with 0 value before dot'      | '0.32'                || 0.32              | Double
+            'number higher than max int'          | '2147483648'          || 2147483648        | Long
+            'just text'                           | '"Test"'              || 'Test'            | String
+            'number with exponent'                | '1.2345e5'            || 1.2345e5          | Double
+            'number higher than max int with dot' | '123456789101112.0'   || 123456789101112.0 | Double
+            'text and numbers'                    | '"String = \'1234\'"' || "String = '1234'" | String
+            'number as String'                    | '"12345"'             || '12345'           | String
+    }
 }