diff --git a/README.md b/README.md index 946f5c4b..db8186e8 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,7 @@ This category targets developers who are already more familiar with the Spring I * **stored-procedures-derby** Provides an example of the stored procedure Outbound Gateway using *[Apache Derby](http://db.apache.org/derby/)* * **stored-procedures-oracle** Provides an example of the stored procedure Outbound Gateway using *ORACLE XE* * **monitoring** The project used in the *[Spring Integration Management and Monitoring Webinar](http://www.springsource.org/node/3598)* Also available on the *[SpringSourceDev YouTube Channel](http://www.youtube.com/SpringSourceDev)* +* **retry-and-more** Provides samples showing the application of MessageHandler Advice Chains to endpoints - retry, circuit breaker, expression evaluating ## Advanced diff --git a/intermediate/retry-and-more/README.md b/intermediate/retry-and-more/README.md new file mode 100644 index 00000000..36ea3925 --- /dev/null +++ b/intermediate/retry-and-more/README.md @@ -0,0 +1,60 @@ +Handler Advice Sample "retry-and-more" +====================================== + +This sample shows how to use the 2.2.0 Handler Advice feature. + +##Stateless Retry Advice Demo + +This class (`StatelessRetryDemo`) demonstrates stateless retry. + +By default, it runs with a simple default retry (3 tries, no backoff, no recovery) + +Run with -Dspring.profiles.active=backoff to run with 3 tries, exponential backoff, no recovery + +Run with -Dspring.profiles.active=recovery to run with 3 tries, no backoff, error message "recovery" + + +In each case enter 'fail n' where n is the number of times you want the service to fail. + +e.g. 'fail 2' will succeed in each case on the third try, 'fail 3' will fail permanently after the third try. + + +##Stateful Retry Advice Demo + +This class (`StatefulRetryDemo`) demonstrates stateful retry. + +It is similar to the default version of the stateless retry but uses AMQP; you will see that the exception are thrown back to the container and the retries are re-delivered by AMQP. + + +##Circuit Breaker Advice Demo + +This class (`CircuitBreakerDemo`) demonstrates the circuit breaker advice. + +In this demo, the target service only succeeds in the last quarter of any minute (seconds 45-59). The breaker's threshold is set to 2, with the breaker going half-open once 15 seconds have elapsed since the last failure. + +You can observe the function of the advice by entering a number of messages over time, and watch the resulting messages. + + +##Expression Evaluating Advice Demo + +`FileTransferDeleteAfterSuccessDemo` + +`FileTransferRenameAfterFailureDemo` + + +These classes show how to configure expressions to be evaluated after either succes, or failure; the application context is defined in expression-advice-context-xml. + +It is a simulation of an application with a file inbound adapter -> ftp outbound adapter. + +The advice on the outbound adapter evaluates an expression after the transfer - delete the file on success, rename it on failure. + +The results of the expression evaluation are then logged (INFO or ERROR). + +No real FTP is involved; mocks are used to simulate the transfer (success or failure). + +In both cases simply add a file ending with .txt in ${java.io.tmpdir}/adviceDemo (e.g. touch /tmp/adviceDemo/x,txt) and the results will appear in the console. + + +##Running the Demos + +In each case, run the main method in each of the demonstration classes. \ No newline at end of file diff --git a/intermediate/retry-and-more/pom.xml b/intermediate/retry-and-more/pom.xml new file mode 100644 index 00000000..425b9976 --- /dev/null +++ b/intermediate/retry-and-more/pom.xml @@ -0,0 +1,92 @@ + + + 4.0.0 + org.springframework.integration.samples + retry-and-more + 2.1.0.BUILD-SNAPSHOT + Samples (Intermediate) - Retry and More + jar + + UTF-8 + 3.1.2.RELEASE + 2.2.0.RC1 + 1.2.16 + 4.10 + + + + + org.springframework.integration + spring-integration-stream + ${spring.integration.version} + + + org.springframework.integration + spring-integration-amqp + ${spring.integration.version} + + + org.springframework.integration + spring-integration-file + ${spring.integration.version} + + + org.springframework.integration + spring-integration-ftp + ${spring.integration.version} + + + log4j + log4j + ${log4j.version} + + + org.mockito + mockito-all + 1.8.5 + + + + org.springframework + spring-test + ${spring.framework.version} + + + junit + junit + ${junit.version} + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + + 1.5 + 1.5 + -Xlint:all + true + false + + + + org.codehaus.mojo + exec-maven-plugin + 1.2 + + ${java.main.class} + + + + + + + repo.springsource.org.milestone + Spring Framework Maven Milestone Repository + https://repo.springsource.org/libs-milestone + + + diff --git a/intermediate/retry-and-more/src/main/java/org/springframework/integration/samples/advice/CircuitBreakerDemo.java b/intermediate/retry-and-more/src/main/java/org/springframework/integration/samples/advice/CircuitBreakerDemo.java new file mode 100644 index 00000000..592c19a0 --- /dev/null +++ b/intermediate/retry-and-more/src/main/java/org/springframework/integration/samples/advice/CircuitBreakerDemo.java @@ -0,0 +1,58 @@ +/* + * Copyright 2002-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.integration.samples.advice; + +import org.apache.log4j.Logger; +import org.springframework.context.support.AbstractApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +/** + * @author Gary Russell + * @since 2.2 + * + */ +public class CircuitBreakerDemo { + + private static final Logger LOGGER = Logger.getLogger(CircuitBreakerDemo.class); + + public static void main(String[] args) { + LOGGER.info("\n=========================================================" + + "\n " + + "\n Welcome to Spring Integration! " + + "\n " + + "\n For more information please visit: " + + "\n http://www.springsource.org/spring-integration " + + "\n " + + "\n=========================================================" ); + + final AbstractApplicationContext context = + new ClassPathXmlApplicationContext("classpath:META-INF/spring/integration/circuit-breaker-advice-context.xml"); + + context.registerShutdownHook(); + + LOGGER.info("\n=========================================================" + + "\n " + + "\n This is the Circuit Breaker Sample - " + + "\n " + + "\n Please enter some text and press return a few times. " + + "\n Service will succeed only in the last quarter " + + "\n minute. Breaker will open after 2 failures and " + + "\n will go half-open after 15 seconds. " + + "\n " + + "\n=========================================================" ); + + } +} diff --git a/intermediate/retry-and-more/src/main/java/org/springframework/integration/samples/advice/ConditionalService.java b/intermediate/retry-and-more/src/main/java/org/springframework/integration/samples/advice/ConditionalService.java new file mode 100644 index 00000000..8a3ea342 --- /dev/null +++ b/intermediate/retry-and-more/src/main/java/org/springframework/integration/samples/advice/ConditionalService.java @@ -0,0 +1,75 @@ +/* + * Copyright 2002-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.integration.samples.advice; + +import java.util.Calendar; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.integration.annotation.Header; + +/** + * @author Gary Russell + * @since 2.2 + * + */ +public class ConditionalService { + + private final Log logger = LogFactory.getLog(this.getClass()); + + private final Map failCount = new HashMap(); + + /** + * If this service receives a payload 'failnnn' where nnn is the number of failures, + * it will fail that many times for a given message id. + * @param payload + * @param id + */ + public void testRetry(String payload, @Header("failingId") String id) { + if (payload.startsWith("fail")) { + int failHowManyTimes = Integer.parseInt(payload.substring(4).trim()); + AtomicInteger failures = failCount.get(id); + if (failures == null) { + failures = new AtomicInteger(); + failCount.put(id, failures); + } + int currentFailures = failures.incrementAndGet(); + if (currentFailures <= failHowManyTimes) { + String message = "Failure " + currentFailures + " of " + failHowManyTimes; + logger.info("Service failure " + message); + throw new RuntimeException(message); + } + } + logger.info("Service success for " + payload); + failCount.remove(id); + } + + /** + * Succeeds only if called any time in the fourth quarter of any minute (seconds 45 thru 59) + * @param payload + */ + public void testCircuitBreaker(String payload) { + Calendar calendar = Calendar.getInstance(); + if (calendar.get(Calendar.SECOND) < 45) { + logger.info("Service failure"); + throw new RuntimeException("Service failed"); + } + logger.info("Service success for " + payload); + } +} diff --git a/intermediate/retry-and-more/src/main/java/org/springframework/integration/samples/advice/FileTransferDeleteAfterSuccessDemo.java b/intermediate/retry-and-more/src/main/java/org/springframework/integration/samples/advice/FileTransferDeleteAfterSuccessDemo.java new file mode 100644 index 00000000..5cbb98e5 --- /dev/null +++ b/intermediate/retry-and-more/src/main/java/org/springframework/integration/samples/advice/FileTransferDeleteAfterSuccessDemo.java @@ -0,0 +1,79 @@ +/* + * Copyright 2002-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.integration.samples.advice; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.apache.commons.net.ftp.FTPFile; +import org.apache.log4j.Logger; +import org.springframework.context.support.AbstractApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.integration.endpoint.SourcePollingChannelAdapter; +import org.springframework.integration.file.remote.session.Session; +import org.springframework.integration.file.remote.session.SessionFactory; + +/** + * @author Gary Russell + * @since 2.2 + * + */ +public class FileTransferDeleteAfterSuccessDemo { + + private static final Logger LOGGER = Logger.getLogger(FileTransferDeleteAfterSuccessDemo.class); + + public static void main(String[] args) throws Exception { + LOGGER.info("\n=========================================================" + + "\n " + + "\n Welcome to Spring Integration! " + + "\n " + + "\n For more information please visit: " + + "\n http://www.springsource.org/spring-integration " + + "\n " + + "\n=========================================================" ); + + final AbstractApplicationContext context = + new ClassPathXmlApplicationContext("classpath:META-INF/spring/integration/expression-advice-context.xml"); + + context.registerShutdownHook(); + + @SuppressWarnings("unchecked") + SessionFactory sessionFactory = context.getBean(SessionFactory.class); + SourcePollingChannelAdapter fileInbound = context.getBean(SourcePollingChannelAdapter.class); + + @SuppressWarnings("unchecked") + Session session = mock(Session.class); + when(sessionFactory.getSession()).thenReturn(session); + fileInbound.start(); + + LOGGER.info("\n=========================================================" + + "\n " + + "\n This is the Expression Advice Sample - " + + "\n " + + "\n Press 'Enter' to terminate. " + + "\n " + + "\n Place a file in ${java.io.tmpdir}/adviceDemo ending " + + "\n with .txt " + + "\n The demo simulates a file transfer followed by the " + + "\n Advice deleting the file; the result of the deletion " + + "\n is logged. " + + "\n " + + "\n=========================================================" ); + + System.in.read(); + System.exit(0); + } +} diff --git a/intermediate/retry-and-more/src/main/java/org/springframework/integration/samples/advice/FileTransferRenameAfterFailureDemo.java b/intermediate/retry-and-more/src/main/java/org/springframework/integration/samples/advice/FileTransferRenameAfterFailureDemo.java new file mode 100644 index 00000000..fbe9006d --- /dev/null +++ b/intermediate/retry-and-more/src/main/java/org/springframework/integration/samples/advice/FileTransferRenameAfterFailureDemo.java @@ -0,0 +1,75 @@ +/* + * Copyright 2002-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.integration.samples.advice; + +import static org.mockito.Mockito.when; + +import org.apache.commons.net.ftp.FTPFile; +import org.apache.log4j.Logger; +import org.springframework.context.support.AbstractApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.integration.endpoint.SourcePollingChannelAdapter; +import org.springframework.integration.file.remote.session.SessionFactory; + +/** + * @author Gary Russell + * @since 2.2 + * + */ +public class FileTransferRenameAfterFailureDemo { + + private static final Logger LOGGER = Logger.getLogger(FileTransferRenameAfterFailureDemo.class); + + public static void main(String[] args) throws Exception { + LOGGER.info("\n=========================================================" + + "\n " + + "\n Welcome to Spring Integration! " + + "\n " + + "\n For more information please visit: " + + "\n http://www.springsource.org/spring-integration " + + "\n " + + "\n=========================================================" ); + + final AbstractApplicationContext context = + new ClassPathXmlApplicationContext("classpath:META-INF/spring/integration/expression-advice-context.xml"); + + context.registerShutdownHook(); + + @SuppressWarnings("unchecked") + SessionFactory sessionFactory = context.getBean(SessionFactory.class); + SourcePollingChannelAdapter fileInbound = context.getBean(SourcePollingChannelAdapter.class); + + when(sessionFactory.getSession()).thenThrow(new RuntimeException("Force Failure")); + fileInbound.start(); + + LOGGER.info("\n=========================================================" + + "\n " + + "\n This is the Expression Advice Sample - " + + "\n " + + "\n Press 'Enter' to terminate. " + + "\n " + + "\n Place a file in ${java.io.tmpdir}/adviceDemo ending " + + "\n with .txt " + + "\n The demo simulates a file transfer failure followed " + + "\n by the Advice renaming the file; the result of the " + + "\n rename is logged. " + + "\n " + + "\n=========================================================" ); + + System.in.read(); + System.exit(0); + } +} diff --git a/intermediate/retry-and-more/src/main/java/org/springframework/integration/samples/advice/StatefulRetryDemo.java b/intermediate/retry-and-more/src/main/java/org/springframework/integration/samples/advice/StatefulRetryDemo.java new file mode 100644 index 00000000..647f20db --- /dev/null +++ b/intermediate/retry-and-more/src/main/java/org/springframework/integration/samples/advice/StatefulRetryDemo.java @@ -0,0 +1,57 @@ +/* + * Copyright 2002-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.integration.samples.advice; + +import org.apache.log4j.Logger; +import org.springframework.context.support.AbstractApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +/** + * @author Gary Russell + * @since 2.2 + * + */ +public class StatefulRetryDemo { + + private static final Logger LOGGER = Logger.getLogger(StatefulRetryDemo.class); + + public static void main(String[] args) { + LOGGER.info("\n=========================================================" + + "\n " + + "\n Welcome to Spring Integration! " + + "\n " + + "\n For more information please visit: " + + "\n http://www.springsource.org/spring-integration " + + "\n " + + "\n=========================================================" ); + + final AbstractApplicationContext context = + new ClassPathXmlApplicationContext("classpath:META-INF/spring/integration/stateful-retry-advice-context.xml"); + + context.registerShutdownHook(); + + LOGGER.info("\n=========================================================" + + "\n " + + "\n This is the Stateful Sample - " + + "\n " + + "\n Please enter some text and press return. " + + "\n 'fail 2' will fail twice, then succeed " + + "\n 'fail 3' will fail and never succeed " + + "\n " + + "\n=========================================================" ); + + } +} diff --git a/intermediate/retry-and-more/src/main/java/org/springframework/integration/samples/advice/StatelessRetryDemo.java b/intermediate/retry-and-more/src/main/java/org/springframework/integration/samples/advice/StatelessRetryDemo.java new file mode 100644 index 00000000..c16df9de --- /dev/null +++ b/intermediate/retry-and-more/src/main/java/org/springframework/integration/samples/advice/StatelessRetryDemo.java @@ -0,0 +1,57 @@ +/* + * Copyright 2002-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.integration.samples.advice; + +import org.apache.log4j.Logger; +import org.springframework.context.support.AbstractApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +/** + * @author Gary Russell + * @since 2.2 + * + */ +public class StatelessRetryDemo { + + private static final Logger LOGGER = Logger.getLogger(StatelessRetryDemo.class); + + public static void main(String[] args) { + LOGGER.info("\n=========================================================" + + "\n " + + "\n Welcome to Spring Integration! " + + "\n " + + "\n For more information please visit: " + + "\n http://www.springsource.org/spring-integration " + + "\n " + + "\n=========================================================" ); + + final AbstractApplicationContext context = + new ClassPathXmlApplicationContext("classpath:META-INF/spring/integration/stateless-retry-advice-context.xml"); + + context.registerShutdownHook(); + + LOGGER.info("\n=========================================================" + + "\n " + + "\n This is the Stateless Sample - " + + "\n " + + "\n Please enter some text and press return. " + + "\n 'fail 2' will fail twice, then succeed " + + "\n 'fail 3' will fail and never succeed " + + "\n " + + "\n=========================================================" ); + + } +} diff --git a/intermediate/retry-and-more/src/main/resources/META-INF/spring/integration/circuit-breaker-advice-context.xml b/intermediate/retry-and-more/src/main/resources/META-INF/spring/integration/circuit-breaker-advice-context.xml new file mode 100644 index 00000000..ddf9691f --- /dev/null +++ b/intermediate/retry-and-more/src/main/resources/META-INF/spring/integration/circuit-breaker-advice-context.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/intermediate/retry-and-more/src/main/resources/META-INF/spring/integration/expression-advice-context.xml b/intermediate/retry-and-more/src/main/resources/META-INF/spring/integration/expression-advice-context.xml new file mode 100644 index 00000000..fce3c05f --- /dev/null +++ b/intermediate/retry-and-more/src/main/resources/META-INF/spring/integration/expression-advice-context.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/intermediate/retry-and-more/src/main/resources/META-INF/spring/integration/stateful-retry-advice-context.xml b/intermediate/retry-and-more/src/main/resources/META-INF/spring/integration/stateful-retry-advice-context.xml new file mode 100644 index 00000000..29252e82 --- /dev/null +++ b/intermediate/retry-and-more/src/main/resources/META-INF/spring/integration/stateful-retry-advice-context.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/intermediate/retry-and-more/src/main/resources/META-INF/spring/integration/stateless-retry-advice-context.xml b/intermediate/retry-and-more/src/main/resources/META-INF/spring/integration/stateless-retry-advice-context.xml new file mode 100644 index 00000000..6e897dac --- /dev/null +++ b/intermediate/retry-and-more/src/main/resources/META-INF/spring/integration/stateless-retry-advice-context.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/intermediate/retry-and-more/src/main/resources/log4j.xml b/intermediate/retry-and-more/src/main/resources/log4j.xml new file mode 100644 index 00000000..d7e06682 --- /dev/null +++ b/intermediate/retry-and-more/src/main/resources/log4j.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file