/*******************************************************************************
* Copyright (c) 2011 GigaSpaces Technologies Ltd. All rights reserved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package org.cloudifysource.usm.tail;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
/**
* RollingFileReader was created in-order for an application to be able to access a file and tail it without locking it.
* whenever lines are added to the file, the RFR will open the file for a brief moment, "grab" the new lines added and
* close the file. the RFR remembers it's file-pointer and when reopening the file, the RFR will read the lines from the
* point where it left-off.
*
* @author adaml
*/
public class RollingFileReader {
private static final int TIMEOUT_BETWEEN_RETRIES = 1000;
private static final int DEFAULT_NUMBER_OF_RETRIES = 5;
private static java.util.logging.Logger logger = java.util.logging.Logger.getLogger(RollingFileReader.class
.getName());
private long lastModified;
private long filePointer;
private final File file;
private boolean exists;
private int retryCounter;
private long fileLength;
/**
* Constructor.
*
* @param file The file to read
*/
public RollingFileReader(final File file) {
this.lastModified = 0;
this.file = file;
this.fileLength = file.length();
this.exists = true;
}
/**
* checks if the modification time of the file matches the last modification time since the file was last tailed.
*
* @return true if the file has been modified since last polled.
*/
public boolean wasModified() {
return this.lastModified != file.lastModified() || this.fileLength != file.length();
}
/**
* reads the new lines added to the log file. The method supports RollingFileAppender tailing by not keeping the
* file open and opening the file only when a changes have been made to it. After reading the changes, the file will
* be closed and all relevant pointers and properties such as last modified date will be saved for the next
* iteration.
*
* note that the file is being closed in-order to enable the RFA to properly roll the file without having lock
* issues.
*
* @return new lines added to the log file.
* @throws IOException Indicates the lines were not read because of an IO exception
*/
public String readLines()
throws IOException {
RandomAccessFile randomAccessFile = null;
try {
randomAccessFile = new RandomAccessFile(this.file, "r");
if (this.filePointer > randomAccessFile.length()) {
// the file must have been rolled. Start form the beginning of the new file.
this.filePointer = 0;
}
// set the file pointer in the new RandomFileAccess.
randomAccessFile.seek(filePointer);
// allocate buffer size.
final byte[] buffer = new byte[(int) randomAccessFile.length() - (int) this.filePointer];
// read all new data into the buffer.
randomAccessFile.read(buffer);
// save the last filePointer location before closing the file.
this.filePointer = randomAccessFile.length();
randomAccessFile.close();
this.lastModified = this.file.lastModified();
retryCounter = 0;
this.fileLength = file.length();
return new String(buffer);
} catch (final FileNotFoundException e) {
// in-case we try to access the file at the exact time it is being rolled.
retryCounter++;
if (retryCounter > DEFAULT_NUMBER_OF_RETRIES) {
logger.warning("In RollingFileReader: file not found." + DEFAULT_NUMBER_OF_RETRIES
+ " Retries failed.");
this.exists = false;
return "";
}
try {
logger.warning("file not found: " + file.getName() + ". Retring attempt #" + retryCounter);
Thread.sleep(TIMEOUT_BETWEEN_RETRIES);
} catch (final InterruptedException e1) {
e1.printStackTrace();
}
return readLines();
} finally {
if (randomAccessFile != null) {
randomAccessFile.close();
}
}
}
/**
* returns false if the file has been removed from the system and was not recreated after a certain time period.
*
* @return returns false if the file has been removed from the system and was not recreated after a certain time
* period.
*/
public boolean exists() {
return exists;
}
}