diff --git a/jrugged-aspects/src/main/java/org/fishwife/jrugged/aspects/CircuitBreaker.java b/jrugged-aspects/src/main/java/org/fishwife/jrugged/aspects/CircuitBreaker.java index e2f3333a..09d2ecd6 100644 --- a/jrugged-aspects/src/main/java/org/fishwife/jrugged/aspects/CircuitBreaker.java +++ b/jrugged-aspects/src/main/java/org/fishwife/jrugged/aspects/CircuitBreaker.java @@ -66,4 +66,10 @@ * @return the amount of time in milliseconds. */ long resetMillis() default -1; + + /** + * Clock type: fully qualified class name of Clock implementation class (implementing interface Clock) + * @return the fully qualified class name of Clock + */ + String clockType() default ""; } diff --git a/jrugged-aspects/src/main/java/org/fishwife/jrugged/aspects/CircuitBreakerAspect.java b/jrugged-aspects/src/main/java/org/fishwife/jrugged/aspects/CircuitBreakerAspect.java index 63a45c56..9e2643be 100644 --- a/jrugged-aspects/src/main/java/org/fishwife/jrugged/aspects/CircuitBreakerAspect.java +++ b/jrugged-aspects/src/main/java/org/fishwife/jrugged/aspects/CircuitBreakerAspect.java @@ -14,18 +14,18 @@ */ package org.fishwife.jrugged.aspects; +import java.util.concurrent.Callable; + import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; -import org.aspectj.lang.annotation.DeclarePrecedence; import org.fishwife.jrugged.CircuitBreakerConfig; import org.fishwife.jrugged.CircuitBreakerFactory; +import org.fishwife.jrugged.Clock; import org.fishwife.jrugged.DefaultFailureInterpreter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.concurrent.Callable; - /** * Surrounds methods decorated with the CircuitBreaker annotation with a named * {@link org.fishwife.jrugged.CircuitBreaker}. @@ -91,8 +91,10 @@ public Object monitor(final ProceedingJoinPoint pjp, circuitBreakerAnnotation.limit(), circuitBreakerAnnotation.windowMillis()); + String clockType = circuitBreakerAnnotation.clockType(); + Clock clock = createClockByType(clockType); CircuitBreakerConfig config = new CircuitBreakerConfig( - circuitBreakerAnnotation.resetMillis(), dfi); + circuitBreakerAnnotation.resetMillis(), dfi, clock); circuitBreaker = circuitBreakerFactory.createCircuitBreaker(name, config); @@ -124,4 +126,16 @@ public Object call() throws Exception { } }); } + + protected Clock createClockByType(String clockType) throws InstantiationException, IllegalAccessException, + java.lang.reflect.InvocationTargetException, NoSuchMethodException, ClassNotFoundException { + Clock clock = null; + if (clockType != null) { + clockType = clockType.trim(); + if (!clockType.isEmpty()) { + clock = (Clock) (Class.forName(clockType).getConstructor().newInstance()); + } + } + return clock; + } } diff --git a/jrugged-aspects/src/test/java/org/fishwife/jrugged/aspects/TestCircuitBreakerAspect.java b/jrugged-aspects/src/test/java/org/fishwife/jrugged/aspects/TestCircuitBreakerAspect.java index 5464a505..72a9c158 100644 --- a/jrugged-aspects/src/test/java/org/fishwife/jrugged/aspects/TestCircuitBreakerAspect.java +++ b/jrugged-aspects/src/test/java/org/fishwife/jrugged/aspects/TestCircuitBreakerAspect.java @@ -18,16 +18,20 @@ import org.aspectj.lang.Signature; import org.fishwife.jrugged.CircuitBreakerException; import org.fishwife.jrugged.CircuitBreakerFactory; +import org.fishwife.jrugged.Clock; +import org.fishwife.jrugged.SystemClock; import org.junit.Before; import org.junit.Test; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertSame; +import static junit.framework.Assert.assertTrue; import static org.easymock.EasyMock.createMock; import static org.easymock.EasyMock.expect; import static org.easymock.EasyMock.replay; import static org.easymock.EasyMock.verify; +import static org.junit.Assert.assertNull; public class TestCircuitBreakerAspect { @@ -39,6 +43,8 @@ public class TestCircuitBreakerAspect { private static final String TEST_CIRCUIT_BREAKER = "TestCircuitBreaker"; + private static final String SYSTEM_CLOCK_TYPE = "org.fishwife.jrugged.SystemClock"; + @Before public void setUp() { aspect = new CircuitBreakerAspect(); @@ -51,6 +57,7 @@ public void setUp() { expect(mockAnnotation.limit()).andReturn(5).anyTimes(); expect(mockAnnotation.resetMillis()).andReturn(30000L).anyTimes(); expect(mockAnnotation.windowMillis()).andReturn(10000L).anyTimes(); + expect(mockAnnotation.clockType()).andReturn(null); @SuppressWarnings("unchecked") Class[] ignores = new Class[0]; expect(mockAnnotation.ignore()).andReturn(ignores); @@ -81,6 +88,7 @@ public void testMonitor() throws Throwable { expect(otherMockAnnotation.limit()).andReturn(5).anyTimes(); expect(otherMockAnnotation.resetMillis()).andReturn(30000L).anyTimes(); expect(otherMockAnnotation.windowMillis()).andReturn(10000L).anyTimes(); + expect(otherMockAnnotation.clockType()).andReturn(SYSTEM_CLOCK_TYPE); @SuppressWarnings("unchecked") Class[] ignores = new Class[0]; expect(otherMockAnnotation.ignore()).andReturn(ignores); @@ -205,6 +213,28 @@ public void testTripBreaker() throws Throwable { verify(mockSignature); } + @Test + public void testCreateClockByTypeWithNullClockTypePassedIn() throws Exception { + assertNull(aspect.createClockByType(null)); + } + + @Test + public void testCreateClockByTypeWithBlankClockTypePassedIn() throws Exception { + assertNull(aspect.createClockByType(" ")); + } + + @Test(expected = ClassNotFoundException.class) + public void testCreateClockByTypeWithNonExistingClockTypePassedIn() throws Exception { + aspect.createClockByType("NonExistingClass"); + } + + @Test + public void testCreateClockByTypeWithClockTypePassedIn() throws Exception { + Clock clock = aspect.createClockByType(SYSTEM_CLOCK_TYPE); + assertNotNull(clock); + assertTrue(clock instanceof SystemClock); + } + private static ProceedingJoinPoint createPjpMock(Signature mockSignature, int times) { ProceedingJoinPoint mockPjp = createMock(ProceedingJoinPoint.class); // XXX: the following two interactions are for logging, so they may happen diff --git a/jrugged-core/src/main/java/org/fishwife/jrugged/CircuitBreakerConfig.java b/jrugged-core/src/main/java/org/fishwife/jrugged/CircuitBreakerConfig.java index 4b6c5d86..b7694ad9 100644 --- a/jrugged-core/src/main/java/org/fishwife/jrugged/CircuitBreakerConfig.java +++ b/jrugged-core/src/main/java/org/fishwife/jrugged/CircuitBreakerConfig.java @@ -22,6 +22,14 @@ public class CircuitBreakerConfig { private FailureInterpreter failureInterpreter; private long resetMillis; + private Clock clock; + + public CircuitBreakerConfig(long resetMillis, + FailureInterpreter failureInterpreter, + Clock clock) { + this(resetMillis, failureInterpreter); + this.clock = clock; + } public CircuitBreakerConfig(long resetMillis, FailureInterpreter failureInterpreter) { @@ -36,4 +44,8 @@ public long getResetMillis() { public FailureInterpreter getFailureInterpreter() { return failureInterpreter; } + + public Clock getClock() { + return clock; + } } diff --git a/jrugged-core/src/main/java/org/fishwife/jrugged/CircuitBreakerFactory.java b/jrugged-core/src/main/java/org/fishwife/jrugged/CircuitBreakerFactory.java index fd1cd870..27fdf270 100644 --- a/jrugged-core/src/main/java/org/fishwife/jrugged/CircuitBreakerFactory.java +++ b/jrugged-core/src/main/java/org/fishwife/jrugged/CircuitBreakerFactory.java @@ -119,6 +119,11 @@ protected void configureCircuitBreaker(String name, resetMillis }); } + + Clock clock = config.getClock(); + if (clock != null) { + circuit.setClock(clock); + } } private void configureDefaultFailureInterpreter(String name, long resetMillis, CircuitBreaker circuit) { diff --git a/jrugged-core/src/test/java/org/fishwife/jrugged/TestCircuitBreakerConfig.java b/jrugged-core/src/test/java/org/fishwife/jrugged/TestCircuitBreakerConfig.java new file mode 100644 index 00000000..050d3d1c --- /dev/null +++ b/jrugged-core/src/test/java/org/fishwife/jrugged/TestCircuitBreakerConfig.java @@ -0,0 +1,40 @@ +package org.fishwife.jrugged; + +import org.junit.Before; +import org.junit.Test; + +import static org.easymock.EasyMock.createMock; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; + +public class TestCircuitBreakerConfig { + private FailureInterpreter mockFailureInterpreter; + private long resetMillis; + private Clock mockClock; + + @Before + public void setUp() { + resetMillis = 1000L; + mockFailureInterpreter = createMock(FailureInterpreter.class); + mockClock = createMock(Clock.class); + } + + @Test + public void testConstructConfigWithOptionalClock() { + CircuitBreakerConfig underTest = new CircuitBreakerConfig(resetMillis, mockFailureInterpreter, mockClock); + + assertEquals(resetMillis, underTest.getResetMillis()); + assertSame(mockFailureInterpreter, underTest.getFailureInterpreter()); + assertSame(mockClock, underTest.getClock()); + } + + @Test + public void testConstructConfigWithoutOptionalClock() { + CircuitBreakerConfig underTest = new CircuitBreakerConfig(resetMillis, mockFailureInterpreter); + + assertEquals(resetMillis, underTest.getResetMillis()); + assertSame(mockFailureInterpreter, underTest.getFailureInterpreter()); + assertNull(underTest.getClock()); + } +}