diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/DefaultFileNameGenerator.java b/spring-integration-file/src/main/java/org/springframework/integration/file/DefaultFileNameGenerator.java index 4a33eacffe..f82900ee18 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/DefaultFileNameGenerator.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/DefaultFileNameGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-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. @@ -19,8 +19,6 @@ package org.springframework.integration.file; import java.io.File; import org.springframework.expression.Expression; -import org.springframework.expression.ExpressionParser; -import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.integration.util.AbstractExpressionEvaluator; import org.springframework.messaging.Message; import org.springframework.util.Assert; @@ -40,42 +38,38 @@ import org.springframework.util.StringUtils; * * @author Mark Fisher * @author Gary Russell + * @author Artem Bilan */ public class DefaultFileNameGenerator extends AbstractExpressionEvaluator implements FileNameGenerator { - private static final String DEFAULT_EXPRESSION = "headers['" + FileHeaders.FILENAME + "']"; - - private final static ExpressionParser parser = new SpelExpressionParser(); - - private volatile Expression expression = parser.parseExpression(DEFAULT_EXPRESSION); + private volatile Expression expression = + EXPRESSION_PARSER.parseExpression("headers['" + FileHeaders.FILENAME + "']"); /** * Specify an expression to be evaluated against the Message * in order to generate a file name. - * * @param expression The expression. */ public void setExpression(String expression) { Assert.hasText(expression, "expression must not be empty"); - this.expression = parser.parseExpression(expression); + this.expression = EXPRESSION_PARSER.parseExpression(expression); } /** * Specify a custom header name to check for the file name. * The default is defined by {@link FileHeaders#FILENAME}. - * * @param headerName The header name. */ public void setHeaderName(String headerName) { Assert.notNull(headerName, "'headerName' must not be null"); - this.expression = parser.parseExpression("headers['" + headerName + "']"); + this.expression = EXPRESSION_PARSER.parseExpression("headers['" + headerName + "']"); } @Override public String generateFileName(Message message) { - Object filenameProperty = this.evaluateExpression(this.expression, message); - if (filenameProperty instanceof String && StringUtils.hasText((String) filenameProperty)) { - return (String) filenameProperty; + Object filename = this.evaluateExpression(this.expression, message); + if (filename instanceof String && StringUtils.hasText((String) filename)) { + return (String) filename; } if (message.getPayload() instanceof File) { return ((File) message.getPayload()).getName(); diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/FileWritingMessageHandler.java b/spring-integration-file/src/main/java/org/springframework/integration/file/FileWritingMessageHandler.java index 51c3f8e959..f1ac5861cf 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/FileWritingMessageHandler.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/FileWritingMessageHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-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. @@ -26,6 +26,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStreamWriter; import java.nio.charset.Charset; +import java.util.regex.Matcher; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -250,8 +251,7 @@ public class FileWritingMessageHandler extends AbstractReplyProducingMessageHand @Override protected void doInit() { - - this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(this.getBeanFactory()); + this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(getBeanFactory()); if (this.destinationDirectoryExpression instanceof LiteralExpression) { final File directory = new File(this.destinationDirectoryExpression.getValue( @@ -259,27 +259,25 @@ public class FileWritingMessageHandler extends AbstractReplyProducingMessageHand validateDestinationDirectory(directory, this.autoCreateDirectory); } + Assert.state(!(this.temporaryFileSuffixSet && FileExistsMode.APPEND.equals(this.fileExistsMode)), + "'temporaryFileSuffix' can not be set when appending to an existing file"); + if (!this.fileNameGeneratorSet && this.fileNameGenerator instanceof BeanFactoryAware) { - ((BeanFactoryAware) this.fileNameGenerator).setBeanFactory(this.getBeanFactory()); + ((BeanFactoryAware) this.fileNameGenerator).setBeanFactory(getBeanFactory()); } } private void validateDestinationDirectory(File destinationDirectory, boolean autoCreateDirectory) { - if (!destinationDirectory.exists() && autoCreateDirectory) { Assert.isTrue(destinationDirectory.mkdirs(), "Destination directory [" + destinationDirectory + "] could not be created."); } - Assert.isTrue(destinationDirectory.exists(), "Destination directory [" + destinationDirectory + "] does not exist."); Assert.isTrue(destinationDirectory.isDirectory(), "Destination path [" + destinationDirectory + "] does not point to a directory."); Assert.isTrue(destinationDirectory.canWrite(), "Destination directory [" + destinationDirectory + "] is not writable."); - Assert.state(!(this.temporaryFileSuffixSet - && FileExistsMode.APPEND.equals(this.fileExistsMode)), - "'temporaryFileSuffix' can not be set when appending to an existing file"); } @Override @@ -305,8 +303,12 @@ public class FileWritingMessageHandler extends AbstractReplyProducingMessageHand (StringUtils.hasText(this.temporaryFileSuffix) && tempFile.exists())); if (!ignore) { - try { + if (!resultFile.exists() && + generatedFileName.replaceAll("/", Matcher.quoteReplacement(File.separator)) + .contains(File.separator)) { + resultFile.getParentFile().mkdirs(); + } if (payload instanceof File) { resultFile = handleFileMessage((File) payload, tempFile, resultFile); } @@ -532,8 +534,7 @@ public class FileWritingMessageHandler extends AbstractReplyProducingMessageHand fileToWriteTo = tempFile; break; default: - throw new IllegalStateException("Unsupported FileExistsMode " - + this.fileExistsMode); + throw new IllegalStateException("Unsupported FileExistsMode " + this.fileExistsMode); } return fileToWriteTo; } diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/FileWritingMessageHandlerTests.java b/spring-integration-file/src/test/java/org/springframework/integration/file/FileWritingMessageHandlerTests.java index bcbb49fdcb..e42923ec35 100644 --- a/spring-integration-file/src/test/java/org/springframework/integration/file/FileWritingMessageHandlerTests.java +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/FileWritingMessageHandlerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-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. @@ -33,18 +33,19 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.io.UnsupportedEncodingException; import org.junit.Before; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; + import org.springframework.beans.factory.BeanFactory; import org.springframework.integration.channel.NullChannel; import org.springframework.integration.channel.QueueChannel; import org.springframework.integration.file.support.FileExistsMode; import org.springframework.integration.support.MessageBuilder; +import org.springframework.integration.test.util.TestUtils; import org.springframework.messaging.Message; import org.springframework.messaging.MessageHandlingException; import org.springframework.messaging.support.GenericMessage; @@ -57,6 +58,7 @@ import org.springframework.util.FileCopyUtils; * @author Gary Russell * @author Tony Falabella * @author Gunnar Hillert + * @author Artem Bilan */ public class FileWritingMessageHandlerTests { @@ -69,6 +71,7 @@ public class FileWritingMessageHandlerTests { @Rule public TemporaryFolder temp = new TemporaryFolder() { + @Override public void create() throws IOException { super.create(); @@ -78,8 +81,9 @@ public class FileWritingMessageHandlerTests { handler.afterPropertiesSet(); sourceFile = temp.newFile("sourceFile"); FileCopyUtils.copy(SAMPLE_CONTENT.getBytes(DEFAULT_ENCODING), - new FileOutputStream(sourceFile, false)); + new FileOutputStream(sourceFile, false)); } + }; private File outputDirectory; @@ -115,6 +119,21 @@ public class FileWritingMessageHandlerTests { assertFileContentIsMatching(result); } + @Test + public void testFileNameHeader() throws Exception { + Message message = MessageBuilder.withPayload(SAMPLE_CONTENT) + .setHeader(FileHeaders.FILENAME, "dir1" + File.separator + "dir2/test") + .build(); + QueueChannel output = new QueueChannel(); + handler.setCharset(DEFAULT_ENCODING); + handler.setOutputChannel(output); + handler.handleMessage(message); + Message result = output.receive(0); + assertFileContentIsMatching(result); + File destFile = (File) result.getPayload(); + assertThat(destFile.getAbsolutePath(), containsString(TestUtils.applySystemFileSeparator("/dir1/dir2/test"))); + } + @Test public void stringPayloadCopiedToNewFileWithNewLines() throws Exception { Message message = MessageBuilder.withPayload(SAMPLE_CONTENT).build(); @@ -196,7 +215,8 @@ public class FileWritingMessageHandlerTests { assertFileContentIs(result, SAMPLE_CONTENT + System.getProperty("line.separator")); } - @Test @Ignore // INT-3289 ignored because it won't fail on all OS + @Test + @Ignore("INT-3289: doesn't fail on all OS") public void testCreateDirFail() { File dir = new File("/foo"); FileWritingMessageHandler handler = new FileWritingMessageHandler(dir); @@ -343,6 +363,7 @@ public class FileWritingMessageHandlerTests { QueueChannel output = new QueueChannel(); handler.setOutputChannel(output); handler.setFileNameGenerator(new FileNameGenerator() { + @Override public String generateFileName(Message message) { return anyFilename; @@ -404,18 +425,18 @@ public class FileWritingMessageHandlerTests { assertFileContentIs(outFile, "foo"); } - void assertFileContentIsMatching(Message result) throws IOException, UnsupportedEncodingException { + void assertFileContentIsMatching(Message result) throws IOException { assertFileContentIs(result, SAMPLE_CONTENT); } - void assertFileContentIs(Message result, String expected) throws IOException, UnsupportedEncodingException { + void assertFileContentIs(Message result, String expected) throws IOException { assertThat(result, is(notNullValue())); assertThat(result.getPayload(), is(instanceOf(File.class))); File destFile = (File) result.getPayload(); assertFileContentIs(destFile, expected); } - void assertFileContentIs(File destFile, String expected) throws IOException, UnsupportedEncodingException { + void assertFileContentIs(File destFile, String expected) throws IOException { assertNotSame(destFile, sourceFile); assertThat(destFile.exists(), is(true)); byte[] destFileContent = FileCopyUtils.copyToByteArray(destFile); diff --git a/src/reference/asciidoc/file.adoc b/src/reference/asciidoc/file.adoc index b17a0bfcc0..de7c7a00d1 100644 --- a/src/reference/asciidoc/file.adoc +++ b/src/reference/asciidoc/file.adoc @@ -459,17 +459,22 @@ Once setup, the `DefaultFileNameGenerator` will employ the following resolution When using the XML namespace support, both, the _File Outbound Channel Adapter_ and the _File Outbound Gateway_ support the following two mutually exclusive configuration attributes: -* `filename-generator` (a reference to a `FileNameGenerator`) implementation) +* `filename-generator` (a reference to a `FileNameGenerator` implementation) * `filename-generator-expression` (an expression evaluating to a `String`) - - While writing files, a temporary file suffix will be used (default: `.writing`). It is appended to the filename while the file is being written. To customize the suffix, you can set the _temporary-file-suffix_ attribute on both the _File Outbound Channel Adapter_ and the _File Outbound Gateway_. NOTE: When using the _APPEND_ file _mode_, the _temporary-file-suffix_ attribute is ignored, since the data is appended to the file directly. +Starting with _version 4.2.5_ the generated file name (as a result of `filename-generator`/`filename-generator-expression` +evaluation) can represent a _sub-path_ together with the target file name. +It is used as a second constructor argument for `File(File parent, String child)` as before, but in the past we didn't +created (`mkdirs()`) directories for _sub-path_ assuming only the _file name_. +This approach is useful for cases when we need to restore the file system tree according the source directory. +For example we unzipping the archive and want to save all file in the target directory at the same order. + [[file-writing-output-directory]] ==== Specifying the Output Directory @@ -478,8 +483,6 @@ Both, the _File Outbound Channel Adapter_ and the _File Outbound Gateway_ provid * _directory_ * _directory-expression_ - - NOTE: The _directory-expression_ attribute is available since Spring Integration 2.2. *Using the directory attribute* diff --git a/src/reference/asciidoc/whats-new.adoc b/src/reference/asciidoc/whats-new.adoc index a79cb73206..bf1e39953d 100644 --- a/src/reference/asciidoc/whats-new.adoc +++ b/src/reference/asciidoc/whats-new.adoc @@ -47,3 +47,8 @@ See <> for more information. The `destination-expression` and `socket-expression` are now available for the ``. See <> for more information. +==== File Changes + +The generated file name for the `FileWritingMessageHandler` can represent _sub-path_ to save the desired directory +structure for file in the target directory. +See <> for more information.