INT-4154: Merge Files Java DSL

JIRA: https://jira.spring.io/browse/INT-4154

* Move `Transformers` for files to the `Files` factory
* Remove `@Deprecated` methods
* Fix `FilesTests` for new packages and removed methods like `.handleWithAdapter()`

Provide JavaDocs for File DSL and implement some new methods like `chmod()` and `renameFunction()`

Fix race condition in the `ScriptsTests` when even small time window delay may lead us to one more message in the queue
This commit is contained in:
Artem Bilan
2016-11-04 16:23:43 -04:00
committed by Gary Russell
parent 507764a3d6
commit f71d6a0e66
16 changed files with 2286 additions and 22 deletions

View File

@@ -36,6 +36,7 @@ import org.springframework.integration.transformer.ObjectToStringTransformer;
import org.springframework.integration.transformer.PayloadDeserializingTransformer;
import org.springframework.integration.transformer.PayloadSerializingTransformer;
import org.springframework.integration.transformer.PayloadTypeConvertingTransformer;
import org.springframework.integration.transformer.StreamTransformer;
import org.springframework.integration.transformer.SyslogToMapTransformer;
import org.springframework.messaging.Message;
import org.springframework.util.Assert;
@@ -231,4 +232,22 @@ public abstract class Transformers {
return new DecodingTransformer<>(codec, typeExpression);
}
/**
* The factory method for the {@link StreamTransformer}.
* @return the {@link StreamTransformer} instance.
*/
public static StreamTransformer fromStream() {
return fromStream(null);
}
/**
* Create an instance with the charset to convert the stream to a
* String; if null a {@code byte[]} will be produced instead.
* @param charset the charset.
* @return the {@link StreamTransformer} instance.
*/
public static StreamTransformer fromStream(String charset) {
return new StreamTransformer(charset);
}
}

View File

@@ -0,0 +1,229 @@
/*
* Copyright 2016 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
*
* 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.springframework.integration.file.dsl;
import java.io.File;
import java.util.Comparator;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.integration.dsl.MessageSourceSpec;
import org.springframework.integration.file.DirectoryScanner;
import org.springframework.integration.file.FileLocker;
import org.springframework.integration.file.FileReadingMessageSource;
import org.springframework.integration.file.config.FileListFilterFactoryBean;
import org.springframework.integration.file.filters.AcceptAllFileListFilter;
import org.springframework.integration.file.filters.AcceptOnceFileListFilter;
import org.springframework.integration.file.filters.FileListFilter;
import org.springframework.integration.file.filters.IgnoreHiddenFileListFilter;
import org.springframework.integration.file.filters.RegexPatternFileListFilter;
import org.springframework.integration.file.filters.SimplePatternFileListFilter;
import org.springframework.integration.file.locking.NioFileLocker;
import org.springframework.util.Assert;
/**
* A {@link MessageSourceSpec} for a {@link FileReadingMessageSource}.
*
* @author Artem Bilan
*
* @since 5.0
*/
public class FileInboundChannelAdapterSpec
extends MessageSourceSpec<FileInboundChannelAdapterSpec, FileReadingMessageSource> {
private final FileListFilterFactoryBean fileListFilterFactoryBean = new FileListFilterFactoryBean();
private FileLocker locker;
FileInboundChannelAdapterSpec() {
this.target = new FileReadingMessageSource();
}
FileInboundChannelAdapterSpec(Comparator<File> receptionOrderComparator) {
this.target = new FileReadingMessageSource(receptionOrderComparator) {
@Override
protected void onInit() {
try {
setFilter(FileInboundChannelAdapterSpec.this.fileListFilterFactoryBean.getObject());
}
catch (Exception e) {
throw new BeanCreationException("The bean for the [" + this + "] can not be instantiated.", e);
}
super.onInit();
}
};
}
/**
* Specify the input directory.
* @param directory the directory.
* @return the spec.
* @see FileReadingMessageSource#setDirectory(File)
*/
FileInboundChannelAdapterSpec directory(File directory) {
this.target.setDirectory(directory);
return _this();
}
/**
* Specify a custom scanner.
* @param scanner the scanner.
* @return the spec.
* @see FileReadingMessageSource#setScanner(DirectoryScanner)
*/
public FileInboundChannelAdapterSpec scanner(DirectoryScanner scanner) {
this.target.setScanner(scanner);
return _this();
}
/**
* Specify whether to create the source directory automatically if it does
* not yet exist upon initialization. By default, this value is
* <em>true</em>. If set to <em>false</em> and the
* source directory does not exist, an Exception will be thrown upon
* initialization.
* @param autoCreateDirectory the autoCreateDirectory.
* @return the spec.
* @see FileReadingMessageSource#setAutoCreateDirectory(boolean)
*/
public FileInboundChannelAdapterSpec autoCreateDirectory(boolean autoCreateDirectory) {
this.target.setAutoCreateDirectory(autoCreateDirectory);
return _this();
}
/**
* Configure the filter.
* @param filter the filter.
* @return the spec.
* @see FileReadingMessageSource#setFilter(FileListFilter)
*/
public FileInboundChannelAdapterSpec filter(FileListFilter<File> filter) {
this.fileListFilterFactoryBean.setFilter(filter);
return _this();
}
/**
* Configure an {@link AcceptOnceFileListFilter} if {@code preventDuplicates == true},
* otherwise - {@link AcceptAllFileListFilter}.
* @param preventDuplicates true to configure an {@link AcceptOnceFileListFilter}.
* @return the spec.
*/
public FileInboundChannelAdapterSpec preventDuplicates(boolean preventDuplicates) {
this.fileListFilterFactoryBean.setPreventDuplicates(preventDuplicates);
return _this();
}
/**
/**
* Configure an {@link IgnoreHiddenFileListFilter} if {@code ignoreHidden == true}.
* @param ignoreHidden true to configure an {@link IgnoreHiddenFileListFilter}.
* @return the spec.
*/
public FileInboundChannelAdapterSpec ignoreHidden(boolean ignoreHidden) {
this.fileListFilterFactoryBean.setIgnoreHidden(ignoreHidden);
return _this();
}
/**
* Configure a {@link SimplePatternFileListFilter}.
* @param pattern The pattern.
* @return the spec.
* @see FileReadingMessageSource#setFilter(FileListFilter)
* @see #filter(FileListFilter)
*/
public FileInboundChannelAdapterSpec patternFilter(String pattern) {
this.fileListFilterFactoryBean.setFilenamePattern(pattern);
return _this();
}
/**
* Configure a {@link RegexPatternFileListFilter}.
* @param regex The regex.
* @return the spec.
* @see FileReadingMessageSource#setFilter(FileListFilter)
* @see #filter(FileListFilter)
*/
public FileInboundChannelAdapterSpec regexFilter(String regex) {
this.fileListFilterFactoryBean.setFilenameRegex(regex);
return _this();
}
/**
* Set a {@link FileLocker} to be used to guard files against
* duplicate processing.
* @param locker the locker.
* @return the spec.
* @see FileReadingMessageSource#setLocker(FileLocker)
*/
public FileInboundChannelAdapterSpec locker(FileLocker locker) {
Assert.isNull(this.locker,
"The 'locker' (" + this.locker + ") is already configured for the FileReadingMessageSource");
this.locker = locker;
this.target.setLocker(locker);
return _this();
}
/**
* Configure an {@link NioFileLocker}.
* @return the spec.
* @see #locker(FileLocker)
*/
public FileInboundChannelAdapterSpec nioLocker() {
return locker(new NioFileLocker());
}
/**
* 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.
* @param scanEachPoll the scanEachPoll.
* @return the spec.
* @see FileReadingMessageSource#setScanEachPoll(boolean)
*/
public FileInboundChannelAdapterSpec scanEachPoll(boolean scanEachPoll) {
this.target.setScanEachPoll(scanEachPoll);
return _this();
}
/**
* Switch this {@link FileReadingMessageSource} to use its internal
* {@link java.nio.file.WatchService} directory scanner.
* @param useWatchService the {@code boolean} flag to enable the use
* of a {@link java.nio.file.WatchService}.
* @return the spec.
* @see #watchEvents
* @see FileReadingMessageSource#setUseWatchService(boolean)
*/
public FileInboundChannelAdapterSpec useWatchService(boolean useWatchService) {
this.target.setUseWatchService(useWatchService);
return this;
}
/**
* The {@link java.nio.file.WatchService} event types.
* If {@link #useWatchService} isn't {@code true}, this option is ignored.
* @param watchEvents the set of {@link FileReadingMessageSource.WatchEventType}.
* @return the spec.
* @see #useWatchService
* @see FileReadingMessageSource#setWatchEvents
*/
public FileInboundChannelAdapterSpec watchEvents(FileReadingMessageSource.WatchEventType... watchEvents) {
this.target.setWatchEvents(watchEvents);
return this;
}
}

View File

