INT-606, INT-666 Added a deleteSourceFiles property to FileWritingMessageHandler, and now FileWritingMessageHandler sends reply Messages with the new File as payload. The outbound-channel-adapter configures the nullChannel for the handler's outputChannel.

This commit is contained in:
Mark Fisher
2009-06-30 21:48:43 +00:00
parent 857d4aaf4f
commit 5fe7ee6b6c
4 changed files with 319 additions and 49 deletions

View File

@@ -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.
* <p>
* If the 'deleteSourceFiles' flag is set to true, the original Files will be
* deleted. The default value for that flag is <em>false</em>. See the
* {@link #setDeleteSourceFiles(boolean)} method javadoc for more information.
* <p>
* 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
* <code>toString()</code> 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 <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.
*/
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 <code>null</code>.
*/
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;
}
}

View File

@@ -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);

View File

@@ -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<Integer>(99));
handler.handleMessage(new GenericMessage<Integer>(99));
assertThat(outputDirectoryFile.listFiles()[0], nullValue());
}
@Test
public void supportedType() throws Exception {
subject.handleMessage(new GenericMessage<String>("test"));
handler.setOutputChannel(new NullChannel());
handler.handleMessage(new GenericMessage<String>("test"));
assertThat(outputDirectoryFile.listFiles()[0], notNullValue());
}
@After
public void emptyOutputDir() {
File[] files = outputDirectoryFile.listFiles();
for (File file : files) {
file.delete();
}
@Test
public void stringPayloadCopiedToNewFile() throws Exception {
Message<?> message = MessageBuilder.withPayload(SAMPLE_CONTENT).build();
QueueChannel output = new QueueChannel();
handler.setCharset(DEFAULT_ENCODING);
handler.setOutputChannel(output);
handler.handleMessage(message);
Message<?> result = output.receive(0);
assertFileContentIsMatching(result);
}
@AfterClass
public static void cleanupOutputDir() throws Exception {
outputDirectoryFile.delete();
@Test
public void byteArrayPayloadCopiedToNewFile() throws Exception {
Message<?> message = MessageBuilder.withPayload(
SAMPLE_CONTENT.getBytes(DEFAULT_ENCODING)).build();
QueueChannel output = new QueueChannel();
handler.setOutputChannel(output);
handler.handleMessage(message);
Message<?> result = output.receive(0);
assertFileContentIsMatching(result);
}
@Test
public void filePayloadCopiedToNewFile() throws Exception {
Message<?> message = MessageBuilder.withPayload(sourceFile).build();
QueueChannel output = new QueueChannel();
handler.setOutputChannel(output);
handler.handleMessage(message);
Message<?> result = output.receive(0);
assertFileContentIsMatching(result);
}
@Test
public void deleteFilesFalseByDefault() throws Exception {
QueueChannel output = new QueueChannel();
handler.setOutputChannel(output);
Message<?> message = MessageBuilder.withPayload(sourceFile).build();
handler.handleMessage(message);
Message<?> result = output.receive(0);
assertFileContentIsMatching(result);
assertTrue(sourceFile.exists());
}
@Test
public void deleteFilesTrueWithFilePayload() throws Exception {
QueueChannel output = new QueueChannel();
handler.setDeleteSourceFiles(true);
handler.setOutputChannel(output);
Message<?> message = MessageBuilder.withPayload(sourceFile).build();
handler.handleMessage(message);
Message<?> result = output.receive(0);
assertFileContentIsMatching(result);
assertFalse(sourceFile.exists());
}
@Test
public void deleteSourceFileWithStringPayloadAndFileInstanceHeader() throws Exception {
QueueChannel output = new QueueChannel();
handler.setCharset(DEFAULT_ENCODING);
handler.setDeleteSourceFiles(true);
handler.setOutputChannel(output);
Message<?> message = MessageBuilder.withPayload(SAMPLE_CONTENT)
.setHeader(FileHeaders.ORIGINAL_FILE, sourceFile)
.build();
assertTrue(sourceFile.exists());
handler.handleMessage(message);
Message<?> result = output.receive(0);
assertFileContentIsMatching(result);
assertFalse(sourceFile.exists());
}
@Test
public void deleteSourceFileWithStringPayloadAndFilePathHeader() throws Exception {
QueueChannel output = new QueueChannel();
handler.setCharset(DEFAULT_ENCODING);
handler.setDeleteSourceFiles(true);
handler.setOutputChannel(output);
Message<?> message = MessageBuilder.withPayload(SAMPLE_CONTENT)
.setHeader(FileHeaders.ORIGINAL_FILE, sourceFile.getAbsolutePath())
.build();
assertTrue(sourceFile.exists());
handler.handleMessage(message);
Message<?> result = output.receive(0);
assertFileContentIsMatching(result);
assertFalse(sourceFile.exists());
}
@Test
public void deleteSourceFileWithByteArrayPayloadAndFileInstanceHeader() throws Exception {
QueueChannel output = new QueueChannel();
handler.setCharset(DEFAULT_ENCODING);
handler.setDeleteSourceFiles(true);
handler.setOutputChannel(output);
Message<?> message = MessageBuilder.withPayload(
SAMPLE_CONTENT.getBytes(DEFAULT_ENCODING))
.setHeader(FileHeaders.ORIGINAL_FILE, sourceFile)
.build();
assertTrue(sourceFile.exists());
handler.handleMessage(message);
Message<?> result = output.receive(0);
assertFileContentIsMatching(result);
assertFalse(sourceFile.exists());
}
@Test
public void deleteSourceFileWithByteArrayPayloadAndFilePathHeader() throws Exception {
QueueChannel output = new QueueChannel();
handler.setCharset(DEFAULT_ENCODING);
handler.setDeleteSourceFiles(true);
handler.setOutputChannel(output);
Message<?> message = MessageBuilder.withPayload(
SAMPLE_CONTENT.getBytes(DEFAULT_ENCODING))
.setHeader(FileHeaders.ORIGINAL_FILE, sourceFile.getAbsolutePath())
.build();
assertTrue(sourceFile.exists());
handler.handleMessage(message);
Message<?> result = output.receive(0);
assertFileContentIsMatching(result);
assertFalse(sourceFile.exists());
}
@Test
public void customFileNameGenerator() throws Exception {
final String anyFilename = "fooBar.test";
QueueChannel output = new QueueChannel();
handler.setOutputChannel(output);
handler.setFileNameGenerator(new FileNameGenerator() {
public String generateFileName(Message<?> message) {
return anyFilename;
}
});
Message<?> message = MessageBuilder.withPayload("test").build();
handler.handleMessage(message);
File result = (File) output.receive(0).getPayload();
assertThat(result.getName(), is(anyFilename));
}
void assertFileContentIsMatching(Message<?> result) throws IOException, UnsupportedEncodingException {
assertThat(result, is(notNullValue()));
assertThat(result.getPayload(), is(File.class));
File destFile = (File) result.getPayload();
assertNotSame(destFile, sourceFile);
assertThat(destFile.exists(), is(true));
byte[] destFileContent = FileCopyUtils.copyToByteArray(destFile);
assertThat(new String(destFileContent, DEFAULT_ENCODING), is(SAMPLE_CONTENT));
}
}

View File

@@ -57,7 +57,7 @@ public class FileOutboundChannelAdapterParserTests {
adapterAccessor.getPropertyValue("handler");
DirectFieldAccessor handlerAccessor = new DirectFieldAccessor(handler);
File expected = new File(System.getProperty("java.io.tmpdir"));
File actual = (File) handlerAccessor.getPropertyValue("parentDirectory");
File actual = (File) handlerAccessor.getPropertyValue("destinationDirectory");
assertEquals(expected, actual);
assertTrue(handlerAccessor.getPropertyValue("fileNameGenerator") instanceof DefaultFileNameGenerator);
}
@@ -69,7 +69,7 @@ public class FileOutboundChannelAdapterParserTests {
adapterAccessor.getPropertyValue("handler");
DirectFieldAccessor handlerAccessor = new DirectFieldAccessor(handler);
File expected = new File(System.getProperty("java.io.tmpdir"));
File actual = (File) handlerAccessor.getPropertyValue("parentDirectory");
File actual = (File) handlerAccessor.getPropertyValue("destinationDirectory");
assertEquals(expected, actual);
assertTrue(handlerAccessor.getPropertyValue("fileNameGenerator") instanceof CustomFileNameGenerator);
}