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.appender.rolling;
018
019import java.text.ParseException;
020import java.util.Date;
021import java.util.Objects;
022import java.util.concurrent.TimeUnit;
023
024import org.apache.logging.log4j.core.Core;
025import org.apache.logging.log4j.core.LogEvent;
026import org.apache.logging.log4j.core.config.Configuration;
027import org.apache.logging.log4j.core.config.ConfigurationScheduler;
028import org.apache.logging.log4j.core.config.CronScheduledFuture;
029import org.apache.logging.log4j.core.config.Scheduled;
030import org.apache.logging.log4j.core.config.plugins.Plugin;
031import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
032import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
033import org.apache.logging.log4j.core.config.plugins.PluginFactory;
034import org.apache.logging.log4j.core.util.CronExpression;
035
036/**
037 * Rolls a file over based on a cron schedule.
038 */
039@Plugin(name = "CronTriggeringPolicy", category = Core.CATEGORY_NAME, printObject = true)
040@Scheduled
041public final class CronTriggeringPolicy extends AbstractTriggeringPolicy {
042
043    private static final String defaultSchedule = "0 0 0 * * ?";
044    private RollingFileManager manager;
045    private final CronExpression cronExpression;
046    private final Configuration configuration;
047    private final boolean checkOnStartup;
048    private volatile Date lastRollDate;
049    private CronScheduledFuture<?> future;
050
051    private CronTriggeringPolicy(final CronExpression schedule, final boolean checkOnStartup,
052            final Configuration configuration) {
053        this.cronExpression = Objects.requireNonNull(schedule, "schedule");
054        this.configuration = Objects.requireNonNull(configuration, "configuration");
055        this.checkOnStartup = checkOnStartup;
056    }
057
058    /**
059     * Initializes the policy.
060     * 
061     * @param aManager
062     *            The RollingFileManager.
063     */
064    @Override
065    public void initialize(final RollingFileManager aManager) {
066        this.manager = aManager;
067        final Date now = new Date();
068        final Date lastRollForFile = cronExpression.getPrevFireTime(new Date(this.manager.getFileTime()));
069        final Date lastRegularRoll = cronExpression.getPrevFireTime(new Date());
070        aManager.getPatternProcessor().setCurrentFileTime(lastRegularRoll.getTime());
071        LOGGER.debug("LastRollForFile {}, LastRegularRole {}", lastRollForFile, lastRegularRoll);
072        aManager.getPatternProcessor().setPrevFileTime(lastRegularRoll.getTime());
073        if (checkOnStartup && lastRollForFile != null && lastRegularRoll != null &&
074                lastRollForFile.before(lastRegularRoll)) {
075            lastRollDate = lastRollForFile;
076            rollover();
077        }
078
079        final ConfigurationScheduler scheduler = configuration.getScheduler();
080        if (!scheduler.isExecutorServiceSet()) {
081            // make sure we have a thread pool
082            scheduler.incrementScheduledItems();
083        }
084        if (!scheduler.isStarted()) {
085            scheduler.start();
086        }
087        lastRollDate = lastRegularRoll;
088        future = scheduler.scheduleWithCron(cronExpression, now, new CronTrigger());
089        LOGGER.debug(scheduler.toString());
090    }
091
092    /**
093     * Determines whether a rollover should occur.
094     * 
095     * @param event
096     *            A reference to the currently event.
097     * @return true if a rollover should occur.
098     */
099    @Override
100    public boolean isTriggeringEvent(final LogEvent event) {
101        return false;
102    }
103
104    public CronExpression getCronExpression() {
105        return cronExpression;
106    }
107
108    /**
109     * Creates a ScheduledTriggeringPolicy.
110     * 
111     * @param configuration
112     *            the Configuration.
113     * @param evaluateOnStartup
114     *            check if the file should be rolled over immediately.
115     * @param schedule
116     *            the cron expression.
117     * @return a ScheduledTriggeringPolicy.
118     */
119    @PluginFactory
120    public static CronTriggeringPolicy createPolicy(@PluginConfiguration final Configuration configuration,
121            @PluginAttribute("evaluateOnStartup") final String evaluateOnStartup,
122            @PluginAttribute("schedule") final String schedule) {
123        CronExpression cronExpression;
124        final boolean checkOnStartup = Boolean.parseBoolean(evaluateOnStartup);
125        if (schedule == null) {
126            LOGGER.info("No schedule specified, defaulting to Daily");
127            cronExpression = getSchedule(defaultSchedule);
128        } else {
129            cronExpression = getSchedule(schedule);
130            if (cronExpression == null) {
131                LOGGER.error("Invalid expression specified. Defaulting to Daily");
132                cronExpression = getSchedule(defaultSchedule);
133            }
134        }
135        return new CronTriggeringPolicy(cronExpression, checkOnStartup, configuration);
136    }
137
138    private static CronExpression getSchedule(final String expression) {
139        try {
140            return new CronExpression(expression);
141        } catch (final ParseException pe) {
142            LOGGER.error("Invalid cron expression - " + expression, pe);
143            return null;
144        }
145    }
146
147    private void rollover() {
148        manager.getPatternProcessor().setPrevFileTime(lastRollDate.getTime());
149        final Date thisRoll = cronExpression.getPrevFireTime(new Date());
150        manager.getPatternProcessor().setCurrentFileTime(thisRoll.getTime());
151        manager.rollover();
152        if (future != null) {
153            lastRollDate = future.getFireTime();
154        }
155    }
156
157    @Override
158    public boolean stop(final long timeout, final TimeUnit timeUnit) {
159        setStopping();
160        final boolean stopped = stop(future);
161        setStopped();
162        return stopped;
163    }
164
165    @Override
166    public String toString() {
167        return "CronTriggeringPolicy(schedule=" + cronExpression.getCronExpression() + ")";
168    }
169
170    private class CronTrigger implements Runnable {
171
172        @Override
173        public void run() {
174            rollover();
175        }
176    }
177}