@@ -0,0 +1,125 @@
/*
* Copyright 2016 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
*
* 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.springframework.integration.file.dsl;
import java.nio.charset.Charset;
import org.springframework.integration.dsl.MessageHandlerSpec;
import org.springframework.integration.file.splitter.FileSplitter;
/**
* The {@link MessageHandlerSpec} for the {@link FileSplitter}.
*
* @author Artem Bilan
*
* @since 5.0
*/
public class FileSplitterSpec extends MessageHandlerSpec<FileSplitterSpec, FileSplitter> {
private final boolean iterator;
private boolean markers;
private boolean markersJson;
private Charset charset;
private boolean applySequence;
FileSplitterSpec() {
this(true);
}
FileSplitterSpec(boolean iterator) {
this(iterator, false);
}
FileSplitterSpec(boolean iterator, boolean markers) {
this.iterator = iterator;
this.markers = markers;
}
/**
* Set the charset to be used when reading the file, when something other than the default
* charset is required.
* @param charset the charset.
* @return the FileSplitterSpec
*/
public FileSplitterSpec charset(String charset) {
return charset(Charset.forName(charset));
}
/**
* Set the charset to be used when reading the file, when something other than the default
* charset is required.
* @param charset the charset.
* @return the FileSplitterSpec
*/
public FileSplitterSpec charset(Charset charset) {
this.charset = charset;
return this;
}
/**
* Specify if {@link FileSplitter} should emit
* {@link org.springframework.integration.file.splitter.FileSplitter.FileMarker}s
* Defaults to {@code false}.
* @return the FileSplitterSpec
* @see FileSplitter
*/
public FileSplitterSpec markers() {
return markers(false);
}
/**
* Specify if {@link FileSplitter} should emit
* {@link org.springframework.integration.file.splitter.FileSplitter.FileMarker}s
* and if they should be converted to the JSON string representation.
* Defaults to {@code false} for markers and {@code false} for markersJson.
* @param asJson the asJson flag to use.
* @return the FileSplitterSpec
* @see FileSplitter
*/
public FileSplitterSpec markers(boolean asJson) {
this.markers = true;
this.markersJson = asJson;
return this;
}
/**
* A {@code boolean} flag to indicate if {@code sequenceDetails} should be
* applied for messages based on the lines from file.
* Defaults to {@code false}.
* @param applySequence the applySequence flag to use.
* @return the FileSplitterSpec
* @see org.springframework.integration.splitter.AbstractMessageSplitter#setApplySequence(boolean)
*/
public FileSplitterSpec applySequence(boolean applySequence) {
this.applySequence = applySequence;
return this;
}
@Override
protected FileSplitter doGet() {
FileSplitter fileSplitter = new FileSplitter(this.iterator, this.markers, this.markersJson);
fileSplitter.setApplySequence(this.applySequence);
fileSplitter.setCharset(this.charset);
return fileSplitter;
}
}

View File

@@ -0,0 +1,238 @@
/*
* Copyright 2016 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
*
* 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.springframework.integration.file.dsl;
import java.nio.charset.Charset;
import java.util.Collection;
import java.util.Collections;
import java.util.function.Function;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.integration.dsl.ComponentsRegistration;
import org.springframework.integration.dsl.MessageHandlerSpec;
import org.springframework.integration.expression.FunctionExpression;
import org.springframework.integration.file.DefaultFileNameGenerator;
import org.springframework.integration.file.FileNameGenerator;
import org.springframework.integration.file.remote.RemoteFileTemplate;
import org.springframework.integration.file.remote.handler.FileTransferringMessageHandler;
import org.springframework.integration.file.remote.session.SessionFactory;
import org.springframework.integration.file.support.FileExistsMode;
import org.springframework.messaging.Message;
import org.springframework.util.Assert;
/**
* The {@link MessageHandlerSpec} for the {@link FileTransferringMessageHandler}.
*
* @param <F> the target file type.
* @param <S> the target {@link FileTransferringMessageHandlerSpec} implementation type.
*
* @author Artem Bilan
*
* @since 5.0
*/
public abstract class FileTransferringMessageHandlerSpec<F, S extends FileTransferringMessageHandlerSpec<F, S>>
extends MessageHandlerSpec<S, FileTransferringMessageHandler<F>>
implements ComponentsRegistration {
private FileNameGenerator fileNameGenerator;
private DefaultFileNameGenerator defaultFileNameGenerator;
protected FileTransferringMessageHandlerSpec(SessionFactory<F> sessionFactory) {
this.target = new FileTransferringMessageHandler<>(sessionFactory);
}
protected FileTransferringMessageHandlerSpec(RemoteFileTemplate<F> remoteFileTemplate) {
this.target = new FileTransferringMessageHandler<>(remoteFileTemplate);
}
protected FileTransferringMessageHandlerSpec(RemoteFileTemplate<F> remoteFileTemplate,
FileExistsMode fileExistsMode) {
this.target = new FileTransferringMessageHandler<>(remoteFileTemplate, fileExistsMode);
}
/**
* A {@code boolean} flag to indicate automatically create the directory or not.
* @param autoCreateDirectory true to automatically create the directory.
* @return the current Spec
*/
public S autoCreateDirectory(boolean autoCreateDirectory) {
this.target.setAutoCreateDirectory(autoCreateDirectory);
return _this();
}
/**
* Specify a remote file separator symbol.
* @param remoteFileSeparator the remote file separator.
* @return the current Spec
*/
public S remoteFileSeparator(String remoteFileSeparator) {
this.target.setRemoteFileSeparator(remoteFileSeparator);
return _this();
}
/**
* Specify a remote directory path.
* @param remoteDirectory the remote directory path.
* @return the current Spec
*/
public S remoteDirectory(String remoteDirectory) {
this.target.setRemoteDirectoryExpression(new LiteralExpression(remoteDirectory));
return _this();
}
/**
* Specify a remote directory path SpEL expression.
* @param remoteDirectoryExpression the remote directory expression
* @return the current Spec
*/
public S remoteDirectoryExpression(String remoteDirectoryExpression) {
this.target.setRemoteDirectoryExpression(PARSER.parseExpression(remoteDirectoryExpression));
return _this();
}
/**
* Specify a remote directory path {@link Function}.
* @param remoteDirectoryFunction the remote directory {@link Function}
* @param <P> the expected payload type.
* @return the current Spec
*/
public <P> S remoteDirectory(Function<Message<P>, String> remoteDirectoryFunction) {
this.target.setRemoteDirectoryExpression(new FunctionExpression<>(remoteDirectoryFunction));
return _this();
}
/**
* Specify a remote directory path.
* @param temporaryRemoteDirectory the temporary remote directory path
* @return the current Spec
*/
public S temporaryRemoteDirectory(String temporaryRemoteDirectory) {
this.target.setTemporaryRemoteDirectoryExpression(new LiteralExpression(temporaryRemoteDirectory));
return _this();
}
/**
* Specify a remote directory path SpEL expression.
* @param temporaryRemoteDirectoryExpression the temporary remote directory path SpEL expression
* @return the current Spec
*/
public S temporaryRemoteDirectoryExpression(String temporaryRemoteDirectoryExpression) {
this.target.setTemporaryRemoteDirectoryExpression(PARSER.parseExpression(temporaryRemoteDirectoryExpression));
return _this();
}
/**
* Specify a remote temporary directory path {@link Function}.
* @param temporaryRemoteDirectoryFunction the temporary remote directory {@link Function}
* @param <P> the expected payload type.
* @return the current Spec
*/
public <P> S temporaryRemoteDirectory(Function<Message<P>, String> temporaryRemoteDirectoryFunction) {
this.target.setTemporaryRemoteDirectoryExpression(new FunctionExpression<>(temporaryRemoteDirectoryFunction));
return _this();
}
/**
* A {@code boolean} flag to use temporary files names or not.
* Defaults to {@code true}.
* @param useTemporaryFileName true to use a temporary file name.
* @return the current Spec
*/
public S useTemporaryFileName(boolean useTemporaryFileName) {
this.target.setUseTemporaryFileName(useTemporaryFileName);
return _this();
}
/**
* Set the file name generator used to generate the remote filename to be used when transferring
* files to the remote system. Default {@link DefaultFileNameGenerator}.
* @param fileNameGenerator the file name generator.
* @return the current Spec
*/
public S fileNameGenerator(FileNameGenerator fileNameGenerator) {
this.fileNameGenerator = fileNameGenerator;
this.target.setFileNameGenerator(fileNameGenerator);
return _this();
}
/**
* Set the {@link DefaultFileNameGenerator} based on the provided SpEL expression.
* @param fileNameGeneratorExpression the SpEL expression for file names generation.
* @return the current Spec
*/
public S fileNameExpression(String fileNameGeneratorExpression) {
Assert.isNull(this.fileNameGenerator,
"'fileNameGenerator' and 'fileNameGeneratorExpression' are mutually exclusive.");
this.defaultFileNameGenerator = new DefaultFileNameGenerator();
this.defaultFileNameGenerator.setExpression(fileNameGeneratorExpression);
return fileNameGenerator(this.defaultFileNameGenerator);
}
/**
* Set the charset to use when converting String payloads to bytes as the content of the
* remote file. Default {@code UTF-8}.
* @param charset the charset.
* @return the current Spec
*/
public S charset(String charset) {
this.target.setCharset(charset);
return _this();
}
/**
* Set the charset to use when converting String payloads to bytes as the content of the
* remote file. Default {@code UTF-8}.
* @param charset the charset.
* @return the current Spec
*/
public S charset(Charset charset) {
Assert.notNull(charset, "'charset' must not be null.");
return charset(charset.name());
}
/**
* Set the temporary suffix to use when transferring files to the remote system.
* Default ".writing".
* @param temporaryFileSuffix the suffix
* @return the current Spec
*/
public S temporaryFileSuffix(String temporaryFileSuffix) {
this.target.setTemporaryFileSuffix(temporaryFileSuffix);
return _this();
}
/**
* Set the file permissions after uploading, e.g. 0600 for
* owner read/write.
* @param chmod the permissions.
* @return the current Spec
*/
public S chmod(int chmod) {
this.target.setChmod(chmod);
return _this();
}
@Override
public Collection<Object> getComponentsToRegister() {
if (this.defaultFileNameGenerator != null) {
return Collections.singletonList(this.defaultFileNameGenerator);
}
return null;
}
}

View File

