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 final long startTime = System.currentTimeMillis();
210 log.trace("Starting refresh data types cache job");
211 if (hasDataTypesChanged()) {
212 log.info("Detected changes in the data types, updating the data type cache.");
213 refreshDataTypesCache();
215 log.trace("Finished refresh data types cache job. Finished in {}ms", (System.currentTimeMillis() - startTime));
216 } catch (final Exception e) {
217 var errorMsg = "Failed to run refresh data types cache job";
218 log.error(EcompLoggerErrorCode.UNKNOWN_ERROR, ApplicationDataTypeCache.class.getName(), errorMsg, e);
219 BeEcompErrorManager.getInstance().logInternalUnexpectedError(APPLICATION_DATA_TYPES_CACHE, errorMsg, ErrorSeverity.INFO);
222 propertyOperation.getJanusGraphGenericDao().commit();
223 } catch (final Exception e) {
224 log.error(EcompLoggerErrorCode.UNKNOWN_ERROR, ApplicationDataTypeCache.class.getName(),
225 "Failed to commit ApplicationDataTypeCache", e);
230 private boolean hasDataTypesChanged() {
231 final List<DataTypeData> dataTypeListFromDatabase = findAllDataTypesLazy();
232 final int dataTypesCacheCopyMap = dataTypesCacheMapSize();
233 if (dataTypeListFromDatabase.size() != dataTypesCacheCopyMap) {
234 log.debug("Total of cached data types '{}' differs from the actual '{}'", dataTypeListFromDatabase.size(), dataTypesCacheCopyMap);
238 if (CollectionUtils.isEmpty(dataTypeListFromDatabase)) {
239 log.debug("Both data type cache and database are empty");
243 return hasDataTypesChanged(dataTypeListFromDatabase, copyDataTypeCache());
246 private int dataTypesCacheMapSize() {
248 for (var i = 0; i < copyDataTypeCache().size(); i++) {
249 count += new ArrayList<>(copyDataTypeCache().values()).get(i).size();
255 private boolean hasDataTypesChanged(final List<DataTypeData> dataTypeListFromDatabase, final Map<String, Map<String, DataTypeDefinition>> dataTypesCacheCopyMap) {
256 return dataTypeListFromDatabase.stream().map(DataTypeData::getDataTypeDataDefinition).anyMatch(actualDataTypeDefinition -> {
257 final String dataTypeName = actualDataTypeDefinition.getName();
258 final String model = actualDataTypeDefinition.getModel();
259 final DataTypeDefinition cachedDataTypeDefinition = dataTypesCacheCopyMap.get(model).get(dataTypeName);
260 if (cachedDataTypeDefinition == null) {
261 log.debug("Datatype '{}' is not present in the cache. ", dataTypeName);
265 final long cachedCreationTime = cachedDataTypeDefinition.getCreationTime() == null ? 0 : cachedDataTypeDefinition.getCreationTime();
266 final long actualCreationTime = actualDataTypeDefinition.getCreationTime() == null ? 0 : actualDataTypeDefinition.getCreationTime();
267 if (cachedCreationTime != actualCreationTime) {
268 log.debug("Datatype '{}' with model '{}' was updated. Cache/database creation time '{}'/'{}'.",
269 dataTypeName, model, cachedCreationTime, actualCreationTime);
272 final long cachedModificationTime =
273 cachedDataTypeDefinition.getModificationTime() == null ? 0 : cachedDataTypeDefinition.getModificationTime();
274 final long actualModificationTime =
275 actualDataTypeDefinition.getModificationTime() == null ? 0 : actualDataTypeDefinition.getModificationTime();
276 if (cachedModificationTime != actualModificationTime) {
277 log.debug("Datatype '{}' was updated. Cache/database modification time '{}'/'{}'.",
278 dataTypeName, cachedModificationTime, actualModificationTime);
286 private Map<String, Map<String, DataTypeDefinition>> copyDataTypeCache() {
288 readWriteLock.readLock().lock();
289 return new HashMap<>(this.dataTypesByModelCacheMap);
291 readWriteLock.readLock().unlock();
295 private void refreshDataTypesCache() {
296 final Map<String, Map<String, DataTypeDefinition>> dataTypesDefinitionMap = findAllDataTypesEager();
297 if (dataTypesDefinitionMap.isEmpty()) {
301 readWriteLock.writeLock().lock();
302 dataTypesByModelCacheMap = dataTypesDefinitionMap;
303 onDataChangeEventEmit();
304 BeEcompErrorManager.getInstance()
305 .logInternalFlowError("ReplaceDataTypesCache", "Succeed to replace the data types cache", ErrorSeverity.INFO);
307 readWriteLock.writeLock().unlock();
311 private Map<String, Map<String, DataTypeDefinition>> findAllDataTypesEager() {
312 log.trace("Fetching data types from database, eager mode");
313 final long startTime = System.currentTimeMillis();
314 final Either<Map<String, Map<String, DataTypeDefinition>>, JanusGraphOperationStatus> allDataTypes = propertyOperation.getAllDataTypes();
315 log.trace("Finish fetching data types from database. Took {}ms", (System.currentTimeMillis() - startTime));
316 if (allDataTypes.isRight()) {
317 final JanusGraphOperationStatus status = allDataTypes.right().value();
318 var errorMsg= String.format("Failed to fetch data types from database. Status is %s", status);
319 log.error(EcompLoggerErrorCode.UNKNOWN_ERROR, ApplicationDataTypeCache.class.getName(), errorMsg);
320 BeEcompErrorManager.getInstance().logInternalConnectionError(APPLICATION_DATA_TYPES_CACHE, errorMsg, ErrorSeverity.ERROR);
321 return Collections.emptyMap();
323 return allDataTypes.left().value();
326 private List<DataTypeData> findAllDataTypesLazy() {
327 log.trace("Fetching data types from database, lazy mode");
328 final long startTime = System.currentTimeMillis();
329 final List<DataTypeData> allDataTypes = dataTypeOperation.getAllDataTypeNodes();
330 log.trace("Finish fetching data types from database. Took {}ms", (System.currentTimeMillis() - startTime));
334 private void onDataChangeEventEmit() {
335 log.trace("Data type cache has changed, sending DataTypesCacheChangedEvent.");
336 applicationEventPublisher.publishEvent(new DataTypesCacheChangedEvent(this, copyDataTypeCache()));
340 * Custom event to notify all interested in cached data changes
342 public static class DataTypesCacheChangedEvent extends ApplicationEvent {
345 private final Map<String, Map<String, DataTypeDefinition>> newData;
347 public DataTypesCacheChangedEvent(final Object source, final Map<String, Map<String, DataTypeDefinition>> newData) {
349 this.newData = newData;