diff --git a/spring-core/src/main/java/org/springframework/util/concurrent/SettableListenableFuture.java b/spring-core/src/main/java/org/springframework/util/concurrent/SettableListenableFuture.java index cc2b079e87..3a61db2b5b 100644 --- a/spring-core/src/main/java/org/springframework/util/concurrent/SettableListenableFuture.java +++ b/spring-core/src/main/java/org/springframework/util/concurrent/SettableListenableFuture.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-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. @@ -23,6 +23,7 @@ import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicReference; import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; /** * A {@link org.springframework.util.concurrent.ListenableFuture ListenableFuture} @@ -49,11 +50,11 @@ public class SettableListenableFuture implements ListenableFuture { /** - * Set the value of this future. This method will return {@code true} if - * the value was set successfully, or {@code false} if the future has already - * been set or cancelled. - * @param value the value that will be set. - * @return {@code true} if the value was successfully set, else {@code false}. + * Set the value of this future. This method will return {@code true} if the + * value was set successfully, or {@code false} if the future has already been + * set or cancelled. + * @param value the value that will be set + * @return {@code true} if the value was successfully set, else {@code false} */ public boolean set(T value) { boolean success = this.settableTask.setValue(value); @@ -64,14 +65,14 @@ public class SettableListenableFuture implements ListenableFuture { } /** - * Set the exception of this future. This method will return {@code true} if - * the exception was set successfully, or {@code false} if the future has already - * been set or cancelled. - * @param exception the value that will be set. - * @return {@code true} if the exception was successfully set, else {@code false}. + * Set the exception of this future. This method will return {@code true} if the + * exception was set successfully, or {@code false} if the future has already been + * set or cancelled. + * @param exception the value that will be set + * @return {@code true} if the exception was successfully set, else {@code false} */ public boolean setException(Throwable exception) { - Assert.notNull(exception, "'exception' must not be null"); + Assert.notNull(exception, "Exception must not be null"); boolean success = this.settableTask.setException(exception); if (success) { this.listenableFuture.run(); @@ -149,7 +150,7 @@ public class SettableListenableFuture implements ListenableFuture { private static class SettableTask implements Callable { - private static final String NO_VALUE = SettableListenableFuture.class.getName() + ".NO_VALUE"; + private static final Object NO_VALUE = new Object(); private final AtomicReference value = new AtomicReference(NO_VALUE); @@ -176,10 +177,11 @@ public class SettableListenableFuture implements ListenableFuture { @SuppressWarnings("unchecked") @Override public T call() throws Exception { - if (value.get() instanceof Exception) { - throw (Exception) value.get(); + Object val = this.value.get(); + if (val instanceof Throwable) { + ReflectionUtils.rethrowException((Throwable) val); } - return (T) value.get(); + return (T) val; } } diff --git a/spring-core/src/test/java/org/springframework/util/concurrent/SettableListenableFutureTests.java b/spring-core/src/test/java/org/springframework/util/concurrent/SettableListenableFutureTests.java index 0e87d4e156..c8037cac86 100644 --- a/spring-core/src/test/java/org/springframework/util/concurrent/SettableListenableFutureTests.java +++ b/spring-core/src/test/java/org/springframework/util/concurrent/SettableListenableFutureTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-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. @@ -21,7 +21,6 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import org.junit.Before; import org.junit.Test; import static org.hamcrest.Matchers.*; @@ -35,12 +34,8 @@ import static org.mockito.Mockito.*; @SuppressWarnings({ "rawtypes", "unchecked" }) public class SettableListenableFutureTests { - private SettableListenableFuture settableListenableFuture; + private final SettableListenableFuture settableListenableFuture = new SettableListenableFuture(); - @Before - public void setUp() { - settableListenableFuture = new SettableListenableFuture(); - } @Test public void validateInitialValues() { @@ -76,6 +71,20 @@ public class SettableListenableFutureTests { } } + @Test + public void throwsSetErrorWrappedInExecutionException() throws ExecutionException, InterruptedException { + Throwable exception = new OutOfMemoryError(); + boolean wasSet = settableListenableFuture.setException(exception); + assertTrue(wasSet); + try { + settableListenableFuture.get(); + fail("Expected ExecutionException"); + } + catch (ExecutionException ex) { + assertThat(ex.getCause(), equalTo(exception)); + } + } + @Test public void setValueTriggersCallback() { String string = "hello"; @@ -85,7 +94,6 @@ public class SettableListenableFutureTests { public void onSuccess(String result) { callbackHolder[0] = result; } - @Override public void onFailure(Throwable ex) { fail("Expected onSuccess() to be called"); @@ -104,7 +112,6 @@ public class SettableListenableFutureTests { public void onSuccess(String result) { callbackHolder[0] = result; } - @Override public void onFailure(Throwable ex) { fail("Expected onSuccess() to be called"); @@ -124,7 +131,6 @@ public class SettableListenableFutureTests { public void onSuccess(String result) { fail("Expected onFailure() to be called"); } - @Override public void onFailure(Throwable ex) { callbackHolder[0] = ex; @@ -143,7 +149,6 @@ public class SettableListenableFutureTests { public void onSuccess(String result) { fail("Expected onFailure() to be called"); } - @Override public void onFailure(Throwable ex) { callbackHolder[0] = ex; @@ -169,7 +174,8 @@ public class SettableListenableFutureTests { try { Thread.sleep(20L); settableListenableFuture.set(string); - } catch (InterruptedException ex) { + } + catch (InterruptedException ex) { throw new RuntimeException(ex); } } @@ -183,7 +189,8 @@ public class SettableListenableFutureTests { try { settableListenableFuture.get(1L, TimeUnit.MILLISECONDS); fail("Expected TimeoutException"); - } catch (TimeoutException ex) { + } + catch (TimeoutException ex) { // expected } } @@ -197,7 +204,8 @@ public class SettableListenableFutureTests { try { Thread.sleep(20L); settableListenableFuture.set(string); - } catch (InterruptedException ex) { + } + catch (InterruptedException ex) { throw new RuntimeException(ex); } } @@ -278,7 +286,8 @@ public class SettableListenableFutureTests { try { Thread.sleep(20L); settableListenableFuture.cancel(true); - } catch (InterruptedException ex) { + } + catch (InterruptedException ex) { throw new RuntimeException(ex); } } @@ -286,7 +295,8 @@ public class SettableListenableFutureTests { try { settableListenableFuture.get(100L, TimeUnit.MILLISECONDS); fail("Expected CancellationException"); - } catch (CancellationException ex) { + } + catch (CancellationException ex) { // expected } } @@ -317,6 +327,7 @@ public class SettableListenableFutureTests { verifyNoMoreInteractions(callback); } + private static class InterruptableSettableListenableFuture extends SettableListenableFuture { private boolean interrupted = false; @@ -330,4 +341,5 @@ public class SettableListenableFutureTests { return interrupted; } } + }