uk.ac.bbsrc.tgac.miso.tools.run.FileQueueMessageSource.java Source code

Java tutorial

Introduction

Here is the source code for uk.ac.bbsrc.tgac.miso.tools.run.FileQueueMessageSource.java

Source

/*
 * Copyright (c) 2012. The Genome Analysis Centre, Norwich, UK
 * MISO project contacts: Robert Davey, Mario Caccamo @ TGAC
 * *********************************************************************
 *
 * This file is part of MISO.
 *
 * MISO is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * MISO is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with MISO.  If not, see <http://www.gnu.org/licenses/>.
 *
 * *********************************************************************
 */

package uk.ac.bbsrc.tgac.miso.tools.run;

import java.io.File;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.PriorityBlockingQueue;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.integration.Message;
import org.springframework.integration.MessagingException;
import org.springframework.integration.aggregator.ResequencingMessageGroupProcessor;
import org.springframework.integration.context.IntegrationObjectSupport;
import org.springframework.integration.core.MessageSource;
import org.springframework.integration.file.DefaultDirectoryScanner;
import org.springframework.integration.file.DirectoryScanner;
import org.springframework.integration.file.FileLocker;
import org.springframework.integration.file.HeadDirectoryScanner;
import org.springframework.integration.file.filters.AcceptOnceFileListFilter;
import org.springframework.integration.file.filters.FileListFilter;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.util.Assert;

/**
 * uk.ac.bbsrc.tgac.miso.notification.core
 * <p/>
 * Modified from the source below to provide a list of files as a result, rather than
 * polling single files sequentially from the queue 
 *
 * @author Rob Davey
 * @date 08-Dec-2010
 * @since 0.0.2
 *
 * --------------------------------
 *
 * {@link MessageSource} that creates messages from a file system directory. To
 * prevent messages for certain files, you may supply a
 * {@link FileListFilter}. By
 * default, an
 * {@link AcceptOnceFileListFilter}
 * is used. It ensures files are picked up only once from the directory.
 * <p/>
 * A common problem with reading files is that a file may be detected before it
 * is ready. The default {@link AcceptOnceFileListFilter}
 * does not prevent this. In most cases, this can be prevented if the
 * file-writing process renames each file as soon as it is ready for reading. A
 * pattern-matching filter that accepts only files that are ready (e.g. based on
 * a known suffix), composed with the default {@link AcceptOnceFileListFilter}
 * would allow for this.
 * <p/>
 * A {@link Comparator} can be used to ensure internal ordering of the Files in
 * a {@link PriorityBlockingQueue}. This does not provide the same guarantees as
 * a {@link ResequencingMessageGroupProcessor}, but in cases where writing files
 * and failure downstream are rare it might be sufficient.
 * <p/>
 * FileReadingMessageSource is fully thread-safe under concurrent
 * <code>receive()</code> invocations and message delivery callbacks.
 *
 * @author Iwein Fuld
 * @author Mark Fisher
 * @author Oleg Zhurakousky
 */
public class FileQueueMessageSource extends IntegrationObjectSupport implements MessageSource<Set<File>> {

    private static final int DEFAULT_INTERNAL_QUEUE_CAPACITY = 5;

    private static final Log logger = LogFactory.getLog(FileQueueMessageSource.class);

    private volatile File directory;

    private volatile DirectoryScanner scanner = new DefaultDirectoryScanner();

    private volatile boolean autoCreateDirectory = true;

    /*
       * {@link PriorityBlockingQueue#iterator()} throws
       * {@link java.util.ConcurrentModificationException} in Java 5.
       * There is no locking around the queue, so there is also no iteration.
       */
    private final PriorityBlockingQueue<File> toBeReceived;

    private volatile boolean scanEachPoll = false;

    /**
     * Creates a FileReadingMessageSource with a naturally ordered queue of unbounded capacity.
     */
    public FileQueueMessageSource() {
        this(null);
    }

    /**
     * Creates a FileReadingMessageSource with a bounded queue of the given
     * capacity. This can be used to reduce the memory footprint of this
     * component when reading from a large directory.
     *
     * @param internalQueueCapacity the size of the queue used to cache files to be received
     *                              internally. This queue can be made larger to optimize the
     *                              directory scanning. With scanEachPoll set to false and the
     *                              queue to a large size, it will be filled once and then
     *                              completely emptied before a new directory listing is done.
     *                              This is particularly useful to reduce scans of large numbers
     *                              of files in a directory.
     */
    public FileQueueMessageSource(int internalQueueCapacity) {
        this(null);
        Assert.isTrue(internalQueueCapacity > 0, "Cannot create a queue with non positive capacity");
        this.setScanner(new HeadDirectoryScanner(internalQueueCapacity));
    }

    /**
     * Creates a FileReadingMessageSource with a {@link PriorityBlockingQueue}
     * ordered with the passed in {@link Comparator}
     * <p/>
     * The size of the queue used should be large enough to hold all the files
     * in the input directory in order to sort all of them, so restricting the
     * size of the queue is mutually exclusive with ordering. No guarantees
     * about file delivery order can be made under concurrent access.
     * <p/>
     *
     * @param receptionOrderComparator the comparator to be used to order the files in the internal
     *                                 queue
     */
    public FileQueueMessageSource(Comparator<File> receptionOrderComparator) {
        this.toBeReceived = new PriorityBlockingQueue<File>(DEFAULT_INTERNAL_QUEUE_CAPACITY,
                receptionOrderComparator);
    }

