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.Objects; 026import java.util.ServiceLoader; 027import java.util.UUID; 028import java.util.concurrent.ConcurrentHashMap; 029import java.util.concurrent.ConcurrentMap; 030import java.util.concurrent.ScheduledFuture; 031import java.util.concurrent.TimeUnit; 032import java.util.concurrent.atomic.AtomicInteger; 033 034import org.apache.logging.log4j.Logger; 035import org.apache.logging.log4j.core.AbstractLifeCycle; 036import org.apache.logging.log4j.core.config.ConfigurationFileWatcher; 037import org.apache.logging.log4j.core.config.ConfigurationScheduler; 038import org.apache.logging.log4j.status.StatusLogger; 039import org.apache.logging.log4j.util.LoaderUtil; 040 041/** 042 * Manages {@link FileWatcher}s. 043 * 044 * @see FileWatcher 045 * @see ConfigurationScheduler 046 */ 047public class WatchManager extends AbstractLifeCycle { 048 049 private final class ConfigurationMonitor { 050 private final Watcher watcher; 051 private volatile long lastModifiedMillis; 052 053 public ConfigurationMonitor(final long lastModifiedMillis, final Watcher watcher) { 054 this.watcher = watcher; 055 this.lastModifiedMillis = lastModifiedMillis; 056 } 057 058 public Watcher getWatcher() { 059 return watcher; 060 } 061 062 private void setLastModifiedMillis(final long lastModifiedMillis) { 063 this.lastModifiedMillis = lastModifiedMillis; 064 } 065 066 @Override 067 public String toString() { 068 return "ConfigurationMonitor [watcher=" + watcher + ", lastModifiedMillis=" + lastModifiedMillis + "]"; 069 } 070 } 071 072 private static class LocalUUID { 073 private static final long LOW_MASK = 0xffffffffL; 074 private static final long MID_MASK = 0xffff00000000L; 075 private static final long HIGH_MASK = 0xfff000000000000L; 076 private static final int NODE_SIZE = 8; 077 private static final int SHIFT_2 = 16; 078 private static final int SHIFT_4 = 32; 079 private static final int SHIFT_6 = 48; 080 private static final int HUNDRED_NANOS_PER_MILLI = 10000; 081 private static final long NUM_100NS_INTERVALS_SINCE_UUID_EPOCH = 0x01b21dd213814000L; 082 private static final AtomicInteger COUNT = new AtomicInteger(0); 083 private static final long TYPE1 = 0x1000L; 084 private static final byte VARIANT = (byte) 0x80; 085 private static final int SEQUENCE_MASK = 0x3FFF; 086 087 088 public static UUID get() { 089 final long time = ((System.currentTimeMillis() * HUNDRED_NANOS_PER_MILLI) + 090 NUM_100NS_INTERVALS_SINCE_UUID_EPOCH) + (COUNT.incrementAndGet() % HUNDRED_NANOS_PER_MILLI); 091 final long timeLow = (time & LOW_MASK) << SHIFT_4; 092 final long timeMid = (time & MID_MASK) >> SHIFT_2; 093 final long timeHi = (time & HIGH_MASK) >> SHIFT_6; 094 final long most = timeLow | timeMid | TYPE1 | timeHi; 095 return new UUID(most, COUNT.incrementAndGet()); 096 } 097 } 098 private final class WatchRunnable implements Runnable { 099 100 // Use a hard class reference here in case a refactoring changes the class name. 101 private final String SIMPLE_NAME = WatchRunnable.class.getSimpleName(); 102 103 @Override 104 public void run() { 105 logger.trace("{} run triggered.", SIMPLE_NAME); 106 for (final Map.Entry<Source, ConfigurationMonitor> entry : watchers.entrySet()) { 107 final Source source = entry.getKey(); 108 final ConfigurationMonitor monitor = entry.getValue(); 109 if (monitor.getWatcher().isModified()) { 110 final long lastModified = monitor.getWatcher().getLastModified(); 111 if (logger.isInfoEnabled()) { 112 logger.info("Source '{}' was modified on {} ({}), previous modification was on {} ({})", source, 113 millisToString(lastModified), lastModified, millisToString(monitor.lastModifiedMillis), 114 monitor.lastModifiedMillis); 115 } 116 monitor.lastModifiedMillis = lastModified; 117 monitor.getWatcher().modified(); 118 } 119 } 120 logger.trace("{} run ended.", SIMPLE_NAME); 121 } 122 } 123 private static Logger logger = StatusLogger.getLogger(); 124 private final ConcurrentMap<Source, ConfigurationMonitor> watchers = new ConcurrentHashMap<>(); 125 private int intervalSeconds = 0; 126 private ScheduledFuture<?> future; 127 128 private final ConfigurationScheduler scheduler; 129 130 private final List<WatchEventService> eventServiceList; 131 132 // This just needs to be a unique key within the WatchEventManager. 133 private final UUID id = LocalUUID.get(); 134 135 public WatchManager(final ConfigurationScheduler scheduler) { 136 this.scheduler = Objects.requireNonNull(scheduler, "scheduler"); 137 eventServiceList = getEventServices(); 138 } 139 140 public void checkFiles() { 141 new WatchRunnable().run(); 142 } 143 144 /** 145 * Return the ConfigurationWaatchers. 146 * 147 * @return the ConfigurationWatchers. 148 * @since 2.11.2 149 */ 150 public Map<Source, Watcher> getConfigurationWatchers() { 151 final Map<Source, Watcher> map = new HashMap<>(watchers.size()); 152 for (final Map.Entry<Source, ConfigurationMonitor> entry : watchers.entrySet()) { 153 map.put(entry.getKey(), entry.getValue().getWatcher()); 154 } 155 return map; 156 } 157 158 private List<WatchEventService> getEventServices() { 159 List<WatchEventService> list = new ArrayList<>(); 160 for (final ClassLoader classLoader : LoaderUtil.getClassLoaders()) { 161 try { 162 final ServiceLoader<WatchEventService> serviceLoader = ServiceLoader 163 .load(WatchEventService.class, classLoader); 164 for (final WatchEventService service : serviceLoader) { 165 list.add(service); 166 } 167 } catch (final Throwable ex) { 168 LOGGER.debug("Unable to retrieve WatchEventService from ClassLoader {}", classLoader, ex); 169 } 170 } 171 return list; 172 } 173 174 public UUID getId() { 175 return this.id; 176 } 177 178 /** 179 * Gets how often this manager checks for file modifications. 180 * 181 * @return how often, in seconds, this manager checks for file modifications. 182 */ 183 public int getIntervalSeconds() { 184 return this.intervalSeconds; 185 } 186 187 /** 188 * Returns a Map of the file watchers. 189 * 190 * @return A Map of the file watchers. 191 * @deprecated use getConfigurationWatchers. 192 */ 193 @Deprecated 194 public Map<File, FileWatcher> getWatchers() { 195 final Map<File, FileWatcher> map = new HashMap<>(watchers.size()); 196 for (Map.Entry<Source, ConfigurationMonitor> entry : watchers.entrySet()) { 197 if (entry.getValue().getWatcher() instanceof ConfigurationFileWatcher) { 198 map.put(entry.getKey().getFile(), (FileWatcher) entry.getValue().getWatcher()); 199 } else { 200 map.put(entry.getKey().getFile(), new WrappedFileWatcher((FileWatcher) entry.getValue().getWatcher())); 201 } 202 } 203 return map; 204 } 205 206 public boolean hasEventListeners() { 207 return eventServiceList.size() > 0; 208 } 209 210 private String millisToString(final long millis) { 211 return new Date(millis).toString(); 212 } 213 214 /** 215 * Resets all file monitors to their current last modified time. If this manager does not watch any file, nothing 216 * happens. 217 * <p> 218 * This allows you to start, stop, reset and start again a manager, without triggering file modified events if the a 219 * watched file has changed during the period of time when the manager was stopped. 220 * </p> 221 * 222 * @since 2.11.0 223 */ 224 public void reset() { 225 logger.debug("Resetting {}", this); 226 for (final Source source : watchers.keySet()) { 227 reset(source); 228 } 229 } 230 231 /** 232 * Resets the file monitor for the given file being watched to its current last modified time. If this manager does 233 * not watch the given file, nothing happens. 234 * <p> 235 * This allows you to start, stop, reset and start again a manager, without triggering file modified events if the 236 * given watched file has changed during the period of time when the manager was stopped. 237 * </p> 238 * 239 * @param file the file for the monitor to reset. 240 * @since 2.11.0 241 */ 242 public void reset(final File file) { 243 if (file == null) { 244 return; 245 } 246 Source source = new Source(file); 247 reset(source); 248 } 249 250 /** 251 * Resets the configuration monitor for the given file being watched to its current last modified time. If this 252 * manager does not watch the given configuration, nothing happens. 253 * <p> 254 * This allows you to start, stop, reset and start again a manager, without triggering file modified events if the 255 * given watched configuration has changed during the period of time when the manager was stopped. 256 * </p> 257 * 258 * @param source the Source for the monitor to reset. 259 * @since 2.12.0 260 */ 261 public void reset(final Source source) { 262 if (source == null) { 263 return; 264 } 265 final ConfigurationMonitor monitor = watchers.get(source); 266 if (monitor != null) { 267 Watcher watcher = monitor.getWatcher(); 268 if (watcher.isModified()) { 269 final long lastModifiedMillis = watcher.getLastModified(); 270 if (logger.isDebugEnabled()) { 271 logger.debug("Resetting file monitor for '{}' from {} ({}) to {} ({})", source.getLocation(), 272 millisToString(monitor.lastModifiedMillis), monitor.lastModifiedMillis, 273 millisToString(lastModifiedMillis), lastModifiedMillis); 274 } 275 monitor.setLastModifiedMillis(lastModifiedMillis); 276 } 277 } 278 } 279 280 public void setIntervalSeconds(final int intervalSeconds) { 281 if (!isStarted()) { 282 if (this.intervalSeconds > 0 && intervalSeconds == 0) { 283 scheduler.decrementScheduledItems(); 284 } else if (this.intervalSeconds == 0 && intervalSeconds > 0) { 285 scheduler.incrementScheduledItems(); 286 } 287 this.intervalSeconds = intervalSeconds; 288 } 289 } 290 291 @Override 292 public void start() { 293 super.start(); 294 295 if (intervalSeconds > 0) { 296 future = scheduler 297 .scheduleWithFixedDelay(new WatchRunnable(), intervalSeconds, intervalSeconds, TimeUnit.SECONDS); 298 } 299 for (WatchEventService service : eventServiceList) { 300 service.subscribe(this); 301 } 302 } 303 304 @Override 305 public boolean stop(final long timeout, final TimeUnit timeUnit) { 306 setStopping(); 307 for (WatchEventService service : eventServiceList) { 308 service.unsubscribe(this); 309 } 310 final boolean stopped = stop(future); 311 setStopped(); 312 return stopped; 313 } 314 315 @Override 316 public String toString() { 317 return "WatchManager [intervalSeconds=" + intervalSeconds + ", watchers=" + watchers + ", scheduler=" 318 + scheduler + ", future=" + future + "]"; 319 } 320 321 /** 322 * Unwatches the given file. 323 * 324 * @param source the Source to stop watching. 325 * the file to stop watching. 326 * @since 2.12.0 327 */ 328 public void unwatch(final Source source) { 329 logger.debug("Unwatching configuration {}", source); 330 watchers.remove(source); 331 } 332 333 /** 334 * Unwatches the given file. 335 * 336 * @param file the file to stop watching. 337 * @since 2.11.0 338 */ 339 public void unwatchFile(final File file) { 340 Source source = new Source(file); 341 unwatch(source); 342 } 343 344 /** 345 * Watches the given file. 346 * 347 * @param source the source to watch. 348 * @param watcher the watcher to notify of file changes. 349 */ 350 public void watch(final Source source, final Watcher watcher) { 351 watcher.watching(source); 352 final long lastModified = watcher.getLastModified(); 353 if (logger.isDebugEnabled()) { 354 logger.debug("Watching configuration '{}' for lastModified {} ({})", source, millisToString(lastModified), 355 lastModified); 356 } 357 watchers.put(source, new ConfigurationMonitor(lastModified, watcher)); 358 } 359 360 /** 361 * Watches the given file. 362 * 363 * @param file the file to watch. 364 * @param fileWatcher the watcher to notify of file changes. 365 */ 366 public void watchFile(final File file, final FileWatcher fileWatcher) { 367 Watcher watcher; 368 if (fileWatcher instanceof Watcher) { 369 watcher = (Watcher) fileWatcher; 370 } else { 371 watcher = new WrappedFileWatcher(fileWatcher); 372 } 373 Source source = new Source(file); 374 watch(source, watcher); 375 } 376}