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.commons.configuration2.reloading; 018 019import java.util.concurrent.Executors; 020import java.util.concurrent.ScheduledExecutorService; 021import java.util.concurrent.ScheduledFuture; 022import java.util.concurrent.ThreadFactory; 023import java.util.concurrent.TimeUnit; 024 025import org.apache.commons.lang3.concurrent.BasicThreadFactory; 026 027/** 028 * <p> 029 * A timer-based trigger for reloading checks. 030 * </p> 031 * <p> 032 * An instance of this class is constructed with a reference to a 033 * {@link ReloadingController} and a period. After calling the {@code start()} 034 * method a periodic task is started which calls 035 * {@link ReloadingController#checkForReloading(Object)} on the associated 036 * reloading controller. This way changes on a configuration source can be 037 * detected without client code having to poll actively. The 038 * {@code ReloadingController} will perform its checks and generates events if 039 * it detects the need for a reloading operation. 040 * </p> 041 * <p> 042 * Triggering of the controller can be disabled by calling the {@code stop()} 043 * method and later be resumed by calling {@code start()} again. When the 044 * trigger is no more needed its {@code shutdown()} method should be called. 045 * </p> 046 * <p> 047 * When creating an instance a {@code ScheduledExecutorService} can be provided 048 * which is then used by the object. Otherwise, a default executor service is 049 * created and used. When shutting down this object it can be specified whether 050 * the {@code ScheduledExecutorService} should be shut down, too. 051 * </p> 052 * 053 * @version $Id: PeriodicReloadingTrigger.java 1842194 2018-09-27 22:24:23Z ggregory $ 054 * @since 2.0 055 * @see ReloadingController 056 */ 057public class PeriodicReloadingTrigger 058{ 059 /** The executor service used by this trigger. */ 060 private final ScheduledExecutorService executorService; 061 062 /** The associated reloading controller. */ 063 private final ReloadingController controller; 064 065 /** The parameter to be passed to the controller. */ 066 private final Object controllerParam; 067 068 /** The period. */ 069 private final long period; 070 071 /** The time unit. */ 072 private final TimeUnit timeUnit; 073 074 /** Stores the future object for the current trigger task. */ 075 private ScheduledFuture<?> triggerTask; 076 077 /** 078 * Creates a new instance of {@code PeriodicReloadingTrigger} and sets all 079 * parameters. 080 * 081 * @param ctrl the {@code ReloadingController} (must not be <b>null</b>) 082 * @param ctrlParam the optional parameter to be passed to the controller 083 * when doing reloading checks 084 * @param triggerPeriod the period in which the controller is triggered 085 * @param unit the time unit for the period 086 * @param exec the executor service to use (can be <b>null</b>, then a 087 * default executor service is created 088 * @throws IllegalArgumentException if a required argument is missing 089 */ 090 public PeriodicReloadingTrigger(final ReloadingController ctrl, final Object ctrlParam, 091 final long triggerPeriod, final TimeUnit unit, final ScheduledExecutorService exec) 092 { 093 if (ctrl == null) 094 { 095 throw new IllegalArgumentException( 096 "ReloadingController must not be null!"); 097 } 098 099 controller = ctrl; 100 controllerParam = ctrlParam; 101 period = triggerPeriod; 102 timeUnit = unit; 103 executorService = 104 (exec != null) ? exec : createDefaultExecutorService(); 105 } 106 107 /** 108 * Creates a new instance of {@code PeriodicReloadingTrigger} with a default 109 * executor service. 110 * 111 * @param ctrl the {@code ReloadingController} (must not be <b>null</b>) 112 * @param ctrlParam the optional parameter to be passed to the controller 113 * when doing reloading checks 114 * @param triggerPeriod the period in which the controller is triggered 115 * @param unit the time unit for the period 116 * @throws IllegalArgumentException if a required argument is missing 117 */ 118 public PeriodicReloadingTrigger(final ReloadingController ctrl, final Object ctrlParam, 119 final long triggerPeriod, final TimeUnit unit) 120 { 121 this(ctrl, ctrlParam, triggerPeriod, unit, null); 122 } 123 124 /** 125 * Starts this trigger. The associated {@code ReloadingController} will be 126 * triggered according to the specified period. The first triggering happens 127 * after a period. If this trigger is already started, this invocation has 128 * no effect. 129 */ 130 public synchronized void start() 131 { 132 if (!isRunning()) 133 { 134 triggerTask = 135 getExecutorService().scheduleAtFixedRate( 136 createTriggerTaskCommand(), period, period, 137 timeUnit); 138 } 139 } 140 141 /** 142 * Stops this trigger. The associated {@code ReloadingController} is no more 143 * triggered. If this trigger is already stopped, this invocation has no 144 * effect. 145 */ 146 public synchronized void stop() 147 { 148 if (isRunning()) 149 { 150 triggerTask.cancel(false); 151 triggerTask = null; 152 } 153 } 154 155 /** 156 * Returns a flag whether this trigger is currently active. 157 * 158 * @return a flag whether this trigger is running 159 */ 160 public synchronized boolean isRunning() 161 { 162 return triggerTask != null; 163 } 164 165 /** 166 * Shuts down this trigger and optionally shuts down the 167 * {@code ScheduledExecutorService} used by this object. This method should 168 * be called if this trigger is no more needed. It ensures that the trigger 169 * is stopped. If the parameter is <b>true</b>, the executor service is also 170 * shut down. This should be done if this trigger is the only user of this 171 * executor service. 172 * 173 * @param shutdownExecutor a flag whether the associated 174 * {@code ScheduledExecutorService} is to be shut down 175 */ 176 public void shutdown(final boolean shutdownExecutor) 177 { 178 stop(); 179 if (shutdownExecutor) 180 { 181 getExecutorService().shutdown(); 182 } 183 } 184 185 /** 186 * Shuts down this trigger and its {@code ScheduledExecutorService}. This is 187 * a shortcut for {@code shutdown(true)}. 188 * 189 * @see #shutdown(boolean) 190 */ 191 public void shutdown() 192 { 193 shutdown(true); 194 } 195 196 /** 197 * Returns the {@code ScheduledExecutorService} used by this object. 198 * 199 * @return the associated {@code ScheduledExecutorService} 200 */ 201 ScheduledExecutorService getExecutorService() 202 { 203 return executorService; 204 } 205 206 /** 207 * Creates the task which triggers the reloading controller. 208 * 209 * @return the newly created trigger task 210 */ 211 private Runnable createTriggerTaskCommand() 212 { 213 return new Runnable() 214 { 215 @Override 216 public void run() 217 { 218 controller.checkForReloading(controllerParam); 219 } 220 }; 221 } 222 223 /** 224 * Creates a default executor service. This method is called if no executor 225 * has been passed to the constructor. 226 * 227 * @return the default executor service 228 */ 229 private static ScheduledExecutorService createDefaultExecutorService() 230 { 231 final ThreadFactory factory = 232 new BasicThreadFactory.Builder() 233 .namingPattern("ReloadingTrigger-%s").daemon(true) 234 .build(); 235 return Executors.newScheduledThreadPool(1, factory); 236 } 237}