From c6f56df79df829f695aad561144d4adb9eabc0e3 Mon Sep 17 00:00:00 2001 From: Janne Valkealahti Date: Sat, 5 Mar 2016 12:30:17 +0000 Subject: [PATCH] Handle annotation method call errors - Instead of halting calling annotated methods when one user level method throws, catch and log those so that all methods are called. - Fixes #180 --- ...dInvokingStateMachineRuntimeProcessor.java | 23 ++++- .../processor/StateMachineHandler.java | 6 +- .../StateMachineHandlerCallHelper.java | 8 +- .../processor/AnnotatedMethodTests.java | 93 ++++++++++++++++++- 4 files changed, 123 insertions(+), 7 deletions(-) diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/MethodInvokingStateMachineRuntimeProcessor.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/MethodInvokingStateMachineRuntimeProcessor.java index 8af63c75..9b6bafb6 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/MethodInvokingStateMachineRuntimeProcessor.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/MethodInvokingStateMachineRuntimeProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2016 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. @@ -32,14 +32,32 @@ public class MethodInvokingStateMachineRuntimeProcessor implements Stat private final StateMachineMethodInvokerHelper delegate; + /** + * Instantiates a new method invoking state machine runtime processor. + * + * @param targetObject the target object + * @param method the method + */ public MethodInvokingStateMachineRuntimeProcessor(Object targetObject, Method method) { delegate = new StateMachineMethodInvokerHelper(targetObject, method); } + /** + * Instantiates a new method invoking state machine runtime processor. + * + * @param targetObject the target object + * @param methodName the method name + */ public MethodInvokingStateMachineRuntimeProcessor(Object targetObject, String methodName) { delegate = new StateMachineMethodInvokerHelper(targetObject, methodName); } + /** + * Instantiates a new method invoking state machine runtime processor. + * + * @param targetObject the target object + * @param annotationType the annotation type + */ public MethodInvokingStateMachineRuntimeProcessor(Object targetObject, Class annotationType) { delegate = new StateMachineMethodInvokerHelper(targetObject, annotationType); } @@ -48,9 +66,8 @@ public class MethodInvokingStateMachineRuntimeProcessor implements Stat public T process(StateMachineRuntime stateMachineRuntime) { try { return delegate.process(stateMachineRuntime); - } catch (Exception e) { + } catch (Throwable e) { throw new RuntimeException("Error processing bean", e); } } - } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineHandler.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineHandler.java index 0a045852..cb708498 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineHandler.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2016 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. @@ -135,4 +135,8 @@ public class StateMachineHandler implements Ordered return processor.process(stateMachineRuntime); } + @Override + public String toString() { + return "StateMachineHandler [beanClass=" + beanClass + "]"; + } } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineHandlerCallHelper.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineHandlerCallHelper.java index f077049c..e019ae59 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineHandlerCallHelper.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/processor/StateMachineHandlerCallHelper.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2016 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. @@ -356,7 +356,11 @@ public class StateMachineHandlerCallHelper implements InitializingBean, Be }; List results = new ArrayList(); for (StateMachineHandler handler : stateMachineHandlers) { - results.add(handler.handle(runtime)); + try { + results.add(handler.handle(runtime)); + } catch (Throwable e) { + log.error("Error processing handler " + handler, e); + } } return results; } diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/processor/AnnotatedMethodTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/processor/AnnotatedMethodTests.java index e00e469a..ef3101e3 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/processor/AnnotatedMethodTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/processor/AnnotatedMethodTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2016 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. @@ -15,6 +15,7 @@ */ package org.springframework.statemachine.processor; +import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; @@ -155,6 +156,25 @@ public class AnnotatedMethodTests extends AbstractStateMachineTests { assertThat(bean2.onMethod0Latch.await(2, TimeUnit.SECONDS), is(true)); } + @Test + @SuppressWarnings("unchecked") + public void testMethodsThrowDoesNotBreakMachine() throws Exception { + context.register(Config6.class, BeanConfig3.class); + context.refresh(); + ObjectStateMachine machine = + context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, ObjectStateMachine.class); + Bean3 bean3 = context.getBean(Bean3.class); + machine.start(); + assertThat(bean3.onMethod0Latch.await(2, TimeUnit.SECONDS), is(true)); + machine.sendEvent(TestEvents.E1); + assertThat(bean3.onMethod1Latch.await(2, TimeUnit.SECONDS), is(true)); + assertThat(bean3.onMethod11Latch.await(2, TimeUnit.SECONDS), is(true)); + assertThat(machine.getState().getIds(), containsInAnyOrder(TestStates.S2)); + machine.sendEvent(TestEvents.E2); + assertThat(bean3.onMethod2Latch.await(2, TimeUnit.SECONDS), is(true)); + assertThat(machine.getState().getIds(), containsInAnyOrder(TestStates.S3)); + } + @WithStateMachine static class Bean1 { @@ -249,6 +269,39 @@ public class AnnotatedMethodTests extends AbstractStateMachineTests { } } + @WithStateMachine + static class Bean3 { + + CountDownLatch onMethod0Latch = new CountDownLatch(1); + CountDownLatch onMethod1Latch = new CountDownLatch(1); + CountDownLatch onMethod11Latch = new CountDownLatch(1); + CountDownLatch onMethod2Latch = new CountDownLatch(1); + + @OnTransition(target = "S1") + public void method0() { + onMethod0Latch.countDown(); + throw new RuntimeException(); + } + + @OnTransition(source = "S1", target = "S2") + public void method1() { + onMethod1Latch.countDown(); + throw new Error(); + } + + @OnTransition(source = "S1", target = "S2") + public void method11() { + onMethod11Latch.countDown(); + throw new Error(); + } + + @OnTransition(source = "S2", target = "S3") + public void method2() { + onMethod2Latch.countDown(); + throw new RuntimeException(); + } + } + @Configuration static class BeanConfig1 { @@ -269,6 +322,16 @@ public class AnnotatedMethodTests extends AbstractStateMachineTests { } + @Configuration + static class BeanConfig3 { + + @Bean + public Bean3 bean3() { + return new Bean3(); + } + + } + @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @OnTransition @@ -493,4 +556,32 @@ public class AnnotatedMethodTests extends AbstractStateMachineTests { } + @Configuration + @EnableStateMachine + static class Config6 extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial(TestStates.S1) + .states(EnumSet.allOf(TestStates.class)); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + .withExternal() + .source(TestStates.S1) + .target(TestStates.S2) + .event(TestEvents.E1) + .and() + .withExternal() + .source(TestStates.S2) + .target(TestStates.S3) + .event(TestEvents.E2); + } + + } + }