diff --git a/org.springframework.integration.file/src/main/java/org/springframework/integration/file/FileWritingMessageHandler.java b/org.springframework.integration.file/src/main/java/org/springframework/integration/file/FileWritingMessageHandler.java index a9f35a1afd..998258a124 100644 --- a/org.springframework.integration.file/src/main/java/org/springframework/integration/file/FileWritingMessageHandler.java +++ b/org.springframework.integration.file/src/main/java/org/springframework/integration/file/FileWritingMessageHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2009 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. @@ -22,8 +22,13 @@ import java.io.IOException; import java.io.OutputStreamWriter; import java.nio.charset.Charset; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + import org.springframework.core.io.Resource; import org.springframework.integration.core.Message; +import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; +import org.springframework.integration.handler.ReplyMessageHolder; import org.springframework.integration.message.MessageHandler; import org.springframework.integration.message.MessageHandlingException; import org.springframework.util.Assert; @@ -31,47 +36,78 @@ import org.springframework.util.FileCopyUtils; /** * A {@link MessageHandler} implementation that writes the Message payload to a - * file. If the payload is a File object, it will copy the File to this - * consumer's directory. If the payload is a byte array or String, it will write + * file. If the payload is a File object, it will copy the File to the specified + * destination directory. If the payload is a byte array or String, it will write * it directly. Otherwise, the payload type is unsupported, and an Exception * will be thrown. *
+ * If the 'deleteSourceFiles' flag is set to true, the original Files will be + * deleted. The default value for that flag is false. See the + * {@link #setDeleteSourceFiles(boolean)} method javadoc for more information. + *
* Other transformers may be useful to precede this handler. For example, any
* Serializable object payload can be converted into a byte array by the
- * {@link org.springframework.integration.transformer.PayloadSerializingTransformer}
- * . Likewise, any Object can be converted to a String based on its
+ * {@link org.springframework.integration.transformer.PayloadSerializingTransformer}.
+ * Likewise, any Object can be converted to a String based on its
* toString() method by the
* {@link org.springframework.integration.transformer.ObjectToStringTransformer}.
*
* @author Mark Fisher
* @author Iwein Fuld
+ * @author Alex Peters
*/
-public class FileWritingMessageHandler implements MessageHandler {
+public class FileWritingMessageHandler extends AbstractReplyProducingMessageHandler {
private static final String TEMPORARY_FILE_SUFFIX =".writing";
-
+
+
+ private final Log logger = LogFactory.getLog(this.getClass());
+
private volatile FileNameGenerator fileNameGenerator = new DefaultFileNameGenerator();
- private final File parentDirectory;
+ private final File destinationDirectory;
+
+ private volatile boolean deleteSourceFiles;
private volatile Charset charset = Charset.defaultCharset();
-
- public FileWritingMessageHandler(Resource parentDirectory) {
+
+
+ public FileWritingMessageHandler(Resource destinationDirectory) {
try {
- Assert.isTrue(parentDirectory.exists(), "Output directory [" + parentDirectory + "] does not exist");
- this.parentDirectory = parentDirectory.getFile();
- Assert.isTrue(this.parentDirectory.isDirectory(), "[" + this.parentDirectory + "] is not a directory");
- Assert.isTrue(this.parentDirectory.canWrite(), "[" + this.parentDirectory + "] should be writable");
+ Assert.isTrue(destinationDirectory.exists(),
+ "Output directory [" + destinationDirectory + "] does not exist");
+ this.destinationDirectory = destinationDirectory.getFile();
+ Assert.isTrue(this.destinationDirectory.isDirectory(),
+ "[" + this.destinationDirectory + "] is not a directory");
+ Assert.isTrue(this.destinationDirectory.canWrite(),
+ "[" + this.destinationDirectory + "] is not writable");
}
catch (IOException e) {
- throw new IllegalArgumentException("Inaccessable output directory", e);
+ throw new IllegalArgumentException("Inaccessible output directory", e);
}
}
+
+ /**
+ * Provide the {@link FileNameGenerator} strategy to use when generating
+ * the destination file's name.
+ */
public void setFileNameGenerator(FileNameGenerator fileNameGenerator) {
+ Assert.notNull(fileNameGenerator, "FileNameGenerator must not be null");
this.fileNameGenerator = fileNameGenerator;
}
+ /**
+ * Specify whether to delete source Files after writing to the destination
+ * directory. The default is false. When set to true, 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.
+ */
+ public void setDeleteSourceFiles(boolean deleteSourceFiles) {
+ this.deleteSourceFiles = deleteSourceFiles;
+ }
+
/**
* Set the charset name to use when writing a File from a String-based
* Message payload.
@@ -82,32 +118,96 @@ public class FileWritingMessageHandler implements MessageHandler {
this.charset = Charset.forName(charset);
}
- public void handleMessage(Message> message) {
- Assert.notNull(message, "message must not be null");
- Object payload = message.getPayload();
+ @Override
+ protected void handleRequestMessage(Message> requestMessage, ReplyMessageHolder replyMessageHolder) {
+ Assert.notNull(requestMessage, "message must not be null");
+ Object payload = requestMessage.getPayload();
Assert.notNull(payload, "message payload must not be null");
- String generatedFileName = this.fileNameGenerator.generateFileName(message);
- File file = new File(parentDirectory, generatedFileName+TEMPORARY_FILE_SUFFIX);
+ String generatedFileName = this.fileNameGenerator.generateFileName(requestMessage);
+ File originalFileFromHeader = this.retrieveOriginalFileFromHeader(requestMessage);
+ File tempFile = new File(this.destinationDirectory, generatedFileName + TEMPORARY_FILE_SUFFIX);
+ File resultFile = new File(this.destinationDirectory, generatedFileName);
try {
if (payload instanceof File) {
- FileCopyUtils.copy((File) payload, file);
+ resultFile = this.handleFileMessage((File) payload, tempFile, resultFile);
}
else if (payload instanceof byte[]) {
- FileCopyUtils.copy((byte[]) payload, file);
+ resultFile = this.handleByteArrayMessage(
+ (byte[]) payload, originalFileFromHeader, tempFile, resultFile);
}
else if (payload instanceof String) {
- OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(file), this.charset);
- FileCopyUtils.copy((String) payload, writer);
+ resultFile = this.handleStringMessage(
+ (String) payload, originalFileFromHeader, tempFile, resultFile);
}
else {
- throw new IllegalArgumentException("unsupported Message payload type [" + payload.getClass().getName()
- + "]");
+ throw new IllegalArgumentException(
+ "unsupported Message payload type [" + payload.getClass().getName() + "]");
}
- file.renameTo(new File(parentDirectory, generatedFileName));
}
catch (Exception e) {
- throw new MessageHandlingException(message, "failed to write Message payload to file", e);
+ throw new MessageHandlingException(requestMessage, "failed to write Message payload to file", e);
+ }
+ if (resultFile != null) {
+ replyMessageHolder.set(resultFile);
}
}
+ /**
+ * Retrieves the File instance from the {@link FileHeaders#ORIGINAL_FILE}
+ * header if available. If the value is not a File instance or a String
+ * representation of a file path, this will return null.
+ */
+ private File retrieveOriginalFileFromHeader(Message> message) {
+ Object value = message.getHeaders().get(FileHeaders.ORIGINAL_FILE);
+ if (value instanceof File) {
+ return (File) value;
+ }
+ if (value instanceof String) {
+ return new File((String) value);
+ }
+ return null;
+ }
+
+ private File handleFileMessage(File sourceFile, File tempFile, File resultFile) throws IOException {
+ if (this.deleteSourceFiles) {
+ if (sourceFile.renameTo(resultFile)) {
+ return resultFile;
+ }
+ if (logger.isInfoEnabled()) {
+ logger.info(String.format("Failed to move file '%s'. Using copy and delete fallback.",
+ sourceFile.getAbsolutePath()));
+ }
+ }
+ FileCopyUtils.copy(sourceFile, tempFile);
+ tempFile.renameTo(resultFile);
+ if (this.deleteSourceFiles) {
+ sourceFile.delete();
+ }
+ return resultFile;
+ }
+
+ private File handleByteArrayMessage(byte[] bytes, File originalFile, File tempFile, File resultFile)
+ throws IOException {
+
+ FileCopyUtils.copy(bytes, tempFile);
+ tempFile.renameTo(resultFile);
+ if (this.deleteSourceFiles && originalFile != null) {
+ originalFile.delete();
+ }
+ return resultFile;
+ }
+
+ private File handleStringMessage(String content, File originalFile, File tempFile, File resultFile)
+ throws IOException {
+
+ OutputStreamWriter writer = new OutputStreamWriter(
+ new FileOutputStream(tempFile), this.charset);
+ FileCopyUtils.copy(content, writer);
+ tempFile.renameTo(resultFile);
+ if (this.deleteSourceFiles && originalFile != null) {
+ originalFile.delete();
+ }
+ return resultFile;
+ }
+
}
diff --git a/org.springframework.integration.file/src/main/java/org/springframework/integration/file/config/FileOutboundChannelAdapterParser.java b/org.springframework.integration.file/src/main/java/org/springframework/integration/file/config/FileOutboundChannelAdapterParser.java
index 6e421b0914..da9799b95c 100644
--- a/org.springframework.integration.file/src/main/java/org/springframework/integration/file/config/FileOutboundChannelAdapterParser.java
+++ b/org.springframework.integration.file/src/main/java/org/springframework/integration/file/config/FileOutboundChannelAdapterParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2008 the original author or authors.
+ * Copyright 2002-2009 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.
@@ -22,6 +22,7 @@ import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.xml.AbstractOutboundChannelAdapterParser;
+import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.util.StringUtils;
/**
@@ -30,7 +31,6 @@ import org.springframework.util.StringUtils;
*
* @author Mark Fisher
* @author Iwein Fuld
- *
*/
public class FileOutboundChannelAdapterParser extends AbstractOutboundChannelAdapterParser {
@@ -46,6 +46,7 @@ public class FileOutboundChannelAdapterParser extends AbstractOutboundChannelAda
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition("org.springframework.integration.file.FileWritingMessageHandler");
builder.addConstructorArgValue(directory);
+ builder.addPropertyReference("outputChannel", IntegrationContextUtils.NULL_CHANNEL_BEAN_NAME);
String fileNameGenerator = element.getAttribute("filename-generator");
if (StringUtils.hasText(fileNameGenerator)) {
builder.addPropertyReference("fileNameGenerator", fileNameGenerator);
diff --git a/org.springframework.integration.file/src/test/java/org/springframework/integration/file/FileWritingMessageHandlerTests.java b/org.springframework.integration.file/src/test/java/org/springframework/integration/file/FileWritingMessageHandlerTests.java
index 6a3c8450cc..f64858ab13 100644
--- a/org.springframework.integration.file/src/test/java/org/springframework/integration/file/FileWritingMessageHandlerTests.java
+++ b/org.springframework.integration.file/src/test/java/org/springframework/integration/file/FileWritingMessageHandlerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2008 the original author or authors.
+ * Copyright 2002-2009 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.
@@ -18,29 +18,47 @@ package org.springframework.integration.file;
import static org.easymock.EasyMock.*;
import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
import org.junit.After;
-import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.core.io.Resource;
+import org.springframework.integration.channel.NullChannel;
+import org.springframework.integration.channel.QueueChannel;
+import org.springframework.integration.core.Message;
import org.springframework.integration.message.GenericMessage;
+import org.springframework.integration.message.MessageBuilder;
import org.springframework.integration.message.MessageHandlingException;
+import org.springframework.util.FileCopyUtils;
/**
* @author Mark Fisher
* @author Iwein Fuld
+ * @author Alex Peters
*/
public class FileWritingMessageHandlerTests {
+ static final String DEFAULT_ENCODING = "UTF-8";
+
+ static final String SAMPLE_CONTENT = "HelloWorld\näöüß";
+
+
+ private File sourceFile;
+
private Resource outputDirectory = createMock(Resource.class);
- private FileWritingMessageHandler subject;
+ private FileWritingMessageHandler handler;
private static File outputDirectoryFile = new File(System.getProperty("java.io.tmpdir") + "/"
+ FileWritingMessageHandlerTests.class.getSimpleName());;
@@ -48,39 +66,190 @@ public class FileWritingMessageHandlerTests {
@BeforeClass
public static void setupOutputDir() throws Exception {
outputDirectoryFile.mkdir();
+ outputDirectoryFile.deleteOnExit();
}
-
@Before
public void setup() throws Exception {
+ sourceFile = File.createTempFile("tempSourceFileForTests", ".txt");
+ sourceFile.deleteOnExit();
+ FileCopyUtils.copy(SAMPLE_CONTENT.getBytes(DEFAULT_ENCODING),
+ new FileOutputStream(sourceFile, false));
expect(outputDirectory.getFile()).andReturn(outputDirectoryFile).anyTimes();
expect(outputDirectory.exists()).andReturn(true).anyTimes();
replay(outputDirectory);
- subject = new FileWritingMessageHandler(outputDirectory);
+ handler = new FileWritingMessageHandler(outputDirectory);
}
+ @After
+ public void emptyOutputDir() {
+ File[] files = outputDirectoryFile.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ file.delete();
+ }
+ }
+ }
+
+
@Test(expected = MessageHandlingException.class)
public void unsupportedType() throws Exception {
- subject.handleMessage(new GenericMessage