Creation of DataNodeBuilder with module name prefix is very slow 93/131493/12
authorToineSiebelink <toine.siebelink@est.tech>
Mon, 24 Oct 2022 13:12:32 +0000 (14:12 +0100)
committerPriyank Maheshwari <priyank.maheshwari@est.tech>
Wed, 26 Oct 2022 13:14:25 +0000 (13:14 +0000)
- Created a new hazelcast distributed cache for anchor data config use cases.
- Module name prefix is added for root data node only.
- Cached module name prefix by anchor name on demand from database at
  first call.
- Introduced a new cache holder to have module name prefix of diff.
  levels.

Issue-ID: CPS-1326
Change-Id: I9072f5efdeea59843cd827ac556d3c0547a3a0cf
Signed-off-by: sourabh_sourabh <sourabh.sourabh@est.tech>
Signed-off-by: ToineSiebelink <toine.siebelink@est.tech>
Signed-off-by: mpriyank <priyank.maheshwari@est.tech>
cps-ri/pom.xml
cps-ri/src/main/java/org/onap/cps/spi/cache/AnchorDataCacheConfig.java [new file with mode: 0644]
cps-ri/src/main/java/org/onap/cps/spi/cache/AnchorDataCacheEntry.java [new file with mode: 0644]
cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java
cps-ri/src/test/groovy/org/onap/cps/spi/cache/AnchorDataCacheConfigSpec.groovy [new file with mode: 0644]
cps-ri/src/test/groovy/org/onap/cps/spi/cache/AnchorDataCacheEntrySpec.groovy [new file with mode: 0644]
cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy
cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsPersistenceSpecBase.groovy
cps-ri/src/test/resources/application.yml
cps-service/src/main/java/org/onap/cps/spi/model/DataNode.java

index 824a8d9..a193baf 100644 (file)
             <groupId>com.fasterxml.jackson.core</groupId>\r
             <artifactId>jackson-databind</artifactId>\r
         </dependency>\r
+        <dependency>\r
+            <groupId>com.hazelcast</groupId>\r
+            <artifactId>hazelcast-spring</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
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/cache/AnchorDataCacheConfig.java b/cps-ri/src/main/java/org/onap/cps/spi/cache/AnchorDataCacheConfig.java
new file mode 100644 (file)
index 0000000..f956120
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * ============LICENSE_START========================================================
+ *  Copyright (C) 2022 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.spi.cache;
+
+import com.hazelcast.config.Config;
+import com.hazelcast.config.MapConfig;
+import com.hazelcast.config.NamedConfig;
+import com.hazelcast.core.Hazelcast;
+import com.hazelcast.core.HazelcastInstance;
+import com.hazelcast.map.IMap;
+import java.util.concurrent.TimeUnit;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Core infrastructure of the hazelcast distributed cache for anchor data config use cases.
+ */
+@Configuration
+public class AnchorDataCacheConfig {
+
+    public static final long ANCHOR_DATA_CACHE_TTL_SECS = TimeUnit.HOURS.toSeconds(1);
+    private static final MapConfig anchorDataCacheMapConfig = createMapConfig("anchorDataCacheMapConfig");
+
+    /**
+     * Distributed instance of anchor data cache that contains module prefix by anchor name as properties.
+     *
+     * @return configured map of anchor data cache
+     */
+    @Bean
+    public IMap<String, AnchorDataCacheEntry> anchorDataCache() {
+        return createHazelcastInstance("hazelCastInstanceCpsRi", anchorDataCacheMapConfig)
+                .getMap("anchorDataCache");
+    }
+
+    private HazelcastInstance createHazelcastInstance(final String hazelcastInstanceName,
+            final NamedConfig namedConfig) {
+        return Hazelcast.newHazelcastInstance(initializeConfig(hazelcastInstanceName, namedConfig));
+    }
+
+    private Config initializeConfig(final String instanceName, final NamedConfig namedConfig) {
+        final Config config = new Config(instanceName);
+        config.addMapConfig((MapConfig) namedConfig);
+        config.setClusterName("cps-ri-caches");
+        return config;
+    }
+
+    private static MapConfig createMapConfig(final String configName) {
+        final MapConfig mapConfig = new MapConfig(configName);
+        mapConfig.setBackupCount(3);
+        mapConfig.setAsyncBackupCount(3);
+        return mapConfig;
+    }
+
+}
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/cache/AnchorDataCacheEntry.java b/cps-ri/src/main/java/org/onap/cps/spi/cache/AnchorDataCacheEntry.java
new file mode 100644 (file)
index 0000000..98f6ec3
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * ============LICENSE_START========================================================
+ *  Copyright (C) 2022 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.spi.cache;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+public class AnchorDataCacheEntry implements Serializable {
+
+    private static final long serialVersionUID = 2111243947810370698L;
+
+    private Map<String, Object> properties = new HashMap<>();
+
+    public Object getProperty(final String propertyName) {
+        return properties.get(propertyName);
+    }
+
+    public void setProperty(final String propertyName, final Object value) {
+        properties.put(propertyName, value);
+    }
+
+    public boolean hasProperty(final String propertyName) {
+        return properties.containsKey(propertyName);
+    }
+}
index ebc851a..06c12a8 100644 (file)
@@ -24,6 +24,7 @@ package org.onap.cps.spi.impl;
 
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSet.Builder;
+import com.hazelcast.map.IMap;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -32,6 +33,7 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.TimeUnit;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
@@ -44,6 +46,8 @@ import org.onap.cps.cpspath.parser.CpsPathUtil;
 import org.onap.cps.cpspath.parser.PathParsingException;
 import org.onap.cps.spi.CpsDataPersistenceService;
 import org.onap.cps.spi.FetchDescendantsOption;