@@ -0,0 +1,242 @@
/*
* Copyright 2016 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
*
* 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.springframework.integration.file.dsl;
import java.io.File;
import java.util.Collection;
import java.util.Collections;
import java.util.function.Function;
import org.springframework.expression.Expression;
import org.springframework.integration.dsl.ComponentsRegistration;
import org.springframework.integration.dsl.MessageHandlerSpec;
import org.springframework.integration.expression.FunctionExpression;
import org.springframework.integration.file.DefaultFileNameGenerator;
import org.springframework.integration.file.FileHeaders;
import org.springframework.integration.file.FileNameGenerator;
import org.springframework.integration.file.FileWritingMessageHandler;
import org.springframework.integration.file.support.FileExistsMode;
import org.springframework.messaging.Message;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.util.Assert;
/**
* The {@link MessageHandlerSpec} for the {@link FileWritingMessageHandler}.
*
* @author Artem Bilan
*
* @since 5.0
*/
public class FileWritingMessageHandlerSpec
extends MessageHandlerSpec<FileWritingMessageHandlerSpec, FileWritingMessageHandler>
implements ComponentsRegistration {
private FileNameGenerator fileNameGenerator;
private DefaultFileNameGenerator defaultFileNameGenerator;
FileWritingMessageHandlerSpec(File destinationDirectory) {
this.target = new FileWritingMessageHandler(destinationDirectory);
}
FileWritingMessageHandlerSpec(String directoryExpression) {
this(PARSER.parseExpression(directoryExpression));
}
<P> FileWritingMessageHandlerSpec(Function<Message<P>, ?> directoryFunction) {
this(new FunctionExpression<>(directoryFunction));
}
FileWritingMessageHandlerSpec(Expression directoryExpression) {
this.target = new FileWritingMessageHandler(directoryExpression);
}
FileWritingMessageHandlerSpec expectReply(boolean expectReply) {
this.target.setExpectReply(expectReply);
if (expectReply) {
this.target.setRequiresReply(true);
}
return _this();
}
/**
* Specify whether to create the destination directory automatically if it
* does not yet exist upon initialization. By default, this value is
* <em>true</em>. If set to <em>false</em> and the
* destination directory does not exist, an Exception will be thrown upon
* initialization.
* @param autoCreateDirectory true to create the directory if needed.
* @return the current Spec
*/
public FileWritingMessageHandlerSpec autoCreateDirectory(boolean autoCreateDirectory) {
this.target.setAutoCreateDirectory(autoCreateDirectory);
return _this();
}
/**
* By default, every file that is in the process of being transferred will
* appear in the file system with an additional suffix, which by default is {@code .writing}.
* @param temporaryFileSuffix The temporary file suffix.
* @return the current Spec
*/
public FileWritingMessageHandlerSpec temporaryFileSuffix(String temporaryFileSuffix) {
this.target.setTemporaryFileSuffix(temporaryFileSuffix);
return _this();
}
/**
* Set the {@link FileExistsMode} that specifies what will happen in
* case the destination exists.
* @param fileExistsMode the {@link FileExistsMode} to consult.
* @return the current Spec
*/
public FileWritingMessageHandlerSpec fileExistsMode(FileExistsMode fileExistsMode) {
this.target.setFileExistsMode(fileExistsMode);
return _this();
}
/**
* Set the file name generator used to generate the target file name.
* Default {@link DefaultFileNameGenerator}.
* @param fileNameGenerator the file name generator.
* @return the current Spec
*/
public FileWritingMessageHandlerSpec fileNameGenerator(FileNameGenerator fileNameGenerator) {
this.fileNameGenerator = fileNameGenerator;
this.target.setFileNameGenerator(fileNameGenerator);
return _this();
}
/**
* Set the {@link DefaultFileNameGenerator} based on the provided SpEL expression.
* @param fileNameExpression the SpEL expression for file names generation.
* @return the current Spec
*/
public FileWritingMessageHandlerSpec fileNameExpression(String fileNameExpression) {
Assert.isNull(this.fileNameGenerator,
"'fileNameGenerator' and 'fileNameGeneratorExpression' are mutually exclusive.");
this.defaultFileNameGenerator = new DefaultFileNameGenerator();
this.defaultFileNameGenerator.setExpression(fileNameExpression);
return fileNameGenerator(this.defaultFileNameGenerator);
}
/**
* Specify whether to delete source Files after writing to the destination
* directory. The default is <em>false</em>. When set to <em>true</em>, it
* will only have an effect if the inbound Message has a File payload or
* a {@link FileHeaders#ORIGINAL_FILE} header value containing either a
* File instance or a String representing the original file path.
* @param deleteSourceFiles true to delete the source files.
* @return the current Spec
*/
public FileWritingMessageHandlerSpec deleteSourceFiles(boolean deleteSourceFiles) {
this.target.setDeleteSourceFiles(deleteSourceFiles);
return _this();
}
/**
* Set the charset to use when converting String payloads to bytes as the content of the file.
* Default {@code UTF-8}.
* @param charset the charset.
* @return the current Spec
*/
public FileWritingMessageHandlerSpec charset(String charset) {
this.target.setCharset(charset);
return _this();
}
/**
* If {@code true} will append a new-line after each write.
* Defaults to {@code false}.
* @param appendNewLine true if a new-line should be written to the file after payload is written.
* @return the spec.
* @see FileWritingMessageHandler#setAppendNewLine(boolean)
*/
public FileWritingMessageHandlerSpec appendNewLine(boolean appendNewLine) {
this.target.setAppendNewLine(appendNewLine);
return this;
}
/**
* Set the buffer size to use while writing to files; default 8192.
* @param bufferSize the buffer size.
* @return the spec.
* @see FileWritingMessageHandler#setBufferSize(int)
*/
public FileWritingMessageHandlerSpec bufferSize(int bufferSize) {
this.target.setBufferSize(bufferSize);
return this;
}
/**
* Set the frequency to flush buffers when {@link FileExistsMode#APPEND_NO_FLUSH} is
* being used.
* @param flushInterval the interval.
* @return the spec.
* @see FileWritingMessageHandler#setBufferSize(int)
*/
public FileWritingMessageHandlerSpec flushInterval(long flushInterval) {
this.target.setFlushInterval(flushInterval);
return this;
}
/**
* Specify a {@link TaskScheduler} for flush task when the {@link FileExistsMode#APPEND_NO_FLUSH} is in use.
* @param taskScheduler the {@link TaskScheduler} to use.
* @return the spec.
* @see FileWritingMessageHandler#setTaskScheduler(TaskScheduler)
*/
public FileWritingMessageHandlerSpec taskScheduler(TaskScheduler taskScheduler) {
this.target.setTaskScheduler(taskScheduler);
return this;
}
/**
* Specify a {@link FileWritingMessageHandler.MessageFlushPredicate} for flush task
* when the {@link FileExistsMode#APPEND_NO_FLUSH} is in use.
* @param flushPredicate the {@link FileWritingMessageHandler.MessageFlushPredicate} to use.
* @return the spec.
* @see FileWritingMessageHandler#setFlushPredicate(FileWritingMessageHandler.MessageFlushPredicate)
*/
public FileWritingMessageHandlerSpec flushPredicate(
FileWritingMessageHandler.MessageFlushPredicate flushPredicate) {
this.target.setFlushPredicate(flushPredicate);
return this;
}
/**
* Set to true to preserve the destination file timestamp. If true and
* the payload is a {@link File}, the payload's {@code lastModified} time will be
* transferred to the destination file.
* @param preserveTimestamp the {@code boolean} flag to use.
* @return the spec.
* @see FileWritingMessageHandler#setPreserveTimestamp(boolean)
*/
public FileWritingMessageHandlerSpec preserveTimestamp(boolean preserveTimestamp) {
this.target.setPreserveTimestamp(preserveTimestamp);
return this;
}
@Override
public Collection<Object> getComponentsToRegister() {
if (this.defaultFileNameGenerator != null) {
return Collections.singletonList(this.defaultFileNameGenerator);
}
return null;
}
}

View File

