Skip to content

GH-9988: Add FileExistsMode expression support #10019

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
May 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2016-2023 the original author or authors.
* Copyright 2016-2025 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.
Expand Down Expand Up @@ -44,6 +44,7 @@
*
* @author Artem Bilan
* @author Gary Russell
* @author Jooyoung Pyoung
*
* @since 5.0
*/
Expand Down Expand Up @@ -358,6 +359,48 @@ public S fileExistsMode(FileExistsMode fileExistsMode) {
return _this();
}

/**
* Specify a SpEL expression to determine the action to take when files already exist.
* Expression evaluation should return a {@link FileExistsMode} or a String representation.
* Used for GET and MGET operations when the file already exists locally,
* or PUT and MPUT when the file exists on the remote system.
* @param fileExistsModeExpression a SpEL expression to evaluate the file exists mode
* @return the Spec.
* @since 6.5
*/
public S fileExistsModeExpression(Expression fileExistsModeExpression) {
this.target.setFileExistsModeExpression(fileExistsModeExpression);
return _this();
}

/**
* Specify a SpEL expression to determine the action to take when files already exist.
* Expression evaluation should return a {@link FileExistsMode} or a String representation.
* Used for GET and MGET operations when the file already exists locally,
* or PUT and MPUT when the file exists on the remote system.
* @param fileExistsModeExpression the String in SpEL syntax.
* @return the Spec.
* @since 6.5
*/
public S fileExistsModeExpression(String fileExistsModeExpression) {
this.target.setFileExistsModeExpressionString(fileExistsModeExpression);
return _this();
}

/**
* Specify a {@link Function} to determine the action to take when files already exist.
* Expression evaluation should return a {@link FileExistsMode} or a String representation.
* Used for GET and MGET operations when the file already exists locally,
* or PUT and MPUT when the file exists on the remote system.
* @param fileExistsModeFunction the {@link Function} to use.
* @param <P> the expected payload type.
* @return the Spec.
* @since 6.5
*/
public <P> S fileExistsModeFunction(Function<Message<P>, Object> fileExistsModeFunction) {
return fileExistsModeExpression(new FunctionExpression<>(fileExistsModeFunction));
}

/**
* Determine whether the remote directory should automatically be created when
* sending files to the remote system.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2024 the original author or authors.
* Copyright 2013-2025 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.
Expand Down Expand Up @@ -63,6 +63,7 @@
* @author Gary Russell
* @author Artem Bilan
* @author Alen Turkovic
* @author Jooyoung Pyoung
*
* @since 3.0
*
Expand Down Expand Up @@ -303,8 +304,6 @@ public String send(Message<?> message, String subDirectory, FileExistsMode... mo

private String send(Message<?> message, String subDirectory, FileExistsMode mode) {
Assert.notNull(this.directoryExpressionProcessor, "'remoteDirectoryExpression' is required");
Assert.isTrue(!FileExistsMode.APPEND.equals(mode) || !this.useTemporaryFileName,
"Cannot append when using a temporary file name");
Assert.isTrue(!FileExistsMode.REPLACE_IF_MODIFIED.equals(mode),
"FilExistsMode.REPLACE_IF_MODIFIED can only be used for local files");
final StreamHolder inputStreamHolder = payloadToInputStream(message);
Expand Down Expand Up @@ -565,7 +564,10 @@ private void sendFileToRemoteDirectory(InputStream inputStream, String temporary
String tempRemoteFilePath = temporaryRemoteDirectory + fileName;
// write remote file first with temporary file extension if enabled

String tempFilePath = tempRemoteFilePath + (this.useTemporaryFileName ? this.temporaryFileSuffix : "");
String tempFilePath = tempRemoteFilePath;
if (!FileExistsMode.APPEND.equals(mode) && this.useTemporaryFileName) {
tempFilePath += this.temporaryFileSuffix;
}

if (this.autoCreateDirectory) {
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 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.
Expand Down Expand Up @@ -75,6 +75,7 @@
* @author Gary Russell
* @author Artem Bilan
* @author Mauro Molinari
* @author Jooyoung Pyoung
*
* @since 2.1
*/
Expand Down Expand Up @@ -114,8 +115,12 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply

private Expression localFilenameGeneratorExpression;

private Expression fileExistsModeExpression;

private FileExistsMode fileExistsMode;

private EvaluationContext standardEvaluationContext;

private Integer chmod;

private boolean remoteFileTemplateExplicitlySet;
Expand Down Expand Up @@ -486,6 +491,32 @@ public void setLocalFilenameGeneratorExpressionString(String localFilenameGenera
this.localFilenameGeneratorExpression = EXPRESSION_PARSER.parseExpression(localFilenameGeneratorExpression);
}

/**
* Specify a SpEL expression to determine the action to take when files already exist.
* Expression evaluation should return a {@link FileExistsMode} object.
* Used for GET and MGET operations when the file already exists locally,
* or PUT and MPUT when the file exists on the remote system.
* @param fileExistsModeExpression the expression to use.
* @since 6.5
*/
public void setFileExistsModeExpression(Expression fileExistsModeExpression) {
Assert.notNull(fileExistsModeExpression, "'fileExistsModeExpression' must not be null");
this.fileExistsModeExpression = fileExistsModeExpression;
}

