001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017package org.apache.logging.log4j.core.util; 018 019import java.io.File; 020import java.util.ArrayList; 021import java.util.Date; 022import java.util.HashMap; 023import java.util.List; 024import java.util.Map; 025import java.util.ServiceLoader; 026import java.util.UUID; 027import java.util.concurrent.ConcurrentHashMap; 028import java.util.concurrent.ConcurrentMap; 029import java.util.concurrent.ScheduledFuture; 030import java.util.concurrent.TimeUnit; 031 032import org.apache.logging.log4j.Logger; 033import org.apache.logging.log4j.core.AbstractLifeCycle; 034import org.apache.logging.log4j.core.config.ConfigurationFileWatcher; 035import org.apache.logging.log4j.core.config.ConfigurationScheduler; 036import org.apache.logging.log4j.status.StatusLogger; 037import org.apache.logging.log4j.util.LoaderUtil; 038 039/** 040 * Manages {@link FileWatcher}s. 041 * 042 * @see FileWatcher 043 * @see ConfigurationScheduler 044 */ 045public class WatchManager extends AbstractLifeCycle { 046 047 private static Logger logger = StatusLogger.getLogger(); 048 private final ConcurrentMap<Source, ConfigurationMonitor> watchers = new ConcurrentHashMap<>(); 049 private int intervalSeconds = 0; 050 private ScheduledFuture<?> future; 051 private final ConfigurationScheduler scheduler; 052 private final List<WatchEventService> eventServiceList; 053 private final UUID id = UuidUtil.getTimeBasedUuid(); 054 055 public WatchManager(final ConfigurationScheduler scheduler) { 056 this.scheduler = scheduler; 057 eventServiceList = getEventServices(); 058 } 059 060 public UUID getId() { 061 return this.id; 062 } 063 064 public boolean hasEventListeners() { 065 return eventServiceList.size() > 0; 066 } 067 068 /** 069 * Resets all file monitors to their current last modified time. If this manager does not watch any file, nothing 070 * happens. 071 * <p> 072 * This allows you to start, stop, reset and start again a manager, without triggering file modified events if the a 073 * watched file has changed during the period of time when the manager was stopped. 074 * </p> 075 * 076 * @since 2.11.0 077 */ 078 public void reset() { 079 logger.debug("Resetting {}", this); 080 for (final Source source : watchers.keySet()) { 081 reset(source); 082 } 083 } 084 085 /** 086 * Resets the file monitor for the given file being watched to its current last modified time. If this manager does 087 * not watch the given file, nothing happens. 088 * <p> 089 * This allows you to start, stop, reset and start again a manager, without triggering file modified events if the 090 * given watched file has changed during the period of time when the manager was stopped. 091 * </p> 092 * 093 * @param file the file for the monitor to reset. 094 * @since 2.11.0 095 */ 096 public void reset(final File file) { 097 if (file == null) { 098 return; 099 } 100 Source source = new Source(file); 101 reset(source); 102 } 103 104 /** 105 * Resets the configuration monitor for the given file being watched to its current last modified time. If this 106 * manager does not watch the given configuration, nothing happens. 107 * <p> 108 * This allows you to start, stop, reset and start again a manager, without triggering file modified events if the 109 * given watched configuration has changed during the period of time when the manager was stopped. 110 * </p> 111 * 112 * @param source the Source for the monitor to reset. 113 * @since 2.12.0 114 */ 115 public void reset(final Source source) { 116 if (source == null) { 117 return; 118 } 119 final ConfigurationMonitor monitor = watchers.get(source); 120 if (monitor != null) { 121 Watcher watcher = monitor.getWatcher(); 122 if (watcher.isModified()) { 123 final long lastModifiedMillis = watcher.getLastModified(); 124 if (logger.isDebugEnabled()) { 125 logger.debug("Resetting file monitor for '{}' from {} ({}) to {} ({})", source.getLocation(), 126 millisToString(monitor.lastModifiedMillis), monitor.lastModifiedMillis, 127 millisToString(lastModifiedMillis), lastModifiedMillis); 128 } 129 monitor.setLastModifiedMillis(lastModifiedMillis); 130 } 131 } 132 } 133 134 public void setIntervalSeconds(final int intervalSeconds) { 135 if (!isStarted()) { 136 if (this.intervalSeconds > 0 && intervalSeconds == 0) { 137 scheduler.decrementScheduledItems(); 138 } else { 139 if (this.intervalSeconds == 0 && intervalSeconds > 0) { 140 scheduler.incrementScheduledItems(); 141 } 142 } 143 this.intervalSeconds = intervalSeconds; 144 } 145 } 146 147 /** 148 * Gets how often this manager checks for file modifications. 149 * 150 * @return how often, in seconds, this manager checks for file modifications. 151 */ 152 public int getIntervalSeconds() { 153 return this.intervalSeconds; 154 } 155 156 @Override 157 public void start() { 158 super.start(); 159 160 if (intervalSeconds > 0) { 161 future = scheduler 162 .scheduleWithFixedDelay(new WatchRunnable(), intervalSeconds, intervalSeconds, TimeUnit.SECONDS); 163 } 164 for (WatchEventService service : eventServiceList) { 165 service.subscribe(this); 166 } 167 } 168 169 @Override 170 public boolean stop(final long timeout, final TimeUnit timeUnit) { 171 setStopping(); 172 for (WatchEventService service : eventServiceList) { 173 service.unsubscribe(this); 174 } 175 final boolean stopped = stop(future); 176 setStopped(); 177 return stopped; 178 } 179 180 /** 181 * Unwatches the given file. 182 * 183 * @param file the file to stop watching. 184 * @since 2.11.0 185 */ 186 public void unwatchFile(final File file) { 187 Source source = new Source(file); 188 unwatch(source); 189 } 190 191 /** 192 * Unwatches the given file. 193 * 194 * @param source the Source to stop watching. 195 * the file to stop watching. 196 * @since 2.12.0 197 */ 198 public void unwatch(final Source source) { 199 logger.debug("Unwatching configuration {}", source); 200 watchers.remove(source); 201 } 202 203 public void checkFiles() { 204 new WatchRunnable().run(); 205 } 206 207 /** 208 * Watches the given file. 209 * 210 * @param file the file to watch. 211 * @param fileWatcher the watcher to notify of file changes. 212 */ 213 public void watchFile(final File file, final FileWatcher fileWatcher) { 214 Watcher watcher; 215 if (fileWatcher instanceof Watcher) { 216 watcher = (Watcher) fileWatcher; 217 } else { 218 watcher = new WrappedFileWatcher(fileWatcher); 219 } 220 Source source = new Source(file); 221 watch(source, watcher); 222 } 223 224 /** 225 * Watches the given file. 226 * 227 * @param source the source to watch. 228 * @param watcher the watcher to notify of file changes. 229 */ 230 public void watch(final Source source, final Watcher watcher) { 231 watcher.watching(source); 232 final long lastModified = watcher.getLastModified(); 233 if (logger.isDebugEnabled()) { 234 logger.debug("Watching configuration '{}' for lastModified {} ({})", source, millisToString(lastModified), 235 lastModified); 236 } 237 watchers.put(source, new ConfigurationMonitor(lastModified, watcher)); 238 } 239 240 /** 241 * Returns a Map of the file watchers. 242 * 243 * @return A Map of the file watchers. 244 * @deprecated use getConfigurationWatchers. 245 */ 246 public Map<File, FileWatcher> getWatchers() { 247 final Map<File, FileWatcher> map = new HashMap<>(watchers.size()); 248 for (Map.Entry<Source, ConfigurationMonitor> entry : watchers.entrySet()) { 249 if (entry.getValue().getWatcher() instanceof ConfigurationFileWatcher) { 250 map.put(entry.getKey().getFile(), (FileWatcher) entry.getValue().getWatcher()); 251 } else { 252 map.put(entry.getKey().getFile(), new WrappedFileWatcher((FileWatcher) entry.getValue().getWatcher())); 253 } 254 } 255 return map; 256 } 257 258 /** 259 * Return the ConfigurationWaatchers. 260 * 261 * @return the ConfigurationWatchers. 262 * @since 2.11.2 263 */ 264 public Map<Source, Watcher> getConfigurationWatchers() { 265 final Map<Source, Watcher> map = new HashMap<>(watchers.size()); 266 for (final Map.Entry<Source, ConfigurationMonitor> entry : watchers.entrySet()) { 267 map.put(entry.getKey(), entry.getValue().getWatcher()); 268 } 269 return map; 270 } 271 272 private String millisToString(final long millis) { 273 return new Date(millis).toString(); 274 } 275 276 private List<WatchEventService> getEventServices() { 277 List<WatchEventService> list = new ArrayList<>(); 278 for (final ClassLoader classLoader : LoaderUtil.getClassLoaders()) { 279 try { 280 final ServiceLoader<WatchEventService> serviceLoader = ServiceLoader 281 .load(WatchEventService.class, classLoader); 282 for (final WatchEventService service : serviceLoader) { 283 list.add(service); 284 } 285 } catch (final Throwable ex) { 286 LOGGER.debug("Unable to retrieve WatchEventService from ClassLoader {}", classLoader, ex); 287 } 288 } 289 return list; 290 } 291 292 private final class WatchRunnable implements Runnable { 293 294 // Use a hard class reference here in case a refactoring changes the class name. 295 private final String SIMPLE_NAME = WatchRunnable.class.getSimpleName(); 296 297 @Override 298 public void run() { 299 logger.trace("{} run triggered.", SIMPLE_NAME); 300 for (final Map.Entry<Source, ConfigurationMonitor> entry : watchers.entrySet()) { 301 final Source source = entry.getKey(); 302 final ConfigurationMonitor monitor = entry.getValue(); 303 if (monitor.getWatcher().isModified()) { 304 final long lastModified = monitor.getWatcher().getLastModified(); 305 if (logger.isInfoEnabled()) { 306 logger.info("Source '{}' was modified on {} ({}), previous modification was on {} ({})", source, 307 millisToString(lastModified), lastModified, millisToString(monitor.lastModifiedMillis), 308 monitor.lastModifiedMillis); 309 } 310 monitor.lastModifiedMillis = lastModified; 311 monitor.getWatcher().modified(); 312 } 313 } 314 logger.trace("{} run ended.", SIMPLE_NAME); 315 } 316 } 317 318 private final class ConfigurationMonitor { 319 private final Watcher watcher; 320 private volatile long lastModifiedMillis; 321 322 public Watcher getWatcher() { 323 return watcher; 324 } 325 326 public ConfigurationMonitor(final long lastModifiedMillis, final Watcher watcher) { 327 this.watcher = watcher; 328 this.lastModifiedMillis = lastModifiedMillis; 329 } 330 331 private void setLastModifiedMillis(final long lastModifiedMillis) { 332 this.lastModifiedMillis = lastModifiedMillis; 333 } 334 335 @Override 336 public String toString() { 337 return "ConfigurationMonitor [watcher=" + watcher + ", lastModifiedMillis=" + lastModifiedMillis + "]"; 338 } 339 340 } 341 342 @Override 343 public String toString() { 344 return "WatchManager [intervalSeconds=" + intervalSeconds + ", watchers=" + watchers + ", scheduler=" 345 + scheduler + ", future=" + future + "]"; 346 } 347}