diff --git a/java/src/main/java/com/bazaarvoice/gumshoe/Context.java b/java/src/main/java/com/bazaarvoice/gumshoe/Context.java index b15a74d..887805f 100644 --- a/java/src/main/java/com/bazaarvoice/gumshoe/Context.java +++ b/java/src/main/java/com/bazaarvoice/gumshoe/Context.java @@ -3,6 +3,7 @@ import java.util.HashMap; import java.util.Map; import java.util.UUID; +import java.util.concurrent.Callable; /** * A data context for events. Contexts can be stacked. Data can be stored @@ -139,6 +140,44 @@ public Context start() { return this; } + /** + * Starts a context and calls the Callable within it and returns the result. The context will + * be finished or failed as appropriate. If an exception bubbles out of the Callable, an + * InContextException runtime exception will be thrown. + * + * @param callable + * @return + */ + public V start(Callable callable) { + try { + start(); + V result = callable.call(); + finish(); + return result; + } catch (Exception exception) { + fail(exception); + throw new InContextException(exception); + } + } + + /** + * Starts a context and calls the VoidCallable within it. The context will be finished or + * failed as appropriate. If an exception bubbles out of the Callable, an InContextException + * runtime exception will be thrown. + * + * @param callable + */ + public void start(VoidCallable callable) { + try { + start(); + callable.call(); + finish(); + } catch (Exception exception) { + fail(exception); + throw new InContextException(exception); + } + } + /** * Emits an event within the current context * diff --git a/java/src/main/java/com/bazaarvoice/gumshoe/InContextException.java b/java/src/main/java/com/bazaarvoice/gumshoe/InContextException.java new file mode 100644 index 0000000..cdf4d21 --- /dev/null +++ b/java/src/main/java/com/bazaarvoice/gumshoe/InContextException.java @@ -0,0 +1,13 @@ +package com.bazaarvoice.gumshoe; + +/** + * Exception raised when an exception happens within a context. + * + * @author lance.woodson + * + */ +public class InContextException extends RuntimeException { + public InContextException(Exception cause) { + super(cause); + } +} diff --git a/java/src/main/java/com/bazaarvoice/gumshoe/VoidCallable.java b/java/src/main/java/com/bazaarvoice/gumshoe/VoidCallable.java new file mode 100644 index 0000000..ca43c14 --- /dev/null +++ b/java/src/main/java/com/bazaarvoice/gumshoe/VoidCallable.java @@ -0,0 +1,11 @@ +package com.bazaarvoice.gumshoe; + +/** + * Interface of objects that can be invoked within a context + * + * @author lance.woodson + * + */ +public interface VoidCallable { + void call() throws Exception; +} diff --git a/java/src/test/java/com/bazaarvoice/gumshoe/ContextTest.java b/java/src/test/java/com/bazaarvoice/gumshoe/ContextTest.java index 0704d03..47fe2e7 100644 --- a/java/src/test/java/com/bazaarvoice/gumshoe/ContextTest.java +++ b/java/src/test/java/com/bazaarvoice/gumshoe/ContextTest.java @@ -9,6 +9,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.Callable; public class ContextTest extends Assert { private EventFactory eventFactory; @@ -97,6 +98,85 @@ public void ensureStartLoadsEventFactorysDataStack() { assertEquals(eventFactory.getDataStack().get("foo"), "bar"); } + @Test + public void ensureStartWithCallableReturnsCallableResult() { + String result = context.start(new Callable() { + @Override + public String call() { + return "test"; + } + }); + assertEquals(result, "test"); + } + + @Test + public void ensureStartWithCallableFinishesStatus() { + context.start(new Callable() { + @Override + public String call() { + return "test"; + } + }); + assertEquals(Context.Status.FINISHED, context.getStatus()); + } + + @Test(expectedExceptions={InContextException.class}) + public void ensureStartWithCallableThrowingExceptionPropagatesException() { + context.start(new Callable() { + @Override + public String call() { + throw new RuntimeException("Ooops, I did it again"); + } + }); + } + + @Test + public void ensureStartWithCallableThrowingExceptionFinishesContext() { + try { + context.start(new Callable() { + @Override + public String call() { + throw new RuntimeException("Ooops, I did it again"); + } + }); + } catch (Exception e) { } + assertEquals(Context.Status.FINISHED, context.getStatus()); + } + + @Test + public void ensureStartWithVoidCallableFinishesContext() { + context.start(new VoidCallable() { + @Override + public void call() { + // do something + } + }); + assertEquals(Context.Status.FINISHED, context.getStatus()); + } + + @Test(expectedExceptions={InContextException.class}) + public void ensureStartWithVoidCallableThrowingExceptionPropagatesException() { + context.start(new VoidCallable() { + @Override + public void call() { + throw new RuntimeException("Ooops, I did it again"); + } + }); + } + + @Test + public void ensureStartWithVoidCallableThrowingExceptionFinishesContext() { + try { + context.start(new VoidCallable() { + @Override + public void call() { + throw new RuntimeException("Ooops, I did it again"); + } + }); + } catch (Exception e) { } + assertEquals(Context.Status.FINISHED, context.getStatus()); + } + @Test(expectedExceptions={IllegalStateException.class}) public void ensureEmitFailsIfContextNotStarted() { context.emit("test");