From 160701488650eead3da9997e5f84fec242b47dc4 Mon Sep 17 00:00:00 2001 From: Chris Schaefer Date: Mon, 30 Jul 2012 22:41:51 -0400 Subject: [PATCH] BATCH-1882: Add support for AMQP backed ItemReader / ItemWriter implementations Add Spring AMQP and Spring Rabbit dependencies. Update EasyMock from 2.3 to 2.4 to allow for concrete class mocking. --- spring-batch-core/ivy.xml | 2 +- spring-batch-infrastructure/ivy.xml | 2 +- spring-batch-infrastructure/pom.xml | 14 +++ .../batch/item/amqp/AmqpItemReader.java | 62 ++++++++++ .../batch/item/amqp/AmqpItemWriter.java | 55 +++++++++ .../batch/item/amqp/AmqpItemReaderTests.java | 109 ++++++++++++++++++ .../batch/item/amqp/AmqpItemWriterTests.java | 56 +++++++++ spring-batch-parent/pom.xml | 19 ++- 8 files changed, 316 insertions(+), 3 deletions(-) create mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/item/amqp/AmqpItemReader.java create mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/item/amqp/AmqpItemWriter.java create mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/amqp/AmqpItemReaderTests.java create mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/amqp/AmqpItemWriterTests.java diff --git a/spring-batch-core/ivy.xml b/spring-batch-core/ivy.xml index 05c007d9d..8525ac87f 100644 --- a/spring-batch-core/ivy.xml +++ b/spring-batch-core/ivy.xml @@ -25,7 +25,7 @@ - + diff --git a/spring-batch-infrastructure/ivy.xml b/spring-batch-infrastructure/ivy.xml index e5180aa7e..20b3a09fb 100644 --- a/spring-batch-infrastructure/ivy.xml +++ b/spring-batch-infrastructure/ivy.xml @@ -21,7 +21,7 @@ - + diff --git a/spring-batch-infrastructure/pom.xml b/spring-batch-infrastructure/pom.xml index c83c86f89..57a8c0d2f 100644 --- a/spring-batch-infrastructure/pom.xml +++ b/spring-batch-infrastructure/pom.xml @@ -43,6 +43,10 @@ org.easymock easymock + + org.easymock + easymockclassextension + org.aspectj aspectjrt @@ -195,6 +199,16 @@ woodstox-core-asl true + + org.springframework.amqp + spring-amqp + true + + + org.springframework.amqp + spring-rabbit + true + diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/amqp/AmqpItemReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/amqp/AmqpItemReader.java new file mode 100644 index 000000000..40b7c6a19 --- /dev/null +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/amqp/AmqpItemReader.java @@ -0,0 +1,62 @@ +/* + * Copyright 2012 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.amqp; + +import org.springframework.amqp.core.AmqpTemplate; +import org.springframework.amqp.core.Message; +import org.springframework.batch.item.ItemReader; +import org.springframework.util.Assert; + +/** + *

+ * AMQP {@link ItemReader} implementation using an {@link AmqpTemplate} to + * receive and/or convert messages. + *

+ * + * @author Chris Schaefer + */ +public class AmqpItemReader implements ItemReader { + private final AmqpTemplate amqpTemplate; + private Class itemType; + + public AmqpItemReader(final AmqpTemplate amqpTemplate) { + Assert.notNull(amqpTemplate, "AmpqTemplate must not be null"); + + this.amqpTemplate = amqpTemplate; + } + + @SuppressWarnings("unchecked") + public T read() { + if (itemType != null && itemType.isAssignableFrom(Message.class)) { + return (T) amqpTemplate.receive(); + } + + Object result = amqpTemplate.receiveAndConvert(); + + if (itemType != null && result != null) { + Assert.state(itemType.isAssignableFrom(result.getClass()), + "Received message payload of wrong type: expected [" + itemType + "]"); + } + + return (T) result; + } + + public void setItemType(Class itemType) { + Assert.notNull(itemType, "Item type cannot be null"); + this.itemType = itemType; + } +} diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/amqp/AmqpItemWriter.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/amqp/AmqpItemWriter.java new file mode 100644 index 000000000..49913d6ac --- /dev/null +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/amqp/AmqpItemWriter.java @@ -0,0 +1,55 @@ +/* + * Copyright 2012 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.amqp; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.amqp.core.AmqpTemplate; +import org.springframework.batch.item.ItemWriter; +import org.springframework.util.Assert; + +import java.util.List; + +/** + *

+ * AMQP {@link ItemWriter} implementation using an {@link AmqpTemplate} to + * send messages. Messages will be sent to the nameless exchange if not specified + * on the provided {@link AmqpTemplate}. + *

+ * + * @author Chris Schaefer + */ +public class AmqpItemWriter implements ItemWriter { + private final AmqpTemplate amqpTemplate; + private final Log log = LogFactory.getLog(getClass()); + + public AmqpItemWriter(final AmqpTemplate amqpTemplate) { + Assert.notNull(amqpTemplate, "AmpqTemplate must not be null"); + + this.amqpTemplate = amqpTemplate; + } + + public void write(final List items) throws Exception { + if (log.isDebugEnabled()) { + log.debug("Writing to AMQP with " + items.size() + " items."); + } + + for (T item : items) { + amqpTemplate.convertAndSend(item); + } + } +} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/amqp/AmqpItemReaderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/amqp/AmqpItemReaderTests.java new file mode 100644 index 000000000..91d2522fb --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/amqp/AmqpItemReaderTests.java @@ -0,0 +1,109 @@ +/* + * Copyright 2012 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.amqp; + +import org.easymock.classextension.EasyMock; +import org.junit.Test; +import org.springframework.amqp.core.AmqpTemplate; +import org.springframework.amqp.core.Message; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** + *

+ * Test cases around {@link AmqpItemReader}. + *

+ * + * @author Chris Schaefer + */ +public class AmqpItemReaderTests { + @Test(expected = IllegalArgumentException.class) + public void testNullAmqpTemplate() { + new AmqpItemReader(null); + } + + @Test + public void testNoItemType() { + final AmqpTemplate amqpTemplate = EasyMock.createMock(AmqpTemplate.class); + EasyMock.expect(amqpTemplate.receiveAndConvert()).andReturn("foo"); + EasyMock.replay(amqpTemplate); + + final AmqpItemReader amqpItemReader = new AmqpItemReader(amqpTemplate); + assertEquals("foo", amqpItemReader.read()); + EasyMock.verify(amqpTemplate); + } + + @Test + public void testNonMessageItemType() { + final AmqpTemplate amqpTemplate = EasyMock.createMock(AmqpTemplate.class); + EasyMock.expect(amqpTemplate.receiveAndConvert()).andReturn("foo"); + EasyMock.replay(amqpTemplate); + + final AmqpItemReader amqpItemReader = new AmqpItemReader(amqpTemplate); + amqpItemReader.setItemType(String.class); + + assertEquals("foo", amqpItemReader.read()); + + EasyMock.verify(amqpTemplate); + } + + @Test + public void testMessageItemType() { + final AmqpTemplate amqpTemplate = EasyMock.createMock(AmqpTemplate.class); + final Message message = EasyMock.createMock(Message.class); + + EasyMock.expect(amqpTemplate.receive()).andReturn(message); + EasyMock.replay(amqpTemplate, message); + + final AmqpItemReader amqpItemReader = new AmqpItemReader(amqpTemplate); + amqpItemReader.setItemType(Message.class); + + assertEquals(message, amqpItemReader.read()); + + EasyMock.verify(amqpTemplate); + } + + @Test + public void testTypeMismatch() { + final AmqpTemplate amqpTemplate = EasyMock.createMock(AmqpTemplate.class); + + EasyMock.expect(amqpTemplate.receiveAndConvert()).andReturn("foo"); + EasyMock.replay(amqpTemplate); + + final AmqpItemReader amqpItemReader = new AmqpItemReader(amqpTemplate); + amqpItemReader.setItemType(Integer.class); + + try { + amqpItemReader.read(); + fail("Expected IllegalStateException"); + } catch (IllegalStateException e) { + assertTrue(e.getMessage().contains("wrong type")); + } + + EasyMock.verify(amqpTemplate); + } + + @Test(expected = IllegalArgumentException.class) + public void testNullItemType() { + final AmqpTemplate amqpTemplate = EasyMock.createMock(AmqpTemplate.class); + + final AmqpItemReader amqpItemReader = new AmqpItemReader(amqpTemplate); + amqpItemReader.setItemType(null); + } +} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/amqp/AmqpItemWriterTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/amqp/AmqpItemWriterTests.java new file mode 100644 index 000000000..f4860971d --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/amqp/AmqpItemWriterTests.java @@ -0,0 +1,56 @@ +/* + * Copyright 2012 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.amqp; + +import org.easymock.EasyMock; +import org.junit.Test; +import org.springframework.amqp.core.AmqpTemplate; + +import java.util.Arrays; + +/** + *

+ * Test cases around {@link AmqpItemWriter}. + *

+ * + * @author Chris Schaefer + */ +public class AmqpItemWriterTests { + @Test(expected = IllegalArgumentException.class) + public void testNullAmqpTemplate() { + new AmqpItemWriter(null); + } + + @Test + public void voidTestWrite() throws Exception { + AmqpTemplate amqpTemplate = EasyMock.createMock(AmqpTemplate.class); + + amqpTemplate.convertAndSend("foo"); + EasyMock.expectLastCall(); + + amqpTemplate.convertAndSend("bar"); + EasyMock.expectLastCall(); + + EasyMock.replay(amqpTemplate); + + AmqpItemWriter amqpItemWriter = new AmqpItemWriter(amqpTemplate); + amqpItemWriter.write(Arrays.asList("foo", "bar")); + + EasyMock.verify(amqpTemplate); + + } +} diff --git a/spring-batch-parent/pom.xml b/spring-batch-parent/pom.xml index fedbfd275..f8806417b 100644 --- a/spring-batch-parent/pom.xml +++ b/spring-batch-parent/pom.xml @@ -32,6 +32,7 @@ org.springframework.ws spring-oxm-tiger 1.5.8 + 1.0.0.RELEASE 4.4 1.0.0.RELEASE false @@ -418,9 +419,15 @@ org.easymock easymock - 2.3 + 2.4 test + + org.easymock + easymockclassextension + 2.4 + test + org.apache.geronimo.specs geronimo-jms_1.1_spec @@ -709,6 +716,16 @@ 1.1.2 true + + org.springframework.amqp + spring-amqp + ${spring.amqp.version} + + + org.springframework.amqp + spring-rabbit + ${spring.amqp.version} +