View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.util;
18  
19  import java.io.File;
20  import java.util.ArrayList;
21  import java.util.Date;
22  import java.util.HashMap;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.ServiceLoader;
26  import java.util.UUID;
27  import java.util.concurrent.ConcurrentHashMap;
28  import java.util.concurrent.ConcurrentMap;
29  import java.util.concurrent.ScheduledFuture;
30  import java.util.concurrent.TimeUnit;
31  
32  import org.apache.logging.log4j.Logger;
33  import org.apache.logging.log4j.core.AbstractLifeCycle;
34  import org.apache.logging.log4j.core.config.ConfigurationFileWatcher;
35  import org.apache.logging.log4j.core.config.ConfigurationScheduler;
36  import org.apache.logging.log4j.status.StatusLogger;
37  import org.apache.logging.log4j.util.LoaderUtil;
38  
39  /**
40   * Manages {@link FileWatcher}s.
41   *
42   * @see FileWatcher
43   * @see ConfigurationScheduler
44   */
45  public class WatchManager extends AbstractLifeCycle {
46  
47      private static Logger logger = StatusLogger.getLogger();
48      private final ConcurrentMap<Source, ConfigurationMonitor> watchers = new ConcurrentHashMap<>();
49      private int intervalSeconds = 0;
50      private ScheduledFuture<?> future;
51      private final ConfigurationScheduler scheduler;
52      private final List<WatchEventService> eventServiceList;
53      private final UUID id = UuidUtil.getTimeBasedUuid();
54  
55      public WatchManager(final ConfigurationScheduler scheduler) {
56          this.scheduler = scheduler;
57          eventServiceList = getEventServices();
58      }
59  
60      public UUID getId() {
61          return this.id;
62      }
63  
64      public boolean hasEventListeners() {
65          return eventServiceList.size() > 0;
66      }
67  
68      /**
69       * Resets all file monitors to their current last modified time. If this manager does not watch any file, nothing
70       * happens.
71       * <p>
72       * This allows you to start, stop, reset and start again a manager, without triggering file modified events if the a
73       * watched file has changed during the period of time when the manager was stopped.
74       * </p>
75       *
76       * @since 2.11.0
77       */
78      public void reset() {
79          logger.debug("Resetting {}", this);
80          for (final Source source : watchers.keySet()) {
81              reset(source);
82          }
83      }
84  
85      /**
86       * Resets the file monitor for the given file being watched to its current last modified time. If this manager does
87       * not watch the given file, nothing happens.
88       * <p>
89       * This allows you to start, stop, reset and start again a manager, without triggering file modified events if the
90       * given watched file has changed during the period of time when the manager was stopped.
91       * </p>
92       *
93       * @param file the file for the monitor to reset.
94       * @since 2.11.0
95       */
96      public void reset(final File file) {
97          if (file == null) {
98              return;
99          }
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 }