@@ -0,0 +1,229 @@
/*
* Copyright 2016 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
*
* 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.springframework.integration.file.dsl;
import java.io.File;
import java.util.Comparator;
import java.util.function.Function;
import org.springframework.expression.Expression;
import org.springframework.integration.file.transformer.FileToByteArrayTransformer;
import org.springframework.integration.file.transformer.FileToStringTransformer;
import org.springframework.messaging.Message;
/**
* The Spring Integration File components Factory.
*
* @author Artem Bilan
*
* @since 5.0
*/
public abstract class Files {
/**
* Create a {@link FileInboundChannelAdapterSpec} builder for the {@code FileReadingMessageSource}.
* @param directory the directory to scan files.
* @return the {@link FileInboundChannelAdapterSpec} instance.
*/
public static FileInboundChannelAdapterSpec inboundAdapter(File directory) {
return inboundAdapter(directory, null);
}
/**
* Create a {@link FileInboundChannelAdapterSpec} builder for the {@code FileReadingMessageSource}.
* @param directory the directory to scan files.
* @param receptionOrderComparator the {@link Comparator} for ordering file objects.
* @return the {@link FileInboundChannelAdapterSpec} instance.
*/
public static FileInboundChannelAdapterSpec inboundAdapter(File directory,
Comparator<File> receptionOrderComparator) {
return new FileInboundChannelAdapterSpec(receptionOrderComparator).directory(directory);
}
/**
* Create a {@link FileWritingMessageHandlerSpec} builder for the one-way {@code FileWritingMessageHandler}.
* @param destinationDirectory the target directory to write files.
* @return the {@link FileWritingMessageHandlerSpec} instance.
*/
public static FileWritingMessageHandlerSpec outboundAdapter(File destinationDirectory) {
return new FileWritingMessageHandlerSpec(destinationDirectory).expectReply(false);
}
/**
* Create a {@link FileWritingMessageHandlerSpec} builder for the one-way {@code FileWritingMessageHandler}.
* @param directoryExpression the SpEL expression to evaluate target directory for writing files.
* @return the {@link FileWritingMessageHandlerSpec} instance.
*/
public static FileWritingMessageHandlerSpec outboundAdapter(String directoryExpression) {
return new FileWritingMessageHandlerSpec(directoryExpression).expectReply(false);
}
/**
* Create a {@link FileWritingMessageHandlerSpec} builder for the one-way {@code FileWritingMessageHandler}.
* @param directoryExpression an expression to evaluate the target directory.
* @return the {@link FileWritingMessageHandlerSpec} instance.
*/
public static FileWritingMessageHandlerSpec outboundAdapter(Expression directoryExpression) {
return new FileWritingMessageHandlerSpec(directoryExpression).expectReply(false);
}
/**
* Create a {@link FileWritingMessageHandlerSpec} builder for the one-way {@code FileWritingMessageHandler}.
* @param directoryFunction the {@link Function} to evaluate the target directory at runtime.
* @param <P> the payload type.
* @return the {@link FileWritingMessageHandlerSpec} instance.
*/
public static <P> FileWritingMessageHandlerSpec outboundAdapter(Function<Message<P>, ?> directoryFunction) {
return new FileWritingMessageHandlerSpec(directoryFunction).expectReply(false);
}
/**
* Create a {@link FileWritingMessageHandlerSpec} builder for the gateway {@code FileWritingMessageHandler}.
* @param destinationDirectory the target directory to write files.
* @return the {@link FileWritingMessageHandlerSpec} instance.
*/
public static FileWritingMessageHandlerSpec outboundGateway(File destinationDirectory) {
return new FileWritingMessageHandlerSpec(destinationDirectory).expectReply(true);
}
/**
* Create a {@link FileWritingMessageHandlerSpec} builder for the gateway {@code FileWritingMessageHandler}.
* @param directoryExpression the SpEL expression to evaluate target directory for writing files.
* @return the {@link FileWritingMessageHandlerSpec} instance.
*/
public static FileWritingMessageHandlerSpec outboundGateway(String directoryExpression) {
return new FileWritingMessageHandlerSpec(directoryExpression).expectReply(true);
}
/**
* Create a {@link FileWritingMessageHandlerSpec} based on the provided {@link Expression} for directory.
* @param directoryExpression an expression to evaluate the target directory.
* @return the FileWritingMessageHandlerSpec instance.
*/
public static FileWritingMessageHandlerSpec outboundGateway(Expression directoryExpression) {
return new FileWritingMessageHandlerSpec(directoryExpression).expectReply(true);
}
/**
* Create a {@link FileWritingMessageHandlerSpec} builder for the gateway {@code FileWritingMessageHandler}.
* @param directoryFunction the {@link Function} to evaluate the target directory at runtime.
* @param <P> the payload type.
* @return the {@link FileWritingMessageHandlerSpec} instance.
*/
public static <P> FileWritingMessageHandlerSpec outboundGateway(Function<Message<P>, ?> directoryFunction) {
return new FileWritingMessageHandlerSpec(directoryFunction).expectReply(true);
}
/**
* Create a {@link TailAdapterSpec} builder for the {@code FileTailingMessageProducerSupport}.
* @param file the file to tail.
* @return the {@link TailAdapterSpec} instance.
*/
public static TailAdapterSpec tailAdapter(File file) {
return new TailAdapterSpec().file(file);
}
/**
* The {@link FileSplitterSpec} builder factory method with default arguments.
* @return the {@link FileSplitterSpec} builder.
*/
public static FileSplitterSpec splitter() {
return splitter(true);
}
/**
* The {@link FileSplitterSpec} builder factory method with {@code iterator} flag specified.
* @param iterator the {@code boolean} flag to specify the {@code iterator} mode or not.
* @return the {@link FileSplitterSpec} builder.
*/
public static FileSplitterSpec splitter(boolean iterator) {
return splitter(iterator, false);
}
/**
* The {@link FileSplitterSpec} builder factory method with {@code iterator} and {@code markers}
* flags specified.
* @param iterator the {@code boolean} flag to specify the {@code iterator} mode or not.
* @param markers true to emit start of file/end of file marker messages before/after the data.
* @return the {@link FileSplitterSpec} builder.
*/
public static FileSplitterSpec splitter(boolean iterator, boolean markers) {
return new FileSplitterSpec(iterator, markers);
}
/**
* Create a {@link FileToStringTransformer} instance with default {@code charset} and no delete files afterwards.
* @return the {@link FileToStringTransformer}.
*/
public static FileToStringTransformer toStringTransformer() {
return toStringTransformer(false);
}
/**
* Create a {@link FileToStringTransformer} instance with default {@code charset} and with delete files flag.
* @param deleteFiles true to delete the file.
* @return the {@link FileToStringTransformer}.
*/
public static FileToStringTransformer toStringTransformer(boolean deleteFiles) {
return toStringTransformer(null, deleteFiles);
}
/**
* Create a {@link FileToStringTransformer} instance with provided {@code charset} and no delete files afterwards.
* @param charset The charset.
* @return the {@link FileToStringTransformer}.
*/
public static FileToStringTransformer toStringTransformer(String charset) {
return toStringTransformer(charset, false);
}
/**
* Create a {@link FileToStringTransformer} instance with provided {@code charset} and delete files flag.
* @param charset The charset.
* @param deleteFiles true to delete the file.
* @return the {@link FileToStringTransformer}.
*/
public static FileToStringTransformer toStringTransformer(String charset, boolean deleteFiles) {
FileToStringTransformer transformer = new FileToStringTransformer();
if (charset != null) {
transformer.setCharset(charset);
}
transformer.setDeleteFiles(deleteFiles);
return transformer;
}
/**
* Create a {@link FileToByteArrayTransformer} instance.
* @return the {@link FileToByteArrayTransformer}.
*/
public static FileToByteArrayTransformer toByteArrayTransformer() {
return toByteArrayTransformer(false);
}
/**
* Create a {@link FileToByteArrayTransformer} instance.
* @param deleteFiles specify whether to delete the File after transformation.
* Default is <em>false</em>.
* @return the {@link FileToByteArrayTransformer}.
*/
public static FileToByteArrayTransformer toByteArrayTransformer(boolean deleteFiles) {
FileToByteArrayTransformer fileToByteArrayTransformer = new FileToByteArrayTransformer();
fileToByteArrayTransformer.setDeleteFiles(deleteFiles);
return fileToByteArrayTransformer;
}
}

View File

