org.carewebframework.api.logging.LogFileTailer.java Source code

Java tutorial

Introduction

Here is the source code for org.carewebframework.api.logging.LogFileTailer.java

Source

/**
 * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. 
 * If a copy of the MPL was not distributed with this file, You can obtain one at 
 * http://mozilla.org/MPL/2.0/.
 * 
 * This Source Code Form is also subject to the terms of the Health-Related Additional
 * Disclaimer of Warranty and Limitation of Liability available at
 * http://www.carewebframework.org/licensing/disclaimer.
 */
package org.carewebframework.api.logging;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.RandomAccessFile;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * <p>
 * A file tailer is designed to monitor a file (i.e. log file) and send notifications when new lines
 * are added to the file. This class has a notification strategy similar to a SAX parser: implement
 * the <code>FileTailerListener</code> interface, create a <code>FileTailer</code> to tail your
 * file, add yourself as a listener, and start the <code>FileTailer</code>.
 * </p>
 * <p>
 * It is somewhat equivalent to the unix command <code>tail -f fileToTail</code>
 * </p>
 * <p>
 * It is your job to interpret the results, build meaningful sets of data, etc. This tailer simply
 * fires notifications containing new file lines, one at a time.
 * 
 * @author Steven Haines {@link "http://www.informit.com/guides/content.aspx?g=java&seqNum=226"}
 */
public class LogFileTailer implements Runnable {

    private static final Log log = LogFactory.getLog(LogFileTailer.class);

    /**
     * How frequently to check for file changes; defaults to 5 seconds
     */
    private long interval = 5000;

    /**
     * Maximum duration <code>this</code> can run. Default set to 120 seconds <i>Note: 1000=1
     * second</i>
     */
    private long maxActiveInterval = 120000;

    /**
     * The file to tail
     */
    private File file;

    /**
     * Defines whether the file tailer should include the entire contents of the existing file or
     * tail from the end of the file when the tailer starts
     */
    private boolean startAtBeginning;

    /**
     * Is the tailer currently tailing? TODO: If the variable were not declared volatile (and
     * without other synchronization), then it would be legal for the thread running the loop to
     * cache the value of the variable at the start of the loop and never read it again. If you
     * don't like infinite loops, this is undesirable.
     */
    private volatile boolean tailing;

    /**
     * Set of listeners
     */
    private final Set<LogFileTailerListener> listeners = new HashSet<LogFileTailerListener>();

    /**
     * Creates a new file tailer that tails an existing file and checks the file for updates every
     * 5000ms
     * 
     * @param file File
     */
    public LogFileTailer(final File file) {
        this.file = file;
    }

    /**
     * Creates a new file tailer
     * 
     * @param file The file to tail
     * @param interval How often to check for updates to the file (default = 5000ms)
     * @param startAtBeginning Should the tailer simply tail or should it process the entire file
     *            and continue tailing (true) or simply start tailing from the end of the file
     * @throws FileNotFoundException When no file found
     */
    public LogFileTailer(final File file, final long interval, final boolean startAtBeginning)
            throws FileNotFoundException {
        if (file == null) {
            throw new NullPointerException("File argument cannot be null");
        } else if (!file.exists()) {
            throw new FileNotFoundException(file.getAbsolutePath() + " does not exist");
        }
        this.startAtBeginning = startAtBeginning;
        this.interval = interval;
        this.file = file;
    }

    /**
     * Adds a FileTailerListener to the list of listeners
     * 
     * @param l FileTailerListener
     */
    public void addFileTailerListener(final LogFileTailerListener l) {
        this.listeners.add(l);
    }

    /**
     * Removes a FileTailerListener from the list of listeners
     * 
     * @param l FileTailListener
     */
    public void removeFileTailerListener(final LogFileTailerListener l) {
        this.listeners.remove(l);
    }

    /**
     * @param line Data read from the <code>file</code>
     */
    protected void fireNewFileLine(final String line) {
        for (final LogFileTailerListener fileTailerListener : this.listeners) {
            final LogFileTailerListener l = fileTailerListener;
            l.newFileLine(line);
        }
    }

    protected void fireMaxActiveIntervalExceeded() {
        for (final LogFileTailerListener fileTailerListener : this.listeners) {
            final LogFileTailerListener l = fileTailerListener;
            l.tailerTerminated();
        }
    }

    /**
     * Stops <code>this</code> from tailing a file
     */
    public void stopTailing() {
        this.tailing = false;
    }

    /**
     * Attempts to change the file that is being tailed by <code>this</code> Note that if
     * <code>this</code> is currently tailing then an exception is thrown
     * 
     * @param file File to change
     * @throws NullPointerException If the <code>file</code> argument is <code>null</code>
     * @throws IllegalStateException Thrown when <code>this</code> is in a running state (i.e.
     *             tailing)
     * @throws FileNotFoundException Thrown when <code>file</code> argument does not exist
     */
    public void changeFile(final File file) throws FileNotFoundException {
        if (isTailing()) {
            throw new IllegalStateException("Cannot Change FileTailer.file while current instance is tailing");
        } else {
            if (file == null) {
                throw new NullPointerException("changeFile(File argument) cannot be null");
            } else if (!file.exists()) {
                throw new FileNotFoundException("File does not exist: " + file.getAbsolutePath());
            }
            this.file = file;
        }
    }

    /**
     * Returns whether <code>this</code> is currently tailing a file
     * 
     * @return boolean
     */
    public boolean isTailing() {
        return this.tailing;
    }

    /**
     * Typically executed via a <code>new Thread(FileTailer).start()</code>
     */
    @Override
    public void run() {
        // The file pointer keeps track of where we are in the file
        long filePointer = 0;
        final long startTime = new Date().getTime();

        // Determine start point
        if (this.startAtBeginning) {
            filePointer = 0;
        } else {
            filePointer = this.file.length();
        }

        try {
            // Start tailing
            this.tailing = true;
            RandomAccessFile file = new RandomAccessFile(this.file, "r");
            while (isTailing()) {
                //check to see if maxActiveInterval has been exceeded
                if (new Date().getTime() - startTime > this.maxActiveInterval) {
                    if (log.isWarnEnabled()) {
                        log.warn("FileTailer exceeded maxActiveInterval: " + this.maxActiveInterval);
                    }
                    stopTailing();
                    fireMaxActiveIntervalExceeded();
                }
                try {
                    // Compare the length of the file to the file pointer
                    final long fileLength = this.file.length();
                    if (fileLength < filePointer) {
                        // file must have been rotated or deleted;
                        // reopen the file and reset the file pointer
                        file = new RandomAccessFile(this.file, "r");
                        filePointer = 0;
                    }

                    if (fileLength > filePointer) {
                        // There is data to read
                        file.seek(filePointer);
                        String line = file.readLine();
                        while (line != null) {
                            fireNewFileLine(line);
                            line = file.readLine();
                        }
                        filePointer = file.getFilePointer();
                    }

                    // Sleep for the specified interval
                    Thread.sleep(this.interval);
                } catch (final Exception e) {
                    log.error(e.getMessage(), e);
                }
            }

            // Close the file that we are tailing
            file.close();
        } catch (final Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    /**
     * Auto generated method comment
     * 
     * @return long max active interval
     */
    public long getMaxActiveInterval() {
        return this.maxActiveInterval;
    }

    /**
     * Set the maximum duration <code>this</code> can run. In seconds.
     * 
     * @param maxActiveInterval amount of time tailer is running (in seconds)
     */
    public void setMaxActiveInterval(final long maxActiveInterval) {
        this.maxActiveInterval = maxActiveInterval;
    }

}