+import org.onap.cps.spi.cache.AnchorDataCacheConfig;
+import org.onap.cps.spi.cache.AnchorDataCacheEntry;
 import org.onap.cps.spi.entities.AnchorEntity;
 import org.onap.cps.spi.entities.DataspaceEntity;
 import org.onap.cps.spi.entities.FragmentEntity;
@@ -73,18 +77,16 @@ import org.springframework.stereotype.Service;
 public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService {
 
     private final DataspaceRepository dataspaceRepository;
-
     private final AnchorRepository anchorRepository;
-
     private final FragmentRepository fragmentRepository;
-
     private final JsonObjectMapper jsonObjectMapper;
-
     private final SessionManager sessionManager;
+    private final IMap<String, AnchorDataCacheEntry> anchorDataCache;
 
     private static final String REG_EX_FOR_OPTIONAL_LIST_INDEX = "(\\[@[\\s\\S]+?]){0,1})";
     private static final Pattern REG_EX_PATTERN_FOR_LIST_ELEMENT_KEY_PREDICATE =
             Pattern.compile("\\[(\\@([^\\/]{0,9999}))\\]$");
+    private static final String TOP_LEVEL_MODULE_PREFIX_PROPERTY_NAME = "topLevelModulePrefix";
 
     @Override
     public void addChildDataNode(final String dataspaceName, final String anchorName, final String parentNodeXpath,
@@ -218,7 +220,9 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
     public DataNode getDataNode(final String dataspaceName, final String anchorName, final String xpath,
                                 final FetchDescendantsOption fetchDescendantsOption) {
         final FragmentEntity fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, xpath);
-        return toDataNode(fragmentEntity, fetchDescendantsOption);
+        final DataNode dataNode = toDataNode(fragmentEntity, fetchDescendantsOption);
+        dataNode.setModuleNamePrefix(getRootModuleNamePrefix(fragmentEntity.getAnchor()));
+        return dataNode;
     }
 
     private FragmentEntity getFragmentByXpath(final String dataspaceName, final String anchorName,
@@ -260,8 +264,11 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
                     : fragmentRepository.findAllByAnchorAndXpathIn(anchorEntity, ancestorXpaths);
         }
         return fragmentEntities.stream()
-                .map(fragmentEntity -> toDataNode(fragmentEntity, fetchDescendantsOption))
-                .collect(Collectors.toUnmodifiableList());
+                .map(fragmentEntity -> {
+                    final DataNode dataNode = toDataNode(fragmentEntity, fetchDescendantsOption);
+                    dataNode.setModuleNamePrefix(getRootModuleNamePrefix(anchorEntity));
+                    return dataNode;
+                }).collect(Collectors.toUnmodifiableList());
     }
 
     @Override
@@ -303,14 +310,23 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
             leaves = jsonObjectMapper.convertJsonString(fragmentEntity.getAttributes(), Map.class);
         }
         return new DataNodeBuilder()
-                .withModuleNamePrefix(getFirstModuleName(fragmentEntity))
                 .withXpath(fragmentEntity.getXpath())
                 .withLeaves(leaves)
                 .withChildDataNodes(childDataNodes).build();
     }
 
