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=========================================================
21 package org.openecomp.mso.bpmn.core;
23 import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
26 import java.io.FileReader;
27 import java.io.IOException;
28 import java.nio.file.ClosedWatchServiceException;
29 import java.nio.file.FileSystems;
30 import java.nio.file.Path;
31 import java.nio.file.WatchEvent;
32 import java.nio.file.WatchKey;
33 import java.nio.file.WatchService;
34 import java.util.ArrayList;
35 import java.util.Arrays;
36 import java.util.Collections;
37 import java.util.HashMap;
38 import java.util.List;
40 import java.util.Map.Entry;
41 import java.util.Properties;
42 import java.util.Timer;
43 import java.util.TimerTask;
44 import java.util.concurrent.ConcurrentHashMap;
48 import org.openecomp.mso.logger.MessageEnum;
49 import org.openecomp.mso.logger.MsoLogger;
52 * Loads the property configuration from file system and refreshes the
53 * properties when the property gets changed.
55 * WARNING: automatic refreshes might not work on network filesystems.
57 public class PropertyConfiguration {
60 * The base name of the MSO BPMN properties file (mso.bpmn.properties).
62 public static final String MSO_BPMN_PROPERTIES = "mso.bpmn.properties";
65 * The base name of the MSO BPMN URN-Mappings properties file (mso.bpmn.urn.properties).
67 public static final String MSO_BPMN_URN_PROPERTIES = "mso.bpmn.urn.properties";
70 * The base name of the MSO Topology properties file (topology.properties).
72 public static final String MSO_TOPOLOGY_PROPERTIES = "topology.properties";
74 * The name of the meta-property holding the time the properties were loaded
77 public static final String TIMESTAMP_PROPERTY = "mso.properties.timestamp";
79 private static final MsoLogger LOGGER = MsoLogger.getMsoLogger(MsoLogger.Catalog.BPEL);
81 private static final List<String> SUPPORTED_FILES =
82 Arrays.asList(MSO_BPMN_PROPERTIES, MSO_BPMN_URN_PROPERTIES, MSO_TOPOLOGY_PROPERTIES);
84 private volatile String msoConfigPath = null;
86 private final ConcurrentHashMap<String, Map<String, String>> propFileCache =
87 new ConcurrentHashMap<String, Map<String, String>>();
89 private final Object CACHELOCK = new Object();
90 private FileWatcherThread fileWatcherThread = null;
92 // The key is the file name
93 private Map<String, TimerTask> timerTaskMap = new HashMap<String, TimerTask>();
96 * Singleton holder pattern eliminates locking when accessing the instance
97 * and still provides for lazy initialization.
99 private static class PropertyConfigurationInstanceHolder {
100 private static PropertyConfiguration instance = new PropertyConfiguration();
104 * Gets the one and only instance of this class.
106 public static PropertyConfiguration getInstance() {
107 return PropertyConfigurationInstanceHolder.instance;
111 * Returns the list of supported files.
113 public static List<String> supportedFiles() {
114 return new ArrayList<String>(SUPPORTED_FILES);
118 * Private Constructor.
120 private PropertyConfiguration() {
125 * May be called to restart the PropertyConfiguration if it was previously shut down.
127 public synchronized void startUp() {
128 msoConfigPath = System.getProperty("mso.config.path");
130 if (msoConfigPath == null) {
131 LOGGER.debug("mso.config.path JVM system property is not set");
136 Path directory = FileSystems.getDefault().getPath(msoConfigPath);
137 WatchService watchService = FileSystems.getDefault().newWatchService();
138 directory.register(watchService, ENTRY_MODIFY);
140 LOGGER.info(MessageEnum.BPMN_GENERAL_INFO, "BPMN", "Starting FileWatcherThread");
141 LOGGER.debug("Starting FileWatcherThread");
142 fileWatcherThread = new FileWatcherThread(watchService);
143 fileWatcherThread.start();
144 } catch (Exception e) {
145 LOGGER.debug("Error occurred while starting FileWatcherThread:", e);
147 MessageEnum.BPMN_GENERAL_EXCEPTION,
149 "Property Configuration",
150 MsoLogger.ErrorCode.UnknownError,
151 "Error occurred while starting FileWatcherThread:" + e);
156 * May be called to shut down the PropertyConfiguration. A shutDown followed
157 * by a startUp will reset the PropertyConfiguration to its initial state.
159 public synchronized void shutDown() {
160 if (fileWatcherThread != null) {
161 LOGGER.debug("Shutting down FileWatcherThread " + System.identityHashCode(fileWatcherThread));
162 fileWatcherThread.shutdown();
164 long waitInSeconds = 10;
167 fileWatcherThread.join(waitInSeconds * 1000);
168 } catch (InterruptedException e) {
169 LOGGER.debug("FileWatcherThread " + System.identityHashCode(fileWatcherThread)
170 + " shutdown did not occur within " + waitInSeconds + " seconds");
173 LOGGER.debug("Finished shutting down FileWatcherThread " + System.identityHashCode(fileWatcherThread));
174 fileWatcherThread = null;
178 msoConfigPath = null;
181 public synchronized boolean isFileWatcherRunning() {
182 return fileWatcherThread != null;
185 public void clearCache() {
186 synchronized(CACHELOCK) {
187 propFileCache.clear();
191 public int cacheSize() {
192 return propFileCache.size();
195 // TODO: throw IOException?
196 public Map<String, String> getProperties(String fileName) {
197 Map<String, String> properties = propFileCache.get(fileName);
199 if (properties == null) {
200 if (!SUPPORTED_FILES.contains(fileName)) {
201 throw new IllegalArgumentException("Not a supported property file: " + fileName);
204 if (msoConfigPath == null) {
205 LOGGER.debug("mso.config.path JVM system property must be set to load " + fileName);
208 MessageEnum.BPMN_GENERAL_EXCEPTION,
211 MsoLogger.ErrorCode.UnknownError,
212 "mso.config.path JVM system property must be set to load " + fileName);
218 properties = readProperties(new File(msoConfigPath, fileName));
219 } catch (Exception e) {
220 LOGGER.debug("Error loading " + fileName);
223 MessageEnum.BPMN_GENERAL_EXCEPTION,
226 MsoLogger.ErrorCode.UnknownError,
227 "Error loading " + fileName, e);
233 return Collections.unmodifiableMap(properties);
237 * Reads properties from the specified file, updates the property file cache, and
238 * returns the properties in a map.
239 * @param file the file to read
240 * @param reload true if this is a reload event
241 * @return a map of properties
243 private Map<String, String> readProperties(File file) throws IOException {
244 String fileName = file.getName();
245 LOGGER.debug("Reading " + fileName);
247 Map<String, String> properties = new HashMap<String, String>();
248 Properties newProperties = new Properties();
250 FileReader reader = null;
252 reader = new FileReader(file);
253 newProperties.load(reader);
255 if (reader != null) {
258 LOGGER.debug("Closed " + fileName);
259 } catch (Exception e) {
265 for (Entry<Object, Object> entry : newProperties.entrySet()) {
266 properties.put(entry.getKey().toString(), entry.getValue().toString());
269 properties.put(TIMESTAMP_PROPERTY, String.valueOf(System.currentTimeMillis()));
271 synchronized(CACHELOCK) {
272 propFileCache.put(fileName, properties);
279 * File watcher thread which monitors a directory for file modification.
281 private class FileWatcherThread extends Thread {
282 private final WatchService watchService;
283 private final Timer timer = new Timer("FileWatcherTimer");
285 public FileWatcherThread(WatchService service) {
286 this.watchService = service;
289 public void shutdown() {
294 LOGGER.info(MessageEnum.BPMN_GENERAL_INFO, "BPMN",
295 "FileWatcherThread started");
297 LOGGER.debug("Started FileWatcherThread " + System.identityHashCode(fileWatcherThread));
300 WatchKey watchKey = null;
302 while (!isInterrupted()) {
304 if (watchKey != null) {
308 watchKey = watchService.take();
310 for (WatchEvent<?> event : watchKey.pollEvents()) {
311 @SuppressWarnings("unchecked")
312 WatchEvent<Path> pathEvent = (WatchEvent<Path>) event;
314 if ("EVENT_OVERFLOW".equals(pathEvent.kind())) {
315 LOGGER.debug("Ignored overflow event for " + msoConfigPath);
319 String fileName = pathEvent.context().getFileName().toString();
321 if (!SUPPORTED_FILES.contains(fileName)) {
322 LOGGER.debug("Ignored modify event for " + fileName);
326 LOGGER.debug("Configuration file has changed: " + fileName);
328 LOGGER.info(MessageEnum.BPMN_GENERAL_INFO, "BPMN",
329 "Configuation file has changed: " + fileName);
331 // There's a potential problem here. The MODIFY event is
332 // triggered as soon as somebody starts writing the file but
333 // there's no obvious way to know when the write is done. If we
334 // read the file while the write is still in progress, then the
335 // cache can really be messed up. As a workaround, we use a timer
336 // to sleep for at least one second, and then we sleep for as long
337 // as it takes for the file's lastModified time to stop changing.
338 // The timer has another benefit: it consolidates multiple events
339 // that we seem to receive when a file is modified.
341 synchronized(timerTaskMap) {
342 TimerTask task = timerTaskMap.get(fileName);
348 File file = new File(msoConfigPath, fileName);
349 task = new DelayTimerTask(timer, file, 1000);
350 timerTaskMap.put(fileName, task);
353 } catch (InterruptedException e) {
355 } catch (ClosedWatchServiceException e) {
357 MessageEnum.BPMN_GENERAL_INFO,
359 "FileWatcherThread shut down because the watch service was closed");
361 } catch (Exception e) {
363 MessageEnum.BPMN_GENERAL_EXCEPTION,
365 "Property Configuration",
366 MsoLogger.ErrorCode.UnknownError,
367 "FileWatcherThread caught unexpected " + e.getClass().getSimpleName(), e);
374 synchronized(timerTaskMap) {
375 timerTaskMap.clear();
379 watchService.close();
380 } catch (IOException e) {
381 LOGGER.debug("FileWatcherThread caught " + e.getClass().getSimpleName()
382 + " while closing the watch service");
385 LOGGER.info(MessageEnum.BPMN_GENERAL_INFO, "BPMN",
386 "FileWatcherThread stopped");
391 private class DelayTimerTask extends TimerTask {
392 private final File file;
393 private final long lastModifiedTime;
394 private final Timer timer;
396 public DelayTimerTask(Timer timer, File file, long delay) {
399 this.lastModifiedTime = file.lastModified();
400 timer.schedule(this, delay);
406 long newLastModifiedTime = file.lastModified();
408 if (newLastModifiedTime == lastModifiedTime) {
410 readProperties(file);
411 } catch (Exception e) {
413 MessageEnum.BPMN_GENERAL_EXCEPTION,
415 "Property Configuration",
416 MsoLogger.ErrorCode.UnknownError,
417 "Unable to reload " + file, e);
420 LOGGER.debug("Delaying reload of " + file + " by 1 second");
422 synchronized(timerTaskMap) {
423 TimerTask task = timerTaskMap.get(file.getName());
425 if (task != null && task != this) {
429 task = new DelayTimerTask(timer, file, 1000);
430 timerTaskMap.put(file.getName(), task);
434 synchronized(timerTaskMap) {
435 TimerTask task = timerTaskMap.get(file.getName());
438 timerTaskMap.remove(file.getName());