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.ArrayList;
24 import java.util.Collections;
25 import java.util.HashMap;
26 import java.util.List;
28 import java.util.Optional;
29 import java.util.concurrent.Executors;
30 import java.util.concurrent.ScheduledExecutorService;
31 import java.util.concurrent.ScheduledFuture;
32 import java.util.concurrent.TimeUnit;
33 import java.util.concurrent.locks.ReentrantReadWriteLock;
34 import javax.annotation.PostConstruct;
35 import javax.annotation.PreDestroy;
36 import lombok.AccessLevel;
38 import org.apache.commons.collections.CollectionUtils;
39 import org.apache.commons.collections.MapUtils;
40 import org.apache.commons.lang3.concurrent.BasicThreadFactory;
41 import org.openecomp.sdc.be.config.BeEcompErrorManager;
42 import org.openecomp.sdc.be.config.BeEcompErrorManager.ErrorSeverity;
43 import org.openecomp.sdc.be.config.Configuration.ApplicationL1CacheInfo;
44 import org.openecomp.sdc.be.config.ConfigurationManager;
45 import org.openecomp.sdc.be.dao.janusgraph.JanusGraphOperationStatus;
46 import org.openecomp.sdc.be.model.DataTypeDefinition;
47 import org.openecomp.sdc.be.model.operations.impl.DataTypeOperation;
48 import org.openecomp.sdc.be.model.operations.impl.PropertyOperation;
49 import org.openecomp.sdc.be.resources.data.DataTypeData;
50 import org.openecomp.sdc.common.log.enums.EcompLoggerErrorCode;
51 import org.openecomp.sdc.common.log.wrappers.Logger;
52 import org.springframework.context.ApplicationEvent;
53 import org.springframework.context.ApplicationEventPublisher;
54 import org.springframework.stereotype.Component;
56 @Component("application-datatype-cache")
57 public class ApplicationDataTypeCache implements ApplicationCache<DataTypeDefinition>, Runnable {
59 private static final String APPLICATION_DATA_TYPES_CACHE = "ApplicationDataTypesCache";
60 private static final Logger log = Logger.getLogger(ApplicationDataTypeCache.class);
62 private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
63 private final PropertyOperation propertyOperation;
64 private final ApplicationEventPublisher applicationEventPublisher;
65 @Getter(AccessLevel.PACKAGE)
66 private final ScheduledExecutorService scheduledPollingService;
67 @Getter(AccessLevel.PACKAGE)
68 private ScheduledFuture<?> scheduledFuture = null;
69 private Map<String, Map<String, DataTypeDefinition>> dataTypesByModelCacheMap = new HashMap<>();
70 private final DataTypeOperation dataTypeOperation;
71 private int firstRunDelayInSec = 30;
72 private int pollingIntervalInSec = 60;
74 public ApplicationDataTypeCache(final PropertyOperation propertyOperation, final ApplicationEventPublisher applicationEventPublisher,
75 final DataTypeOperation dataTypeOperation) {
76 this.propertyOperation = propertyOperation;
77 this.applicationEventPublisher = applicationEventPublisher;
78 this.dataTypeOperation = dataTypeOperation;
79 scheduledPollingService = Executors
80 .newScheduledThreadPool(1, new BasicThreadFactory.Builder().namingPattern("ApplicationDataTypeCacheThread-%d").build());
85 final Optional<ApplicationL1CacheInfo> dataTypeCacheConfigOptional = getDataTypeCacheConfig();
86 if (dataTypeCacheConfigOptional.isEmpty()) {
87 BeEcompErrorManager.getInstance()
88 .logInternalFlowError(APPLICATION_DATA_TYPES_CACHE, "Data types cache is not configured and will be disabled", ErrorSeverity.INFO);
91 final ApplicationL1CacheInfo dataTypesCacheInfo = dataTypeCacheConfigOptional.get();
92 if (!Boolean.TRUE.equals(dataTypesCacheInfo.getEnabled())) {
93 BeEcompErrorManager.getInstance().logInternalFlowError(APPLICATION_DATA_TYPES_CACHE, "Data types cache is disabled", ErrorSeverity.INFO);
96 loadConfigurationValues(dataTypesCacheInfo);
97 if (scheduledPollingService != null) {
98 log.debug("Starting ApplicationDataTypeCache polling task. Initial delay {}s and polling interval {}s",
99 firstRunDelayInSec, pollingIntervalInSec);
100 scheduledFuture = scheduledPollingService
101 .scheduleAtFixedRate(this, firstRunDelayInSec, pollingIntervalInSec, TimeUnit.SECONDS);
105 private void loadConfigurationValues(final ApplicationL1CacheInfo dataTypesCacheInfo) {
106 final Integer firstRunDelay = dataTypesCacheInfo.getFirstRunDelay();
107 if (firstRunDelay != null) {
108 firstRunDelayInSec = firstRunDelay;
110 log.trace("ApplicationDataTypesCache initial delay configured to {} seconds.", firstRunDelayInSec);
112 final Integer intervalInSec = dataTypesCacheInfo.getPollIntervalInSec();
113 if (intervalInSec != null) {
114 pollingIntervalInSec = intervalInSec;
116 log.trace("ApplicationDataTypesCache polling interval configured to {} seconds.", pollingIntervalInSec);
119 private Optional<ApplicationL1CacheInfo> getDataTypeCacheConfig() {
120 final var applicationL1CacheConfig = ConfigurationManager.getConfigurationManager().getConfiguration().getApplicationL1Cache();
121 if (applicationL1CacheConfig == null || applicationL1CacheConfig.getDatatypes() == null) {
122 return Optional.empty();
124 return Optional.ofNullable(applicationL1CacheConfig.getDatatypes());
129 if (scheduledFuture != null) {
130 boolean result = scheduledFuture.cancel(true);
131 log.debug("Stop polling task. result = {}", result);
132 scheduledFuture = null;
137 private void shutdownExecutor() {
138 if (scheduledPollingService == null) {
141 scheduledPollingService.shutdown(); // Disable new tasks from being
145 // Wait a while for existing tasks to terminate
146 if (!scheduledPollingService.awaitTermination(60, TimeUnit.SECONDS)) {
147 scheduledPollingService.shutdownNow(); // Cancel currently
151 // Wait a while for tasks to respond to being cancelled
152 if (!scheduledPollingService.awaitTermination(60, TimeUnit.SECONDS)) {
153 log.debug("Pool did not terminate");
156 } catch (InterruptedException ie) {
157 // (Re-)Cancel if current thread also interrupted
158 scheduledPollingService.shutdownNow();
159 // Preserve interrupt status
160 Thread.currentThread().interrupt();
164 private Either<Map<String, Map<String, DataTypeDefinition>>, JanusGraphOperationStatus> getAllDataTypesFromGraph() {
165 return propertyOperation.getAllDataTypes();
168 public Either<Map<String, DataTypeDefinition>, JanusGraphOperationStatus> getAll(final String model) {
170 readWriteLock.readLock().lock();
171 if (MapUtils.isEmpty(dataTypesByModelCacheMap) || !dataTypesByModelCacheMap.containsKey(model)) {
172 final var dataTypesFound = getAllDataTypesFromGraph();
173 if (dataTypesFound.isRight()) {
174 return Either.right(dataTypesFound.right().value());
176 dataTypesByModelCacheMap = dataTypesFound.left().value();
178 return Either.left(getDataTypeDefinitionMapByModel(model));
180 readWriteLock.readLock().unlock();
185 public Either<DataTypeDefinition, JanusGraphOperationStatus> get(final String model, final String uniqueId) {
187 readWriteLock.readLock().lock();
188 if (MapUtils.isEmpty(dataTypesByModelCacheMap)) {
189 return propertyOperation.getDataTypeByUid(uniqueId);
191 final Optional<DataTypeDefinition> dataTypeDefinition = getDataTypeDefinitionMapByModel(model).values().stream()
192 .filter(p -> p.getUniqueId().equals(uniqueId)).findFirst();
193 if (dataTypeDefinition.isEmpty()) {
194 return propertyOperation.getDataTypeByUid(uniqueId);
196 return Either.left(new DataTypeDefinition(dataTypeDefinition.get()));
198 readWriteLock.readLock().unlock();
202 private Map<String, DataTypeDefinition> getDataTypeDefinitionMapByModel(final String model) {
203 return dataTypesByModelCacheMap.containsKey(model) ? dataTypesByModelCacheMap.get(model) : new HashMap<>();
209 refreshDataTypesCacheIfStale();
210 } catch (final Exception e) {
211 var errorMsg = "Failed to run refresh data types cache job";
212 log.error(EcompLoggerErrorCode.UNKNOWN_ERROR, ApplicationDataTypeCache.class.getName(), errorMsg, e);
213 BeEcompErrorManager.getInstance().logInternalUnexpectedError(APPLICATION_DATA_TYPES_CACHE, errorMsg, ErrorSeverity.INFO);
216 propertyOperation.getJanusGraphGenericDao().commit();
217 } catch (final Exception e) {
218 log.error(EcompLoggerErrorCode.UNKNOWN_ERROR, ApplicationDataTypeCache.class.getName(),
219 "Failed to commit ApplicationDataTypeCache", e);
224 private boolean hasDataTypesChanged() {
225 final List<DataTypeData> dataTypeListFromDatabase = findAllDataTypesLazy();
226 final int dataTypesCacheCopyMap = dataTypesCacheMapSize();
227 if (dataTypeListFromDatabase.size() != dataTypesCacheCopyMap) {
228 log.debug("Total of cached data types '{}' differs from the actual '{}'", dataTypeListFromDatabase.size(), dataTypesCacheCopyMap);
232 if (CollectionUtils.isEmpty(dataTypeListFromDatabase)) {
233 log.debug("Both data type cache and database are empty");
237 return hasDataTypesChanged(dataTypeListFromDatabase, copyDataTypeCache());
240 private int dataTypesCacheMapSize() {
242 for (var i = 0; i < copyDataTypeCache().size(); i++) {
243 count += new ArrayList<>(copyDataTypeCache().values()).get(i).size();
249 private boolean hasDataTypesChanged(final List<DataTypeData> dataTypeListFromDatabase, final Map<String, Map<String, DataTypeDefinition>> dataTypesCacheCopyMap) {
250 return dataTypeListFromDatabase.stream().map(DataTypeData::getDataTypeDataDefinition).anyMatch(actualDataTypeDefinition -> {
251 final String dataTypeName = actualDataTypeDefinition.getName();
252 final String model = actualDataTypeDefinition.getModel();
253 final DataTypeDefinition cachedDataTypeDefinition = dataTypesCacheCopyMap.get(model).get(dataTypeName);
254 if (cachedDataTypeDefinition == null) {
255 log.debug("Datatype '{}' is not present in the cache. ", dataTypeName);
259 final long cachedCreationTime = cachedDataTypeDefinition.getCreationTime() == null ? 0 : cachedDataTypeDefinition.getCreationTime();
260 final long actualCreationTime = actualDataTypeDefinition.getCreationTime() == null ? 0 : actualDataTypeDefinition.getCreationTime();
261 if (cachedCreationTime != actualCreationTime) {
262 log.debug("Datatype '{}' with model '{}' was updated. Cache/database creation time '{}'/'{}'.",
263 dataTypeName, model, cachedCreationTime, actualCreationTime);
266 final long cachedModificationTime =
267 cachedDataTypeDefinition.getModificationTime() == null ? 0 : cachedDataTypeDefinition.getModificationTime();
268 final long actualModificationTime =
269 actualDataTypeDefinition.getModificationTime() == null ? 0 : actualDataTypeDefinition.getModificationTime();
270 if (cachedModificationTime != actualModificationTime) {
271 log.debug("Datatype '{}' was updated. Cache/database modification time '{}'/'{}'.",
272 dataTypeName, cachedModificationTime, actualModificationTime);
280 private Map<String, Map<String, DataTypeDefinition>> copyDataTypeCache() {
282 readWriteLock.readLock().lock();
283 return new HashMap<>(this.dataTypesByModelCacheMap);
285 readWriteLock.readLock().unlock();
289 public void refreshDataTypesCacheIfStale() {
290 final long startTime = System.currentTimeMillis();
291 log.trace("Starting refresh data types cache");
292 if (hasDataTypesChanged()) {
293 log.info("Detected changes in the data types, updating the data type cache.");
294 refreshDataTypesCache();
296 log.trace("Finished refresh data types cache. Finished in {}ms", (System.currentTimeMillis() - startTime));
299 private void refreshDataTypesCache() {
300 final Map<String, Map<String, DataTypeDefinition>> dataTypesDefinitionMap = findAllDataTypesEager();
301 if (dataTypesDefinitionMap.isEmpty()) {
305 readWriteLock.writeLock().lock();
306 dataTypesByModelCacheMap = dataTypesDefinitionMap;
307 onDataChangeEventEmit();
308 BeEcompErrorManager.getInstance()
309 .logInternalFlowError("ReplaceDataTypesCache", "Succeed to replace the data types cache", ErrorSeverity.INFO);
311 readWriteLock.writeLock().unlock();
315 private Map<String, Map<String, DataTypeDefinition>> findAllDataTypesEager() {
316 log.trace("Fetching data types from database, eager mode");
317 final long startTime = System.currentTimeMillis();
318 final Either<Map<String, Map<String, DataTypeDefinition>>, JanusGraphOperationStatus> allDataTypes = propertyOperation.getAllDataTypes();
319 log.trace("Finish fetching data types from database. Took {}ms", (System.currentTimeMillis() - startTime));
320 if (allDataTypes.isRight()) {
321 final JanusGraphOperationStatus status = allDataTypes.right().value();
322 var errorMsg= String.format("Failed to fetch data types from database. Status is %s", status);
323 log.error(EcompLoggerErrorCode.UNKNOWN_ERROR, ApplicationDataTypeCache.class.getName(), errorMsg);
324 BeEcompErrorManager.getInstance().logInternalConnectionError(APPLICATION_DATA_TYPES_CACHE, errorMsg, ErrorSeverity.ERROR);
325 return Collections.emptyMap();
327 return allDataTypes.left().value();
330 private List<DataTypeData> findAllDataTypesLazy() {
331 log.trace("Fetching data types from database, lazy mode");
332 final long startTime = System.currentTimeMillis();
333 final List<DataTypeData> allDataTypes = dataTypeOperation.getAllDataTypeNodes();
334 log.trace("Finish fetching data types from database. Took {}ms", (System.currentTimeMillis() - startTime));
338 private void onDataChangeEventEmit() {
339 log.trace("Data type cache has changed, sending DataTypesCacheChangedEvent.");
340 applicationEventPublisher.publishEvent(new DataTypesCacheChangedEvent(this, copyDataTypeCache()));
344 * Custom event to notify all interested in cached data changes
346 public static class DataTypesCacheChangedEvent extends ApplicationEvent {
349 private final Map<String, Map<String, DataTypeDefinition>> newData;
351 public DataTypesCacheChangedEvent(final Object source, final Map<String, Map<String, DataTypeDefinition>> newData) {
353 this.newData = newData;