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 * https://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 {@link ReloadingController} and a period. After 033 * calling the {@code start()} method a periodic task is started which calls 034 * {@link ReloadingController#checkForReloading(Object)} on the associated reloading controller. This way changes on a 035 * configuration source can be detected without client code having to poll actively. The {@code ReloadingController} 036 * will perform its checks and generates events if it detects the need for a reloading operation. 037 * </p> 038 * <p> 039 * Triggering of the controller can be disabled by calling the {@code stop()} method and later be resumed by calling 040 * {@code start()} again. When the trigger is no more needed its {@code shutdown()} method should be called. 041 * </p> 042 * <p> 043 * When creating an instance a {@code ScheduledExecutorService} can be provided which is then used by the object. 044 * Otherwise, a default executor service is created and used. When shutting down this object it can be specified whether 045 * the {@code ScheduledExecutorService} should be shut down, too. 046 * </p> 047 * 048 * @since 2.0 049 * @see ReloadingController 050 */ 051public class PeriodicReloadingTrigger { 052 053 /** 054 * Creates a default executor service. This method is called if no executor has been passed to the constructor. 055 * 056 * @return the default executor service 057 */ 058 private static ScheduledExecutorService createDefaultExecutorService() { 059 final ThreadFactory factory = BasicThreadFactory.builder().namingPattern("ReloadingTrigger-%s").daemon(true).build(); 060 return Executors.newScheduledThreadPool(1, factory); 061 } 062 063 /** The executor service used by this trigger. */ 064 private final ScheduledExecutorService executorService; 065 066 /** The associated reloading controller. */ 067 private final ReloadingController controller; 068 069 /** The parameter to be passed to the controller. */ 070 private final Object controllerParam; 071 072 /** The period. */ 073 private final long period; 074 075 /** The time unit. */ 076 private final TimeUnit timeUnit; 077 078 /** Stores the future object for the current trigger task. */ 079 private ScheduledFuture<?> triggerTask; 080 081 /** 082 * Creates a new instance of {@code PeriodicReloadingTrigger} with a default executor service. 083 * 084 * @param ctrl the {@code ReloadingController} (must not be <strong>null</strong>) 085 * @param ctrlParam the optional parameter to be passed to the controller when doing reloading checks 086 * @param triggerPeriod the period in which the controller is triggered 087 * @param unit the time unit for the period 088 * @throws IllegalArgumentException if a required argument is missing 089 */ 090 public PeriodicReloadingTrigger(final ReloadingController ctrl, final Object ctrlParam, final long triggerPeriod, final TimeUnit unit) { 091 this(ctrl, ctrlParam, triggerPeriod, unit, null); 092 } 093 094 /** 095 * Creates a new instance of {@code PeriodicReloadingTrigger} and sets all parameters. 096 * 097 * @param ctrl the {@code ReloadingController} (must not be <strong>null</strong>) 098 * @param ctrlParam the optional parameter to be passed to the controller when doing reloading checks 099 * @param triggerPeriod the period in which the controller is triggered 100 * @param unit the time unit for the period 101 * @param exec the executor service to use (can be <strong>null</strong>, then a default executor service is created 102 * @throws IllegalArgumentException if a required argument is missing 103 */ 104 public PeriodicReloadingTrigger(final ReloadingController ctrl, final Object ctrlParam, final long triggerPeriod, final TimeUnit unit, 105 final ScheduledExecutorService exec) { 106 if (ctrl == null) { 107 throw new IllegalArgumentException("ReloadingController must not be null!"); 108 } 109 110 controller = ctrl; 111 controllerParam = ctrlParam; 112 period = triggerPeriod; 113 timeUnit = unit; 114 executorService = exec != null ? exec : createDefaultExecutorService(); 115 } 116 117 /** 118 * Creates the task which triggers the reloading controller. 119 * 120 * @return the newly created trigger task 121 */ 122 private Runnable createTriggerTaskCommand() { 123 return () -> controller.checkForReloading(controllerParam); 124 } 125 126 /** 127 * Gets the {@code ScheduledExecutorService} used by this object. 128 * 129 * @return the associated {@code ScheduledExecutorService} 130 */ 131 ScheduledExecutorService getExecutorService() { 132 return executorService; 133 } 134 135 /** 136 * Returns a flag whether this trigger is currently active. 137 * 138 * @return a flag whether this trigger is running 139 */ 140 public synchronized boolean isRunning() { 141 return triggerTask != null; 142 } 143 144 /** 145 * Shuts down this trigger and its {@code ScheduledExecutorService}. This is a shortcut for {@code shutdown(true)}. 146 * 147 * @see #shutdown(boolean) 148 */ 149 public void shutdown() { 150 shutdown(true); 151 } 152 153 /** 154 * Shuts down this trigger and optionally shuts down the {@code ScheduledExecutorService} used by this object. This 155 * method should be called if this trigger is no more needed. It ensures that the trigger is stopped. If the parameter 156 * is <strong>true</strong>, the executor service is also shut down. This should be done if this trigger is the only user of this 157 * executor service. 158 * 159 * @param shutdownExecutor a flag whether the associated {@code ScheduledExecutorService} is to be shut down 160 */ 161 public void shutdown(final boolean shutdownExecutor) { 162 stop(); 163 if (shutdownExecutor) { 164 getExecutorService().shutdown(); 165 } 166 } 167 168 /** 169 * Starts this trigger. The associated {@code ReloadingController} will be triggered according to the specified period. 170 * The first triggering happens after a period. If this trigger is already started, this invocation has no effect. 171 */ 172 public synchronized void start() { 173 if (!isRunning()) { 174 triggerTask = getExecutorService().scheduleAtFixedRate(createTriggerTaskCommand(), period, period, timeUnit); 175 } 176 } 177 178 /** 179 * Stops this trigger. The associated {@code ReloadingController} is no more triggered. If this trigger is already 180 * stopped, this invocation has no effect. 181 */ 182 public synchronized void stop() { 183 if (isRunning()) { 184 triggerTask.cancel(false); 185 triggerTask = null; 186 } 187 } 188}