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