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.commons.configuration2.reloading;
18  
19  import java.util.concurrent.Executors;
20  import java.util.concurrent.ScheduledExecutorService;
21  import java.util.concurrent.ScheduledFuture;
22  import java.util.concurrent.ThreadFactory;
23  import java.util.concurrent.TimeUnit;
24  
25  import org.apache.commons.lang3.concurrent.BasicThreadFactory;
26  
27  /**
28   * <p>
29   * A timer-based trigger for reloading checks.
30   * </p>
31   * <p>
32   * An instance of this class is constructed with a reference to a {@link ReloadingController} and a period. After
33   * calling the {@code start()} method a periodic task is started which calls
34   * {@link ReloadingController#checkForReloading(Object)} on the associated reloading controller. This way changes on a
35   * configuration source can be detected without client code having to poll actively. The {@code ReloadingController}
36   * will perform its checks and generates events if it detects the need for a reloading operation.
37   * </p>
38   * <p>
39   * Triggering of the controller can be disabled by calling the {@code stop()} method and later be resumed by calling
40   * {@code start()} again. When the trigger is no more needed its {@code shutdown()} method should be called.
41   * </p>
42   * <p>
43   * When creating an instance a {@code ScheduledExecutorService} can be provided which is then used by the object.
44   * Otherwise, a default executor service is created and used. When shutting down this object it can be specified whether
45   * the {@code ScheduledExecutorService} should be shut down, too.
46   * </p>
47   *
48   * @since 2.0
49   * @see ReloadingController
50   */
51  public class PeriodicReloadingTrigger {
52      /** The executor service used by this trigger. */
53      private final ScheduledExecutorService executorService;
54  
55      /** The associated reloading controller. */
56      private final ReloadingController controller;
57  
58      /** The parameter to be passed to the controller. */
59      private final Object controllerParam;
60  
61      /** The period. */
62      private final long period;
63  
64      /** The time unit. */
65      private final TimeUnit timeUnit;
66  
67      /** Stores the future object for the current trigger task. */
68      private ScheduledFuture<?> triggerTask;
69  
70      /**
71       * Creates a new instance of {@code PeriodicReloadingTrigger} and sets all parameters.
72       *
73       * @param ctrl the {@code ReloadingController} (must not be <b>null</b>)
74       * @param ctrlParam the optional parameter to be passed to the controller when doing reloading checks
75       * @param triggerPeriod the period in which the controller is triggered
76       * @param unit the time unit for the period
77       * @param exec the executor service to use (can be <b>null</b>, then a default executor service is created
78       * @throws IllegalArgumentException if a required argument is missing
79       */
80      public PeriodicReloadingTrigger(final ReloadingController ctrl, final Object ctrlParam, final long triggerPeriod, final TimeUnit unit,
81          final ScheduledExecutorService exec) {
82          if (ctrl == null) {
83              throw new IllegalArgumentException("ReloadingController must not be null!");
84          }
85  
86          controller = ctrl;
87          controllerParam = ctrlParam;
88          period = triggerPeriod;
89          timeUnit = unit;
90          executorService = exec != null ? exec : createDefaultExecutorService();
91      }
92  
93      /**
94       * Creates a new instance of {@code PeriodicReloadingTrigger} with a default executor service.
95       *
96       * @param ctrl the {@code ReloadingController} (must not be <b>null</b>)
97       * @param ctrlParam the optional parameter to be passed to the controller when doing reloading checks
98       * @param triggerPeriod the period in which the controller is triggered
99       * @param unit the time unit for the period
100      * @throws IllegalArgumentException if a required argument is missing
101      */
102     public PeriodicReloadingTrigger(final ReloadingController ctrl, final Object ctrlParam, final long triggerPeriod, final TimeUnit unit) {
103         this(ctrl, ctrlParam, triggerPeriod, unit, null);
104     }
105 
106     /**
107      * Starts this trigger. The associated {@code ReloadingController} will be triggered according to the specified period.
108      * The first triggering happens after a period. If this trigger is already started, this invocation has no effect.
109      */
110     public synchronized void start() {
111         if (!isRunning()) {
112             triggerTask = getExecutorService().scheduleAtFixedRate(createTriggerTaskCommand(), period, period, timeUnit);
113         }
114     }
115 
116     /**
117      * Stops this trigger. The associated {@code ReloadingController} is no more triggered. If this trigger is already
118      * stopped, this invocation has no effect.
119      */
120     public synchronized void stop() {
121         if (isRunning()) {
122             triggerTask.cancel(false);
123             triggerTask = null;
124         }
125     }
126 
127     /**
128      * Returns a flag whether this trigger is currently active.
129      *
130      * @return a flag whether this trigger is running
131      */
132     public synchronized boolean isRunning() {
133         return triggerTask != null;
134     }
135 
136     /**
137      * Shuts down this trigger and optionally shuts down the {@code ScheduledExecutorService} used by this object. This
138      * method should be called if this trigger is no more needed. It ensures that the trigger is stopped. If the parameter
139      * is <b>true</b>, the executor service is also shut down. This should be done if this trigger is the only user of this
140      * executor service.
141      *
142      * @param shutdownExecutor a flag whether the associated {@code ScheduledExecutorService} is to be shut down
143      */
144     public void shutdown(final boolean shutdownExecutor) {
145         stop();
146         if (shutdownExecutor) {
147             getExecutorService().shutdown();
148         }
149     }
150 
151     /**
152      * Shuts down this trigger and its {@code ScheduledExecutorService}. This is a shortcut for {@code shutdown(true)}.
153      *
154      * @see #shutdown(boolean)
155      */
156     public void shutdown() {
157         shutdown(true);
158     }
159 
160     /**
161      * Gets the {@code ScheduledExecutorService} used by this object.
162      *
163      * @return the associated {@code ScheduledExecutorService}
164      */
165     ScheduledExecutorService getExecutorService() {
166         return executorService;
167     }
168 
169     /**
170      * Creates the task which triggers the reloading controller.
171      *
172      * @return the newly created trigger task
173      */
174     private Runnable createTriggerTaskCommand() {
175         return () -> controller.checkForReloading(controllerParam);
176     }
177 
178     /**
179      * Creates a default executor service. This method is called if no executor has been passed to the constructor.
180      *
181      * @return the default executor service
182      */
183     private static ScheduledExecutorService createDefaultExecutorService() {
184         final ThreadFactory factory = new BasicThreadFactory.Builder().namingPattern("ReloadingTrigger-%s").daemon(true).build();
185         return Executors.newScheduledThreadPool(1, factory);
186     }
187 }