@@ -0,0 +1,226 @@
/*
* Copyright 2016 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
*
* 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.springframework.integration.file.dsl;
import java.io.File;
import java.util.Collection;
import java.util.Collections;
import java.util.function.Function;
import org.springframework.expression.Expression;
import org.springframework.integration.dsl.ComponentsRegistration;
import org.springframework.integration.dsl.MessageSourceSpec;
import org.springframework.integration.expression.FunctionExpression;
import org.springframework.integration.file.filters.CompositeFileListFilter;
import org.springframework.integration.file.filters.FileListFilter;
import org.springframework.integration.file.remote.synchronizer.AbstractInboundFileSynchronizer;
import org.springframework.integration.file.remote.synchronizer.AbstractInboundFileSynchronizingMessageSource;
/**
* A {@link MessageSourceSpec} for an {@link AbstractInboundFileSynchronizingMessageSource}.
*
* @param <F> the target file type.
* @param <S> the target {@link RemoteFileInboundChannelAdapterSpec} implementation type.
* @param <MS> the target {@link AbstractInboundFileSynchronizingMessageSource} implementation type.
*
* @author Artem Bilan
*
* @since 5.0
*/
public abstract class RemoteFileInboundChannelAdapterSpec<F, S extends RemoteFileInboundChannelAdapterSpec<F, S, MS>,
MS extends AbstractInboundFileSynchronizingMessageSource<F>>
extends MessageSourceSpec<S, MS> implements ComponentsRegistration {
protected final AbstractInboundFileSynchronizer<F> synchronizer;
private CompositeFileListFilter<F> filter;
protected RemoteFileInboundChannelAdapterSpec(AbstractInboundFileSynchronizer<F> synchronizer) {
this.synchronizer = synchronizer;
}
/**
* Configure whether the local directory should be created by the adapter.
* @param autoCreateLocalDirectory the autoCreateLocalDirectory
* @return the spec.
*/
public S autoCreateLocalDirectory(boolean autoCreateLocalDirectory) {
this.target.setAutoCreateLocalDirectory(autoCreateLocalDirectory);
return _this();
}
/**
* Configure the local directory to copy files to.
* @param localDirectory the localDirectory.
* @return the spec.
*/
public S localDirectory(File localDirectory) {
this.target.setLocalDirectory(localDirectory);
return _this();
}
/**
* A {@link FileListFilter} used to determine which files will generate messages
* after they have been synchronized.
* @param localFileListFilter the localFileListFilter.
* @return the spec.
* @see AbstractInboundFileSynchronizingMessageSource#setLocalFilter(FileListFilter)
*/
public S localFilter(FileListFilter<File> localFileListFilter) {
this.target.setLocalFilter(localFileListFilter);
return _this();
}
/**
* Configure the file name path separator used by the remote system. Defaults to '/'.
* @param remoteFileSeparator the remoteFileSeparator.
* @return the spec.
*/
public S remoteFileSeparator(String remoteFileSeparator) {
this.synchronizer.setRemoteFileSeparator(remoteFileSeparator);
return _this();
}
/**
* Configure a SpEL expression to generate the local file name; the root object for
* the evaluation is the remote file name.
* @param localFilenameExpression the localFilenameExpression.
* @return the spec.
*/
public S localFilenameExpression(String localFilenameExpression) {
return localFilenameExpression(PARSER.parseExpression(localFilenameExpression));
}
/**
* Configure a {@link Function} to be invoked to generate the local file name;
* argument passed to the {@code apply} method is the remote file name.
* @param localFilenameFunction the localFilenameFunction.
* @return the spec.
* @see FunctionExpression
*/
public S localFilename(Function<String, String> localFilenameFunction) {
return localFilenameExpression(new FunctionExpression<>(localFilenameFunction));
}
/**
* Configure a SpEL expression to generate the local file name; the root object for
* the evaluation is the remote file name.
* @param localFilenameExpression the localFilenameExpression.
* @return the spec.
*/
public S localFilenameExpression(Expression localFilenameExpression) {
this.synchronizer.setLocalFilenameGeneratorExpression(localFilenameExpression);
return _this();
}
/**
* Configure a suffix to temporarily apply to the local filename; when copied the
* file is renamed to its final name. Default: '.writing'.
* @param temporaryFileSuffix the temporaryFileSuffix.
* @return the spec.
*/
public S temporaryFileSuffix(String temporaryFileSuffix) {
this.synchronizer.setTemporaryFileSuffix(temporaryFileSuffix);
return _this();
}
/**
* Specify the full path to the remote directory.
* @param remoteDirectory the remoteDirectory.
* @return the spec.
* @see AbstractInboundFileSynchronizer#setRemoteDirectory(String)
*/
public S remoteDirectory(String remoteDirectory) {
this.synchronizer.setRemoteDirectory(remoteDirectory);
return _this();
}
/**
* Specify an expression that evaluates to the full path to the remote directory.
* @param remoteDirectoryExpression The remote directory expression.
* @return the spec.
*/
public S remoteDirectoryExpression(Expression remoteDirectoryExpression) {
this.synchronizer.setRemoteDirectoryExpression(remoteDirectoryExpression);
return _this();
}
/**
* Configure a {@link FileListFilter} to be applied to the remote files before
* copying them.
* @param filter the filter.
* @return the spec.
*/
public S filter(FileListFilter<F> filter) {
if (this.filter == null) {
if (filter instanceof CompositeFileListFilter) {
this.filter = (CompositeFileListFilter<F>) filter;
}
else {
this.filter = new CompositeFileListFilter<F>();
this.filter.addFilter(filter);
}
this.synchronizer.setFilter(this.filter);
}
else {
this.filter.addFilter(filter);
}
return _this();
}
/**
* Configure a simple pattern filter (e.g. '*.txt').
* @param pattern the pattern.
* @return the spec.
* @see #filter(FileListFilter)
*/
public abstract S patternFilter(String pattern);
/**
* Configure a regex pattern filter (e.g. '[0-9].*.txt').
* @param regex the regex.
* @return the spec.
* @see #filter(FileListFilter)
*/
public abstract S regexFilter(String regex);
/**
* Set to true to enable deletion of remote files after successful transfer.
* @param deleteRemoteFiles true to delete.
* @return the spec.
*/
public S deleteRemoteFiles(boolean deleteRemoteFiles) {
this.synchronizer.setDeleteRemoteFiles(deleteRemoteFiles);
return _this();
}
/**
* Set to true to enable the preservation of the remote file timestamp when transferring.
* @param preserveTimestamp true to preserve.
* @return the spec.
*/
public S preserveTimestamp(boolean preserveTimestamp) {
this.synchronizer.setPreserveTimestamp(preserveTimestamp);
return _this();
}
@Override
public Collection<Object> getComponentsToRegister() {
return Collections.singletonList(this.synchronizer);
}
}

View File

@@ -0,0 +1,287 @@
/*
* Copyright 2016 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
*
* 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.springframework.integration.file.dsl;
import java.io.File;
import java.util.function.Function;
import org.springframework.expression.Expression;
import org.springframework.integration.dsl.MessageHandlerSpec;
import org.springframework.integration.expression.FunctionExpression;
import org.springframework.integration.file.filters.CompositeFileListFilter;
import org.springframework.integration.file.filters.FileListFilter;
import org.springframework.integration.file.filters.RegexPatternFileListFilter;
import org.springframework.integration.file.filters.SimplePatternFileListFilter;
import org.springframework.integration.file.remote.gateway.AbstractRemoteFileOutboundGateway;
import org.springframework.messaging.Message;
/**
* The {@link MessageHandlerSpec} for the {@link AbstractRemoteFileOutboundGateway}.
*
* @param <F> the target file type.
* @param <S> the target {@link RemoteFileOutboundGatewaySpec} implementation type.
*
* @author Artem Bilan
*
* @since 5.0
*/
public abstract class RemoteFileOutboundGatewaySpec<F, S extends RemoteFileOutboundGatewaySpec<F, S>>
extends MessageHandlerSpec<S, AbstractRemoteFileOutboundGateway<F>> {
private CompositeFileListFilter<F> filter;
private CompositeFileListFilter<File> mputFilter;
protected RemoteFileOutboundGatewaySpec(AbstractRemoteFileOutboundGateway<F> outboundGateway) {
this.target = outboundGateway;
this.target.setRequiresReply(true);
}
/**
* Specify the array of options for various gateway commands.
* @param options the options to set.
* @return the spec
* @see #options(AbstractRemoteFileOutboundGateway.Option...)
*/
public S options(String options) {
this.target.setOptions(options);
return _this();
}
/**
* Specify the array of {@link AbstractRemoteFileOutboundGateway.Option}
* for various gateway commands.
* @param options the options to set.
* @return the spec
*/
public S options(AbstractRemoteFileOutboundGateway.Option... options) {
this.target.setOptions(options);
return _this();
}
/**
* Set the file separator when dealing with remote files; default '/'.
* @param remoteFileSeparator the separator.
* @return the spec
*/
public S remoteFileSeparator(String remoteFileSeparator) {
this.target.setRemoteFileSeparator(remoteFileSeparator);
return _this();
}
/**
* Specify a directory path where remote files will be transferred to.
* @param localDirectory the localDirectory to set
* @return the spec
*/
public S localDirectory(File localDirectory) {
this.target.setLocalDirectory(localDirectory);
return _this();
}
/**
* Specify a SpEL expression to evaluate directory path where remote files will be transferred to.
* @param localDirectoryExpression the SpEL to determine the local directory.
* @return the spec
*/
public S localDirectoryExpression(String localDirectoryExpression) {
return localDirectoryExpression(PARSER.parseExpression(localDirectoryExpression));
}
/**
* Specify a {@link Function} to evaluate directory path where remote files will be transferred to.
* @param localDirectoryFunction the {@link Function} to determine the local directory.
* @param <P> the expected payload type.
* @return the spec
*/
public <P> S localDirectory(Function<Message<P>, String> localDirectoryFunction) {
return localDirectoryExpression(new FunctionExpression<>(localDirectoryFunction));
}
/**
* Specify a SpEL expression to evaluate directory path where remote files will be transferred to.
* @param localDirectoryExpression a SpEL expression to evaluate the local directory.
* @return the Spec.
*/
public S localDirectoryExpression(Expression localDirectoryExpression) {
this.target.setLocalDirectoryExpression(localDirectoryExpression);
return _this();
}
/**
* A {@code boolean} flag to identify if local directory should be created automatically.
* Defaults to {@code true}.
* @param autoCreateLocalDirectory the autoCreateLocalDirectory to set
* @return the Spec.
*/
public S autoCreateLocalDirectory(boolean autoCreateLocalDirectory) {
this.target.setAutoCreateLocalDirectory(autoCreateLocalDirectory);
return _this();
}
/**
* Set the temporary suffix to use when transferring files to the remote system.
* Default {@code .writing}.
* @param temporaryFileSuffix the temporaryFileSuffix to set
* @return the Spec.
*/
public S temporaryFileSuffix(String temporaryFileSuffix) {
this.target.setTemporaryFileSuffix(temporaryFileSuffix);
return _this();
}
/**
* Set a {@link FileListFilter} to filter remote files.
* @param filter the filter to set
* @return the Spec.
*/
public S filter(FileListFilter<F> filter) {
if (this.filter == null) {
if (filter instanceof CompositeFileListFilter) {
this.filter = (CompositeFileListFilter<F>) filter;
}
else {
this.filter = new CompositeFileListFilter<>();
this.filter.addFilter(filter);
}
this.target.setFilter(this.filter);
}
else {
this.filter.addFilter(filter);
}
return _this();
}
/**
* A {@link FileListFilter} that runs against the <em>local</em> file system view when
* using {@code MPUT} command.
* @param filter the filter to set
* @return the Spec.
*/
public S mputFilter(FileListFilter<File> filter) {
if (this.mputFilter == null) {
if (filter instanceof CompositeFileListFilter) {
this.mputFilter = (CompositeFileListFilter<File>) filter;
}
else {
this.mputFilter = new CompositeFileListFilter<>();
this.mputFilter.addFilter(filter);
}
this.target.setMputFilter(this.mputFilter);
}
else {
this.mputFilter.addFilter(filter);
}
return _this();
}
/**
* A {@link SimplePatternFileListFilter} that runs against the <em>local</em> file system view when
* using {@code MPUT} command.
* @param pattern the {@link SimplePatternFileListFilter} for {@code MPUT} command.
* @return the Spec.
*/
public S patternMputFilter(String pattern) {
return mputFilter(new SimplePatternFileListFilter(pattern));
}
/**
* A {@link SimplePatternFileListFilter} that runs against the <em>local</em> file system view when
* using {@code MPUT} command.
* @param regex the {@link SimplePatternFileListFilter} for {@code MPUT} command.
* @return the Spec.
*/
public S regexMpuFilter(String regex) {
return mputFilter(new RegexPatternFileListFilter(regex));
}
/**
* Specify a SpEL expression for files renaming during transfer.
* @param expression the String in SpEL syntax.
* @return the Spec.
*/
public S renameExpression(String expression) {
this.target.setRenameExpressionString(expression);
return _this();
}
/**
* Specify a SpEL expression for files renaming during transfer.
* @param expression the String in SpEL syntax.
* @return the Spec.
*/
public S renameExpression(Expression expression) {
this.target.setRenameExpression(expression);
return _this();
}
/**
* Specify a {@link Function} for files renaming during transfer.
* @param renameFunction the {@link Function} to use.
* @param <P> the expected payload type.
* @return the Spec.
*/
public <P> S renameFunction(Function<Message<P>, String> renameFunction) {
this.target.setRenameExpression(new FunctionExpression<>(renameFunction));
return _this();
}
/**
* Specify a SpEL expression for local files renaming after downloading.
* @param localFilenameExpression the SpEL expression to use.
* @return the Spec.
*/
public S localFilenameExpression(String localFilenameExpression) {
return localFilenameExpression(PARSER.parseExpression(localFilenameExpression));
}
/**
* Specify a {@link Function} for local files renaming after downloading.
* @param localFilenameFunction the {@link Function} to use.
* @param <P> the expected payload type.
* @return the Spec.
*/
public <P> S localFilename(Function<Message<P>, String> localFilenameFunction) {
return localFilenameExpression(new FunctionExpression<>(localFilenameFunction));
}
/**
* Specify a SpEL expression for local files renaming after downloading.
* @param localFilenameExpression a SpEL expression to evaluate the local file name.
* @return the Spec.
*/
public S localFilenameExpression(Expression localFilenameExpression) {
this.target.setLocalFilenameGeneratorExpression(localFilenameExpression);
return _this();
}
/**
* Set the file permissions after uploading, e.g. 0600 for
* owner read/write.
* @param chmod the permissions.
* @return the current Spec
*/
public S chmod(int chmod) {
this.target.setChmod(chmod);
return _this();
}
public abstract S patternFileNameFilter(String pattern);
public abstract S regexFileNameFilter(String regex);
}