-    private String getFirstModuleName(final FragmentEntity fragmentEntity) {
-        final SchemaSetEntity schemaSetEntity = fragmentEntity.getAnchor().getSchemaSet();
+    private String getRootModuleNamePrefix(final AnchorEntity anchorEntity) {
+        final String cachedModuleNamePrefix = getModuleNamePrefixFromCache(anchorEntity);
+        if (cachedModuleNamePrefix != null) {
+            return cachedModuleNamePrefix;
+        }
+        final String moduleNamePrefix = buildSchemaContextAndRetrieveModulePrefix(anchorEntity);
+        cacheModuleNamePrefix(anchorEntity.getName(), moduleNamePrefix);
+        return moduleNamePrefix;
+    }
+
+    private String buildSchemaContextAndRetrieveModulePrefix(final AnchorEntity anchorEntity) {
+        final SchemaSetEntity schemaSetEntity = anchorEntity.getSchemaSet();
         final Map<String, String> yangResourceNameToContent =
                 schemaSetEntity.getYangResources().stream().collect(
                         Collectors.toMap(YangResourceEntity::getFileName, YangResourceEntity::getContent));
@@ -319,6 +335,24 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
         return schemaContext.getModules().iterator().next().getName();
     }
 
+    private void cacheModuleNamePrefix(final String anchorName, final String moduleNamePrefix) {
+        final AnchorDataCacheEntry anchorDataCacheEntry = new AnchorDataCacheEntry();
+        anchorDataCacheEntry.setProperty(TOP_LEVEL_MODULE_PREFIX_PROPERTY_NAME, moduleNamePrefix);
+        if (anchorDataCache.putIfAbsent(anchorName, anchorDataCacheEntry,
+            AnchorDataCacheConfig.ANCHOR_DATA_CACHE_TTL_SECS, TimeUnit.SECONDS) == null) {
+            log.debug("Module name prefix for an anchor  {} is cached", anchorName);
+        }
+    }
+
+    private String getModuleNamePrefixFromCache(final AnchorEntity anchorEntity) {
+        if (anchorDataCache.containsKey(anchorEntity.getName())) {
+            final AnchorDataCacheEntry anchorDataCacheEntry = anchorDataCache.get(anchorEntity.getName());
+            return anchorDataCacheEntry.hasProperty(TOP_LEVEL_MODULE_PREFIX_PROPERTY_NAME)
+                    ? anchorDataCacheEntry.getProperty(TOP_LEVEL_MODULE_PREFIX_PROPERTY_NAME).toString() : null;
+        }
+        return null;
+    }
+
     private List<DataNode> getChildDataNodes(final FragmentEntity fragmentEntity,
                                              final FetchDescendantsOption fetchDescendantsOption) {
         if (fetchDescendantsOption.hasNext()) {
diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/cache/AnchorDataCacheConfigSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/cache/AnchorDataCacheConfigSpec.groovy
new file mode 100644 (file)
index 0000000..a77db1b
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2022 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.spi.cache
+import com.hazelcast.core.Hazelcast
+import com.hazelcast.map.IMap
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.context.SpringBootTest
+import org.springframework.test.context.ContextConfiguration
+import spock.lang.Specification
+
+@SpringBootTest
+@ContextConfiguration(classes = [AnchorDataCacheConfig])
+class AnchorDataCacheConfigSpec extends Specification {
+
+    @Autowired
+    private IMap<String, AnchorDataCacheEntry> anchorDataCache
+
+    def 'Embedded (hazelcast) cache for Anchor Data.'() {
+        expect: 'system is able to create an instance of the Anchor data cache'
+            assert null != anchorDataCache
+        and: 'there is at least 1 instance'
+            assert Hazelcast.allHazelcastInstances.size() > 0
+        and: 'anchorDataCache is present'
+            assert Hazelcast.allHazelcastInstances.name.contains('hazelCastInstanceCpsRi')
+    }
+
+    def 'Verify configs for Distributed Caches'(){
+        given: 'the Anchor Data Cache config'
+            def anchorDataCacheConfig =  Hazelcast.getHazelcastInstanceByName('hazelCastInstanceCpsRi').config.mapConfigs.get('anchorDataCacheMapConfig')
+        expect: 'system created instance with correct config'
+            assert anchorDataCacheConfig.backupCount == 3
+            assert anchorDataCacheConfig.asyncBackupCount == 3
+    }
+}
diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/cache/AnchorDataCacheEntrySpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/cache/AnchorDataCacheEntrySpec.groovy
new file mode 100644 (file)
index 0000000..103631e
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2022 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.spi.cache
+
+import org.onap.cps.spi.cache.AnchorDataCacheEntry
+import spock.lang.Specification
+
+class AnchorDataCacheEntrySpec extends Specification {
+
+    def objectUnderTest = new AnchorDataCacheEntry()
+
+    def 'Anchor Data Cache Properties Management.'() {
+        when: 'a property named "sample" is added to the cache'
+            objectUnderTest.setProperty('sample', 123)
+        then: 'the cache has that property'
+            assert objectUnderTest.hasProperty('sample')
+        and: 'the value is correct'
+            assert objectUnderTest.getProperty('sample') == 123
+        and: 'the cache does not have an an object called "something else"'
+            assert objectUnderTest.hasProperty('something else') == false
+    }
+}
index 470b03a..3b15b76 100644 (file)
@@ -22,6 +22,7 @@ package org.onap.cps.spi.impl
 import com.fasterxml.jackson.databind.ObjectMapper
 import org.hibernate.StaleStateException
 import org.onap.cps.spi.FetchDescendantsOption
+import org.onap.cps.spi.cache.AnchorDataCacheEntry
 import org.onap.cps.spi.entities.AnchorEntity
 import org.onap.cps.spi.entities.FragmentEntity
 import org.onap.cps.spi.entities.SchemaSetEntity
@@ -37,6 +38,7 @@ import org.onap.cps.spi.utils.SessionManager
 import org.onap.cps.utils.JsonObjectMapper
 import spock.lang.Shared
 import spock.lang.Specification
+import com.hazelcast.map.IMap;
 
 class CpsDataPersistenceServiceSpec extends Specification {
 
@@ -45,9 +47,10 @@ class CpsDataPersistenceServiceSpec extends Specification {
     def mockFragmentRepository = Mock(FragmentRepository)
     def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
     def mockSessionManager = Mock(SessionManager)
+    def mockAnchorDataCache =  Mock(IMap<String, AnchorDataCacheEntry>)
 
     def objectUnderTest = new CpsDataPersistenceServiceImpl(
-            mockDataspaceRepository, mockAnchorRepository, mockFragmentRepository, jsonObjectMapper,mockSessionManager)
+            mockDataspaceRepository, mockAnchorRepository, mockFragmentRepository, jsonObjectMapper, mockSessionManager, mockAnchorDataCache)
 
     @Shared
     def NEW_RESOURCE_CONTENT = 'module stores {\n' +
@@ -211,4 +214,4 @@ class CpsDataPersistenceServiceSpec extends Specification {
         }
         return dataNode
     }
-}
\ No newline at end of file
+}
index d6f10d8..1fbff65 100644 (file)
 package org.onap.cps.spi.impl
 
 import com.fasterxml.jackson.databind.ObjectMapper
+import com.hazelcast.config.Config
+import com.hazelcast.instance.impl.HazelcastInstanceFactory
+import com.hazelcast.map.IMap
 import org.onap.cps.DatabaseTestContainer
+import org.onap.cps.spi.cache.AnchorDataCacheEntry
 import org.onap.cps.spi.repository.AnchorRepository
 import org.onap.cps.spi.repository.DataspaceRepository
 import org.onap.cps.spi.repository.FragmentRepository
@@ -58,6 +62,12 @@ class CpsPersistenceSpecBase extends Specification {
     @SpringBean
     JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
 
+    // Instantiate Hazelcast with different name for testing purposes!
+    @SpringBean
+    IMap<String, AnchorDataCacheEntry> anchorDataCache = HazelcastInstanceFactory
+        .getOrCreateHazelcastInstance(new Config('hazelcastTestInstance'))
+        .getMap('testAnchorDataCacheMap')
+
     static final String CLEAR_DATA = '/data/clear-all.sql'
 
     static final String DATASPACE_NAME = 'DATASPACE-001'
index 18e6ed6..e835b77 100644 (file)
@@ -1,5 +1,6 @@
 # ============LICENSE_START=======================================================
 # Copyright (C) 2021 Pantheon.tech
+# Modifications Copyright (C) 2022 Nordix Foundation.
 # ================================================================================
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -18,7 +19,7 @@
 spring:
   jpa:
     ddl-auto: create
-    show-sql: true
+    show-sql: false
     properties:
       hibernate:
         enable_lazy_load_no_trans: true
@@ -33,4 +34,4 @@ spring:
     initialization-mode: always
 
   liquibase:
-    change-log: classpath:changelog/changelog-master.yaml
\ No newline at end of file
+    change-log: classpath:changelog/changelog-master.yaml
index c77daaf..19d5ade 100644 (file)
@@ -45,6 +45,7 @@ public class DataNode implements Serializable {
     private String anchorName;
     private ModuleReference moduleReference;
     private String xpath;
+    @Setter(AccessLevel.PUBLIC)
     private String moduleNamePrefix;
     private Map<String, Object> leaves = Collections.emptyMap();
     private Collection<String> xpathsChildren;