1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v1.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  package ch.qos.logback.core.rolling;
15  
16  import static ch.qos.logback.core.CoreConstants.CODES_URL;
17  
18  import java.io.File;
19  import java.util.Date;
20  
21  import ch.qos.logback.core.CoreConstants;
22  import ch.qos.logback.core.rolling.helper.*;
23  
24  /**
25   * When rolling over, <code>FixedWindowRollingPolicy</code> renames files
26   * according to a fixed window algorithm.
27   * 
28   * For more information about this policy, please refer to the online manual at
29   * http://logback.qos.ch/manual/appenders.html#FixedWindowRollingPolicy
30   * 
31   * @author Ceki G&uuml;lc&uuml;
32   */
33  public class FixedWindowRollingPolicy extends RollingPolicyBase {
34      static final String FNP_NOT_SET = "The \"FileNamePattern\" property must be set before using FixedWindowRollingPolicy. ";
35      static final String PRUDENT_MODE_UNSUPPORTED = "See also " + CODES_URL + "#tbr_fnp_prudent_unsupported";
36      static final String SEE_PARENT_FN_NOT_SET = "Please refer to " + CODES_URL + "#fwrp_parentFileName_not_set";
37      int maxIndex;
38      int minIndex;
39      RenameUtil util = new RenameUtil();
40      Compressor compressor;
41  
42      public static final String ZIP_ENTRY_DATE_PATTERN = "yyyy-MM-dd_HHmm";
43  
44      /**
45       * It's almost always a bad idea to have a large window size, say over 20.
46       */
47      private static int MAX_WINDOW_SIZE = 20;
48  
49      public FixedWindowRollingPolicy() {
50          minIndex = 1;
51          maxIndex = 7;
52      }
53  
54      public void start() {
55          util.setContext(this.context);
56  
57          if (fileNamePatternStr != null) {
58              determineCompressionMode();
59              adjustCompressionModeAndFileNamePatternStrIfNecessary();
60              fileNamePattern = new FileNamePattern(fileNamePatternStr, this.context);
61          } else {
62              addError(FNP_NOT_SET);
63              addError(CoreConstants.SEE_FNP_NOT_SET);
64              throw new IllegalStateException(FNP_NOT_SET + CoreConstants.SEE_FNP_NOT_SET);
65          }
66  
67          if (isParentPrudent()) {
68              addError("Prudent mode is not supported with FixedWindowRollingPolicy.");
69              addError(PRUDENT_MODE_UNSUPPORTED);
70              throw new IllegalStateException("Prudent mode is not supported.");
71          }
72  
73          if (getParentsRawFileProperty() == null) {
74              addError("The File name property must be set before using this rolling policy.");
75              addError(SEE_PARENT_FN_NOT_SET);
76              throw new IllegalStateException("The \"File\" option must be set.");
77          }
78  
79          if (maxIndex < minIndex) {
80              addWarn("MaxIndex (" + maxIndex + ") cannot be smaller than MinIndex (" + minIndex + ").");
81              addWarn("Setting maxIndex to equal minIndex.");
82              maxIndex = minIndex;
83          }
84  
85          final int maxWindowSize = getMaxWindowSize();
86          if ((maxIndex - minIndex) > maxWindowSize) {
87              addWarn("Large window sizes are not allowed.");
88              maxIndex = minIndex + maxWindowSize;
89              addWarn("MaxIndex reduced to " + maxIndex);
90          }
91  
92          IntegerTokenConverter itc = fileNamePattern.getIntegerTokenConverter();
93  
94          if (itc == null) {
95              throw new IllegalStateException(
96                      "FileNamePattern [" + fileNamePattern.getPattern() + "] does not contain a valid IntegerToken");
97          }
98  
99          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 }