bmpn sonar issue fix
[so.git] / bpmn / MSOCoreBPMN / src / main / java / org / openecomp / mso / bpmn / core / PropertyConfiguration.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP - SO
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6  * Copyright (C) 2017 Huawei Technologies Co., Ltd. All rights reserved.
7  * ================================================================================
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  * ============LICENSE_END=========================================================
20  */
21
22 package org.openecomp.mso.bpmn.core;
23
24 import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
25
26 import java.io.File;
27 import java.io.FileReader;
28 import java.io.IOException;
29 import java.nio.file.ClosedWatchServiceException;
30 import java.nio.file.FileSystems;
31 import java.nio.file.Path;
32 import java.nio.file.WatchEvent;
33 import java.nio.file.WatchKey;
34 import java.nio.file.WatchService;
35 import java.util.ArrayList;
36 import java.util.Arrays;
37 import java.util.Collections;
38 import java.util.HashMap;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.Map.Entry;
42 import java.util.Properties;
43 import java.util.Timer;
44 import java.util.TimerTask;
45 import java.util.concurrent.ConcurrentHashMap;
46
47 import org.slf4j.MDC;
48
49 import org.openecomp.mso.logger.MessageEnum;
50 import org.openecomp.mso.logger.MsoLogger;
51
52 /**
53  * Loads the property configuration from file system and refreshes the
54  * properties when the property gets changed.
55  *
56  * WARNING: automatic refreshes might not work on network filesystems.
57  */
58 public class PropertyConfiguration {
59
60         /**
61          * The base name of the MSO BPMN properties file (mso.bpmn.properties).
62          */
63         public static final String MSO_BPMN_PROPERTIES = "mso.bpmn.properties";
64
65         /**
66          * The base name of the MSO BPMN URN-Mappings properties file (mso.bpmn.urn.properties).
67          */
68         public static final String MSO_BPMN_URN_PROPERTIES = "mso.bpmn.urn.properties";
69
70         /**
71          * The base name of the MSO Topology properties file (topology.properties).
72          */
73         public static final String MSO_TOPOLOGY_PROPERTIES = "topology.properties";
74         /**
75          * The name of the meta-property holding the time the properties were loaded
76          * from the file.
77          */
78         public static final String TIMESTAMP_PROPERTY = "mso.properties.timestamp";
79
80         private static final MsoLogger LOGGER = MsoLogger.getMsoLogger(MsoLogger.Catalog.BPEL);
81
82         private static final List<String> SUPPORTED_FILES =
83                 Arrays.asList(MSO_BPMN_PROPERTIES, MSO_BPMN_URN_PROPERTIES, MSO_TOPOLOGY_PROPERTIES);
84
85         private volatile String msoConfigPath = null;
86
87         private final ConcurrentHashMap<String, Map<String, String>> propFileCache =
88                 new ConcurrentHashMap<String, Map<String, String>>();
89
90         private final Object CACHELOCK = new Object();
91         private FileWatcherThread fileWatcherThread = null;
92
93         // The key is the file name
94         private Map<String, TimerTask> timerTaskMap = new HashMap<String, TimerTask>();
95
96         /**
97          * Singleton holder pattern eliminates locking when accessing the instance
98          * and still provides for lazy initialization.
99          */
100         private static class PropertyConfigurationInstanceHolder {
101                 private static PropertyConfiguration instance = new PropertyConfiguration();
102         }
103
104         /**
105          * Gets the one and only instance of this class.
106          */
107         public static PropertyConfiguration getInstance() {
108                 return PropertyConfigurationInstanceHolder.instance;
109         }
110
111         /**
112          * Returns the list of supported files.
113          */
114         public static List<String> supportedFiles() {
115                 return new ArrayList<String>(SUPPORTED_FILES);
116         }
117
118         /**
119          * Private Constructor.
120          */
121         private PropertyConfiguration() {
122                 startUp();
123         }
124
125         /**
126          * May be called to restart the PropertyConfiguration if it was previously shut down.
127          */
128         public synchronized void startUp() {
129                 msoConfigPath = System.getProperty("mso.config.path");
130
131                 if (msoConfigPath == null) {
132                         LOGGER.debug("mso.config.path JVM system property is not set");
133                         return;
134                 }
135
136                 try {
137                         Path directory = FileSystems.getDefault().getPath(msoConfigPath);
138                         WatchService watchService = FileSystems.getDefault().newWatchService();
139                         directory.register(watchService, ENTRY_MODIFY);
140
141                         LOGGER.info(MessageEnum.BPMN_GENERAL_INFO, "BPMN", "Starting FileWatcherThread");
142                         LOGGER.debug("Starting FileWatcherThread");
143                         fileWatcherThread = new FileWatcherThread(watchService);
144                         fileWatcherThread.start();
145                 } catch (Exception e) {
146                         LOGGER.debug("Error occurred while starting FileWatcherThread:", e);
147                         LOGGER.error(
148                                 MessageEnum.BPMN_GENERAL_EXCEPTION,
149                                 "BPMN",
150                                 "Property Configuration",
151                                 MsoLogger.ErrorCode.UnknownError,
152                                 "Error occurred while starting FileWatcherThread:" + e);
153                 }
154         }
155
156         /**
157          * May be called to shut down the PropertyConfiguration.  A shutDown followed
158          * by a startUp will reset the PropertyConfiguration to its initial state.
159          */
160         public synchronized void shutDown() {
161                 if (fileWatcherThread != null) {
162                         LOGGER.debug("Shutting down FileWatcherThread " + System.identityHashCode(fileWatcherThread));
163                         fileWatcherThread.shutdown();
164
165                         long waitInSeconds = 10;
166
167                         try {
168                                 fileWatcherThread.join(waitInSeconds * 1000);
169                         } catch (InterruptedException e) {
170                                 LOGGER.debug("FileWatcherThread " + System.identityHashCode(fileWatcherThread)
171                                         + " shutdown did not occur within " + waitInSeconds + " seconds",e);
172                         }
173
174                         LOGGER.debug("Finished shutting down FileWatcherThread " + System.identityHashCode(fileWatcherThread));
175                         fileWatcherThread = null;
176                 }
177
178                 clearCache();
179                 msoConfigPath = null;
180         }
181
182         public synchronized boolean isFileWatcherRunning() {
183                 return fileWatcherThread != null;
184         }
185
186         public void clearCache() {
187                 synchronized(CACHELOCK) {
188                         propFileCache.clear();
189                 }
190         }
191
192         public int cacheSize() {
193                 return propFileCache.size();
194         }
195
196         // TODO: throw IOException?
197         public Map<String, String> getProperties(String fileName) {
198                 Map<String, String> properties = propFileCache.get(fileName);
199
200                 if (properties == null) {
201                         if (!SUPPORTED_FILES.contains(fileName)) {
202                                 throw new IllegalArgumentException("Not a supported property file: " + fileName);
203                         }
204
205                         if (msoConfigPath == null) {
206                                 LOGGER.debug("mso.config.path JVM system property must be set to load " + fileName);
207
208                                 LOGGER.error(
209                                                 MessageEnum.BPMN_GENERAL_EXCEPTION,
210                                                 "BPMN",
211                                                 MDC.get(fileName),
212                                                 MsoLogger.ErrorCode.UnknownError,
213                                                 "mso.config.path JVM system property must be set to load " + fileName);
214
215                                 return null;
216                         }
217
218                         try {
219                                 properties = readProperties(new File(msoConfigPath, fileName));
220                         } catch (Exception e) {
221                                 LOGGER.debug("Error loading " + fileName);
222
223                                 LOGGER.error(
224                                                 MessageEnum.BPMN_GENERAL_EXCEPTION,
225                                                 "BPMN",
226                                                 MDC.get(fileName),
227                                                 MsoLogger.ErrorCode.UnknownError,
228                                                 "Error loading " + fileName, e);
229
230                                 return null;
231                         }
232                 }
233
234                 return Collections.unmodifiableMap(properties);
235         }
236
237         /**
238          * Reads properties from the specified file, updates the property file cache, and
239          * returns the properties in a map.
240          * @param file the file to read
241          * @param reload true if this is a reload event
242          * @return a map of properties
243          */
244         private Map<String, String> readProperties(File file) throws IOException {
245                 String fileName = file.getName();
246                 LOGGER.debug("Reading " + fileName);
247
248                 Map<String, String> properties = new HashMap<String, String>();
249                 Properties newProperties = new Properties();
250
251                 FileReader reader = null;
252                 try {
253                         reader = new FileReader(file);
254                         newProperties.load(reader);
255                 } finally {
256                         if (reader != null) {
257                                 try {
258                                         reader.close();
259                                         LOGGER.debug("Closed " + fileName);
260                                 } catch (Exception e) {
261                                         LOGGER.debug("Exception :",e);
262                                 }
263                         }
264                 }
265
266                 for (Entry<Object, Object> entry : newProperties.entrySet()) {
267                         properties.put(entry.getKey().toString(), entry.getValue().toString());
268                 }
269
270                 properties.put(TIMESTAMP_PROPERTY, String.valueOf(System.currentTimeMillis()));
271
272                 synchronized(CACHELOCK) {
273                         propFileCache.put(fileName, properties);
274                 }
275
276                 return properties;
277         }
278
279         /**
280          * File watcher thread which monitors a directory for file modification.
281          */
282         private class FileWatcherThread extends Thread {
283                 private final WatchService watchService;
284                 private final Timer timer = new Timer("FileWatcherTimer");
285
286                 public FileWatcherThread(WatchService service) {
287                         this.watchService = service;
288                 }
289
290                 public void shutdown() {
291                         interrupt();
292                 }
293
294                 public void run() {
295                         LOGGER.info(MessageEnum.BPMN_GENERAL_INFO, "BPMN",
296                                 "FileWatcherThread started");
297
298                         LOGGER.debug("Started FileWatcherThread " + System.identityHashCode(fileWatcherThread));
299
300                         try {
301                                 WatchKey watchKey = null;
302
303                                 while (!isInterrupted()) {
304                                         try {
305                                                 if (watchKey != null) {
306                                                         watchKey.reset();
307                                                 }
308
309                                                 watchKey = watchService.take();
310
311                                                 for (WatchEvent<?> event : watchKey.pollEvents()) {
312                                                         @SuppressWarnings("unchecked")
313                                                         WatchEvent<Path> pathEvent = (WatchEvent<Path>) event;
314
315                                                         if ("EVENT_OVERFLOW".equals(pathEvent.kind())) {
316                                                                 LOGGER.debug("Ignored overflow event for " + msoConfigPath);
317                                                                 continue;
318                                                         }
319
320                                                         String fileName = pathEvent.context().getFileName().toString();
321
322                                                         if (!SUPPORTED_FILES.contains(fileName)) {
323                                                                 LOGGER.debug("Ignored modify event for " + fileName);
324                                                                 continue;
325                                                         }
326
327                                                         LOGGER.debug("Configuration file has changed: " + fileName);
328
329                                                         LOGGER.info(MessageEnum.BPMN_GENERAL_INFO, "BPMN",
330                                                                         "Configuation file has changed: " + fileName);
331
332                                                         // There's a potential problem here. The MODIFY event is
333                                                         // triggered as soon as somebody starts writing the file but
334                                                         // there's no obvious way to know when the write is done.  If we
335                                                         // read the file while the write is still in progress, then the
336                                                         // cache can really be messed up. As a workaround, we use a timer
337                                                         // to sleep for at least one second, and then we sleep for as long
338                                                         // as it takes for the file's lastModified time to stop changing.
339                                                         // The timer has another benefit: it consolidates multiple events
340                                                         // that we seem to receive when a file is modified.
341
342                                                         synchronized(timerTaskMap) {
343                                                                 TimerTask task = timerTaskMap.get(fileName);
344
345                                                                 if (task != null) {
346                                                                         task.cancel();
347                                                                 }
348
349                                                                 File file = new File(msoConfigPath, fileName);
350                                                                 task = new DelayTimerTask(timer, file, 1000);
351                                                                 timerTaskMap.put(fileName, task);
352                                                         }
353                                                 }
354                                         } catch (InterruptedException e) {
355                                                 LOGGER.debug("InterruptedException :",e);
356                                                 break;
357                                         } catch (ClosedWatchServiceException e) {
358                                                 LOGGER.info(
359                                                                 MessageEnum.BPMN_GENERAL_INFO,
360                                                                 "BPMN",
361                                                                 "FileWatcherThread shut down because the watch service was closed");
362                                                 LOGGER.debug("ClosedWatchServiceException :",e);
363                                                 break;
364                                         } catch (Exception e) {
365                                                 LOGGER.error(
366                                                                 MessageEnum.BPMN_GENERAL_EXCEPTION,
367                                                                 "BPMN",
368                                                                 "Property Configuration",
369                                                                 MsoLogger.ErrorCode.UnknownError,
370                                                                 "FileWatcherThread caught unexpected " + e.getClass().getSimpleName(), e);
371                                         }
372
373                                 }
374                         } finally {
375                                 timer.cancel();
376
377                                 synchronized(timerTaskMap) {
378                                         timerTaskMap.clear();
379                                 }
380
381                                 try {
382                                         watchService.close();
383                                 } catch (IOException e) {
384                                         LOGGER.debug("FileWatcherThread caught " + e.getClass().getSimpleName()
385                                                 + " while closing the watch service",e);
386                                 }
387
388                                 LOGGER.info(MessageEnum.BPMN_GENERAL_INFO, "BPMN",
389                                         "FileWatcherThread stopped");
390                         }
391                 }
392         }
393
394         private class DelayTimerTask extends TimerTask {
395                 private final File file;
396                 private final long lastModifiedTime;
397                 private final Timer timer;
398
399                 public DelayTimerTask(Timer timer, File file, long delay) {
400                         this.timer = timer;
401                         this.file = file;
402                         this.lastModifiedTime = file.lastModified();
403                         timer.schedule(this, delay);
404                 }
405
406                 @Override
407                 public void run() {
408                         try {
409                                 long newLastModifiedTime = file.lastModified();
410
411                                 if (newLastModifiedTime == lastModifiedTime) {
412                                         try {
413                                                 readProperties(file);
414                                         } catch (Exception e) {
415                                                 LOGGER.error(
416                                                         MessageEnum.BPMN_GENERAL_EXCEPTION,
417                                                         "BPMN",
418                                                         "Property Configuration",
419                                                         MsoLogger.ErrorCode.UnknownError,
420                                                         "Unable to reload " + file, e);
421                                         }
422                                 } else {
423                                         LOGGER.debug("Delaying reload of " + file + " by 1 second");
424
425                                         synchronized(timerTaskMap) {
426                                                 TimerTask task = timerTaskMap.get(file.getName());
427
428                                                 if (task != null && task != this) {
429                                                         task.cancel();
430                                                 }
431
432                                                 task = new DelayTimerTask(timer, file, 1000);
433                                                 timerTaskMap.put(file.getName(), task);
434                                         }
435                                 }
436                         } finally {
437                                 synchronized(timerTaskMap) {
438                                         TimerTask task = timerTaskMap.get(file.getName());
439
440                                         if (task == this) {
441                                                 timerTaskMap.remove(file.getName());
442                                         }
443                                 }
444                         }
445                 }
446         }
447 }