/**
* Specify a SpEL expression to determine the action to take when files already exist.
* Expression evaluation should return a {@link FileExistsMode} object.
* Used for GET and MGET operations when the file already exists locally,
* or PUT and MPUT when the file exists on the remote system.
* @param fileExistsModeExpression the String in SpEL syntax.
* @since 6.5
*/
public void setFileExistsModeExpressionString(String fileExistsModeExpression) {
Assert.hasText(fileExistsModeExpression, "'fileExistsModeExpression' must not be empty");
this.fileExistsModeExpression = EXPRESSION_PARSER.parseExpression(fileExistsModeExpression);
}

/**
* Determine the action to take when using GET and MGET operations when the file
* already exists locally, or PUT and MPUT when the file exists on the remote
Expand All @@ -495,9 +526,6 @@ public void setLocalFilenameGeneratorExpressionString(String localFilenameGenera
*/
public void setFileExistsMode(FileExistsMode fileExistsMode) {
this.fileExistsMode = fileExistsMode;
if (FileExistsMode.APPEND.equals(fileExistsMode)) {
this.remoteFileTemplate.setUseTemporaryFileName(false);
}
}

/**
Expand Down Expand Up @@ -539,6 +567,7 @@ protected void doInit() {
Assert.isNull(this.filter, "Filters are not supported with the rm and get commands");
}

this.standardEvaluationContext = ExpressionUtils.createStandardEvaluationContext(getBeanFactory());
if ((Command.GET.equals(this.command) && !this.options.contains(Option.STREAM))
|| Command.MGET.equals(this.command)) {
Assert.notNull(this.localDirectoryExpression, "localDirectory must not be null");
Expand All @@ -553,6 +582,11 @@ protected void doInit() {
Option.RECURSIVE.toString() + " to obtain files in subdirectories");
}

if (FileExistsMode.APPEND.equals(this.fileExistsMode) && this.remoteFileTemplate.isUseTemporaryFileName()) {
logger.warn("FileExistsMode.APPEND is incompatible with useTemporaryFileName=true. " +
"Temporary filename will be ignored for APPEND mode.");
}

populateBeanFactoryIntoComponentsIfAny();
if (!this.remoteFileTemplateExplicitlySet) {
this.remoteFileTemplate.afterPropertiesSet();
Expand All @@ -573,7 +607,7 @@ private void populateBeanFactoryIntoComponentsIfAny() {
private void setupLocalDirectory() {
File localDirectory =
ExpressionUtils.expressionToFile(this.localDirectoryExpression,
ExpressionUtils.createStandardEvaluationContext(getBeanFactory()), null,
this.standardEvaluationContext, null,
"localDirectoryExpression");
if (!localDirectory.exists()) {
try {
Expand Down Expand Up @@ -845,7 +879,8 @@ private String doPut(Message<?> requestMessage, String subDirectory) {
* @since 5.0
*/
protected String put(Message<?> message, Session<F> session, String subDirectory) {
String path = this.remoteFileTemplate.send(message, subDirectory, this.fileExistsMode);
FileExistsMode existsMode = resolveFileExistsMode(message);
String path = this.remoteFileTemplate.send(message, subDirectory, existsMode);
if (path == null) {
throw new MessagingException(message, "No local file found for " + message);
}
Expand Down Expand Up @@ -1130,7 +1165,7 @@ protected File get(Message<?> message, Session<F> session, String remoteDir, //
}
final File localFile =
new File(generateLocalDirectory(message, remoteDir), generateLocalFileName(message, remoteFilename));
FileExistsMode existsMode = this.fileExistsMode;
FileExistsMode existsMode = resolveFileExistsMode(message);
boolean appending = FileExistsMode.APPEND.equals(existsMode);
boolean exists = localFile.exists();
boolean replacing = exists && (FileExistsMode.REPLACE.equals(existsMode)
Expand Down Expand Up @@ -1351,6 +1386,31 @@ protected String getRemoteFilename(String remoteFilePath) {
}
}

private FileExistsMode resolveFileExistsMode(Message<?> message) {
if (this.fileExistsModeExpression != null) {
Object evaluationResult = this.fileExistsModeExpression.getValue(this.standardEvaluationContext, message);
if (evaluationResult instanceof FileExistsMode resolvedMode) {
return resolvedMode;
}
else if (evaluationResult instanceof String modeAsString) {
try {
return FileExistsMode.valueOf(modeAsString.toUpperCase());
}
catch (IllegalArgumentException ex) {
throw new MessagingException(message,
"Invalid FileExistsMode string: '" + modeAsString + "'. Expected one of: " +
Arrays.toString(FileExistsMode.values()), ex);
}
}
else if (evaluationResult != null) {
throw new MessagingException(message,
"Expression returned invalid type for FileExistsMode: " +
evaluationResult.getClass().getName() + ". Expected FileExistsMode or String.");
}
}
return this.fileExistsMode;
}

private File generateLocalDirectory(Message<?> message, String remoteDirectory) {
EvaluationContext evaluationContext = ExpressionUtils.createStandardEvaluationContext(getBeanFactory());
if (remoteDirectory != null) {
Expand Down
Loading