Reformat catalog-model
[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.HashMap;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.Map.Entry;
27 import java.util.Set;
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.Lock;
33 import java.util.concurrent.locks.ReentrantReadWriteLock;
34 import java.util.stream.Collectors;
35 import javax.annotation.PostConstruct;
36 import javax.annotation.PreDestroy;
37 import javax.annotation.Resource;
38 import lombok.Getter;
39 import org.apache.commons.lang3.concurrent.BasicThreadFactory;
40 import org.apache.commons.lang3.tuple.ImmutablePair;
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.ApplicationL1CacheConfig;
44 import org.openecomp.sdc.be.config.Configuration.ApplicationL1CacheInfo;
45 import org.openecomp.sdc.be.config.ConfigurationManager;
46 import org.openecomp.sdc.be.dao.janusgraph.JanusGraphOperationStatus;
47 import org.openecomp.sdc.be.datatypes.elements.DataTypeDataDefinition;
48 import org.openecomp.sdc.be.model.DataTypeDefinition;
49 import org.openecomp.sdc.be.model.operations.impl.PropertyOperation;
50 import org.openecomp.sdc.be.resources.data.DataTypeData;
51 import org.openecomp.sdc.common.log.wrappers.Logger;
52 import org.springframework.beans.factory.annotation.Autowired;
53 import org.springframework.context.ApplicationEvent;
54 import org.springframework.context.ApplicationEventPublisher;
55 import org.springframework.stereotype.Component;
56
57 @Component("application-datatype-cache")
58 public class ApplicationDataTypeCache implements ApplicationCache<DataTypeDefinition>, Runnable {
59
60     private static final String APPLICATION_DATA_TYPES_CACHE = "ApplicationDataTypesCache";
61     private static final Logger log = Logger.getLogger(ApplicationDataTypeCache.class.getName());
62     private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
63     private final Lock r = rwl.readLock();
64     private final Lock w = rwl.writeLock();
65     ScheduledFuture<?> scheduledFuture = null;
66     private Map<String, DataTypeDefinition> data = new HashMap<>();
67     private ScheduledExecutorService scheduledPollingService = Executors
68         .newScheduledThreadPool(1, new BasicThreadFactory.Builder().namingPattern("ApplicationDataTypeCacheThread-%d").build());
69     private int firstRunDelayInSec = 30;
70     private int pollingIntervalInSec = 60;
71     @Resource
72     private PropertyOperation propertyOperation;
73     @Autowired
74     private ApplicationEventPublisher applicationEventPublisher;
75
76     @PostConstruct
77     public void init() {
78         ApplicationL1CacheConfig applicationL1CacheConfig = ConfigurationManager.getConfigurationManager().getConfiguration().getApplicationL1Cache();
79         if (applicationL1CacheConfig != null) {
80             if (applicationL1CacheConfig.getDatatypes() != null) {
81                 ApplicationL1CacheInfo datatypesInfo = applicationL1CacheConfig.getDatatypes();
82                 if (datatypesInfo.getEnabled()) {
83                     Integer intervalInSec = datatypesInfo.getPollIntervalInSec();
84                     if (intervalInSec != null) {
85                         pollingIntervalInSec = intervalInSec;
86                     }
87                     Integer firstRunDelay = datatypesInfo.getFirstRunDelay();
88                     if (firstRunDelay != null) {
89                         firstRunDelayInSec = firstRunDelay;
90                     }
91                     log.trace("ApplicationDataTypesCache polling interval is {} seconds.", pollingIntervalInSec);
92                     if (scheduledPollingService != null) {
93                         log.debug("Start ApplicationDataTypeCache polling task. polling interval {} seconds", pollingIntervalInSec);
94                         scheduledFuture = scheduledPollingService
95                             .scheduleAtFixedRate(this, firstRunDelayInSec, pollingIntervalInSec, TimeUnit.SECONDS);
96                     }
97                 }
98             } else {
99                 BeEcompErrorManager.getInstance().logInternalFlowError(APPLICATION_DATA_TYPES_CACHE, "Cache is disabled", ErrorSeverity.INFO);
100             }
101         } else {
102             BeEcompErrorManager.getInstance().logInternalFlowError(APPLICATION_DATA_TYPES_CACHE, "Cache is disabled", ErrorSeverity.INFO);
103         }
104     }
105
106     @PreDestroy
107     void destroy() {
108         if (scheduledFuture != null) {
109             boolean result = scheduledFuture.cancel(true);
110             log.debug("Stop polling task. result = {}", result);
111             scheduledFuture = null;
112         }
113         shutdownExecutor();
114     }
115
116     private void shutdownExecutor() {
117         if (scheduledPollingService == null) {
118             return;
119         }
120         scheduledPollingService.shutdown(); // Disable new tasks from being
121
122         // submitted
123         try {
124             // Wait a while for existing tasks to terminate
125             if (!scheduledPollingService.awaitTermination(60, TimeUnit.SECONDS)) {
126                 scheduledPollingService.shutdownNow(); // Cancel currently
127
128                 // executing tasks
129
130                 // Wait a while for tasks to respond to being cancelled
131                 if (!scheduledPollingService.awaitTermination(60, TimeUnit.SECONDS)) {
132                     log.debug("Pool did not terminate");
133                 }
134             }
135         } catch (InterruptedException ie) {
136             // (Re-)Cancel if current thread also interrupted
137             scheduledPollingService.shutdownNow();
138             // Preserve interrupt status
139             Thread.currentThread().interrupt();
140         }
141     }
142
143     private Either<Map<String, DataTypeDefinition>, JanusGraphOperationStatus> getAllDataTypesFromGraph() {
144         return propertyOperation.getAllDataTypes();
145     }
146
147     @Override
148     public Either<Map<String, DataTypeDefinition>, JanusGraphOperationStatus> getAll() {
149         try {
150             r.lock();
151             if (data == null || data.isEmpty()) {
152                 return getAllDataTypesFromGraph();
153             }
154             return Either.left(data);
155         } finally {
156             r.unlock();
157         }
158     }
159
160     @Override
161     public Either<DataTypeDefinition, JanusGraphOperationStatus> get(String uniqueId) {
162         try {
163             r.lock();
164             if (data == null || data.isEmpty()) {
165                 return propertyOperation.getDataTypeByUid(uniqueId);
166             } else {
167                 DataTypeDefinition dataTypeDefinition = data.values().stream().filter(p -> p.getUniqueId().equals(uniqueId)).findFirst().orElse(null);
168                 if (dataTypeDefinition == null) {
169                     return propertyOperation.getDataTypeByUid(uniqueId);
170                 } else {
171                     return Either.left(dataTypeDefinition);
172                 }
173             }
174         } finally {
175             r.unlock();
176         }
177     }
178
179     @Override
180     public void run() {
181         log.trace("run() method. polling db to fetch data types");
182         try {
183             Long start = System.currentTimeMillis();
184             log.trace("Start fetching all data types from db");
185             Either<List<DataTypeData>, JanusGraphOperationStatus> allDataTypeNodes = propertyOperation.getAllDataTypeNodes();
186             Long end = System.currentTimeMillis();
187             log.trace("Finish fetching all data types from db. Took {} Milliseconds", (end - start));
188             if (allDataTypeNodes.isRight()) {
189                 JanusGraphOperationStatus status = allDataTypeNodes.right().value();
190                 if (status != JanusGraphOperationStatus.OK) {
191                     log.debug("ApplicationDataTypesCache - Failed to fetch all data types nodes");
192                     BeEcompErrorManager.getInstance()
193                         .logInternalConnectionError("FetchDataTypes", "Failed to fetch data types from graph(cache)", ErrorSeverity.INFO);
194                 }
195             } else {
196                 List<DataTypeData> list = allDataTypeNodes.left().value();
197                 if (list != null) {
198                     Map<String, ImmutablePair<Long, Long>> dataTypeNameToModificationTime = list.stream().collect(Collectors
199                         .toMap(p -> p.getDataTypeDataDefinition().getName(), p -> new ImmutablePair<>(p.getDataTypeDataDefinition().getCreationTime(),
200                             p.getDataTypeDataDefinition().getModificationTime())));
201                     Map<String, ImmutablePair<Long, Long>> currentDataTypeToModificationTime = new HashMap<>();
202                     try {
203                         r.lock();
204                         if (data != null) {
205                             currentDataTypeToModificationTime = data.values().stream().collect(Collectors
206                                 .toMap(DataTypeDataDefinition::getName, p -> new ImmutablePair<>(p.getCreationTime(), p.getModificationTime())));
207                         }
208                     } finally {
209                         r.unlock();
210                     }
211                     boolean isChanged = compareDataTypes(dataTypeNameToModificationTime, currentDataTypeToModificationTime);
212                     if (isChanged) {
213                         replaceAllData();
214                     }
215                 }
216             }
217         } catch (Exception e) {
218             log.debug("unexpected error occured", e);
219             BeEcompErrorManager.getInstance()
220                 .logInternalUnexpectedError(APPLICATION_DATA_TYPES_CACHE, "Failed to run refresh data types job", ErrorSeverity.INFO);
221         } finally {
222             try {
223                 propertyOperation.getJanusGraphGenericDao().commit();
224             } catch (Exception e) {
225                 log.trace("Failed to commit ApplicationDataTypeCache", e);
226             }
227         }
228     }
229
230     private boolean compareDataTypes(Map<String, ImmutablePair<Long, Long>> dataTypeNameToModificationTime,
231                                      Map<String, ImmutablePair<Long, Long>> currentDataTypeToModificationTime) {
232         if (dataTypeNameToModificationTime.size() != currentDataTypeToModificationTime.size()) {
233             return true;
234         } else {
235             Set<String> currentkeySet = currentDataTypeToModificationTime.keySet();
236             Set<String> keySet = dataTypeNameToModificationTime.keySet();
237             if (currentkeySet.containsAll(keySet)) {
238                 for (Entry<String, ImmutablePair<Long, Long>> entry : dataTypeNameToModificationTime.entrySet()) {
239                     String dataTypeName = entry.getKey();
240                     ImmutablePair<Long, Long> creationAndModificationTimes = entry.getValue();
241                     long creationTime = creationAndModificationTimes.getLeft() == null ? 0 : creationAndModificationTimes.getLeft().longValue();
242                     long modificationTime = creationAndModificationTimes.getRight() == null ? 0 : creationAndModificationTimes.getRight().longValue();
243                     ImmutablePair<Long, Long> currentEntry = currentDataTypeToModificationTime.get(dataTypeName);
244                     long currentCreationTime = currentEntry.getLeft() == null ? 0 : currentEntry.getLeft().longValue();
245                     long currentModificationTime = currentEntry.getRight() == null ? 0 : currentEntry.getRight().longValue();
246                     if (creationTime > currentCreationTime || modificationTime > currentModificationTime) {
247                         log.debug("Datatype {} was updated. Creation Time  {} vs {}. Modification Time {} vs {}", dataTypeName, currentCreationTime,
248                             creationTime, currentModificationTime, modificationTime);
249                         return true;
250                     }
251                 }
252             } else {
253                 return true;
254             }
255         }
256         return false;
257     }
258
259     private void replaceAllData() {
260         Either<Map<String, DataTypeDefinition>, JanusGraphOperationStatus> allDataTypes = propertyOperation.getAllDataTypes();
261         if (allDataTypes.isRight()) {
262             JanusGraphOperationStatus status = allDataTypes.right().value();
263             log.debug("Failed to fetch all data types from db. Status is {}", status);
264         } else {
265             try {
266                 w.lock();
267                 data = allDataTypes.left().value();
268                 // send notification on data types change
269                 onDataChangeEventEmit(data);
270                 BeEcompErrorManager.getInstance()
271                     .logInternalFlowError("ReplaceDataTypesCache", "Succeed to replace the data types cache", ErrorSeverity.INFO);
272             } finally {
273                 w.unlock();
274             }
275         }
276     }
277
278     private void onDataChangeEventEmit(Map<String, DataTypeDefinition> newData) {
279         log.trace("Cache data has changed, sending event to all listening for this change.");
280         DataTypesCacheChangedEvent dataTypesCacheChangedEvent = new DataTypesCacheChangedEvent(this, newData);
281         applicationEventPublisher.publishEvent(dataTypesCacheChangedEvent);
282     }
283
284     /**
285      * Custom event to notify all interested in cached data changes
286      */
287     public static class DataTypesCacheChangedEvent extends ApplicationEvent {
288
289         @Getter
290         private Map<String, DataTypeDefinition> newData;
291
292         public DataTypesCacheChangedEvent(Object source, Map<String, DataTypeDefinition> newData) {
293             super(source);
294             this.newData = newData;
295         }
296     }
297 }