2 * ============LICENSE_START=======================================================
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
11 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
20 package org.openecomp.sdc.be.model.cache;
22 import fj.data.Either;
23 import java.util.Collections;
24 import java.util.HashMap;
25 import java.util.List;
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;
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;
54 @Component("application-datatype-cache")
55 public class ApplicationDataTypeCache implements ApplicationCache<DataTypeDefinition>, Runnable {
57 private static final String APPLICATION_DATA_TYPES_CACHE = "ApplicationDataTypesCache";
58 private static final Logger log = Logger.getLogger(ApplicationDataTypeCache.class);
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;
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());
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);
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);
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);
100 private void loadConfigurationValues(final ApplicationL1CacheInfo dataTypesCacheInfo) {
101 final Integer firstRunDelay = dataTypesCacheInfo.getFirstRunDelay();
102 if (firstRunDelay != null) {
103 firstRunDelayInSec = firstRunDelay;
105 log.trace("ApplicationDataTypesCache initial delay configured to {} seconds.", firstRunDelayInSec);
107 final Integer intervalInSec = dataTypesCacheInfo.getPollIntervalInSec();
108 if (intervalInSec != null) {
109 pollingIntervalInSec = intervalInSec;
111 log.trace("ApplicationDataTypesCache polling interval configured to {} seconds.", pollingIntervalInSec);
114 private Optional<ApplicationL1CacheInfo> getDataTypeCacheConfig() {
115 final var applicationL1CacheConfig = ConfigurationManager.getConfigurationManager().getConfiguration().getApplicationL1Cache();
116 if (applicationL1CacheConfig == null || applicationL1CacheConfig.getDatatypes() == null) {
117 return Optional.empty();
119 return Optional.ofNullable(applicationL1CacheConfig.getDatatypes());
124 if (scheduledFuture != null) {
125 boolean result = scheduledFuture.cancel(true);
126 log.debug("Stop polling task. result = {}", result);
127 scheduledFuture = null;
132 private void shutdownExecutor() {
133 if (scheduledPollingService == null) {
136 scheduledPollingService.shutdown(); // Disable new tasks from being
140 // Wait a while for existing tasks to terminate
141 if (!scheduledPollingService.awaitTermination(60, TimeUnit.SECONDS)) {
142 scheduledPollingService.shutdownNow(); // Cancel currently
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");
151 } catch (InterruptedException ie) {
152 // (Re-)Cancel if current thread also interrupted
153 scheduledPollingService.shutdownNow();
154 // Preserve interrupt status
155 Thread.currentThread().interrupt();
159 private Either<Map<String, DataTypeDefinition>, JanusGraphOperationStatus> getAllDataTypesFromGraph() {
160 return propertyOperation.getAllDataTypes();
164 public Either<Map<String, DataTypeDefinition>, JanusGraphOperationStatus> getAll() {
166 readWriteLock.readLock().lock();
167 if (MapUtils.isEmpty(dataTypesCacheMap)) {
168 return getAllDataTypesFromGraph();
170 return Either.left(new HashMap<>(dataTypesCacheMap));
172 readWriteLock.readLock().unlock();
177 public Either<DataTypeDefinition, JanusGraphOperationStatus> get(String uniqueId) {
179 readWriteLock.readLock().lock();
180 if (MapUtils.isEmpty(dataTypesCacheMap)) {
181 return propertyOperation.getDataTypeByUid(uniqueId);
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);
189 return Either.left(new DataTypeDefinition(dataTypeDefinition.get()));
191 readWriteLock.readLock().unlock();
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();
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);
211 propertyOperation.getJanusGraphGenericDao().commit();
212 } catch (final Exception e) {
213 log.error(EcompLoggerErrorCode.UNKNOWN_ERROR, ApplicationDataTypeCache.class.getName(),
214 "Failed to commit ApplicationDataTypeCache", e);
219 private boolean hasDataTypesChanged() {
220 final List<DataTypeData> dataTypeListFromDatabase = findAllDataTypesLazy();
221 final Map<String, DataTypeDefinition> dataTypesCacheCopyMap = copyDataTypeCache();
223 if (dataTypeListFromDatabase.size() != dataTypesCacheCopyMap.size()) {
224 log.debug("Total of cached data types '{}' differs from the actual '{}'", dataTypeListFromDatabase.size(), dataTypesCacheCopyMap.size());
228 if (CollectionUtils.isEmpty(dataTypeListFromDatabase)) {
229 log.debug("Both data type cache and database are empty");
233 return hasDataTypesChanged(dataTypeListFromDatabase, dataTypesCacheCopyMap);
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);
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);
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);
266 private Map<String, DataTypeDefinition> copyDataTypeCache() {
268 readWriteLock.readLock().lock();
269 return new HashMap<>(this.dataTypesCacheMap);
271 readWriteLock.readLock().unlock();
275 private void refreshDataTypesCache() {
276 final Map<String, DataTypeDefinition> dataTypesDefinitionMap = findAllDataTypesEager();
277 if (dataTypesDefinitionMap.isEmpty()) {
281 readWriteLock.writeLock().lock();
282 dataTypesCacheMap = dataTypesDefinitionMap;
283 onDataChangeEventEmit();
284 BeEcompErrorManager.getInstance()
285 .logInternalFlowError("ReplaceDataTypesCache", "Succeed to replace the data types cache", ErrorSeverity.INFO);
287 readWriteLock.writeLock().unlock();
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();
303 return allDataTypes.left().value();
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();
318 return allDataTypes.left().value();
321 private void onDataChangeEventEmit() {
322 log.trace("Data type cache has changed, sending DataTypesCacheChangedEvent.");
323 applicationEventPublisher.publishEvent(new DataTypesCacheChangedEvent(this, copyDataTypeCache()));
327 * Custom event to notify all interested in cached data changes
329 public static class DataTypesCacheChangedEvent extends ApplicationEvent {
332 private final Map<String, DataTypeDefinition> newData;
334 public DataTypesCacheChangedEvent(final Object source, final Map<String, DataTypeDefinition> newData) {
336 this.newData = newData;