001: /* ====================================================================
002: * The Apache Software License, Version 1.1
003: *
004: * Copyright (c) 1997-2003 The Apache Software Foundation. All rights
005: * reserved.
006: *
007: * Redistribution and use in source and binary forms, with or without
008: * modification, are permitted provided that the following conditions
009: * are met:
010: *
011: * 1. Redistributions of source code must retain the above copyright
012: * notice, this list of conditions and the following disclaimer.
013: *
014: * 2. Redistributions in binary form must reproduce the above copyright
015: * notice, this list of conditions and the following disclaimer in
016: * the documentation and/or other materials provided with the
017: * distribution.
018: *
019: * 3. The end-user documentation included with the redistribution,
020: * if any, must include the following acknowledgment:
021: * "This product includes software developed by the
022: * Apache Software Foundation (http://www.apache.org/)."
023: * Alternately, this acknowledgment may appear in the software
024: * itself, if and wherever such third-party acknowledgments
025: * normally appear.
026: *
027: * 4. The names "Jakarta", "Avalon", and "Apache Software Foundation"
028: * must not be used to endorse or promote products derived from this
029: * software without prior written permission. For written
030: * permission, please contact apache@apache.org.
031: *
032: * 5. Products derived from this software may not be called "Apache",
033: * nor may "Apache" appear in their name, without prior written
034: * permission of the Apache Software Foundation.
035: *
036: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
037: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
038: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
039: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
040: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
041: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
042: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
043: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
044: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
045: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
046: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
047: * SUCH DAMAGE.
048: * ====================================================================
049: *
050: * This software consists of voluntary contributions made by many
051: * individuals on behalf of the Apache Software Foundation. For more
052: * information on the Apache Software Foundation, please see
053: * <http://www.apache.org/>.
054: */
055: package org.apache.log.output.io.rotate;
056:
057: import java.io.File;
058: import java.text.DecimalFormat;
059: import java.text.FieldPosition;
060: import java.text.NumberFormat;
061:
062: /**
063: * strategy for naming log files based on appending revolving suffix.
064: * If the initial rotation is not specified then the class will attempt to
065: * calculate the rotation number via the following algorithm.
066: *
067: * It will search for the file with the highest number in the rotation. It will
068: * then increment its rotation number and use that number. If all files in rotation
069: * are present then it will then set the initial rotation to the next rotation after
070: * the most recently created file.
071: *
072: * @author <a href="mailto:dev@avalon.apache.org">Avalon Development Team</a>
073: * @author <a href="mailto:bh22351@i-one.at">Bernhard Huber</a>
074: * @author <a href="mailto:peter@apache.org">Peter Donald</a>
075: * @author <a href="mailto:david.gray@hic.gov.au">David Gray</a>
076: */
077: public class RevolvingFileStrategy implements FileStrategy {
078: ///revolving suffix formatting pattern. ie. "'.'000000"
079: private static final String PATTERN = "'.'000000";
080:
081: ///a revolving suffix formatter
082: private DecimalFormat m_decimalFormat;
083:
084: ///current revolving suffix
085: private int m_rotation;
086:
087: ///max revolving value.
088: private int m_maxRotations;
089:
090: ///the base file name.
091: private File m_baseFile;
092:
093: /**
094: * Creation of a new instane ofthe revolving file strategy.
095: * @param baseFile the base file
096: * @param maxRotations the maximum number of rotations ??
097: */
098: public RevolvingFileStrategy(final File baseFile,
099: final int maxRotations) {
100: this (baseFile, -1, maxRotations);
101: }
102:
103: /**
104: * Creation of a new instane ofthe revolving file strategy.
105: * @param baseFile the base file
106: * @param initialRotation the number of initial rotations ??
107: * @param maxRotations the maximum number of rotations??
108: */
109: public RevolvingFileStrategy(final File baseFile,
110: final int initialRotation, final int maxRotations) {
111: m_decimalFormat = new DecimalFormat(PATTERN);
112:
113: m_baseFile = baseFile;
114: m_rotation = initialRotation;
115: m_maxRotations = maxRotations;
116:
117: if (-1 == m_maxRotations) {
118: m_maxRotations = Integer.MAX_VALUE;
119: }
120:
121: if (-1 == initialRotation) {
122: m_rotation = calculateInitialRotation();
123: }
124:
125: if (m_rotation > m_maxRotations) {
126: m_rotation = m_maxRotations;
127: }
128:
129: if (m_rotation < 0) {
130: m_rotation = 0;
131: }
132: }
133:
134: /**
135: * Calculate the real file name from the base filename.
136: *
137: * @return File the calculated file name
138: */
139: public File nextFile() {
140: final StringBuffer sb = new StringBuffer();
141: final FieldPosition position = new FieldPosition(
142: NumberFormat.INTEGER_FIELD);
143: sb.append(m_baseFile);
144:
145: final StringBuffer result = m_decimalFormat.format(m_rotation,
146: sb, position);
147: m_rotation += 1;
148:
149: if (m_rotation >= m_maxRotations) {
150: m_rotation = 0;
151: }
152:
153: return new File(result.toString());
154: }
155:
156: /**
157: * Retrieve the current rotation number.
158: *
159: * @return the current rotation number.
160: */
161: public int getCurrentRotation() {
162: return m_rotation;
163: }
164:
165: /**
166: * Method that searches through files that
167: * match the pattern for resolving file and determine
168: * the last generation written to.
169: *
170: * @return the initial rotation
171: */
172: private int calculateInitialRotation() {
173: final File[] matchingFiles = getMatchingFiles();
174: if (null == matchingFiles || 0 == matchingFiles.length) {
175: return 0;
176: }
177:
178: final int[] rotations = calculateRotations(matchingFiles);
179:
180: //First we go through and look for maximumRotation
181: int maxRotation = 0;
182: for (int i = 0; i < rotations.length; i++) {
183: final int rotation = rotations[i];
184: if (rotation > maxRotation) {
185: maxRotation = rotation;
186: }
187: }
188:
189: //If the max rotation present on filessytem
190: //is less than max rotation possible then return that
191: //rotation
192: if (m_maxRotations != maxRotation) {
193: return maxRotation + 1;
194: }
195:
196: //Okay now we need to calculate the youngest file for our rotation
197: long time = matchingFiles[0].lastModified();
198:
199: //index of oldest file
200: int oldest = rotations[0];
201: for (int i = 0; i < matchingFiles.length; i++) {
202: final File file = matchingFiles[i];
203: final long lastModified = file.lastModified();
204: if (lastModified < time) {
205: time = lastModified;
206: oldest = rotations[i];
207: }
208: }
209:
210: return oldest;
211: }
212:
213: /**
214: * Generate an array of rotation numbers for all the files specified.
215: *
216: * @param matchingFiles the files to generate rotation numbers for
217: * @return the array containing rotations
218: */
219: private int[] calculateRotations(final File[] matchingFiles) {
220: final int[] results = new int[matchingFiles.length];
221:
222: for (int i = 0; i < matchingFiles.length; i++) {
223: final File file = matchingFiles[i];
224:
225: // The files may be returned in any order
226: //therefore calc the rotation number
227: try {
228: results[i] = calculateRotationForFile(file);
229: } catch (final NumberFormatException nfe) {
230: //If bad log file detected then set to -1
231: results[i] = -1;
232: }
233: }
234: return results;
235: }
236:
237: /**
238: * Return the rotation for the specified file
239: *
240: * @param file the file to check
241: * @return the rotation of the file
242: */
243: private int calculateRotationForFile(final File file) {
244: final String filename = file.toString();
245: final int length = filename.length();
246: final int minDigits = m_decimalFormat.getMinimumIntegerDigits();
247: final String rotation = filename.substring(length - minDigits);
248:
249: return Integer.parseInt(rotation);
250: }
251:
252: /**
253: * Get a list of files that could have been part of the rotation.
254: *
255: * @return the list of files that match
256: */
257: private File[] getMatchingFiles() {
258: // First get the path of the base file. Note that this path includes
259: // the path and the base of the file name itself.
260: final String fullFilePathName = m_baseFile.getPath();
261:
262: // Try to find the last path separator (if it exists)
263: final int fileSeparatorPosition = fullFilePathName
264: .lastIndexOf(File.separator);
265:
266: // If the last path separator does not exist the baseFile is a pure file name
267: File basePath;
268: String baseFileName;
269: if (fileSeparatorPosition < 0) {
270: // assume the current directory
271: basePath = new File(".");
272: baseFileName = fullFilePathName;
273: } else {
274: // Extract the sub-directory structure
275: String m_parentPath = fullFilePathName.substring(0,
276: fileSeparatorPosition);
277: baseFileName = fullFilePathName
278: .substring(fileSeparatorPosition + 1);
279: basePath = new File(m_parentPath);
280: }
281:
282: return basePath.listFiles(new BaseFileNameFilter(baseFileName));
283: }
284: }
|