From 953fb3a5b4955f1794f8bbc66ccdb68d9e2bec9b Mon Sep 17 00:00:00 2001 From: dsyer Date: Tue, 12 Jan 2010 17:05:56 +0000 Subject: [PATCH] RESOLVED - issue BATCH-1239: Add email-sending item writer --- spring-batch-infrastructure/pom.xml | 11 +- .../item/mail/DefaultMailErrorHandler.java | 63 +++++++++ .../batch/item/mail/MailErrorHandler.java | 45 +++++++ .../mail/SimpleMailMessageItemWriter.java | 113 ++++++++++++++++ .../mail/javamail/MimeMessageItemWriter.java | 117 ++++++++++++++++ .../mail/DefaultMailErrorHandlerTests.java | 62 +++++++++ .../SimpleMailMessageItemWriterTests.java | 114 ++++++++++++++++ .../javamail/MimeMessageItemWriterTests.java | 119 +++++++++++++++++ .../batch/item/sample/FooService.java | 3 +- spring-batch-samples/.springBeans | 3 +- spring-batch-samples/pom.xml | 10 +- .../batch/sample/domain/mail/User.java | 61 +++++++++ .../mail/internal/TestMailErrorHandler.java | 54 ++++++++ .../domain/mail/internal/TestMailSender.java | 73 ++++++++++ .../mail/internal/UserMailItemProcessor.java | 45 +++++++ .../src/main/resources/jobs/mailJob.xml | 51 +++++++ .../src/main/resources/log4j.properties | 4 +- .../batch/sample/MailJobFunctionalTests.java | 126 ++++++++++++++++++ 18 files changed, 1065 insertions(+), 9 deletions(-) create mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/item/mail/DefaultMailErrorHandler.java create mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/item/mail/MailErrorHandler.java create mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/item/mail/SimpleMailMessageItemWriter.java create mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/item/mail/javamail/MimeMessageItemWriter.java create mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/mail/DefaultMailErrorHandlerTests.java create mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/mail/SimpleMailMessageItemWriterTests.java create mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/mail/javamail/MimeMessageItemWriterTests.java create mode 100644 spring-batch-samples/src/main/java/org/springframework/batch/sample/domain/mail/User.java create mode 100644 spring-batch-samples/src/main/java/org/springframework/batch/sample/domain/mail/internal/TestMailErrorHandler.java create mode 100644 spring-batch-samples/src/main/java/org/springframework/batch/sample/domain/mail/internal/TestMailSender.java create mode 100644 spring-batch-samples/src/main/java/org/springframework/batch/sample/domain/mail/internal/UserMailItemProcessor.java create mode 100644 spring-batch-samples/src/main/resources/jobs/mailJob.xml create mode 100644 spring-batch-samples/src/test/java/org/springframework/batch/sample/MailJobFunctionalTests.java diff --git a/spring-batch-infrastructure/pom.xml b/spring-batch-infrastructure/pom.xml index e6e7a2339..c9648633e 100644 --- a/spring-batch-infrastructure/pom.xml +++ b/spring-batch-infrastructure/pom.xml @@ -1,5 +1,6 @@ - + 4.0.0 spring-batch-infrastructure 2.1.0.CI-SNAPSHOT @@ -139,6 +140,12 @@ persistence-api true + + javax.mail + mail + 1.4 + true + org.springframework.ws spring-oxm-tiger @@ -184,7 +191,7 @@ org.codehaus.woodstox - woodstox-core-asl + woodstox-core-asl true diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/mail/DefaultMailErrorHandler.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/mail/DefaultMailErrorHandler.java new file mode 100644 index 000000000..082623131 --- /dev/null +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/mail/DefaultMailErrorHandler.java @@ -0,0 +1,63 @@ +/* + * Copyright 2006-2010 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.batch.item.mail; + +import javax.mail.MessagingException; + +import org.springframework.mail.MailException; +import org.springframework.mail.MailMessage; +import org.springframework.mail.MailSendException; + +/** + * This {@link MailErrorHandler} implementation simply rethrows the exception it + * receives. + * + * @author Dan Garrette + * @author Dave Syer + * + * @since 2.1 + */ +public class DefaultMailErrorHandler implements MailErrorHandler { + + private static final int DEFAULT_MAX_MESSAGE_LENGTH = 1024; + + private int maxMessageLength = DEFAULT_MAX_MESSAGE_LENGTH; + + /** + * The limit for the size of message that will be copied to the exception + * message. Output will be truncated beyond that. Default value is 1024. + * + * @param maxMessageLength the maximum message length + */ + public void setMaxMessageLength(int maxMessageLength) { + this.maxMessageLength = maxMessageLength; + } + + /** + * Wraps the input exception with a runtime {@link MailException}. The + * exception message will contain the failed message. + * + * @param message a failed message + * @param exception a MessagingException + * @throws MailException a translation of the MessagingException + * @see MailErrorHandler#handle(MailMessage, MessagingException) + */ + public void handle(MailMessage message, MessagingException exception) throws MailException { + String msg = message.toString(); + throw new MailSendException("Mail server send failed: " + + msg.substring(0, Math.min(maxMessageLength, msg.length())), exception); + } +} diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/mail/MailErrorHandler.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/mail/MailErrorHandler.java new file mode 100644 index 000000000..f5268b48e --- /dev/null +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/mail/MailErrorHandler.java @@ -0,0 +1,45 @@ +/* + * Copyright 2006-2010 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.batch.item.mail; + +import javax.mail.MessagingException; + +import org.springframework.mail.MailException; +import org.springframework.mail.MailMessage; + +/** + * This class is used to handle errors that occur when email messages are unable + * to be sent. + * + * @author Dan Garrette + * @author Dave Syer + * + * @since 2.1 + */ +public interface MailErrorHandler { + + /** + * This method will be called for each message that failed sending in the + * chunk. If an exception is thrown from this method, then it will propagate + * to the caller. + * + * @param message the failed message + * @param exception the exception that caused the failure + * @throws MailException if the exception cannot be handled + */ + public void handle(MailMessage message, MessagingException exception) throws MailException; + +} diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/mail/SimpleMailMessageItemWriter.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/mail/SimpleMailMessageItemWriter.java new file mode 100644 index 000000000..2aa6e64b7 --- /dev/null +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/mail/SimpleMailMessageItemWriter.java @@ -0,0 +1,113 @@ +/* + * Copyright 2006-2010 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.batch.item.mail; + +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import javax.mail.MessagingException; + +import org.springframework.batch.item.ItemWriter; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.mail.MailException; +import org.springframework.mail.MailSendException; +import org.springframework.mail.MailSender; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.util.Assert; + +/** + *

+ * A simple {@link ItemWriter} that can send mail messages. If it fails there is + * no guarantee about which of the messages were sent, but the ones that failed + * can be picked up in the error handler. Because the mail protocol is not + * transactional, failures should be dealt with here if possible rather than + * allowing them to be rethrown (which is the default). + *

+ * + *

+ * Delegates the actual sending of messages to a {@link MailSender}, using the + * batch method {@link MailSender#send(SimpleMailMessage[])}, which normally + * uses a single server connection for the whole batch (depending on the + * implementation). The efficiency of for large volumes of messages (repeated + * calls to the item writer) might be improved by the use of a special + * {@link MailSender} that caches connections to the server in between calls. + *

+ * + *

+ * Stateless, so automatically restartable. + *

+ * + * @author Dave Syer + * + * @since 2.1 + * + */ +public class SimpleMailMessageItemWriter implements ItemWriter, InitializingBean { + + private MailSender mailSender; + + private MailErrorHandler mailErrorHandler = new DefaultMailErrorHandler(); + + /** + * A {@link MailSender} to be used to send messages in {@link #write(List)}. + * + * @param mailSender + */ + public void setMailSender(MailSender mailSender) { + this.mailSender = mailSender; + } + + /** + * The handler for failed messages. Defaults to a + * {@link DefaultMailErrorHandler}. + * + * @param mailErrorHandler the mail error handler to set + */ + public void setMailErrorHandler(MailErrorHandler mailErrorHandler) { + this.mailErrorHandler = mailErrorHandler; + } + + /** + * Check mandatory properties (mailSender). + * + * @throws IllegalStateException if the mandatory properties are not set + * + * @see InitializingBean#afterPropertiesSet() + */ + @Override + public void afterPropertiesSet() throws IllegalStateException { + Assert.state(mailSender != null, "A MailSender must be provided."); + } + + /** + * @param items the items to send + * @see ItemWriter#write(List) + */ + public void write(List items) throws MailException { + try { + mailSender.send(items.toArray(new SimpleMailMessage[items.size()])); + } + catch (MailSendException e) { + @SuppressWarnings("unchecked") + Map failedMessages = e.getFailedMessages(); + for (Entry entry : failedMessages.entrySet()) { + mailErrorHandler.handle(entry.getKey(), entry.getValue()); + } + } + } + +} diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/mail/javamail/MimeMessageItemWriter.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/mail/javamail/MimeMessageItemWriter.java new file mode 100644 index 000000000..f326cb323 --- /dev/null +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/mail/javamail/MimeMessageItemWriter.java @@ -0,0 +1,117 @@ +/* + * Copyright 2006-2010 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.batch.item.mail.javamail; + +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import javax.mail.MessagingException; +import javax.mail.internet.MimeMessage; + +import org.springframework.batch.item.ItemWriter; +import org.springframework.batch.item.mail.DefaultMailErrorHandler; +import org.springframework.batch.item.mail.MailErrorHandler; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.mail.MailException; +import org.springframework.mail.MailSendException; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMailMessage; +import org.springframework.util.Assert; + +/** + *

+ * A simple {@link ItemWriter} that can send mail messages. If it fails there is + * no guarantee about which of the messages were sent, but the ones that failed + * can be picked up in the error handler. Because the mail protocol is not + * transactional, failures should be dealt with here if possible rather than + * allowing them to be rethrown (which is the default). + *

+ * + *

+ * Delegates the actual sending of messages to a {@link JavaMailSender}, using the + * batch method {@link JavaMailSender#send(MimeMessage[])}, which normally uses + * a single server connection for the whole batch (depending on the + * implementation). The efficiency of for large volumes of messages (repeated + * calls to the item writer) might be improved by the use of a special + * {@link JavaMailSender} that caches connections to the server in between + * calls. + *

+ * + *

+ * Stateless, so automatically restartable. + *

+ * + * @author Dave Syer + * + * @since 2.1 + * + */ +public class MimeMessageItemWriter implements ItemWriter { + + private JavaMailSender mailSender; + + private MailErrorHandler mailErrorHandler = new DefaultMailErrorHandler(); + + /** + * A {@link JavaMailSender} to be used to send messages in {@link #write(List)}. + * + * @param mailSender + */ + public void setJavaMailSender(JavaMailSender mailSender) { + this.mailSender = mailSender; + } + + /** + * The handler for failed messages. Defaults to a + * {@link DefaultMailErrorHandler}. + * + * @param mailErrorHandler the mail error handler to set + */ + public void setMailErrorHandler(MailErrorHandler mailErrorHandler) { + this.mailErrorHandler = mailErrorHandler; + } + + /** + * Check mandatory properties (mailSender). + * + * @throws IllegalStateException if the mandatory properties are not set + * + * @see InitializingBean#afterPropertiesSet() + */ + public void afterPropertiesSet() throws IllegalStateException { + Assert.state(mailSender != null, "A MailSender must be provided."); + } + + /** + * @param items the items to send + * @see ItemWriter#write(List) + */ + @Override + public void write(List items) throws MailException { + try { + mailSender.send(items.toArray(new MimeMessage[items.size()])); + } + catch (MailSendException e) { + @SuppressWarnings("unchecked") + Map failedMessages = e.getFailedMessages(); + for (Entry entry : failedMessages.entrySet()) { + mailErrorHandler.handle(new MimeMailMessage(entry.getKey()), entry.getValue()); + } + } + } + +} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/mail/DefaultMailErrorHandlerTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/mail/DefaultMailErrorHandlerTests.java new file mode 100644 index 000000000..2783b7bc7 --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/mail/DefaultMailErrorHandlerTests.java @@ -0,0 +1,62 @@ +/* + * Copyright 2006-2010 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.batch.item.mail; + +import static org.junit.Assert.*; + +import javax.mail.MessagingException; + +import org.junit.Test; +import org.springframework.mail.MailException; +import org.springframework.mail.MailMessage; +import org.springframework.mail.MailSendException; +import org.springframework.mail.SimpleMailMessage; + +/** + * @author Dave Syer + * + * @since 2.1 + * + */ +public class DefaultMailErrorHandlerTests { + + private DefaultMailErrorHandler handler = new DefaultMailErrorHandler(); + + /** + * Test method for {@link DefaultMailErrorHandler#setMaxMessageLength(int)}. + */ + @Test + public void testSetMaxMessageLength() { + handler.setMaxMessageLength(20); + try { + SimpleMailMessage message = new SimpleMailMessage(); + handler.handle(message, new MessagingException()); + fail("Expected MailException"); + } catch (MailException e) { + String msg = e.getMessage(); + assertTrue("Wrong message: "+msg, msg.matches(".*SimpleMailMessage: f;.*")); + } + } + + /** + * Test method for {@link DefaultMailErrorHandler#handle(MailMessage, MessagingException)}. + */ + @Test(expected=MailSendException.class) + public void testHandle() { + handler.handle(new SimpleMailMessage(), new MessagingException()); + } + +} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/mail/SimpleMailMessageItemWriterTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/mail/SimpleMailMessageItemWriterTests.java new file mode 100644 index 000000000..029232bfb --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/mail/SimpleMailMessageItemWriterTests.java @@ -0,0 +1,114 @@ +/* + * Copyright 2006-2010 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.batch.item.mail; + +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; +import java.util.Collections; +import java.util.concurrent.atomic.AtomicReference; + +import javax.mail.MessagingException; + +import org.easymock.EasyMock; +import org.junit.Before; +import org.junit.Test; +import org.springframework.mail.MailException; +import org.springframework.mail.MailMessage; +import org.springframework.mail.MailSendException; +import org.springframework.mail.MailSender; +import org.springframework.mail.SimpleMailMessage; + +/** + * @author Dave Syer + * + * @since 2.1 + * + */ +public class SimpleMailMessageItemWriterTests { + + private SimpleMailMessageItemWriter writer = new SimpleMailMessageItemWriter(); + + private MailSender mailSender = EasyMock.createMock(MailSender.class); + + @Before + public void setUp() { + writer.setMailSender(mailSender); + } + + @Test + public void testSend() throws Exception { + + SimpleMailMessage foo = new SimpleMailMessage(); + SimpleMailMessage bar = new SimpleMailMessage(); + SimpleMailMessage[] items = new SimpleMailMessage[] { foo, bar }; + + mailSender.send(EasyMock.aryEq(items)); + EasyMock.expectLastCall(); + EasyMock.replay(mailSender); + + writer.write(Arrays.asList(items)); + + EasyMock.verify(mailSender); + + } + + @Test(expected = MailSendException.class) + public void testDefaultErrorHandler() throws Exception { + + SimpleMailMessage foo = new SimpleMailMessage(); + SimpleMailMessage bar = new SimpleMailMessage(); + SimpleMailMessage[] items = new SimpleMailMessage[] { foo, bar }; + + mailSender.send(EasyMock.aryEq(items)); + EasyMock.expectLastCall().andThrow( + new MailSendException(Collections.singletonMap(foo, new MessagingException("FOO")))); + EasyMock.replay(mailSender); + + writer.write(Arrays.asList(items)); + + EasyMock.verify(mailSender); + + } + + @Test + public void testCustomErrorHandler() throws Exception { + + final AtomicReference content = new AtomicReference(); + writer.setMailErrorHandler(new MailErrorHandler() { + public void handle(MailMessage message, MessagingException exception) throws MailException { + content.set(exception.getMessage()); + } + }); + + SimpleMailMessage foo = new SimpleMailMessage(); + SimpleMailMessage bar = new SimpleMailMessage(); + SimpleMailMessage[] items = new SimpleMailMessage[] { foo, bar }; + + mailSender.send(EasyMock.aryEq(items)); + EasyMock.expectLastCall().andThrow( + new MailSendException(Collections.singletonMap(foo, new MessagingException("FOO")))); + EasyMock.replay(mailSender); + + writer.write(Arrays.asList(items)); + + assertEquals("FOO", content.get()); + + EasyMock.verify(mailSender); + + } + +} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/mail/javamail/MimeMessageItemWriterTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/mail/javamail/MimeMessageItemWriterTests.java new file mode 100644 index 000000000..dfef86c5b --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/mail/javamail/MimeMessageItemWriterTests.java @@ -0,0 +1,119 @@ +/* + * Copyright 2006-2010 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.batch.item.mail.javamail; + +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Properties; +import java.util.concurrent.atomic.AtomicReference; + +import javax.mail.MessagingException; +import javax.mail.Session; +import javax.mail.internet.MimeMessage; + +import org.easymock.EasyMock; +import org.junit.Before; +import org.junit.Test; +import org.springframework.batch.item.mail.MailErrorHandler; +import org.springframework.mail.MailException; +import org.springframework.mail.MailMessage; +import org.springframework.mail.MailSendException; +import org.springframework.mail.javamail.JavaMailSender; + +/** + * @author Dave Syer + * + * @since 2.1 + * + */ +public class MimeMessageItemWriterTests { + + private MimeMessageItemWriter writer = new MimeMessageItemWriter(); + + private JavaMailSender mailSender = EasyMock.createMock(JavaMailSender.class); + + private Session session = Session.getDefaultInstance(new Properties()); + + @Before + public void setUp() { + writer.setJavaMailSender(mailSender); + } + + @Test + public void testSend() throws Exception { + + MimeMessage foo = new MimeMessage(session); + MimeMessage bar = new MimeMessage(session); + MimeMessage[] items = new MimeMessage[] { foo, bar }; + + mailSender.send(EasyMock.aryEq(items)); + EasyMock.expectLastCall(); + EasyMock.replay(mailSender); + + writer.write(Arrays.asList(items)); + + EasyMock.verify(mailSender); + + } + + @Test(expected = MailSendException.class) + public void testDefaultErrorHandler() throws Exception { + + MimeMessage foo = new MimeMessage(session); + MimeMessage bar = new MimeMessage(session); + MimeMessage[] items = new MimeMessage[] { foo, bar }; + + mailSender.send(EasyMock.aryEq(items)); + EasyMock.expectLastCall().andThrow( + new MailSendException(Collections.singletonMap(foo, new MessagingException("FOO")))); + EasyMock.replay(mailSender); + + writer.write(Arrays.asList(items)); + + EasyMock.verify(mailSender); + + } + + @Test + public void testCustomErrorHandler() throws Exception { + + final AtomicReference content = new AtomicReference(); + writer.setMailErrorHandler(new MailErrorHandler() { + public void handle(MailMessage message, MessagingException exception) throws MailException { + content.set(exception.getMessage()); + } + }); + + MimeMessage foo = new MimeMessage(session); + MimeMessage bar = new MimeMessage(session); + MimeMessage[] items = new MimeMessage[] { foo, bar }; + + mailSender.send(EasyMock.aryEq(items)); + EasyMock.expectLastCall().andThrow( + new MailSendException(Collections.singletonMap(foo, new MessagingException("FOO")))); + EasyMock.replay(mailSender); + + writer.write(Arrays.asList(items)); + + assertEquals("FOO", content.get()); + + EasyMock.verify(mailSender); + + } + +} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/sample/FooService.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/sample/FooService.java index dd357aec1..bf8c19528 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/sample/FooService.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/sample/FooService.java @@ -52,7 +52,6 @@ public class FooService { public List getProcessedFooNameValuePairs() { return processedFooNameValuePairs; - } - + } } diff --git a/spring-batch-samples/.springBeans b/spring-batch-samples/.springBeans index 672a1b8b0..3bd6aede5 100644 --- a/spring-batch-samples/.springBeans +++ b/spring-batch-samples/.springBeans @@ -1,7 +1,7 @@ 1 - + @@ -61,6 +61,7 @@ src/test/resources/org/springframework/batch/sample/common/ColumnRangePartitionerTests-context.xml src/test/resources/job-runner-context.xml src/test/resources/org/springframework/batch/sample/JobStepFunctionalTests-context.xml + src/main/resources/jobs/mailJob.xml diff --git a/spring-batch-samples/pom.xml b/spring-batch-samples/pom.xml index c8a857d18..4dfc7a8a4 100644 --- a/spring-batch-samples/pom.xml +++ b/spring-batch-samples/pom.xml @@ -1,5 +1,6 @@ - + 4.0.0 spring-batch-samples jar @@ -117,7 +118,7 @@ org.codehaus.woodstox - woodstox-core-asl + woodstox-core-asl javax.servlet @@ -241,6 +242,11 @@ true runtime + + javax.mail + mail + 1.4.1 + diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/sample/domain/mail/User.java b/spring-batch-samples/src/main/java/org/springframework/batch/sample/domain/mail/User.java new file mode 100644 index 000000000..72956657f --- /dev/null +++ b/spring-batch-samples/src/main/java/org/springframework/batch/sample/domain/mail/User.java @@ -0,0 +1,61 @@ +/* + * Copyright 2006-2010 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.batch.sample.domain.mail; + +/** + * @author Dan Garrette + * @author Dave Syer + * + * @since 2.1 + */ +public class User { + private int id; + private String name; + private String email; + + public User() { + } + + public User( int id, String name, String email ) { + this.id = id; + this.name = name; + this.email = email; + } + + public int getId() { + return id; + } + + public void setId( int id ) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName( String name ) { + this.name = name; + } + + public String getEmail() { + return email; + } + + public void setEmail( String email ) { + this.email = email; + } +} diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/sample/domain/mail/internal/TestMailErrorHandler.java b/spring-batch-samples/src/main/java/org/springframework/batch/sample/domain/mail/internal/TestMailErrorHandler.java new file mode 100644 index 000000000..c294223dd --- /dev/null +++ b/spring-batch-samples/src/main/java/org/springframework/batch/sample/domain/mail/internal/TestMailErrorHandler.java @@ -0,0 +1,54 @@ +/* + * Copyright 2006-2010 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.batch.sample.domain.mail.internal; + +import java.util.ArrayList; +import java.util.List; + +import javax.mail.MessagingException; + +import org.springframework.batch.item.mail.MailErrorHandler; +import org.springframework.mail.MailMessage; +import org.springframework.mail.SimpleMailMessage; + +/** + * This handler prints out failed messages with their exceptions. It also + * maintains a list of all failed messages it receives for lookup later by an + * assertion. + * + * @author Dan Garrette + * @author Dave Syer + * + * @since 2.1 + */ +public class TestMailErrorHandler implements MailErrorHandler { + + private List failedMessages = new ArrayList(); + + public void handle(MailMessage failedMessage, MessagingException ex) { + this.failedMessages.add(failedMessage); + System.out.println("Mail message failed: " + failedMessage); + System.out.println(ex); + } + + public List getFailedMessages() { + return failedMessages; + } + + public void clear() { + this.failedMessages.clear(); + } +} diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/sample/domain/mail/internal/TestMailSender.java b/spring-batch-samples/src/main/java/org/springframework/batch/sample/domain/mail/internal/TestMailSender.java new file mode 100644 index 000000000..ef6f7db8f --- /dev/null +++ b/spring-batch-samples/src/main/java/org/springframework/batch/sample/domain/mail/internal/TestMailSender.java @@ -0,0 +1,73 @@ +/* + * Copyright 2006-2010 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.batch.sample.domain.mail.internal; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import javax.mail.MessagingException; + +import org.springframework.mail.MailException; +import org.springframework.mail.MailSendException; +import org.springframework.mail.MailSender; +import org.springframework.mail.SimpleMailMessage; + +/** + * @author Dan Garrette + * @author Dave Syer + * + * @since 2.1 + */ +public class TestMailSender implements MailSender { + + private List subjectsToFail = new ArrayList(); + + private List received = new ArrayList(); + + public void clear() { + received.clear(); + } + + public void send(SimpleMailMessage simpleMessage) throws MailException { + throw new UnsupportedOperationException("Not implememted. Use send(SimpleMailMessage[])."); + } + + public void setSubjectsToFail(List subjectsToFail) { + this.subjectsToFail = subjectsToFail; + } + + public void send(SimpleMailMessage[] simpleMessages) throws MailException { + Map failedMessages = new LinkedHashMap(); + for (SimpleMailMessage simpleMessage : simpleMessages) { + if (subjectsToFail.contains(simpleMessage.getSubject())) { + failedMessages.put(simpleMessage, new MessagingException()); + } + else { + received.add(simpleMessage); + } + } + if (!failedMessages.isEmpty()) { + throw new MailSendException(failedMessages); + } + } + + public List getReceivedMessages() { + return received; + } + +} diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/sample/domain/mail/internal/UserMailItemProcessor.java b/spring-batch-samples/src/main/java/org/springframework/batch/sample/domain/mail/internal/UserMailItemProcessor.java new file mode 100644 index 000000000..38c6b4215 --- /dev/null +++ b/spring-batch-samples/src/main/java/org/springframework/batch/sample/domain/mail/internal/UserMailItemProcessor.java @@ -0,0 +1,45 @@ +/* + * Copyright 2006-2010 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.batch.sample.domain.mail.internal; + +import java.util.Date; + +import org.springframework.batch.item.ItemProcessor; +import org.springframework.batch.sample.domain.mail.User; +import org.springframework.mail.SimpleMailMessage; + +/** + * @author Dan Garrette + * @author Dave Syer + * + * @since 2.1 + */ +public class UserMailItemProcessor implements + ItemProcessor { + + /** + * @see org.springframework.batch.item.ItemProcessor#process(java.lang.Object) + */ + public SimpleMailMessage process( User user ) throws Exception { + SimpleMailMessage message = new SimpleMailMessage(); + message.setTo( user.getEmail() ); + message.setFrom( "communications@thecompany.com" ); + message.setSubject( user.getName() + "'s Account Info" ); + message.setSentDate( new Date() ); + message.setText( "Hello " + user.getName() ); + return message; + } +} diff --git a/spring-batch-samples/src/main/resources/jobs/mailJob.xml b/spring-batch-samples/src/main/resources/jobs/mailJob.xml new file mode 100644 index 000000000..949125e79 --- /dev/null +++ b/spring-batch-samples/src/main/resources/jobs/mailJob.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + John Adams's Account Info + James Madison's Account Info + + + + + diff --git a/spring-batch-samples/src/main/resources/log4j.properties b/spring-batch-samples/src/main/resources/log4j.properties index 5c28adfd2..9e20bcd32 100644 --- a/spring-batch-samples/src/main/resources/log4j.properties +++ b/spring-batch-samples/src/main/resources/log4j.properties @@ -28,6 +28,6 @@ log4j.appender.chainsaw.layout=org.apache.log4j.xml.XMLLayout #log4j.logger.org.springframework.orm=debug ### debug your specific package or classes with the following example -log4j.logger.org.springframework.jdbc=debug -log4j.logger.org.springframework.batch=debug +log4j.logger.org.springframework.jdbc=info +log4j.logger.org.springframework.batch=info log4j.logger.org.springframework.batch.sample=debug diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/sample/MailJobFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/sample/MailJobFunctionalTests.java new file mode 100644 index 000000000..7b0c22cf3 --- /dev/null +++ b/spring-batch-samples/src/test/java/org/springframework/batch/sample/MailJobFunctionalTests.java @@ -0,0 +1,126 @@ +/* + * Copyright 2006-2010 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.batch.sample; + +import static org.junit.Assert.assertEquals; + +import java.util.Iterator; +import java.util.List; + +import javax.sql.DataSource; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.sample.domain.mail.internal.TestMailErrorHandler; +import org.springframework.batch.sample.domain.mail.internal.TestMailSender; +import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.simple.SimpleJdbcTemplate; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author Dan Garrette + * @author Dave Syer + * + * @Since 2.1 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations = { "/simple-job-launcher-context.xml", "/jobs/mailJob.xml", "/job-runner-context.xml" }) +public class MailJobFunctionalTests { + + private static final String email = "to@company.com"; + + private static final Object[] USER1 = new Object[] { 1, "George Washington", email }; + + private static final Object[] USER2_SKIP = new Object[] { 2, "John Adams", "FAILURE" }; + + private static final Object[] USER3 = new Object[] { 3, "Thomas Jefferson", email }; + + private static final Object[] USER4_SKIP = new Object[] { 4, "James Madison", "FAILURE" }; + + private static final Object[] USER5 = new Object[] { 5, "James Monroe", email }; + + private static final Object[] USER6 = new Object[] { 6, "John Quincy Adams", email }; + + private static final Object[] USER7 = new Object[] { 7, "Andrew Jackson", email }; + + private static final Object[] USER8 = new Object[] { 8, "Martin Van Buren", email }; + + private SimpleJdbcTemplate jdbcTemplate; + + @Autowired + private JobLauncherTestUtils jobLauncherTestUtils; + + @Autowired + private TestMailErrorHandler errorHandler; + + @Autowired + private TestMailSender mailSender; + + @Autowired + public void setDataSource(DataSource dataSource) { + jdbcTemplate = new SimpleJdbcTemplate(dataSource); + } + + @Before + public void before() { + mailSender.clear(); + errorHandler.clear(); + jdbcTemplate.update("create table USERS (ID INTEGER, NAME VARCHAR(40), EMAIL VARCHAR(20))"); + } + + @After + public void after() throws Exception { + jdbcTemplate.update("drop table USERS"); + } + + @Test + public void testSkip() throws Exception { + this.createUsers(new Object[][] { USER1, USER2_SKIP, USER3, USER4_SKIP, USER5, USER6, USER7, USER8 }); + + JobExecution jobExecution = jobLauncherTestUtils.launchJob(); + assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus()); + + List receivedMessages = mailSender.getReceivedMessages(); + assertEquals(6, receivedMessages.size()); + Iterator emailIter = receivedMessages.iterator(); + for (Object[] record : new Object[][] { USER1, USER3, USER5, USER6, USER7, USER8 }) { + SimpleMailMessage email = emailIter.next(); + assertEquals("Hello " + record[1], email.getText()); + } + + assertEquals(2, this.errorHandler.getFailedMessages().size()); + Iterator failureItr = this.errorHandler.getFailedMessages().iterator(); + for (Object[] record : new Object[][] { USER2_SKIP, USER4_SKIP }) { + SimpleMailMessage email = failureItr.next(); + assertEquals("Hello " + record[1], email.getText()); + } + } + + private void createUsers(Object[][] records) { + for (Object[] record : records) { + jdbcTemplate.update("insert into USERS values (?,?,?)", record); + } + } + + +}