org.springframework.integration.file.remote.synchronizer.AbstractInboundFileSynchronizingMessageSource.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.integration.file.remote.synchronizer.AbstractInboundFileSynchronizingMessageSource.java

Source

/*
 * Copyright 2002-2019 the original author or authors.
 *
 * 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
 *
 *      https://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.springframework.integration.file.remote.synchronizer;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.regex.Pattern;

import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.context.Lifecycle;
import org.springframework.integration.endpoint.AbstractFetchLimitingMessageSource;
import org.springframework.integration.file.DefaultDirectoryScanner;
import org.springframework.integration.file.DirectoryScanner;
import org.springframework.integration.file.FileReadingMessageSource;
import org.springframework.integration.file.filters.CompositeFileListFilter;
import org.springframework.integration.file.filters.FileListFilter;
import org.springframework.integration.file.filters.FileSystemPersistentAcceptOnceFileListFilter;
import org.springframework.integration.file.filters.RegexPatternFileListFilter;
import org.springframework.integration.metadata.SimpleMetadataStore;
import org.springframework.integration.support.AbstractIntegrationMessageBuilder;
import org.springframework.util.Assert;

/**
 * Factors out the common logic between the FTP and SFTP adapters. Designed to
 * be extensible to handle adapters whose task it is to synchronize a remote
 * file system with a local file system (NB: this does *NOT* handle pushing
 * files TO the remote file system that exist uniquely in the local file system.
 * It only handles pulling from the remote file system - as you would expect
 * from an 'inbound' adapter).
 * <p>
 * The base class supports configuration of whether the remote file system and
 * local file system's directories should be created on start (what 'creating a
 * directory' means to the specific adapter is of course implementation
 * specific).
 * <p>
 * This class is to be used as a pair with an implementation of
 * {@link AbstractInboundFileSynchronizer}. The synchronizer must
 * handle the work of actually connecting to the remote file system and
 * delivering new {@link File}s.
 *
 * @author Josh Long
 * @author Oleg Zhurakousky
 * @author Gary Russell
 * @author Artem Bilan
 * @author Venil Noronha
 */