View File

@@ -0,0 +1,186 @@
/*
* Copyright 2016 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
*
* 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.springframework.integration.file.dsl;
import java.io.File;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.core.task.TaskExecutor;
import org.springframework.integration.channel.NullChannel;
import org.springframework.integration.dsl.MessageProducerSpec;
import org.springframework.integration.file.config.FileTailInboundChannelAdapterFactoryBean;
import org.springframework.integration.file.tail.FileTailingMessageProducerSupport;
import org.springframework.messaging.MessageChannel;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.util.Assert;
/**
* A {@link MessageProducerSpec} for file tailing adapters.
*
* @author Artem Bilan
*
* @since 5.0
*/
public class TailAdapterSpec extends MessageProducerSpec<TailAdapterSpec, FileTailingMessageProducerSupport> {
private final FileTailInboundChannelAdapterFactoryBean factoryBean = new FileTailInboundChannelAdapterFactoryBean();
private MessageChannel outputChannel;
private MessageChannel errorChannel;
TailAdapterSpec() {
super(null);
this.factoryBean.setBeanFactory(new DefaultListableBeanFactory());
}
TailAdapterSpec file(File file) {
Assert.notNull(file);
this.factoryBean.setFile(file);
return _this();
}
/**
* Specify the options string for native {@code tail} command.
* @param nativeOptions the nativeOptions.
* @return the spec.
* @see org.springframework.integration.file.tail.OSDelegatingFileTailingMessageProducer#setOptions(String)
*/
public TailAdapterSpec nativeOptions(String nativeOptions) {
this.factoryBean.setNativeOptions(nativeOptions);
return _this();
}
/**
* Configure a task executor. Defaults to a
* {@link org.springframework.core.task.SimpleAsyncTaskExecutor}.
* @param taskExecutor the taskExecutor.
* @return the spec.
*/
public TailAdapterSpec taskExecutor(TaskExecutor taskExecutor) {
this.factoryBean.setTaskExecutor(taskExecutor);
return _this();
}
/**
* Set a task scheduler - defaults to the integration 'taskScheduler'.
* @param taskScheduler the taskScheduler.
* @return the spec.
*/
public TailAdapterSpec taskScheduler(TaskScheduler taskScheduler) {
this.factoryBean.setTaskScheduler(taskScheduler);
return _this();
}
/**
* The delay between checks of the file for new content in milliseconds.
* @param delay the delay.
* @return the spec.
* @see org.springframework.integration.file.tail.ApacheCommonsFileTailingMessageProducer#setPollingDelay(long)
*/
public TailAdapterSpec delay(long delay) {
this.factoryBean.setDelay(delay);
return _this();
}
/**
* The delay in milliseconds between attempts to tail a non-existent file,
* or between attempts to execute a process if it fails for any reason.
* @param fileDelay the fileDelay.
* @return the spec.
* @see FileTailingMessageProducerSupport#setTailAttemptsDelay(long)
*/
public TailAdapterSpec fileDelay(long fileDelay) {
this.factoryBean.setFileDelay(fileDelay);
return _this();
}
/**
* If {@code true}, tail from the end of the file, otherwise include all lines from the beginning.
* Default {@code true}.
* @param end the end.
* @return the spec.
* @see org.springframework.integration.file.tail.ApacheCommonsFileTailingMessageProducer#setEnd(boolean)
*/
public TailAdapterSpec end(boolean end) {
this.factoryBean.setEnd(end);
return _this();
}
/**
* If {@code true}, close and reopen the file between reading chunks.
* Default {@code false}.
* @param reopen the reopen.
* @return the spec.
* @see org.springframework.integration.file.tail.ApacheCommonsFileTailingMessageProducer#setReopen(boolean)
*/
public TailAdapterSpec reopen(boolean reopen) {
this.factoryBean.setReopen(reopen);
return _this();
}
@Override
public TailAdapterSpec id(String id) {
this.factoryBean.setBeanName(id);
return _this();
}
@Override
public TailAdapterSpec phase(int phase) {
this.factoryBean.setPhase(phase);
return _this();
}
@Override
public TailAdapterSpec autoStartup(boolean autoStartup) {
this.factoryBean.setAutoStartup(autoStartup);
return _this();
}
@Override
public TailAdapterSpec outputChannel(MessageChannel outputChannel) {
this.outputChannel = outputChannel;
return _this();
}
@Override
public TailAdapterSpec errorChannel(MessageChannel errorChannel) {
this.errorChannel = errorChannel;
return _this();
}
@Override
protected FileTailingMessageProducerSupport doGet() {
if (this.outputChannel == null) {
this.factoryBean.setOutputChannel(new NullChannel());
}
FileTailingMessageProducerSupport tailingMessageProducerSupport = null;
try {
this.factoryBean.afterPropertiesSet();
tailingMessageProducerSupport = this.factoryBean.getObject();
}
catch (Exception e) {
throw new IllegalStateException(e);
}
if (this.errorChannel != null) {
tailingMessageProducerSupport.setErrorChannel(this.errorChannel);
}
tailingMessageProducerSupport.setOutputChannel(this.outputChannel);
return tailingMessageProducerSupport;
}
}

View File

@@ -0,0 +1,4 @@
/**
* Provides File Components support for Spring Integration Java DSL.
*/
package org.springframework.integration.file.dsl;

View File

@@ -205,11 +205,11 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
private final MessageSessionCallback<F, ?> messageSessionCallback;
private volatile ExpressionEvaluatingMessageProcessor<String> renameProcessor =
new ExpressionEvaluatingMessageProcessor<String>(
new SpelExpressionParser().parseExpression("headers." + FileHeaders.RENAME_TO));
protected final Set<Option> options = new HashSet<>();
protected volatile Set<Option> options = new HashSet<Option>();
private volatile ExpressionEvaluatingMessageProcessor<String> renameProcessor =
new ExpressionEvaluatingMessageProcessor<>(
new SpelExpressionParser().parseExpression("headers." + FileHeaders.RENAME_TO));
private volatile Expression localDirectoryExpression;
@@ -313,20 +313,36 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
}
/**
* Specify the array of options for various gateway commands.
* @param options the options to set
*/
public void setOptions(String options) {
String[] opts = options.split("\\s");
for (String opt : opts) {
String trimmedOpt = opt.trim();
if (StringUtils.hasLength(trimmedOpt)) {
this.options.add(Option.toOption(trimmedOpt));
}
}
Assert.hasText(options, "'options' must not be empty.");
this.options.clear();
Arrays.stream(options.split("\\s"))
.filter(StringUtils::hasText)
.map(s -> Option.toOption(s.trim()))
.forEach(this.options::add);
}
/**
* @param remoteFileSeparator the remoteFileSeparator to set
* Specify the array of options for various gateway commands.
* @param options the {@link Option} array to use.
* @since 5.0
*/
public void setOptions(Option... options) {
Assert.notNull(options, "'options' must not be null");
Assert.noNullElements(options, "'options' cannot contain null element");
this.options.clear();
Collections.addAll(this.options, options);
}
/**
* Set the file separator when dealing with remote files; default '/'.
* @param remoteFileSeparator the separator.
* @see RemoteFileTemplate#setRemoteFileSeparator(String)
*/
public void setRemoteFileSeparator(String remoteFileSeparator) {
@@ -334,6 +350,7 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
}
/**
* Specify a directory path where remote files will be transferred to.
* @param localDirectory the localDirectory to set
*/
public void setLocalDirectory(File localDirectory) {
@@ -342,11 +359,17 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
}
}
/**
* Specify a SpEL expression to evaluate directory path where remote files will be transferred to.
* @param localDirectoryExpression the SpEL to determine the local directory.
*/
public void setLocalDirectoryExpression(Expression localDirectoryExpression) {
this.localDirectoryExpression = localDirectoryExpression;
}
/**
* A {@code boolean} flag to identify if local directory should be created automatically.
* Defaults to {@code true}.
* @param autoCreateLocalDirectory the autoCreateLocalDirectory to set
*/
public void setAutoCreateLocalDirectory(boolean autoCreateLocalDirectory) {
@@ -354,6 +377,8 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
}
/**
* Set the temporary suffix to use when transferring files to the remote system.
* Default {@code .writing}.
* @param temporaryFileSuffix the temporaryFileSuffix to set
* @see RemoteFileTemplate#setTemporaryFileSuffix(String)
*/
@@ -362,6 +387,7 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
}
/**
* Set a {@link FileListFilter} to filter remote files.
* @param filter the filter to set
*/
public void setFilter(FileListFilter<F> filter) {
@@ -369,6 +395,8 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
}
/**
* A {@link FileListFilter} that runs against the <em>local</em> file system view when
* using {@code MPUT} command.
* @param filter the filter to set
*/
public void setMputFilter(FileListFilter<File> filter) {
@@ -376,6 +404,7 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
}
/**
* Specify a SpEL expression for files renaming during transfer.
* @param renameExpression the expression to use.
* @since 4.3
*/
@@ -384,6 +413,7 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
}
/**
* Specify a SpEL expression for files renaming during transfer.
* @param renameExpression the String in SpEL syntax.
* @since 4.3
*/
@@ -393,6 +423,7 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
}
/**
* Specify a SpEL expression for local files renaming after downloading.
* @param localFilenameGeneratorExpression the expression to use.
* @since 3.0
*/
@@ -402,6 +433,7 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
}
/**
* Specify a SpEL expression for local files renaming after downloading.
* @param localFilenameGeneratorExpression the String in SpEL syntax.
* @since 4.3
*/

