2 * ============LICENSE_START=======================================================
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
12 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
22 package org.openecomp.mso.bpmn.core;
24 import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
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;
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;
49 import org.openecomp.mso.logger.MessageEnum;
50 import org.openecomp.mso.logger.MsoLogger;
53 * Loads the property configuration from file system and refreshes the
54 * properties when the property gets changed.
56 * WARNING: automatic refreshes might not work on network filesystems.
58 public class PropertyConfiguration {
61 * The base name of the MSO BPMN properties file (mso.bpmn.properties).
63 public static final String MSO_BPMN_PROPERTIES = "mso.bpmn.properties";
66 * The base name of the MSO BPMN URN-Mappings properties file (mso.bpmn.urn.properties).
68 public static final String MSO_BPMN_URN_PROPERTIES = "mso.bpmn.urn.properties";
71 * The base name of the MSO Topology properties file (topology.properties).
73 public static final String MSO_TOPOLOGY_PROPERTIES = "topology.properties";
75 * The name of the meta-property holding the time the properties were loaded
78 public static final String TIMESTAMP_PROPERTY = "mso.properties.timestamp";
80 private static final MsoLogger LOGGER = MsoLogger.getMsoLogger(MsoLogger.Catalog.BPEL);
82 private static final List<String> SUPPORTED_FILES =
83 Arrays.asList(MSO_BPMN_PROPERTIES, MSO_BPMN_URN_PROPERTIES, MSO_TOPOLOGY_PROPERTIES);
85 private volatile String msoConfigPath = null;
87 private final ConcurrentHashMap<String, Map<String, String>> propFileCache =
88 new ConcurrentHashMap<String, Map<String, String>>();
90 private final Object CACHELOCK = new Object();
91 private FileWatcherThread fileWatcherThread = null;
93 // The key is the file name
94 private Map<String, TimerTask> timerTaskMap = new HashMap<String, TimerTask>();
97 * Singleton holder pattern eliminates locking when accessing the instance
98 * and still provides for lazy initialization.
100 private static class PropertyConfigurationInstanceHolder {
101 private static PropertyConfiguration instance = new PropertyConfiguration();
105 * Gets the one and only instance of this class.
107 public static PropertyConfiguration getInstance() {
108 return PropertyConfigurationInstanceHolder.instance;
112 * Returns the list of supported files.
114 public static List<String> supportedFiles() {
115 return new ArrayList<String>(SUPPORTED_FILES);
119 * Private Constructor.
121 private PropertyConfiguration() {
126 * May be called to restart the PropertyConfiguration if it was previously shut down.
128 public synchronized void startUp() {
129 msoConfigPath = System.getProperty("mso.config.path");
131 if (msoConfigPath == null) {
132 LOGGER.debug("mso.config.path JVM system property is not set");
137 Path directory = FileSystems.getDefault().getPath(msoConfigPath);
138 WatchService watchService = FileSystems.getDefault().newWatchService();
139 directory.register(watchService, ENTRY_MODIFY);
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);
148 MessageEnum.BPMN_GENERAL_EXCEPTION,
150 "Property Configuration",
151 MsoLogger.ErrorCode.UnknownError,
152 "Error occurred while starting FileWatcherThread:" + e);
157 * May be called to shut down the PropertyConfiguration. A shutDown followed
158 * by a startUp will reset the PropertyConfiguration to its initial state.
160 public synchronized void shutDown() {
161 if (fileWatcherThread != null) {
162 LOGGER.debug("Shutting down FileWatcherThread " + System.identityHashCode(fileWatcherThread));
163 fileWatcherThread.shutdown();
165 long waitInSeconds = 10;
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);
174 LOGGER.debug("Finished shutting down FileWatcherThread " + System.identityHashCode(fileWatcherThread));
175 fileWatcherThread = null;
179 msoConfigPath = null;
182 public synchronized boolean isFileWatcherRunning() {
183 return fileWatcherThread != null;
186 public void clearCache() {
187 synchronized(CACHELOCK) {
188 propFileCache.clear();
192 public int cacheSize() {
193 return propFileCache.size();
196 // TODO: throw IOException?
197 public Map<String, String> getProperties(String fileName) {
198 Map<String, String> properties = propFileCache.get(fileName);
200 if (properties == null) {
201 if (!SUPPORTED_FILES.contains(fileName)) {
202 throw new IllegalArgumentException("Not a supported property file: " + fileName);
205 if (msoConfigPath == null) {
206 LOGGER.debug("mso.config.path JVM system property must be set to load " + fileName);
209 MessageEnum.BPMN_GENERAL_EXCEPTION,
212 MsoLogger.ErrorCode.UnknownError,
213 "mso.config.path JVM system property must be set to load " + fileName);
219 properties = readProperties(new File(msoConfigPath, fileName));
220 } catch (Exception e) {
221 LOGGER.debug("Error loading " + fileName);
224 MessageEnum.BPMN_GENERAL_EXCEPTION,
227 MsoLogger.ErrorCode.UnknownError,
228 "Error loading " + fileName, e);
234 return Collections.unmodifiableMap(properties);
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
244 private Map<String, String> readProperties(File file) throws IOException {
245 String fileName = file.getName();
246 LOGGER.debug("Reading " + fileName);
248 Map<String, String> properties = new HashMap<String, String>();
249 Properties newProperties = new Properties();
251 FileReader reader = null;
253 reader = new FileReader(file);
254 newProperties.load(reader);
256 if (reader != null) {
259 LOGGER.debug("Closed " + fileName);
260 } catch (Exception e) {
261 LOGGER.debug("Exception :",e);
266 for (Entry<Object, Object> entry : newProperties.entrySet()) {
267 properties.put(entry.getKey().toString(), entry.getValue().toString());
270 properties.put(TIMESTAMP_PROPERTY, String.valueOf(System.currentTimeMillis()));
272 synchronized(CACHELOCK) {
273 propFileCache.put(fileName, properties);
280 * File watcher thread which monitors a directory for file modification.
282 private class FileWatcherThread extends Thread {
283 private final WatchService watchService;
284 private final Timer timer = new Timer("FileWatcherTimer");
286 public FileWatcherThread(WatchService service) {
287 this.watchService = service;
290 public void shutdown() {
295 LOGGER.info(MessageEnum.BPMN_GENERAL_INFO, "BPMN",
296 "FileWatcherThread started");
298 LOGGER.debug("Started FileWatcherThread " + System.identityHashCode(fileWatcherThread));
301 WatchKey watchKey = null;
303 while (!isInterrupted()) {
305 if (watchKey != null) {
309 watchKey = watchService.take();
311 for (WatchEvent<?> event : watchKey.pollEvents()) {
312 @SuppressWarnings("unchecked")
313 WatchEvent<Path> pathEvent = (WatchEvent<Path>) event;
315 if ("EVENT_OVERFLOW".equals(pathEvent.kind())) {
316 LOGGER.debug("Ignored overflow event for " + msoConfigPath);
320 String fileName = pathEvent.context().getFileName().toString();
322 if (!SUPPORTED_FILES.contains(fileName)) {
323 LOGGER.debug("Ignored modify event for " + fileName);
327 LOGGER.debug("Configuration file has changed: " + fileName);
329 LOGGER.info(MessageEnum.BPMN_GENERAL_INFO, "BPMN",
330 "Configuation file has changed: " + fileName);
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.
342 synchronized(timerTaskMap) {
343 TimerTask task = timerTaskMap.get(fileName);
349 File file = new File(msoConfigPath, fileName);
350 task = new DelayTimerTask(timer, file, 1000);
351 timerTaskMap.put(fileName, task);
354 } catch (InterruptedException e) {
355 LOGGER.debug("InterruptedException :",e);
357 } catch (ClosedWatchServiceException e) {
359 MessageEnum.BPMN_GENERAL_INFO,
361 "FileWatcherThread shut down because the watch service was closed");
362 LOGGER.debug("ClosedWatchServiceException :",e);
364 } catch (Exception e) {
366 MessageEnum.BPMN_GENERAL_EXCEPTION,
368 "Property Configuration",
369 MsoLogger.ErrorCode.UnknownError,
370 "FileWatcherThread caught unexpected " + e.getClass().getSimpleName(), e);
377 synchronized(timerTaskMap) {
378 timerTaskMap.clear();
382 watchService.close();
383 } catch (IOException e) {
384 LOGGER.debug("FileWatcherThread caught " + e.getClass().getSimpleName()
385 + " while closing the watch service",e);
388 LOGGER.info(MessageEnum.BPMN_GENERAL_INFO, "BPMN",
389 "FileWatcherThread stopped");
394 private class DelayTimerTask extends TimerTask {
395 private final File file;
396 private final long lastModifiedTime;
397 private final Timer timer;
399 public DelayTimerTask(Timer timer, File file, long delay) {
402 this.lastModifiedTime = file.lastModified();
403 timer.schedule(this, delay);
409 long newLastModifiedTime = file.lastModified();
411 if (newLastModifiedTime == lastModifiedTime) {
413 readProperties(file);
414 } catch (Exception e) {
416 MessageEnum.BPMN_GENERAL_EXCEPTION,
418 "Property Configuration",
419 MsoLogger.ErrorCode.UnknownError,
420 "Unable to reload " + file, e);
423 LOGGER.debug("Delaying reload of " + file + " by 1 second");
425 synchronized(timerTaskMap) {
426 TimerTask task = timerTaskMap.get(file.getName());
428 if (task != null && task != this) {
432 task = new DelayTimerTask(timer, file, 1000);
433 timerTaskMap.put(file.getName(), task);
437 synchronized(timerTaskMap) {
438 TimerTask task = timerTaskMap.get(file.getName());
441 timerTaskMap.remove(file.getName());