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.logging.log4j.core.config;
018
019import org.apache.logging.log4j.Logger;
020import org.apache.logging.log4j.core.AbstractLifeCycle;
021import org.apache.logging.log4j.core.async.DaemonThreadFactory;
022import org.apache.logging.log4j.core.util.CronExpression;
023import org.apache.logging.log4j.status.StatusLogger;
024
025import java.util.Date;
026import java.util.concurrent.Callable;
027import java.util.concurrent.ScheduledExecutorService;
028import java.util.concurrent.ScheduledFuture;
029import java.util.concurrent.ScheduledThreadPoolExecutor;
030import java.util.concurrent.TimeUnit;
031
032/**
033 *
034 */
035public class ConfigurationScheduler extends AbstractLifeCycle {
036
037    private static final Logger LOGGER = StatusLogger.getLogger();
038    private ScheduledExecutorService executorService;
039
040    private int scheduledItems = 0;
041
042
043    @Override
044    public void start() {
045        super.start();
046        if (scheduledItems > 0) {
047            LOGGER.debug("Starting {} Log4j2Scheduled threads", scheduledItems);
048            if (scheduledItems > 5) {
049                scheduledItems = 5;
050            }
051            executorService = new ScheduledThreadPoolExecutor(scheduledItems, new DaemonThreadFactory("Log4j2Scheduled-"));
052        } else {
053            LOGGER.debug("No scheduled items");
054        }
055    }
056
057    @Override
058    public void stop() {
059        if (executorService != null) {
060            LOGGER.debug("Stopping Log4j2Scheduled threads.");
061            executorService.shutdown();
062        }
063        super.stop();
064    }
065
066    /**
067     * Increment the number of threads in the pool.
068     */
069    public void incrementScheduledItems() {
070        if (!isStarted()) {
071            ++scheduledItems;
072        } else {
073            LOGGER.error("Attempted to increment scheduled items after start");
074        }
075    }
076
077    /**
078     * Decrement the number of threads in the pool
079     */
080    public void decrementScheduledItems() {
081        if (!isStarted() && scheduledItems > 0) {
082            --scheduledItems;
083        }
084    }
085
086    /**
087     * Creates and executes a ScheduledFuture that becomes enabled after the given delay.
088     * @param callable the function to execute.
089     * @param delay the time from now to delay execution.
090     * @param unit the time unit of the delay parameter.
091     * @return a ScheduledFuture that can be used to extract result or cancel.
092     *
093     */
094    public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
095        return executorService.schedule(callable, delay, unit);
096    }
097
098    /**
099     * Creates and executes a one-shot action that becomes enabled after the given delay.
100     * @param command the task to execute.
101     * @param delay the time from now to delay execution.
102     * @param unit the time unit of the delay parameter.
103     * @return a ScheduledFuture representing pending completion of the task and whose get() method will return null
104     * upon completion.
105     */
106    public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
107        return executorService.schedule(command, delay, unit);
108    }
109
110
111    /**
112     * Creates and executes an action that first based on a cron expression.
113     * @param cronExpression the cron expression describing the schedule.
114     * @param command The Runnable to run,
115     * @return a ScheduledFuture representing the next time the command will run.
116     */
117    public CronScheduledFuture<?> scheduleWithCron(CronExpression cronExpression, Runnable command) {
118        CronRunnable runnable = new CronRunnable(command, cronExpression);
119        ScheduledFuture<?> future = schedule(runnable, nextFireInterval(cronExpression), TimeUnit.MILLISECONDS);
120        CronScheduledFuture<?> cronScheduledFuture = new CronScheduledFuture<>(future);
121        runnable.setScheduledFuture(cronScheduledFuture);
122        return cronScheduledFuture;
123    }
124
125
126    /**
127     * Creates and executes a periodic action that becomes enabled first after the given initial delay, and subsequently
128     * with the given period; that is executions will commence after initialDelay then initialDelay+period,
129     * then initialDelay + 2 * period, and so on.
130     * @param command the task to execute.
131     * @param initialDelay the time to delay first execution.
132     * @param period the period between successive executions.
133     * @param unit the time unit of the initialDelay and period parameters.
134     * @return a ScheduledFuture representing pending completion of the task, and whose get() method will throw an
135     * exception upon cancellation
136     */
137    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
138        return executorService.scheduleAtFixedRate(command, initialDelay, period, unit);
139    }
140
141    /**
142     * Creates and executes a periodic action that becomes enabled first after the given initial delay, and
143     * subsequently with the given delay between the termination of one execution and the commencement of the next.
144     * @param command the task to execute.
145     * @param initialDelay the time to delay first execution.
146     * @param delay the delay between the termination of one execution and the commencement of the next.
147     * @param unit the time unit of the initialDelay and delay parameters
148     * @return a ScheduledFuture representing pending completion of the task, and whose get() method will throw an
149     * exception upon cancellation
150     */
151    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) {
152        return executorService.scheduleWithFixedDelay(command, initialDelay, delay, unit);
153    }
154
155    private class CronRunnable implements Runnable {
156
157        private final CronExpression cronExpression;
158        private final Runnable runnable;
159        private CronScheduledFuture<?> scheduledFuture;
160
161        public CronRunnable(Runnable runnable, CronExpression cronExpression) {
162            this.cronExpression = cronExpression;
163            this.runnable = runnable;
164        }
165
166        public void setScheduledFuture(CronScheduledFuture<?> future) {
167            this.scheduledFuture = future;
168        }
169
170        @Override
171        public void run() {
172            try {
173                runnable.run();
174            } catch(Throwable ex) {
175                LOGGER.error("Error running command", ex);
176            } finally {
177                ScheduledFuture<?> future = schedule(this, nextFireInterval(cronExpression), TimeUnit.MILLISECONDS);
178                scheduledFuture.setScheduledFuture(future);
179            }
180        }
181    }
182
183    private long nextFireInterval(CronExpression cronExpression) {
184        Date now = new Date();
185        Date fireDate = cronExpression.getNextValidTimeAfter(now);
186        return fireDate.getTime() - now.getTime();
187    }
188
189}