001/*
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2026, QOS.ch. All rights reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under
006 * either the terms of the Eclipse Public License v2.0 as published by
007 * the Eclipse Foundation
008 *
009 *   or (per the licensee's choosing)
010 *
011 * under the terms of the GNU Lesser General Public License version 2.1
012 * as published by the Free Software Foundation.
013 */
014package ch.qos.logback.core.rolling;
015
016import static ch.qos.logback.core.CoreConstants.CODES_URL;
017
018import java.io.File;
019import java.util.Date;
020
021import ch.qos.logback.core.CoreConstants;
022import ch.qos.logback.core.rolling.helper.*;
023
024/**
025 * When rolling over, <code>FixedWindowRollingPolicy</code> renames files
026 * according to a fixed window algorithm.
027 * 
028 * For more information about this policy, please refer to the online manual at
029 * http://logback.qos.ch/manual/appenders.html#FixedWindowRollingPolicy
030 * 
031 * @author Ceki G&uuml;lc&uuml;
032 */
033public class FixedWindowRollingPolicy extends RollingPolicyBase {
034    static final String FNP_NOT_SET = "The \"FileNamePattern\" property must be set before using FixedWindowRollingPolicy. ";
035    static final String PRUDENT_MODE_UNSUPPORTED = "See also " + CODES_URL + "#tbr_fnp_prudent_unsupported";
036    static final String SEE_PARENT_FN_NOT_SET = "Please refer to " + CODES_URL + "#fwrp_parentFileName_not_set";
037    int maxIndex;
038    int minIndex;
039    RenameUtil util = new RenameUtil();
040    Compressor compressor;
041
042    public static final String ZIP_ENTRY_DATE_PATTERN = "yyyy-MM-dd_HHmm";
043
044    /**
045     * It's almost always a bad idea to have a large window size, say over 20.
046     */
047    private static int MAX_WINDOW_SIZE = 20;
048
049    public FixedWindowRollingPolicy() {
050        minIndex = 1;
051        maxIndex = 7;
052    }
053
054    public void start() {
055        util.setContext(this.context);
056
057        if (fileNamePatternStr != null) {
058            determineCompressionMode();
059            adjustCompressionModeAndFileNamePatternStrIfNecessary();
060            fileNamePattern = new FileNamePattern(fileNamePatternStr, this.context);
061        } else {
062            addError(FNP_NOT_SET);
063            addError(CoreConstants.SEE_FNP_NOT_SET);
064            throw new IllegalStateException(FNP_NOT_SET + CoreConstants.SEE_FNP_NOT_SET);
065        }
066
067        if (isParentPrudent()) {
068            addError("Prudent mode is not supported with FixedWindowRollingPolicy.");
069            addError(PRUDENT_MODE_UNSUPPORTED);
070            throw new IllegalStateException("Prudent mode is not supported.");
071        }
072
073        if (getParentsRawFileProperty() == null) {
074            addError("The File name property must be set before using this rolling policy.");
075            addError(SEE_PARENT_FN_NOT_SET);
076            throw new IllegalStateException("The \"File\" option must be set.");
077        }
078
079        if (maxIndex < minIndex) {
080            addWarn("MaxIndex (" + maxIndex + ") cannot be smaller than MinIndex (" + minIndex + ").");
081            addWarn("Setting maxIndex to equal minIndex.");
082            maxIndex = minIndex;
083        }
084
085        final int maxWindowSize = getMaxWindowSize();
086        if ((maxIndex - minIndex) > maxWindowSize) {
087            addWarn("Large window sizes are not allowed.");
088            maxIndex = minIndex + maxWindowSize;
089            addWarn("MaxIndex reduced to " + maxIndex);
090        }
091
092        IntegerTokenConverter itc = fileNamePattern.getIntegerTokenConverter();
093
094        if (itc == null) {
095            throw new IllegalStateException(
096                    "FileNamePattern [" + fileNamePattern.getPattern() + "] does not contain a valid IntegerToken");
097        }
098
099        if (compressionMode == CompressionMode.ZIP) {
100            String zipEntryFileNamePatternStr = transformFileNamePatternFromInt2Date(fileNamePatternStr);
101            zipEntryFileNamePattern = new FileNamePattern(zipEntryFileNamePatternStr, context);
102        }
103        compressor = new Compressor(compressionMode);
104        compressor.setContext(this.context);
105        super.start();
106    }
107
108    /**
109     * Subclasses can override this method to increase the max window size, if
110     * required. This is to address LOGBACK-266.
111     * 
112     * @return
113     */
114    protected int getMaxWindowSize() {
115        return MAX_WINDOW_SIZE;
116    }
117
118    private String transformFileNamePatternFromInt2Date(String fileNamePatternStr) {
119        String slashified = FileFilterUtil.slashify(fileNamePatternStr);
120        String stemOfFileNamePattern = FileFilterUtil.afterLastSlash(slashified);
121        return stemOfFileNamePattern.replace("%i", "%d{" + ZIP_ENTRY_DATE_PATTERN + "}");
122    }
123
124    public void rollover() throws RolloverFailure {
125
126        // Inside this method it is guaranteed that the hereto active log file is
127        // closed.
128        // If maxIndex <= 0, then there is no file renaming to be done.
129        if (maxIndex >= 0) {
130            // Delete the oldest file, to keep Windows happy.
131            File file = new File(fileNamePattern.convertInt(maxIndex));
132
133            if (file.exists()) {
134                file.delete();
135            }
136
137            // Map {(maxIndex - 1), ..., minIndex} to {maxIndex, ..., minIndex+1}
138            for (int i = maxIndex - 1; i >= minIndex; i--) {
139                String toRenameStr = fileNamePattern.convertInt(i);
140                File toRename = new File(toRenameStr);
141                // no point in trying to rename a nonexistent file
142                if (toRename.exists()) {
143                    util.rename(toRenameStr, fileNamePattern.convertInt(i + 1));
144                } else {
145                    addInfo("Skipping roll-over for inexistent file " + toRenameStr);
146                }
147            }
148
149            // move active file name to min
150            switch (compressionMode) {
151            case NONE:
152                util.rename(getActiveFileName(), fileNamePattern.convertInt(minIndex));
153                break;
154            case GZ:
155            case XZ:
156                compressor.compress(getActiveFileName(), fileNamePattern.convertInt(minIndex), null);
157                break;
158            case ZIP:
159                compressor.compress(getActiveFileName(), fileNamePattern.convertInt(minIndex),
160                        zipEntryFileNamePattern.convert(new Date()));
161                break;
162            }
163        }
164    }
165
166    /**
167     * Return the value of the parent's RawFile property.
168     */
169    public String getActiveFileName() {
170        return getParentsRawFileProperty();
171    }
172
173    public int getMaxIndex() {
174        return maxIndex;
175    }
176
177    public int getMinIndex() {
178        return minIndex;
179    }
180
181    public void setMaxIndex(int maxIndex) {
182        this.maxIndex = maxIndex;
183    }
184
185    public void setMinIndex(int minIndex) {
186        this.minIndex = minIndex;
187    }
188}