package com.linkedin.databus.core;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.apache.log4j.Logger;
import com.linkedin.databus2.core.DatabusException;
/**
*
* Thread that polls for new files in trail directory, filters and ranks
* using the filter callback and notifies the listener through callback
* of new file in the trail order.
*/
public class TrailFileNotifier
extends DatabusThreadBase
{
public static final String MODULE = TrailFileNotifier.class.getName();
public static final Logger LOG = Logger.getLogger(MODULE);
private final File _dir;
private final long _pollIntervalMs;
private final TrailFileManager _fileFilter;
private final TrailFileListener _trailFileListener;
// Trail files starting from "_initialFileToBeginProcessing" will be processed. Any trail file that is older than _initialFileToBeginProcessing in trail file order will not be read.
private final File _initialFileToBeginProcessing;
// High WaterMark File that has been processed. Every poll cycle, only trail files that are later (in trail file order) than lastSeenFile will be processed.
private File _lastSeenFile;
private boolean _shutdownOnError = false;
public TrailFileNotifier(
File dir,
TrailFileManager fileFilter,
File startFile,
long pollIntervalMs,
TrailFileListener trailFileListener)
{
super(MODULE);
_dir = dir;
_initialFileToBeginProcessing = startFile;
_pollIntervalMs = pollIntervalMs;
_trailFileListener = trailFileListener;
_fileFilter = fileFilter;
}
@Override
public boolean runOnce()
{
LOG.debug("TrailFileNotifier running one cycle !!");
boolean success = fetchOneTime();
if ( ! success)
{
_shutdownOnError = true;
return false;
}
try
{
Thread.sleep(_pollIntervalMs);
} catch (InterruptedException ie) {
LOG.info("Got interrupted while sleeping for :" + _pollIntervalMs + " ms");
}
return true;
}
@Override
public void beforeRun()
{}
@Override
public void afterRun()
{}
/**
* One cycle of fetching trail files
*/
public synchronized boolean fetchOneTime()
{
List<File> candidateTrailFiles = getCandidateTrailFiles();
if (LOG.isDebugEnabled())
LOG.debug("Final List of Files in Directory :" + candidateTrailFiles);
try
{
for (File t : candidateTrailFiles)
{
if (LOG.isDebugEnabled())
LOG.debug("Adding trail file :" + t);
_trailFileListener.onNewTrailFile(t);
_lastSeenFile = t;
}
} catch (Exception ex) {
LOG.error("Got exception while enqueuing trail file", ex);
_trailFileListener.onError(ex);
return false;
}
return true;
}
public synchronized List<File> getCandidateTrailFiles()
{
File[] filesInDirArray = _dir.listFiles();
if (LOG.isDebugEnabled())
LOG.debug("Files in Directory :" + Arrays.toString(filesInDirArray));
List<File> candidateTrailFiles = new ArrayList<File>();
// filtering out old trail files
if ( null != filesInDirArray) // filesInDirArray could be null if path doesn't exist or I/O error
{
for (File t : filesInDirArray)
{
if ( (null == t) || (! _fileFilter.isTrailFile(t)))
continue;
if ((null != _initialFileToBeginProcessing) && (_fileFilter.compareFileName(t, _initialFileToBeginProcessing) < 0))
continue;
if ( (null != _lastSeenFile) && ( _fileFilter.compareFileName(t, _lastSeenFile) <= 0 ))
continue;
candidateTrailFiles.add(t);
}
}
if (LOG.isDebugEnabled())
LOG.debug("Candidate Files in Directory :" + candidateTrailFiles);
// Order the trail files
Collections.sort(candidateTrailFiles, new Comparator<File>() {
@Override
public int compare(File o1, File o2) {
return _fileFilter.compareFileName(o1, o2);
}});
return candidateTrailFiles;
}
public boolean isShutdownOnError()
{
return _shutdownOnError;
}
/**
*
* Interface called by ChunkedInputStream to filter and order the trail files
*
*/
public static interface TrailFileManager
{
/**
*
* Callback for sorting the files in trail file order
*
* @param file1 : file in trail directory
* @param file2 : another file in trail directory
* @return 0 if file1 == file2, -1 if file1 appears before file2 in trail file order, 1 otherwise
*/
public int compareFileName(File file1, File file2);
/**
* If the file is a trail file, return true else return false
*
* @param file
* @return
*/
public boolean isTrailFile(File file);
/**
* Returns true if file2 is the logical next file to file1.
* If this cannot be determined with the file objects alone, return true
*
* @param file1 File1
* @param file2 File2
* @return
*/
public boolean isNextFileInSequence(File file1, File file2);
}
/**
*
* Callback Interface to notify clients about new trail file presence
*/
public static interface TrailFileListener
{
/**
* Callback to notify new trail file in the directory.
*
* Contract:
* (a) The file path is guaranteed to be absolute.
* (b) This callback is called in the order of files that are generated.
* @param New trail file
* @throws DatabusException if validation fails on the callback.
*/
public void onNewTrailFile(File file) throws DatabusException;
/**
* Allows callback to cleanup if TrailFileNotifier is shutting-down because of error
* @param ex
*/
public void onError(Throwable ex);
}
}