public abstract class AbstractInboundFileSynchronizingMessageSource<F>
        extends AbstractFetchLimitingMessageSource<File> implements Lifecycle {

    /**
     * An implementation that will handle the chores of actually connecting to and synchronizing
     * the remote file system with the local one, in an inbound direction.
     */
    private final AbstractInboundFileSynchronizer<F> synchronizer;

    /**
     * The actual {@link LocalFileReadingMessageSource} that monitors the local file system once files are synchronized.
     */
    private final LocalFileReadingMessageSource fileSource;

    /**
     * Should the endpoint attempt to create the local directory? True by default.
     */
    private boolean autoCreateLocalDirectory = true;

    /**
     * Directory to which things should be synchronized locally.
     */
    private File localDirectory;

    private FileListFilter<File> localFileListFilter;

    /**
     * Whether the {@link DirectoryScanner} was explicitly set.
     */
    private boolean scannerExplicitlySet = false;

    private volatile boolean running;

    public AbstractInboundFileSynchronizingMessageSource(AbstractInboundFileSynchronizer<F> synchronizer) {
        this(synchronizer, null);
    }

    public AbstractInboundFileSynchronizingMessageSource(AbstractInboundFileSynchronizer<F> synchronizer,
            Comparator<File> comparator) {

        Assert.notNull(synchronizer, "synchronizer must not be null");
        this.synchronizer = synchronizer;
        if (comparator == null) {
            this.fileSource = new LocalFileReadingMessageSource();
        } else {
            this.fileSource = new LocalFileReadingMessageSource(comparator);
        }
    }

    public void setAutoCreateLocalDirectory(boolean autoCreateLocalDirectory) {
        this.autoCreateLocalDirectory = autoCreateLocalDirectory;
    }

    public void setLocalDirectory(File localDirectory) {
        this.localDirectory = localDirectory;
    }

    /**
     * A {@link FileListFilter} used to determine which files will generate messages
     * after they have been synchronized. It will be combined with a filter that
     * will prevent accessing files that are in the process of being synchronized
     * (files having the {@link AbstractInboundFileSynchronizer#getTemporaryFileSuffix()}).
     * <p> The default is an {@link FileSystemPersistentAcceptOnceFileListFilter}
     * which filters duplicate file names (processed during the current execution).
     * @param localFileListFilter The local file list filter.
     */
    public void setLocalFilter(FileListFilter<File> localFileListFilter) {
        this.localFileListFilter = localFileListFilter;
    }

    /**
     * Switch the local {@link FileReadingMessageSource} to use its internal
     * {@code FileReadingMessageSource.WatchServiceDirectoryScanner}.
     * @param useWatchService the {@code boolean} flag to switch to
     * {@code FileReadingMessageSource.WatchServiceDirectoryScanner} on {@code true}.
     * @since 5.0
     */
    public void setUseWatchService(boolean useWatchService) {
        this.fileSource.setUseWatchService(useWatchService);
        if (useWatchService) {
            this.fileSource.setWatchEvents(FileReadingMessageSource.WatchEventType.CREATE,
                    FileReadingMessageSource.WatchEventType.MODIFY, FileReadingMessageSource.WatchEventType.DELETE);
        }
    }

    /**
     * Switch the local {@link FileReadingMessageSource} to use a custom
     * {@link DirectoryScanner}.
     * @param scanner the {@link DirectoryScanner} to use.
     * @since 5.0
     */
    public void setScanner(DirectoryScanner scanner) {
        this.fileSource.setScanner(scanner);
        this.scannerExplicitlySet = true;
    }

    /**
     * Return the underlying synchronizer.
     * @return the synchronizer.
     * @since 5.0.7
     */
    public AbstractInboundFileSynchronizer<F> getSynchronizer() {
        return this.synchronizer;
    }

    @Override
    protected void onInit() {
        Assert.notNull(this.localDirectory, "localDirectory must not be null");
        try {
            if (!this.localDirectory.exists()) {
                if (this.autoCreateLocalDirectory) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("The '" + this.localDirectory + "' directory doesn't exist; Will create.");
                    }
                    if (!this.localDirectory.mkdirs() && this.logger.isWarnEnabled()) {
                        this.logger.warn("Failed to create directories for " + this.localDirectory);
                    }
                } else {
                    throw new FileNotFoundException(this.localDirectory.getName());
                }
            }
            this.fileSource.setDirectory(this.localDirectory);
            initFiltersAndScanner();
            if (this.getBeanFactory() != null) {
                this.fileSource.setBeanFactory(this.getBeanFactory());
            }
            this.fileSource.afterPropertiesSet();
            this.synchronizer.afterPropertiesSet();
        } catch (RuntimeException e) { // NOSONAR catch and throw
            throw e;
        } catch (Exception e) {
            throw new BeanInitializationException("Failure during initialization for: " + this, e);
        }
    }

    private void initFiltersAndScanner() {
        if (this.localFileListFilter == null) {
            this.localFileListFilter = new FileSystemPersistentAcceptOnceFileListFilter(new SimpleMetadataStore(),
                    getComponentName());
        }
        FileListFilter<File> filter = buildFilter();
        if (this.scannerExplicitlySet) {
            Assert.state(!this.fileSource.isUseWatchService(),
                    "'useWatchService' and 'scanner' are mutually exclusive.");
            this.fileSource.getScanner().setFilter(filter);
        } else if (!this.fileSource.isUseWatchService()) {
            DirectoryScanner directoryScanner = new DefaultDirectoryScanner();
            directoryScanner.setFilter(filter);
            this.fileSource.setScanner(directoryScanner);
        } else {
            this.fileSource.setFilter(filter);
        }
    }

    @Override
    public void start() {
        this.running = true;
        this.fileSource.start();
    }

    @Override
    public void stop() {
        this.running = false;
        try {
            this.fileSource.stop();
            this.synchronizer.close();
        } catch (IOException e) {
            logger.error("Error closing synchronizer", e);
        }
    }

    @Override
    public boolean isRunning() {
        return this.running;
    }

    /**
     * Polls from the file source. If the result is not null, it will be returned.
     * If the result is null, it attempts to sync up with the remote directory to populate the file source.
     * At most, maxFetchSize files will be fetched.
     * Then, it polls the file source again and returns the result, whether or not it is null.
     * @param maxFetchSize the maximum files to fetch.
     */
    @Override
    public final AbstractIntegrationMessageBuilder<File> doReceive(int maxFetchSize) {
        AbstractIntegrationMessageBuilder<File> messageBuilder = this.fileSource.doReceive();
        if (messageBuilder == null) {
            this.synchronizer.synchronizeToLocalDirectory(this.localDirectory, maxFetchSize);
            messageBuilder = this.fileSource.doReceive();
        }

        return messageBuilder;
    }

    private FileListFilter<File> buildFilter() {
        Pattern completePattern = Pattern.compile("^.*(?<!" + this.synchronizer.getTemporaryFileSuffix() + ")$");
        return new CompositeFileListFilter<>(
                Arrays.asList(this.localFileListFilter, new RegexPatternFileListFilter(completePattern)));
    }

    /**
     * The {@link FileReadingMessageSource} extension to increase visibility
     * for the {@link FileReadingMessageSource#doReceive()}
     */
    private static final class LocalFileReadingMessageSource extends FileReadingMessageSource {

        LocalFileReadingMessageSource() {
        }

        LocalFileReadingMessageSource(Comparator<File> receptionOrderComparator) {
            super(receptionOrderComparator);
        }

        @Override
        protected AbstractIntegrationMessageBuilder<File> doReceive() { // NOSONAR - not useless, increases visibility
            return super.doReceive();
        }

    }

}