View File

@@ -17,6 +17,7 @@
package org.springframework.integration.file.remote.handler;
import org.springframework.expression.Expression;
import org.springframework.integration.file.DefaultFileNameGenerator;
import org.springframework.integration.file.FileNameGenerator;
import org.springframework.integration.file.remote.RemoteFileTemplate;
import org.springframework.integration.file.remote.session.SessionFactory;
@@ -62,6 +63,7 @@ public class FileTransferringMessageHandler<F> extends AbstractMessageHandler {
/**
* A {@code boolean} flag to indicate automatically create the directory or not.
* @param autoCreateDirectory true to automatically create the directory.
* @see RemoteFileTemplate#setAutoCreateDirectory(boolean)
*/
@@ -70,6 +72,7 @@ public class FileTransferringMessageHandler<F> extends AbstractMessageHandler {
}
/**
* Specify a remote file separator symbol.
* @param remoteFileSeparator the remote file separator.
* @see RemoteFileTemplate#setRemoteFileSeparator(String)
*/
@@ -78,6 +81,7 @@ public class FileTransferringMessageHandler<F> extends AbstractMessageHandler {
}
/**
* Specify a remote directory path SpEL expression.
* @param remoteDirectoryExpression the remote directory expression
* @see RemoteFileTemplate#setRemoteDirectoryExpression(Expression)
*/
@@ -86,6 +90,7 @@ public class FileTransferringMessageHandler<F> extends AbstractMessageHandler {
}
/**
* Specify a remote directory path SpEL expression.
* @param temporaryRemoteDirectoryExpression the temporary remote directory expression
* @see RemoteFileTemplate#setTemporaryRemoteDirectoryExpression(Expression)
*/
@@ -102,6 +107,8 @@ public class FileTransferringMessageHandler<F> extends AbstractMessageHandler {
}
/**
* A {@code boolean} flag to use temporary files names or not.
* Defaults to {@code true}.
* @param useTemporaryFileName true to use a temporary file name.
* @see RemoteFileTemplate#setUseTemporaryFileName(boolean)
*/
@@ -110,6 +117,8 @@ public class FileTransferringMessageHandler<F> extends AbstractMessageHandler {
}
/**
* Set the file name generator used to generate the remote filename to be used when transferring
* files to the remote system. Default {@link DefaultFileNameGenerator}.
* @param fileNameGenerator the file name generator.
* @see RemoteFileTemplate#setFileNameGenerator(FileNameGenerator)
*/
@@ -118,6 +127,8 @@ public class FileTransferringMessageHandler<F> extends AbstractMessageHandler {
}
/**
* Set the charset to use when converting String payloads to bytes as the content of the
* remote file. Default {@code UTF-8}.
* @param charset the charset.
* @see RemoteFileTemplate#setCharset(String)
*/
@@ -126,6 +137,8 @@ public class FileTransferringMessageHandler<F> extends AbstractMessageHandler {
}
/**
* Set the temporary suffix to use when transferring files to the remote system.
* Default ".writing".
* @param temporaryFileSuffix the temporary file suffix.
* @see RemoteFileTemplate#setTemporaryFileSuffix(String)
*/

View File

@@ -185,10 +185,6 @@ public abstract class AbstractInboundFileSynchronizer<F>
this.preserveTimestamp = preserveTimestamp;
}
public void setIntegrationEvaluationContext(EvaluationContext evaluationContext) {
this.evaluationContext = evaluationContext;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;

View File

@@ -84,7 +84,7 @@ public class OSDelegatingFileTailingMessageProducer extends FileTailingMessagePr
super.doStart();
destroyProcess();
this.command = "tail " + this.options + " " + this.getFile().getAbsolutePath();
this.getTaskExecutor().execute(() -> runExec());
this.getTaskExecutor().execute(this::runExec);
}
@Override
@@ -170,8 +170,8 @@ public class OSDelegatingFileTailingMessageProducer extends FileTailingMessagePr
if (logger.isInfoEnabled()) {
logger.info("Restarting tail process in " + getMissingFileDelay() + " milliseconds");
}
getRequiredTaskScheduler().schedule((Runnable) () -> runExec(),
new Date(System.currentTimeMillis() + getMissingFileDelay()));
getRequiredTaskScheduler()
.schedule(this::runExec, new Date(System.currentTimeMillis() + getMissingFileDelay()));
}
});
}

View File

