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.Date; 021import java.util.HashMap; 022import java.util.Map; 023import java.util.concurrent.ConcurrentHashMap; 024import java.util.concurrent.ConcurrentMap; 025import java.util.concurrent.ScheduledFuture; 026import java.util.concurrent.TimeUnit; 027 028import org.apache.logging.log4j.Logger; 029import org.apache.logging.log4j.core.AbstractLifeCycle; 030import org.apache.logging.log4j.core.config.ConfigurationScheduler; 031import org.apache.logging.log4j.status.StatusLogger; 032 033/** 034 * Manages {@link FileWatcher}s. 035 * 036 * @see FileWatcher 037 * @see ConfigurationScheduler 038 */ 039public class WatchManager extends AbstractLifeCycle { 040 041 private static Logger logger = StatusLogger.getLogger(); 042 private final ConcurrentMap<File, FileMonitor> watchers = new ConcurrentHashMap<>(); 043 private int intervalSeconds = 0; 044 private ScheduledFuture<?> future; 045 private final ConfigurationScheduler scheduler; 046 047 public WatchManager(final ConfigurationScheduler scheduler) { 048 this.scheduler = scheduler; 049 } 050 051 /** 052 * Resets all file monitors to their current last modified time. If this manager does not watch any file, nothing 053 * happens. 054 * <p> 055 * This allows you to start, stop, reset and start again a manager, without triggering file modified events if the a 056 * watched file has changed during the period of time when the manager was stopped. 057 * </p> 058 * 059 * @since 2.11.0 060 */ 061 public void reset() { 062 logger.debug("Resetting {}", this); 063 for (final File file : watchers.keySet()) { 064 reset(file); 065 } 066 } 067 068 /** 069 * Resets the file monitor for the given file being watched to its current last modified time. If this manager does 070 * not watch the given file, nothing happens. 071 * <p> 072 * This allows you to start, stop, reset and start again a manager, without triggering file modified events if the 073 * given watched file has changed during the period of time when the manager was stopped. 074 * </p> 075 * 076 * @param file 077 * the file for the monitor to reset. 078 * @since 2.11.0 079 */ 080 public void reset(final File file) { 081 if (file == null) { 082 return; 083 } 084 final FileMonitor fileMonitor = watchers.get(file); 085 if (fileMonitor != null) { 086 final long lastModifiedMillis = file.lastModified(); 087 if (lastModifiedMillis != fileMonitor.lastModifiedMillis) { 088 if (logger.isDebugEnabled()) { 089 logger.debug("Resetting file monitor for '{}' from {} ({}) to {} ({})", file, 090 millisToString(fileMonitor.lastModifiedMillis), fileMonitor.lastModifiedMillis, 091 millisToString(lastModifiedMillis), lastModifiedMillis); 092 } 093 fileMonitor.setLastModifiedMillis(lastModifiedMillis); 094 } 095 } 096 } 097 098 public void setIntervalSeconds(final int intervalSeconds) { 099 if (!isStarted()) { 100 if (this.intervalSeconds > 0 && intervalSeconds == 0) { 101 scheduler.decrementScheduledItems(); 102 } else if (this.intervalSeconds == 0 && intervalSeconds > 0) { 103 scheduler.incrementScheduledItems(); 104 } 105 this.intervalSeconds = intervalSeconds; 106 } 107 } 108 109 /** 110 * Gets how often this manager checks for file modifications. 111 * 112 * @return how often, in seconds, this manager checks for file modifications. 113 */ 114 public int getIntervalSeconds() { 115 return this.intervalSeconds; 116 } 117 118 @Override 119 public void start() { 120 super.start(); 121 if (intervalSeconds > 0) { 122 future = scheduler.scheduleWithFixedDelay(new WatchRunnable(), intervalSeconds, intervalSeconds, 123 TimeUnit.SECONDS); 124 } 125 } 126 127 @Override 128 public boolean stop(final long timeout, final TimeUnit timeUnit) { 129 setStopping(); 130 final boolean stopped = stop(future); 131 setStopped(); 132 return stopped; 133 } 134 135 /** 136 * Unwatches the given file. 137 * 138 * @param file 139 * the file to stop watching. 140 * @since 2.11.0 141 */ 142 public void unwatchFile(final File file) { 143 logger.debug("Unwatching file '{}'", file); 144 watchers.remove(file); 145 } 146 147 /** 148 * Watches the given file. 149 * 150 * @param file 151 * the file to watch. 152 * @param watcher 153 * the watcher to notify of file changes. 154 */ 155 public void watchFile(final File file, final FileWatcher watcher) { 156 final long lastModified = file.lastModified(); 157 if (logger.isDebugEnabled()) { 158 logger.debug("Watching file '{}' for lastModified {} ({})", file, millisToString(lastModified), lastModified); 159 } 160 watchers.put(file, new FileMonitor(lastModified, watcher)); 161 } 162 163 public Map<File, FileWatcher> getWatchers() { 164 final Map<File, FileWatcher> map = new HashMap<>(watchers.size()); 165 for (final Map.Entry<File, FileMonitor> entry : watchers.entrySet()) { 166 map.put(entry.getKey(), entry.getValue().fileWatcher); 167 } 168 return map; 169 } 170 171 private String millisToString(final long millis) { 172 return new Date(millis).toString(); 173 } 174 175 private final class WatchRunnable implements Runnable { 176 177 // Use a hard class reference here in case a refactoring changes the class name. 178 private final String SIMPLE_NAME = WatchRunnable.class.getSimpleName(); 179 180 @Override 181 public void run() { 182 logger.trace("{} run triggered.", SIMPLE_NAME); 183 for (final Map.Entry<File, FileMonitor> entry : watchers.entrySet()) { 184 final File file = entry.getKey(); 185 final FileMonitor fileMonitor = entry.getValue(); 186 final long lastModfied = file.lastModified(); 187 if (fileModified(fileMonitor, lastModfied)) { 188 if (logger.isInfoEnabled()) { 189 logger.info("File '{}' was modified on {} ({}), previous modification was on {} ({})", file, 190 millisToString(lastModfied), lastModfied, millisToString(fileMonitor.lastModifiedMillis), 191 fileMonitor.lastModifiedMillis); 192 } 193 fileMonitor.lastModifiedMillis = lastModfied; 194 fileMonitor.fileWatcher.fileModified(file); 195 } 196 } 197 logger.trace("{} run ended.", SIMPLE_NAME); 198 } 199 200 private boolean fileModified(final FileMonitor fileMonitor, final long lastModifiedMillis) { 201 return lastModifiedMillis != fileMonitor.lastModifiedMillis; 202 } 203 } 204 205 private final class FileMonitor { 206 private final FileWatcher fileWatcher; 207 private volatile long lastModifiedMillis; 208 209 public FileMonitor(final long lastModifiedMillis, final FileWatcher fileWatcher) { 210 this.fileWatcher = fileWatcher; 211 this.lastModifiedMillis = lastModifiedMillis; 212 } 213 214 private void setLastModifiedMillis(final long lastModifiedMillis) { 215 this.lastModifiedMillis = lastModifiedMillis; 216 } 217 218 @Override 219 public String toString() { 220 return "FileMonitor [fileWatcher=" + fileWatcher + ", lastModifiedMillis=" + lastModifiedMillis + "]"; 221 } 222 223 } 224 225 @Override 226 public String toString() { 227 return "WatchManager [intervalSeconds=" + intervalSeconds + ", watchers=" + watchers + ", scheduler=" 228 + scheduler + ", future=" + future + "]"; 229 } 230}