    /**
     * Specify the input directory.
     *
     * @param directory to monitor
     */
    public void setDirectory(File directory) {
        Assert.notNull(directory, "directory must not be null");
        this.directory = directory;
    }

    /**
     * Optionally specify a custom scanner, for example the
     * {@link org.springframework.integration.file.RecursiveLeafOnlyDirectoryScanner}
     *
     * @param scanner scanner implementation
     */
    public void setScanner(DirectoryScanner scanner) {
        this.scanner = scanner;
    }

    /**
     * Specify whether to create the source directory automatically if it does
     * not yet exist upon initialization. By default, this value is
     * <emphasis>true</emphasis>. If set to <emphasis>false</emphasis> and the
     * source directory does not exist, an Exception will be thrown upon
     * initialization.
     *
     * @param autoCreateDirectory should the directory to be monitored be created when this
     *                            component starts up?
     */
    public void setAutoCreateDirectory(boolean autoCreateDirectory) {
        this.autoCreateDirectory = autoCreateDirectory;
    }

    /**
     * Sets a {@link FileListFilter}. By default a
     * {@link org.springframework.integration.file.filters.AbstractFileListFilter}
     * with no bounds is used. In most cases a customized {@link FileListFilter} will
     * be needed to deal with modification and duplication concerns. If multiple
     * filters are required a
     * {@link org.springframework.integration.file.filters.CompositeFileListFilter}
     * can be used to group them together.
     * <p/>
     * <b>The supplied filter must be thread safe.</b>.
     *
     * @param filter a filter
     */
    public void setFilter(FileListFilter<File> filter) {
        Assert.notNull(filter, "'filter' must not be null");
        this.scanner.setFilter(filter);
    }

    /**
     * Optional. Sets a {@link FileLocker} to be used to guard files against
     * duplicate processing.
     * <p/>
     * <b>The supplied FileLocker must be thread safe</b>
     *
     * @param locker a locker
     */
    public void setLocker(FileLocker locker) {
        Assert.notNull(locker, "'fileLocker' must not be null.");
        this.scanner.setLocker(locker);
    }

    /**
     * Optional. Set this flag if you want to make sure the internal queue is
     * refreshed with the latest content of the input directory on each poll.
     * <p/>
     * By default this implementation will empty its queue before looking at the
     * directory again. In cases where order is relevant it is important to
     * consider the effects of setting this flag. The internal
     * {@link java.util.concurrent.BlockingQueue} that this class is keeping
     * will more likely be out of sync with the file system if this flag is set
     * to <code>false</code>, but it will change more often (causing expensive
     * reordering) if it is set to <code>true</code>.
     *
     * @param scanEachPoll whether or not the component should re-scan (as opposed to not
     *                     rescanning until the entire backlog has been delivered)
     */
    public void setScanEachPoll(boolean scanEachPoll) {
        this.scanEachPoll = scanEachPoll;
    }

    public String getComponentType() {
        return "file:inbound-channel-adapter";
    }

    protected void onInit() {
        Assert.notNull(directory, "'directory' must not be null");
        if (!this.directory.exists() && this.autoCreateDirectory) {
            this.directory.mkdirs();
        }
        Assert.isTrue(this.directory.exists(), "Source directory [" + directory + "] does not exist.");
        Assert.isTrue(this.directory.isDirectory(),
                "Source path [" + this.directory + "] does not point to a directory.");
        Assert.isTrue(this.directory.canRead(), "Source directory [" + this.directory + "] is not readable.");
    }

    public Message<Set<File>> receive() throws MessagingException {
        Message<Set<File>> message = null;

        // rescan only if needed or explicitly configured
        if (scanEachPoll || toBeReceived.isEmpty()) {
            scanInputDirectory();
        }

        //instead of doing a poll() for a single files, drain the whole queue into a set
        Set<File> files = new HashSet<File>();
        toBeReceived.drainTo(files);

        for (File file : files) {
            while ((file != null) && !scanner.tryClaim(file)) {
                files.remove(file);
            }
        }

        if (!files.isEmpty()) {
            message = MessageBuilder.withPayload(files).build();
            if (logger.isDebugEnabled()) {
                logger.debug("Created message: [" + message + "]");
            }
        }
        return message;
    }

    private void scanInputDirectory() {
        List<File> filteredFiles = scanner.listFiles(directory);
        Set<File> freshFiles = new HashSet<File>(filteredFiles);
        if (!freshFiles.isEmpty()) {
            toBeReceived.addAll(freshFiles);
            if (logger.isDebugEnabled()) {
                logger.debug("Added to queue: " + freshFiles);
            }
        }
    }

    /**
     * Adds the failed message back to the 'toBeReceived' queue if there is room.
     *
     * @param failedMessage the {@link org.springframework.integration.Message} that failed
     */
    public void onFailure(Message<File> failedMessage) {
        if (logger.isWarnEnabled()) {
            logger.warn("Failed to send: " + failedMessage);
        }
        toBeReceived.offer(failedMessage.getPayload());
    }

    /**
     * The message is just logged. It was already removed from the queue during
     * the call to <code>receive()</code>
     *
     * @param sentMessage the message that was successfully delivered
     */
    public void onSend(Message<File> sentMessage) {
        if (logger.isDebugEnabled()) {
            logger.debug("Sent: " + sentMessage);
        }
    }
}