@@ -0,0 +1,440 @@
/*
* Copyright 2016 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
*
* 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.springframework.integration.file.dsl;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.springframework.aop.TargetSource;
import org.springframework.aop.framework.Advised;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.Lifecycle;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.IntegrationMessageHeaderAccessor;
import org.springframework.integration.annotation.IntegrationComponentScan;
import org.springframework.integration.annotation.MessagingGateway;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.config.EnableIntegration;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlowDefinition;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.integration.dsl.Pollers;
import org.springframework.integration.dsl.StandardIntegrationFlow;
import org.springframework.integration.dsl.channel.MessageChannels;
import org.springframework.integration.file.DefaultFileNameGenerator;
import org.springframework.integration.file.FileHeaders;
import org.springframework.integration.file.FileReadingMessageSource;
import org.springframework.integration.file.splitter.FileSplitter;
import org.springframework.integration.file.support.FileExistsMode;
import org.springframework.integration.file.tail.ApacheCommonsFileTailingMessageProducer;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessageHandlingException;
import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.support.ErrorMessage;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.stereotype.Service;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.StreamUtils;
/**
* @author Artem Bilan
*
* @since 5.0
*/
@RunWith(SpringRunner.class)
@DirtiesContext
public class FileTests {
@ClassRule
public static final TemporaryFolder tmpDir = new TemporaryFolder();
@Autowired
private ListableBeanFactory beanFactory;
@Autowired
private ControlBusGateway controlBus;
@Autowired
@Qualifier("fileFlow1Input")
private MessageChannel fileFlow1Input;
@Autowired
@Qualifier("fileWriting.handler")
private MessageHandler fileWritingMessageHandler;
@Autowired
@Qualifier("tailChannel")
private PollableChannel tailChannel;
@Autowired
private ApacheCommonsFileTailingMessageProducer tailer;
@Autowired
@Qualifier("fileReadingResultChannel")
private PollableChannel fileReadingResultChannel;
@Autowired
@Qualifier("fileWritingInput")
private MessageChannel fileWritingInput;
@Autowired
@Qualifier("fileWritingResultChannel")
private PollableChannel fileWritingResultChannel;
@Autowired
@Qualifier("fileSplittingResultChannel")
private PollableChannel fileSplittingResultChannel;
@Autowired
@Qualifier("fileTriggerFlow.input")
private MessageChannel fileTriggerFlowInput;
@Autowired
private CountDownLatch flushPredicateCalled;
@Test
public void testFileHandler() throws Exception {
Message<?> message = MessageBuilder.withPayload("foo").setHeader(FileHeaders.FILENAME, "foo").build();
try {
this.fileFlow1Input.send(message);
fail("NullPointerException expected");
}
catch (Exception e) {
assertThat(e, instanceOf(MessageHandlingException.class));
assertThat(e.getCause(), instanceOf(NullPointerException.class));
}
DefaultFileNameGenerator fileNameGenerator = new DefaultFileNameGenerator();
fileNameGenerator.setBeanFactory(this.beanFactory);
Object targetFileWritingMessageHandler = this.fileWritingMessageHandler;
if (this.fileWritingMessageHandler instanceof Advised) {
TargetSource targetSource = ((Advised) this.fileWritingMessageHandler).getTargetSource();
if (targetSource != null) {
targetFileWritingMessageHandler = targetSource.getTarget();
}
}
DirectFieldAccessor dfa = new DirectFieldAccessor(targetFileWritingMessageHandler);
dfa.setPropertyValue("fileNameGenerator", fileNameGenerator);
this.fileFlow1Input.send(message);
assertTrue(new File(tmpDir.getRoot(), "foo").exists());
this.fileTriggerFlowInput.send(new GenericMessage<>("trigger"));
assertTrue(this.flushPredicateCalled.await(10, TimeUnit.SECONDS));
}
@Test
public void testMessageProducerFlow() throws Exception {
FileOutputStream file = new FileOutputStream(new File(tmpDir.getRoot(), "TailTest"));
for (int i = 0; i < 50; i++) {
file.write((i + "\n").getBytes());
}
this.tailer.start();
for (int i = 0; i < 50; i++) {
Message<?> message = this.tailChannel.receive(5000);
assertNotNull(message);
assertEquals("hello " + i, message.getPayload());
}
assertNull(this.tailChannel.receive(1));
this.controlBus.send("@tailer.stop()");
file.close();
}
@Autowired
private PollableChannel filePollingErrorChannel;
@Test
public void testFileReadingFlow() throws Exception {
List<Integer> evens = new ArrayList<>(25);
for (int i = 0; i < 50; i++) {
boolean even = i % 2 == 0;
String extension = even ? ".sitest" : ".foofile";
if (even) {
evens.add(i);
}
FileOutputStream file = new FileOutputStream(new File(tmpDir.getRoot(), i + extension));
file.write(("" + i).getBytes());
file.flush();
file.close();
}
Message<?> message = fileReadingResultChannel.receive(60000);
assertNotNull(message);
Object payload = message.getPayload();
assertThat(payload, instanceOf(List.class));
@SuppressWarnings("unchecked")
List<String> result = (List<String>) payload;
assertEquals(25, result.size());
result.forEach(s -> assertTrue(evens.contains(Integer.parseInt(s))));
new File(tmpDir.getRoot(), "a.sitest").createNewFile();
Message<?> receive = this.filePollingErrorChannel.receive(60000);
assertNotNull(receive);
assertThat(receive, instanceOf(ErrorMessage.class));
}
@Test
public void testFileWritingFlow() throws Exception {
String payload = "Spring Integration";
this.fileWritingInput.send(new GenericMessage<>(payload));
Message<?> receive = this.fileWritingResultChannel.receive(1000);
assertNotNull(receive);
assertThat(receive.getPayload(), instanceOf(File.class));
File resultFile = (File) receive.getPayload();
assertThat(resultFile.getAbsolutePath(),
endsWith(TestUtils.applySystemFileSeparator("fileWritingFlow/foo.write")));
String fileContent = StreamUtils.copyToString(new FileInputStream(resultFile), Charset.defaultCharset());
assertEquals(payload, fileContent);
}
@Autowired
@Qualifier("fileSplitter.handler")
private MessageHandler fileSplitter;
@Test
public void testFileSplitterFlow() throws Exception {
FileOutputStream file = new FileOutputStream(new File(tmpDir.getRoot(), "foo.tmp"));
file.write(("HelloWorld\näöüß").getBytes(Charset.defaultCharset()));
file.flush();
file.close();
Message<?> receive = this.fileSplittingResultChannel.receive(10000);
assertNotNull(receive);
assertThat(receive.getPayload(), instanceOf(FileSplitter.FileMarker.class)); // FileMarker.Mark.START
assertEquals(0, receive.getHeaders().get(IntegrationMessageHeaderAccessor.SEQUENCE_SIZE));
receive = this.fileSplittingResultChannel.receive(10000);
assertNotNull(receive); //HelloWorld
receive = this.fileSplittingResultChannel.receive(10000);
assertNotNull(receive); //äöüß
receive = this.fileSplittingResultChannel.receive(10000);
assertNotNull(receive);
assertThat(receive.getPayload(), instanceOf(FileSplitter.FileMarker.class)); // FileMarker.Mark.END
assertNull(this.fileSplittingResultChannel.receive(1));
assertEquals(StandardCharsets.US_ASCII, TestUtils.getPropertyValue(this.fileSplitter, "charset"));
}
@Autowired
@Qualifier("dynamicAdaptersResult")
PollableChannel dynamicAdaptersResult;
@Autowired
private MyService myService;
@Test
public void testDynamicFileFlows() throws Exception {
File newFolder1 = tmpDir.newFolder();
FileOutputStream file = new FileOutputStream(new File(newFolder1, "foo"));
file.write(("foo").getBytes());
file.flush();
file.close();
File newFolder2 = tmpDir.newFolder();
file = new FileOutputStream(new File(newFolder2, "bar"));
file.write(("bar").getBytes());
file.flush();
file.close();
this.myService.pollDirectories(newFolder1, newFolder2);
Set<String> payloads = new TreeSet<>();
Message<?> receive = this.dynamicAdaptersResult.receive(10000);
assertNotNull(receive);
payloads.add((String) receive.getPayload());
receive = this.dynamicAdaptersResult.receive(10000);
assertNotNull(receive);
payloads.add((String) receive.getPayload());
assertArrayEquals(new String[] { "bar", "foo" }, payloads.toArray());
}
@MessagingGateway(defaultRequestChannel = "controlBus.input")
private interface ControlBusGateway {
void send(String command);
}
@Configuration
@EnableIntegration
@ComponentScan
@IntegrationComponentScan
public static class ContextConfiguration {
@Bean
public IntegrationFlow controlBus() {
return IntegrationFlowDefinition::controlBus;
}
@Bean
public IntegrationFlow fileTriggerFlow() {
return f -> f.handle("fileWriting.handler", "trigger");
}
@Bean
public CountDownLatch flushPredicateCalled() {
return new CountDownLatch(1);
}
@Bean
public IntegrationFlow fileFlow1() {
return IntegrationFlows.from("fileFlow1Input")
.handle(Files.outboundAdapter(tmpDir.getRoot())
.fileNameGenerator(message -> null)
.fileExistsMode(FileExistsMode.APPEND_NO_FLUSH)
.flushPredicate((fileAbsolutePath, lastWrite, filterMessage) -> {
flushPredicateCalled().countDown();
return true;
}),
c -> c.id("fileWriting"))
.get();
}
@Bean
public IntegrationFlow tailFlow() {
return IntegrationFlows
.from(Files.tailAdapter(new File(tmpDir.getRoot(), "TailTest"))
.delay(500)
.end(false)
.id("tailer")
.autoStartup(false))
.transform("hello "::concat)
.channel(MessageChannels.queue("tailChannel"))
.get();
}
@Bean
public IntegrationFlow fileReadingFlow() {
return IntegrationFlows
.from(Files.inboundAdapter(tmpDir.getRoot())
.patternFilter("*.sitest")
.useWatchService(true)
.watchEvents(FileReadingMessageSource.WatchEventType.CREATE,
FileReadingMessageSource.WatchEventType.MODIFY),
e -> e.poller(Pollers.fixedDelay(100)
.errorChannel("filePollingErrorChannel")))
.filter(File.class, p -> !p.getName().startsWith("a"),
e -> e.throwExceptionOnRejection(true))
.transform(Files.toStringTransformer())
.aggregate(a -> a.correlationExpression("1")
.releaseStrategy(g -> g.size() == 25))
.channel(MessageChannels.queue("fileReadingResultChannel"))
.get();
}
@Bean
public PollableChannel filePollingErrorChannel() {
return new QueueChannel();
}
@Bean
public IntegrationFlow fileWritingFlow() {
return IntegrationFlows.from("fileWritingInput")
.enrichHeaders(h -> h.header(FileHeaders.FILENAME, "foo.write")
.header("directory", new File(tmpDir.getRoot(), "fileWritingFlow")))
.handle(Files.outboundGateway(m -> m.getHeaders().get("directory")))
.channel(MessageChannels.queue("fileWritingResultChannel"))
.get();
}
@Bean
public IntegrationFlow fileSplitterFlow() {
return IntegrationFlows
.from(Files.inboundAdapter(tmpDir.getRoot())
.patternFilter("foo.tmp"),
e -> e.poller(p -> p.fixedDelay(100)))
.split(Files.splitter()
.markers()
.charset(StandardCharsets.US_ASCII)
.applySequence(true),
e -> e.id("fileSplitter"))
.channel(c -> c.queue("fileSplittingResultChannel"))
.get();
}
@Bean
public PollableChannel dynamicAdaptersResult() {
return new QueueChannel();
}
}
@Service
public static class MyService {
@Autowired
private AutowireCapableBeanFactory beanFactory;
@Autowired
@Qualifier("dynamicAdaptersResult")
PollableChannel dynamicAdaptersResult;
void pollDirectories(File... directories) {
for (File directory : directories) {
StandardIntegrationFlow integrationFlow = IntegrationFlows
.from(Files.inboundAdapter(directory),
e -> e.poller(p -> p.fixedDelay(1000))
.id(directory.getName() + ".adapter"))
.transform(Files.toStringTransformer(),
e -> e.id(directory.getName() + ".transformer"))
.channel(this.dynamicAdaptersResult)
.get();
this.beanFactory.initializeBean(integrationFlow, directory.getName());
this.beanFactory.getBean(directory.getName() + ".transformer", Lifecycle.class).start();
this.beanFactory.getBean(directory.getName() + ".adapter", Lifecycle.class).start();
}
}
}
}

View File

@@ -178,13 +178,11 @@ public class ScriptsTests {
assertThat(payload, Matchers.instanceOf(Date.class));
// Some time window to avoid dates collision
Thread.sleep(500);
Thread.sleep(2);
assertTrue(((Date) payload).before(new Date()));
assertNotNull(this.messageSourceChannel.receive(20000));
assertNull(this.messageSourceChannel.receive(10));
}
@Configuration