Refactor data types cache
[sdc.git] / catalog-model / src / main / java / org / openecomp / sdc / be / model / cache / ApplicationDataTypeCache.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * SDC
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END=========================================================
19  */
20 package org.openecomp.sdc.be.model.cache;
21
22 import fj.data.Either;
23 import java.util.Collections;
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Optional;
28 import java.util.concurrent.Executors;
29 import java.util.concurrent.ScheduledExecutorService;
30 import java.util.concurrent.ScheduledFuture;
31 import java.util.concurrent.TimeUnit;
32 import java.util.concurrent.locks.ReentrantReadWriteLock;
33 import javax.annotation.PostConstruct;
34 import javax.annotation.PreDestroy;
35 import lombok.AccessLevel;
36 import lombok.Getter;
37 import org.apache.commons.collections.CollectionUtils;
38 import org.apache.commons.collections.MapUtils;
39 import org.apache.commons.lang3.concurrent.BasicThreadFactory;
40 import org.openecomp.sdc.be.config.BeEcompErrorManager;
41 import org.openecomp.sdc.be.config.BeEcompErrorManager.ErrorSeverity;
42 import org.openecomp.sdc.be.config.Configuration.ApplicationL1CacheInfo;
43 import org.openecomp.sdc.be.config.ConfigurationManager;
44 import org.openecomp.sdc.be.dao.janusgraph.JanusGraphOperationStatus;
45 import org.openecomp.sdc.be.model.DataTypeDefinition;
46 import org.openecomp.sdc.be.model.operations.impl.PropertyOperation;
47 import org.openecomp.sdc.be.resources.data.DataTypeData;
48 import org.openecomp.sdc.common.log.enums.EcompLoggerErrorCode;
49 import org.openecomp.sdc.common.log.wrappers.Logger;
50 import org.springframework.context.ApplicationEvent;
51 import org.springframework.context.ApplicationEventPublisher;
52 import org.springframework.stereotype.Component;
53
54 @Component("application-datatype-cache")
55 public class ApplicationDataTypeCache implements ApplicationCache<DataTypeDefinition>, Runnable {
56
57     private static final String APPLICATION_DATA_TYPES_CACHE = "ApplicationDataTypesCache";
58     private static final Logger log = Logger.getLogger(ApplicationDataTypeCache.class);
59
60     private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
61     private final PropertyOperation propertyOperation;
62     private final ApplicationEventPublisher applicationEventPublisher;
63     @Getter(AccessLevel.PACKAGE)
64     private final ScheduledExecutorService scheduledPollingService;
65     @Getter(AccessLevel.PACKAGE)
66     private ScheduledFuture<?> scheduledFuture = null;
67     private Map<String, DataTypeDefinition> dataTypesCacheMap = new HashMap<>();
68     private int firstRunDelayInSec = 30;
69     private int pollingIntervalInSec = 60;
70
71     public ApplicationDataTypeCache(final PropertyOperation propertyOperation, final ApplicationEventPublisher applicationEventPublisher) {
72         this.propertyOperation = propertyOperation;
73         this.applicationEventPublisher = applicationEventPublisher;
74         scheduledPollingService = Executors
75             .newScheduledThreadPool(1, new BasicThreadFactory.Builder().namingPattern("ApplicationDataTypeCacheThread-%d").build());
76     }
77
78     @PostConstruct
79     void init() {
80         final Optional<ApplicationL1CacheInfo> dataTypeCacheConfigOptional = getDataTypeCacheConfig();
81         if (dataTypeCacheConfigOptional.isEmpty()) {
82             BeEcompErrorManager.getInstance()
83                 .logInternalFlowError(APPLICATION_DATA_TYPES_CACHE, "Data types cache is not configured and will be disabled", ErrorSeverity.INFO);
84             return;
85         }
86         final ApplicationL1CacheInfo dataTypesCacheInfo = dataTypeCacheConfigOptional.get();
87         if (!Boolean.TRUE.equals(dataTypesCacheInfo.getEnabled())) {
88             BeEcompErrorManager.getInstance().logInternalFlowError(APPLICATION_DATA_TYPES_CACHE, "Data types cache is disabled", ErrorSeverity.INFO);
89             return;
90         }
91         loadConfigurationValues(dataTypesCacheInfo);
92         if (scheduledPollingService != null) {
93             log.debug("Starting ApplicationDataTypeCache polling task. Initial delay {}s and polling interval {}s",
94                 firstRunDelayInSec, pollingIntervalInSec);
95             scheduledFuture = scheduledPollingService
96                 .scheduleAtFixedRate(this, firstRunDelayInSec, pollingIntervalInSec, TimeUnit.SECONDS);
97         }
98     }
99
100     private void loadConfigurationValues(final ApplicationL1CacheInfo dataTypesCacheInfo) {
101         final Integer firstRunDelay = dataTypesCacheInfo.getFirstRunDelay();
102         if (firstRunDelay != null) {
103             firstRunDelayInSec = firstRunDelay;
104         }
105         log.trace("ApplicationDataTypesCache initial delay configured to {} seconds.", firstRunDelayInSec);
106
107         final Integer intervalInSec = dataTypesCacheInfo.getPollIntervalInSec();
108         if (intervalInSec != null) {
109             pollingIntervalInSec = intervalInSec;
110         }
111         log.trace("ApplicationDataTypesCache polling interval configured to {} seconds.", pollingIntervalInSec);
112     }
113
114     private Optional<ApplicationL1CacheInfo> getDataTypeCacheConfig() {
115         final var applicationL1CacheConfig = ConfigurationManager.getConfigurationManager().getConfiguration().getApplicationL1Cache();
116         if (applicationL1CacheConfig == null || applicationL1CacheConfig.getDatatypes() == null) {
117             return Optional.empty();
118         }
119         return Optional.ofNullable(applicationL1CacheConfig.getDatatypes());
120     }
121
122     @PreDestroy
123     void destroy() {
124         if (scheduledFuture != null) {
125             boolean result = scheduledFuture.cancel(true);
126             log.debug("Stop polling task. result = {}", result);
127             scheduledFuture = null;
128         }
129         shutdownExecutor();
130     }
131
132     private void shutdownExecutor() {
133         if (scheduledPollingService == null) {
134             return;
135         }
136         scheduledPollingService.shutdown(); // Disable new tasks from being
137
138         // submitted
139         try {
140             // Wait a while for existing tasks to terminate
141             if (!scheduledPollingService.awaitTermination(60, TimeUnit.SECONDS)) {
142                 scheduledPollingService.shutdownNow(); // Cancel currently
143
144                 // executing tasks
145
146                 // Wait a while for tasks to respond to being cancelled
147                 if (!scheduledPollingService.awaitTermination(60, TimeUnit.SECONDS)) {
148                     log.debug("Pool did not terminate");
149                 }
150             }
151         } catch (InterruptedException ie) {
152             // (Re-)Cancel if current thread also interrupted
153             scheduledPollingService.shutdownNow();
154             // Preserve interrupt status
155             Thread.currentThread().interrupt();
156         }
157     }
158
159     private Either<Map<String, DataTypeDefinition>, JanusGraphOperationStatus> getAllDataTypesFromGraph() {
160         return propertyOperation.getAllDataTypes();
161     }
162
163     @Override
164     public Either<Map<String, DataTypeDefinition>, JanusGraphOperationStatus> getAll() {
165         try {
166             readWriteLock.readLock().lock();
167             if (MapUtils.isEmpty(dataTypesCacheMap)) {
168                 return getAllDataTypesFromGraph();
169             }
170             return Either.left(new HashMap<>(dataTypesCacheMap));
171         } finally {
172             readWriteLock.readLock().unlock();
173         }
174     }
175
176     @Override
177     public Either<DataTypeDefinition, JanusGraphOperationStatus> get(String uniqueId) {
178         try {
179             readWriteLock.readLock().lock();
180             if (MapUtils.isEmpty(dataTypesCacheMap)) {
181                 return propertyOperation.getDataTypeByUid(uniqueId);
182             }
183
184             final Optional<DataTypeDefinition> dataTypeDefinition = dataTypesCacheMap.values().stream()
185                 .filter(p -> p.getUniqueId().equals(uniqueId)).findFirst();
186             if (dataTypeDefinition.isEmpty()) {
187                 return propertyOperation.getDataTypeByUid(uniqueId);
188             }
189             return Either.left(new DataTypeDefinition(dataTypeDefinition.get()));
190         } finally {
191             readWriteLock.readLock().unlock();
192         }
193     }
194
195     @Override
196     public void run() {
197         try {
198             final long startTime = System.currentTimeMillis();
199             log.trace("Starting refresh data types cache job");
200             if (hasDataTypesChanged()) {
201                 log.info("Detected changes in the data types, updating the data type cache.");
202                 refreshDataTypesCache();
203             }
204             log.trace("Finished refresh data types cache job. Finished in {}ms", (System.currentTimeMillis() - startTime));
205         } catch (final Exception e) {
206             var errorMsg = "Failed to run refresh data types cache job";
207             log.error(EcompLoggerErrorCode.UNKNOWN_ERROR, ApplicationDataTypeCache.class.getName(), errorMsg, e);
208             BeEcompErrorManager.getInstance().logInternalUnexpectedError(APPLICATION_DATA_TYPES_CACHE, errorMsg, ErrorSeverity.INFO);
209         } finally {
210             try {
211                 propertyOperation.getJanusGraphGenericDao().commit();
212             } catch (final Exception e) {
213                 log.error(EcompLoggerErrorCode.UNKNOWN_ERROR, ApplicationDataTypeCache.class.getName(),
214                     "Failed to commit ApplicationDataTypeCache", e);
215             }
216         }
217     }
218
219     private boolean hasDataTypesChanged() {
220         final List<DataTypeData> dataTypeListFromDatabase = findAllDataTypesLazy();
221         final Map<String, DataTypeDefinition> dataTypesCacheCopyMap = copyDataTypeCache();
222
223         if (dataTypeListFromDatabase.size() != dataTypesCacheCopyMap.size()) {
224             log.debug("Total of cached data types '{}' differs from the actual '{}'", dataTypeListFromDatabase.size(),  dataTypesCacheCopyMap.size());
225             return true;
226         }
227
228         if (CollectionUtils.isEmpty(dataTypeListFromDatabase)) {
229             log.debug("Both data type cache and database are empty");
230             return false;
231         }
232
233         return hasDataTypesChanged(dataTypeListFromDatabase, dataTypesCacheCopyMap);
234     }
235
236     private boolean hasDataTypesChanged(final List<DataTypeData> dataTypeListFromDatabase, final Map<String, DataTypeDefinition> dataTypesCacheCopyMap) {
237         return dataTypeListFromDatabase.stream().map(DataTypeData::getDataTypeDataDefinition).anyMatch(actualDataTypeDefinition -> {
238             final String dataTypeName = actualDataTypeDefinition.getName();
239             final DataTypeDefinition cachedDataTypeDefinition = dataTypesCacheCopyMap.get(dataTypeName);
240             if (cachedDataTypeDefinition == null) {
241                 log.debug("Datatype '{}' is not present in the cache. ", dataTypeName);
242                 return true;
243             }
244
245             final long cachedCreationTime = cachedDataTypeDefinition.getCreationTime() == null ? 0 : cachedDataTypeDefinition.getCreationTime();
246             final long actualCreationTime = actualDataTypeDefinition.getCreationTime() == null ? 0 : actualDataTypeDefinition.getCreationTime();
247             if (cachedCreationTime != actualCreationTime) {
248                 log.debug("Datatype '{}' was updated. Cache/database creation time '{}'/'{}'.",
249                     dataTypeName, cachedCreationTime, actualCreationTime);
250                 return true;
251             }
252             final long cachedModificationTime =
253                 cachedDataTypeDefinition.getModificationTime() == null ? 0 : cachedDataTypeDefinition.getModificationTime();
254             final long actualModificationTime =
255                 actualDataTypeDefinition.getModificationTime() == null ? 0 : actualDataTypeDefinition.getModificationTime();
256             if (cachedModificationTime != actualModificationTime) {
257                 log.debug("Datatype '{}' was updated. Cache/database modification time '{}'/'{}'.",
258                     dataTypeName, cachedModificationTime, actualModificationTime);
259                 return true;
260             }
261
262             return false;
263         });
264     }
265
266     private Map<String, DataTypeDefinition> copyDataTypeCache() {
267         try {
268             readWriteLock.readLock().lock();
269             return new HashMap<>(this.dataTypesCacheMap);
270         } finally {
271             readWriteLock.readLock().unlock();
272         }
273     }
274
275     private void refreshDataTypesCache() {
276         final Map<String, DataTypeDefinition> dataTypesDefinitionMap = findAllDataTypesEager();
277         if (dataTypesDefinitionMap.isEmpty()) {
278             return;
279         }
280         try {
281             readWriteLock.writeLock().lock();
282             dataTypesCacheMap = dataTypesDefinitionMap;
283             onDataChangeEventEmit();
284             BeEcompErrorManager.getInstance()
285                 .logInternalFlowError("ReplaceDataTypesCache", "Succeed to replace the data types cache", ErrorSeverity.INFO);
286         } finally {
287             readWriteLock.writeLock().unlock();
288         }
289     }
290
291     private Map<String, DataTypeDefinition> findAllDataTypesEager() {
292         log.trace("Fetching data types from database, eager mode");
293         final long startTime = System.currentTimeMillis();
294         final Either<Map<String, DataTypeDefinition>, JanusGraphOperationStatus> allDataTypes = propertyOperation.getAllDataTypes();
295         log.trace("Finish fetching data types from database. Took {}ms", (System.currentTimeMillis() - startTime));
296         if (allDataTypes.isRight()) {
297             final JanusGraphOperationStatus status = allDataTypes.right().value();
298             var errorMsg= String.format("Failed to fetch data types from database. Status is %s", status);
299             log.error(EcompLoggerErrorCode.UNKNOWN_ERROR, ApplicationDataTypeCache.class.getName(), errorMsg);
300             BeEcompErrorManager.getInstance().logInternalConnectionError(APPLICATION_DATA_TYPES_CACHE, errorMsg, ErrorSeverity.ERROR);
301             return Collections.emptyMap();
302         }
303         return allDataTypes.left().value();
304     }
305
306     private List<DataTypeData> findAllDataTypesLazy() {
307         log.trace("Fetching data types from database, lazy mode");
308         final long startTime = System.currentTimeMillis();
309         final Either<List<DataTypeData>, JanusGraphOperationStatus> allDataTypes = propertyOperation.getAllDataTypeNodes();
310         log.trace("Finish fetching data types from database. Took {}ms", (System.currentTimeMillis() - startTime));
311         if (allDataTypes.isRight()) {
312             final JanusGraphOperationStatus status = allDataTypes.right().value();
313             var errorMsg= String.format("Failed to fetch data types from database. Status is %s", status);
314             log.error(EcompLoggerErrorCode.UNKNOWN_ERROR, ApplicationDataTypeCache.class.getName(), errorMsg);
315             BeEcompErrorManager.getInstance().logInternalConnectionError(APPLICATION_DATA_TYPES_CACHE, errorMsg, ErrorSeverity.ERROR);
316             return Collections.emptyList();
317         }
318         return allDataTypes.left().value();
319     }
320
321     private void onDataChangeEventEmit() {
322         log.trace("Data type cache has changed, sending DataTypesCacheChangedEvent.");
323         applicationEventPublisher.publishEvent(new DataTypesCacheChangedEvent(this, copyDataTypeCache()));
324     }
325
326     /**
327      * Custom event to notify all interested in cached data changes
328      */
329     public static class DataTypesCacheChangedEvent extends ApplicationEvent {
330
331         @Getter
332         private final Map<String, DataTypeDefinition> newData;
333
334         public DataTypesCacheChangedEvent(final Object source, final Map<String, DataTypeDefinition> newData) {
335             super(source);
336             this.newData = newData;
337         }
338     }
339
340 }