    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}