() {
+ public Object call() throws Exception {
+ try {
+ return pjp.proceed();
+ } catch (Throwable e) {
+ if (e instanceof Exception) {
+ throw (Exception) e;
+ } else if (e instanceof Error) {
+ throw (Error) e;
+ } else {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ });
+ }
+}
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 e2927e4c..1e9fc3cf 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
@@ -16,8 +16,8 @@
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
-import org.fishwife.jrugged.CircuitBreakerException;
-import org.fishwife.jrugged.CircuitBreakerFactory;
+import org.fishwife.jrugged.BreakerException;
+import org.fishwife.jrugged.BreakerFactory;
import org.junit.Before;
import org.junit.Test;
@@ -104,7 +104,7 @@ public void testSetCircuitBreakerFactory() throws Throwable {
expect(mockPjp.proceed()).andReturn(null);
replay(mockPjp);
- CircuitBreakerFactory factory = new CircuitBreakerFactory();
+ BreakerFactory factory = new BreakerFactory();
aspect.setCircuitBreakerFactory(factory);
aspect.monitor(mockPjp, mockAnnotation);
@@ -169,7 +169,7 @@ public void testGetCircuitBreakerFactory() throws Throwable {
replay(mockPjp);
aspect.monitor(mockPjp, mockAnnotation);
- CircuitBreakerFactory circuitBreakerFactory =
+ BreakerFactory circuitBreakerFactory =
aspect.getCircuitBreakerFactory();
assertNotNull(circuitBreakerFactory);
@@ -196,7 +196,7 @@ public void testTripBreaker() throws Throwable {
callMonitorCatchThrowable(mockPjp, e);
}
- CircuitBreakerException cbe = new CircuitBreakerException();
+ BreakerException cbe = new BreakerException();
callMonitorCatchThrowable(mockPjp, cbe);
verifyBreakerExists(TEST_CIRCUIT_BREAKER);
diff --git a/jrugged-core/pom.xml b/jrugged-core/pom.xml
index ab1779be..ed0cf107 100644
--- a/jrugged-core/pom.xml
+++ b/jrugged-core/pom.xml
@@ -26,4 +26,12 @@
jar
jrugged-core
https://github.com/Comcast/jrugged
+
+
+
+ org.easymock
+ easymock
+ 3.4
+
+
diff --git a/jrugged-core/src/main/java/org/fishwife/jrugged/Breaker.java b/jrugged-core/src/main/java/org/fishwife/jrugged/Breaker.java
new file mode 100644
index 00000000..34b059e9
--- /dev/null
+++ b/jrugged-core/src/main/java/org/fishwife/jrugged/Breaker.java
@@ -0,0 +1,479 @@
+
+/* Breaker.java
+ *
+ * Copyright 2009-2015 Comcast Interactive Media, LLC.
+ *
+ * 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.fishwife.jrugged;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.atomic.AtomicLong;
+
+/** A {@link Breaker} can be used with a service to throttle traffic
+ * to a failed subsystem (particularly one we might not be able to monitor,
+ * such as a peer system which must be accessed over the network). Service
+ * calls are wrapped by the Breaker.
+ *
+ * When everything is operating normally, the Breaker
+ * is CLOSED and the calls are allowed through.
+ *
+ * When a call fails, however, the Breaker and
+ * SkepticBreaker act differently. See corresponding
+ * documentation for more details.
+ */
+public abstract class Breaker implements MonitoredService, ServiceWrapper {
+ /**
+ * Represents whether a {@link Breaker} is OPEN, HALF_CLOSED,
+ * or CLOSED.
+ */
+ protected enum BreakerState {
+ /** An OPEN breaker has tripped and will not allow requests
+ through. */
+ OPEN,
+
+ /** A HALF_CLOSED breaker has completed its cooldown
+ period and will allow one request through as a "test request." */
+ HALF_CLOSED,
+
+ /** A CLOSED breaker is operating normally and allowing
+ requests through. */
+ CLOSED
+ }
+
+ protected Throwable tripException = null;
+
+ /**
+ * Returns the last exception that caused the breaker to trip, NULL if never tripped.
+ *
+ * @return Throwable
+ */
+ public Throwable getTripException() {
+ return tripException;
+ }
+
+ /**
+ * Returns the last exception that caused the breaker to trip, empty String
+ * if never tripped.
+ *
+ * @return Throwable
+ */
+ public String getTripExceptionAsString() {
+ if (tripException == null) {
+ return "";
+ } else {
+ return getFullStackTrace(tripException);
+
+ }
+ }
+
+ /** Current state of the breaker. */
+ protected volatile BreakerState state = BreakerState.CLOSED;
+
+ /** The time the breaker last tripped, in milliseconds since the
+ epoch. */
+ protected AtomicLong lastFailure = new AtomicLong(0L);
+
+ /** How many times the breaker has tripped during its lifetime. */
+ protected AtomicLong openCount = new AtomicLong(0L);
+
+ /** The {@link FailureInterpreter} to use to determine whether a
+ given failure should cause the breaker to trip. */
+ protected FailureInterpreter failureInterpreter =
+ new DefaultFailureInterpreter();
+
+ /** Helper class to allow throwing an application-specific
+ * exception rather than the default {@link
+ * BreakerException}. */
+ protected BreakerExceptionMapper extends Exception> exceptionMapper;
+
+ protected List cbNotifyList =
+ Collections.synchronizedList(new ArrayList());
+
+ protected boolean isHardTrip;
+
+ /**
+ * Bypass this Breaker - used for testing, or other operational
+ * situations where verification of the Break might be required.
+ */
+ protected boolean byPass = false;
+
+ /* DEFAULT NAME WAS HERE */
+ /* NOTE: I still included variable 'name' in SkepticBreaker */
+
+ /** The name for the Breaker. */
+ protected String name = "Breaker";
+
+ /** Creates a {@link Breaker} with a {@link
+ * DefaultFailureInterpreter} and the default "tripped" exception
+ * behavior (throwing a {@link BreakerException}). */
+ public Breaker() {
+ }
+
+ /** Creates a {@link Breaker} with a {@link
+ * DefaultFailureInterpreter} and the default "tripped" exception
+ * behavior (throwing a {@link BreakerException}).
+ * @param name the name for the {@link Breaker}.
+ */
+ public Breaker(String name) {
+ this.name = name;
+ }
+
+ /** Creates a {@link Breaker} with the specified {@link
+ * FailureInterpreter} and the default "tripped" exception
+ * behavior (throwing a {@link BreakerException}).
+ * @param fi the FailureInterpreter to use when
+ * determining whether a specific failure ought to cause the
+ * breaker to trip
+ */
+ public Breaker(FailureInterpreter fi) {
+ failureInterpreter = fi;
+ }
+
+ /** Creates a {@link Breaker} with the specified {@link
+ * FailureInterpreter} and the default "tripped" exception
+ * behavior (throwing a {@link BreakerException}).
+ * @param name the name for the {@link Breaker}.
+ * @param fi the FailureInterpreter to use when
+ * determining whether a specific failure ought to cause the
+ * breaker to trip
+ */
+ public Breaker(String name, FailureInterpreter fi) {
+ this.name = name;
+ failureInterpreter = fi;
+ }
+
+ /** Creates a {@link Breaker} with a {@link
+ * DefaultFailureInterpreter} and using the supplied {@link
+ * BreakerExceptionMapper} when client calls are made
+ * while the breaker is tripped.
+ * @param name the name for the {@link Breaker}.
+ * @param mapper helper used to translate a {@link
+ * BreakerException} into an application-specific one */
+ public Breaker(String name, BreakerExceptionMapper extends Exception> mapper) {
+ this.name = name;
+ exceptionMapper = mapper;
+ }
+
+ /** Creates a {@link Breaker} with the provided {@link
+ * FailureInterpreter} and using the provided {@link
+ * BreakerExceptionMapper} when client calls are made
+ * while the breaker is tripped.
+ * @param name the name for the {@link Breaker}.
+ * @param fi the FailureInterpreter to use when
+ * determining whether a specific failure ought to cause the
+ * breaker to trip
+ * @param mapper helper used to translate a {@link
+ * BreakerException} into an application-specific one */
+ public Breaker(String name,
+ FailureInterpreter fi,
+ BreakerExceptionMapper extends Exception> mapper) {
+ this.name = name;
+ failureInterpreter = fi;
+ exceptionMapper = mapper;
+ }
+
+ /** Wrap the given service call with the {@link Breaker}
+ * protection logic.
+ * @param c the {@link Callable} to attempt
+ * @return whatever c would return on success
+ * @throws BreakerException if the
+ * breaker was OPEN or HALF_CLOSED and this attempt wasn't the
+ * reset attempt
+ * @throws Exception if c throws one during
+ * execution
+ */
+ public abstract V invoke(Callable c) throws Exception;
+
+ /** Wrap the given service call with the {@link Breaker}
+ * protection logic.
+ * @param r the {@link Runnable} to attempt
+ * @throws BreakerException if the
+ * breaker was OPEN or HALF_CLOSED and this attempt wasn't the
+ * reset attempt
+ * @throws Exception if c throws one during
+ * execution
+ */
+ public abstract void invoke(Runnable r) throws Exception;
+
+ /** Wrap the given service call with the {@link CircuitBreaker}
+ * protection logic.
+ * @param r the {@link Runnable} to attempt
+ * @param result what to return after r succeeds
+ * @return result
+ * @throws BreakerException if the
+ * breaker was OPEN or HALF_CLOSED and this attempt wasn't the
+ * reset attempt
+ * @throws Exception if c throws one during
+ * execution
+ */
+ public abstract V invoke(Runnable r, V result) throws Exception;
+
+ /**
+ * When called with true - causes the {@link Breaker} to byPass
+ * its functionality allowing requests to be executed unmolested
+ * until the Breaker is reset or the byPass
+ * is manually set to false.
+ *
+ * @param b Set this breaker into bypass mode
+ */
+ public void setByPassState(boolean b) {
+ byPass = b;
+ notifyBreakerStateChange(getStatus());
+ }
+
+ /**
+ * Get the current state of the {@link Breaker} byPass
+ *
+ * @return boolean the byPass flag's current value
+ */
+ public boolean getByPassState() {
+ return byPass;
+ }
+
+ /**
+ * Causes the {@link Breaker} to trip and OPEN; no new
+ * requests will be allowed until the Breaker
+ * resets.
+ */
+ public abstract void trip();
+
+ /**
+ * Manually trips the Breaker until {@link #reset()} is invoked.
+ */
+ public void tripHard() {
+ this.trip();
+ isHardTrip = true;
+ }
+
+ /**
+ * Returns the last time the breaker tripped OPEN, measured in
+ * milliseconds since the Epoch.
+ * @return long the last failure time
+ */
+ public long getLastTripTime() {
+ return lastFailure.get();
+ }
+
+ /**
+ * Returns the number of times the breaker has tripped OPEN during
+ * its lifetime.
+ * @return long the number of times the circuit breaker tripped
+ */
+ public long getTripCount() {
+ return openCount.get();
+ }
+
+ /**
+ * Manually set the breaker to be reset and ready for use. This
+ * is only useful after a manual trip otherwise the breaker will
+ * trip automatically again if the service is still unavailable.
+ * Just like a real breaker. WOOT!!!
+ */
+ public abstract void reset();
+
+ /**
+ * Returns the current {@link org.fishwife.jrugged.Status} of the
+ * {@link Breaker}. In this case, it really refers to the
+ * status of the client service. If the
+ * Breaker is CLOSED, we report that the
+ * client is UP; if it is HALF_CLOSED, we report that the client
+ * is DEGRADED; if it is OPEN, we report the client is DOWN.
+ *
+ * @return Status the current status of the breaker
+ */
+ public Status getStatus() {
+ return getServiceStatus().getStatus();
+ }
+
+ /**
+ * Get the current {@link ServiceStatus} of the
+ * {@link CircuitBreaker}, including the name,
+ * {@link org.fishwife.jrugged.Status}, and reason.
+ * @return the {@link ServiceStatus}.
+ */
+ public abstract ServiceStatus getServiceStatus();
+
+ /* ----GET RESET MILLIS WAS HERE ---*/
+ /* ----SET RESET MILLIS WAS HERE ---*/
+
+ /** Returns a {@link String} representation of the breaker's
+ * status; potentially useful for exposing to monitoring software.
+ * @return String which is "GREEN" if
+ * the breaker is CLOSED; "YELLOW" if the breaker
+ * is HALF_CLOSED; and "RED" if the breaker is
+ * OPEN (tripped). */
+ public String getHealthCheck() {
+ return getStatus().getSignal();
+ }
+
+ /**
+ * Specifies the failure tolerance limit for the {@link
+ * DefaultFailureInterpreter} that comes with a {@link
+ * Breaker} by default.
+ * @see DefaultFailureInterpreter
+ * @param limit the number of tolerated failures in a window
+ */
+ public void setLimit(int limit) {
+ FailureInterpreter fi = getFailureInterpreter();
+ if (!(fi instanceof DefaultFailureInterpreter)) {
+ throw new IllegalStateException("setLimit() not supported: this Breaker's FailureInterpreter isn't a DefaultFailureInterpreter.");
+ }
+ ((DefaultFailureInterpreter)fi).setLimit(limit);
+ }
+
+ /**
+ * Specifies a set of {@link Throwable} classes that should not
+ * be considered failures by the {@link Breaker}.
+ * @see DefaultFailureInterpreter
+ * @param ignore a {@link java.util.Collection} of {@link Throwable}
+ * classes
+ */
+ public void setIgnore(Collection> ignore) {
+ FailureInterpreter fi = getFailureInterpreter();
+ if (!(fi instanceof DefaultFailureInterpreter)) {
+ throw new IllegalStateException("setIgnore() not supported: this Breaker's FailureInterpreter isn't a DefaultFailureInterpreter.");
+ }
+
+ @SuppressWarnings("unchecked")
+ Class extends Throwable>[] classes = new Class[ignore.size()];
+ int i = 0;
+ for(Class extends Throwable> c : ignore) {
+ classes[i] = c;
+ i++;
+ }
+ ((DefaultFailureInterpreter)fi).setIgnore(classes);
+ }
+
+ /**
+ * Specifies the tolerance window in milliseconds for the {@link
+ * DefaultFailureInterpreter} that comes with a {@link
+ * Breaker} by default.
+ * @see DefaultFailureInterpreter
+ * @param windowMillis length of the window in milliseconds
+ */
+ public void setWindowMillis(long windowMillis) {
+ FailureInterpreter fi = getFailureInterpreter();
+ if (!(fi instanceof DefaultFailureInterpreter)) {
+ throw new IllegalStateException("setWindowMillis() not supported: this Breaker's FailureInterpreter isn't a DefaultFailureInterpreter.");
+ }
+ ((DefaultFailureInterpreter)fi).setWindowMillis(windowMillis);
+ }
+
+ /**
+ * Specifies a helper that determines whether a given failure will
+ * cause the breaker to trip or not.
+ *
+ * @param failureInterpreter the {@link FailureInterpreter} to use
+ */
+ public void setFailureInterpreter(FailureInterpreter failureInterpreter) {
+ this.failureInterpreter = failureInterpreter;
+ }
+
+ /**
+ * Get the failure interpreter for this instance. The failure
+ * interpreter provides the configuration for determining which
+ * exceptions trip the circuit breaker, in what time interval,
+ * etc.
+ *
+ * @return {@link FailureInterpreter} for this instance or null if no
+ * failure interpreter was set.
+ */
+ public FailureInterpreter getFailureInterpreter() {
+ return this.failureInterpreter;
+ }
+
+ /**
+ * A helper that converts BreakerExceptions into a known
+ * 'application' exception.
+ *
+ * @param mapper my converter object
+ */
+ public void setExceptionMapper(BreakerExceptionMapper extends Exception> mapper) {
+ this.exceptionMapper = mapper;
+ }
+
+ /**
+ * Add an interested party for {@link Breaker} events, like up,
+ * down, degraded status state changes.
+ *
+ * @param listener an interested party for {@link Breaker} status events.
+ */
+ public void addListener(BreakerNotificationCallback listener) {
+ cbNotifyList.add(listener);
+ }
+
+ /**
+ * Set a list of interested parties for {@link Breaker} events, like up,
+ * down, degraded status state changes.
+ *
+ * @param listeners a list of interested parties for {@link Breaker} status events.
+ */
+ public void setListeners(ArrayList listeners) {
+ cbNotifyList = Collections.synchronizedList(listeners);
+ }
+
+ /**
+ * Get the helper that converts {@link BreakerException}s into
+ * application-specific exceptions.
+ * @return {@link BreakerExceptionMapper} my converter object, or
+ * null if one is not currently set.
+ */
+ public BreakerExceptionMapper extends Exception> getExceptionMapper(){
+ return this.exceptionMapper;
+ }
+
+ protected Exception mapException(BreakerException cbe) {
+ if (exceptionMapper == null)
+ return cbe;
+
+ return exceptionMapper.map(this, cbe);
+ }
+
+ protected abstract void handleFailure(Throwable cause) throws Exception;
+
+ /**
+ * Reports a successful service call to the {@link Breaker},
+ * putting the Breaker back into the CLOSED
+ * state serving requests.
+ */
+ protected abstract void close();
+
+ /* canAttempt() was here */
+
+ protected void notifyBreakerStateChange(Status status) {
+ if (cbNotifyList != null && cbNotifyList.size() >= 1) {
+ for (BreakerNotificationCallback notifyObject : cbNotifyList) {
+ notifyObject.notify(status);
+ }
+ }
+ }
+
+ /**
+ * @return boolean whether the breaker will allow a request
+ * through or not.
+ */
+ protected abstract boolean allowRequest();
+
+ private String getFullStackTrace(Throwable t) {
+ StringWriter sw = new StringWriter();
+ t.printStackTrace(new PrintWriter(sw));
+ return sw.toString();
+ }
+}
diff --git a/jrugged-core/src/main/java/org/fishwife/jrugged/BreakerConfig.java b/jrugged-core/src/main/java/org/fishwife/jrugged/BreakerConfig.java
new file mode 100644
index 00000000..0153e42f
--- /dev/null
+++ b/jrugged-core/src/main/java/org/fishwife/jrugged/BreakerConfig.java
@@ -0,0 +1,24 @@
+/* Copyright 2009-2015 Comcast Interactive Media, LLC.
+
+ 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.fishwife.jrugged;
+
+/**
+ * The BreakerConfig class holds a
+ * {@link org.fishwife.jrugged.Breaker} configuration.
+ */
+public interface BreakerConfig {
+
+ public FailureInterpreter getFailureInterpreter();
+}
diff --git a/jrugged-core/src/main/java/org/fishwife/jrugged/CircuitBreakerException.java b/jrugged-core/src/main/java/org/fishwife/jrugged/BreakerException.java
similarity index 89%
rename from jrugged-core/src/main/java/org/fishwife/jrugged/CircuitBreakerException.java
rename to jrugged-core/src/main/java/org/fishwife/jrugged/BreakerException.java
index bfd28bcf..d421d458 100644
--- a/jrugged-core/src/main/java/org/fishwife/jrugged/CircuitBreakerException.java
+++ b/jrugged-core/src/main/java/org/fishwife/jrugged/BreakerException.java
@@ -20,10 +20,10 @@
* This exception gets thrown by a {@link CircuitBreaker} if a wrapped
* call is disallowed by a tripped (OPEN) breaker.
*/
-public class CircuitBreakerException extends RuntimeException {
+public class BreakerException extends RuntimeException {
private static final long serialVersionUID = 1L;
/** Default constructor. */
- public CircuitBreakerException() { }
+ public BreakerException() { }
}
diff --git a/jrugged-core/src/main/java/org/fishwife/jrugged/CircuitBreakerExceptionMapper.java b/jrugged-core/src/main/java/org/fishwife/jrugged/BreakerExceptionMapper.java
similarity index 72%
rename from jrugged-core/src/main/java/org/fishwife/jrugged/CircuitBreakerExceptionMapper.java
rename to jrugged-core/src/main/java/org/fishwife/jrugged/BreakerExceptionMapper.java
index d08fae44..225b42b1 100644
--- a/jrugged-core/src/main/java/org/fishwife/jrugged/CircuitBreakerExceptionMapper.java
+++ b/jrugged-core/src/main/java/org/fishwife/jrugged/BreakerExceptionMapper.java
@@ -15,21 +15,22 @@
package org.fishwife.jrugged;
/**
- * Allows the user to map the standard {@link CircuitBreakerException}
+ * Allows the user to map the standard {@link BreakerException}
* thrown by a tripped {@link CircuitBreaker} into an application
* specific exception.
*
* @param is the application specific exception type.
*/
-public interface CircuitBreakerExceptionMapper {
+public interface BreakerExceptionMapper {
/**
- * Turns a {@link CircuitBreakerException} into the desired exception (T)
+ * Turns a {@link BreakerException} into the desired exception (T)
*
* @param breaker the {@link CircuitBreaker}
- * @param e the CircuitBreakerException I get
+ * @param e the BreakerException I get
* @return the {@link Exception} I want thrown instead
*/
- public T map(CircuitBreaker breaker, CircuitBreakerException e);
-
+ public T map(Breaker breaker, BreakerException e);
+
+
}
diff --git a/jrugged-core/src/main/java/org/fishwife/jrugged/BreakerFactory.java b/jrugged-core/src/main/java/org/fishwife/jrugged/BreakerFactory.java
new file mode 100644
index 00000000..9f2a3c32
--- /dev/null
+++ b/jrugged-core/src/main/java/org/fishwife/jrugged/BreakerFactory.java
@@ -0,0 +1,461 @@
+/* Copyright 2009-2015 Comcast Interactive Media, LLC.
+
+ 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.fishwife.jrugged;
+
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Factory to create new {@link CircuitBreaker} and {@link SkepticBreaker}
+ * instances and keep track of them.
+ */
+public class BreakerFactory {
+
+ public static final String CIRCUIT_BREAKER_CONFIG_KEY_PREFIX = "circuit";
+ public static final String SKEPTIC_BREAKER_CONFIG_KEY_PREFIX = "skeptic";
+ public static final String LIMIT_KEY = "limit";
+ public static final String WINDOWMILLIS_KEY = "windowMillis";
+ public static final String RESETMILLIS_KEY = "resetMillis";
+
+ public static final String WAITMULT_KEY = "waitMult";
+ public static final String GOODMULT_KEY = "goodMult";
+ public static final String WAITBASE_KEY = "waitBase";
+ public static final String GOODBASE_KEY = "goodBase";
+ public static final String MAXLEVEL_KEY = "maxLevel";
+
+ private final ConcurrentHashMap circuitBreakerMap =
+ new ConcurrentHashMap();
+ private final ConcurrentHashMap skepticBreakerMap =
+ new ConcurrentHashMap();
+
+ private Properties circuitBreakerProperties;
+ private Properties skepticBreakerProperties;
+
+ private final Logger logger = LoggerFactory.getLogger(getClass());
+
+ /**
+ * Create a new {@link CircuitBreaker} and map it to the provided name.
+ * If the CircuitBreaker already exists, then the existing instance is
+ * returned.
+ * @param name the name of the {@link CircuitBreaker}
+ * @param config the {@link CircuitBreakerConfig} with the configuration
+ * values.
+ * @return the created {@link CircuitBreaker}
+ */
+ public synchronized CircuitBreaker createCircuitBreaker(String name,
+ CircuitBreakerConfig config) {
+ CircuitBreaker circuitBreaker = findCircuitBreaker(name);
+
+ if (circuitBreaker == null) {
+ circuitBreaker = new CircuitBreaker(name);
+
+ configureCircuitBreaker(name, circuitBreaker, config);
+ addCircuitBreakerToMap(name, circuitBreaker);
+ }
+
+ return circuitBreaker;
+ }
+
+ /**
+ * Create a new {@link SkepticBreaker} and map it to the provided name.
+ * If the SkepticBreaker already exists, then the existing instance is
+ * returned.
+ * @param name the name of the {@link SkepticBreaker}
+ * @param config the {@link SkepticBreakerConfig} with the configuration
+ * values.
+ * @return the created {@link SkepticBreaker}
+ */
+ public synchronized SkepticBreaker createSkepticBreaker(String name,
+ SkepticBreakerConfig config) {
+ SkepticBreaker skepticBreaker = findSkepticBreaker(name);
+
+ if (skepticBreaker == null) {
+ skepticBreaker = new SkepticBreaker(name);
+
+ configureSkepticBreaker(name, skepticBreaker, config);
+ addSkepticBreakerToMap(name, skepticBreaker);
+ }
+
+ return skepticBreaker;
+ }
+
+ /**
+ * Set the {@link Properties} object to search for {@link CircuitBreaker}
+ * property override values. The override values can be specified as:
+ * circuit.{circuit_name}.limit
+ * circuit.{circuit_name}.resetmillis
+ * circuit.{circuit_name}.windowmillis
+ * @param properties the {@link Properties} object to search.
+ */
+ public void setCircuitBreakerProperties(Properties properties) {
+ this.circuitBreakerProperties = properties;
+ }
+
+ /**
+ * Set the {@link Properties} object to search for {@link SkepticBreaker}
+ * property override values. The override values can be specified as:
+ * skeptic.{skeptic_name}.limit
+ * skeptic.{skeptic_name}.waitmult
+ * skeptic.{skeptic_name}.goodmult
+ * skeptic.{skeptic_name}.waitbase
+ * skeptic.{skeptic_name}.goodbase
+ * skeptic.{skeptic_name}.maxlevel
+ * skeptic.{skeptic_name}.windowmillis
+ * @param properties the {@link Properties} object to search.
+ */
+ public void setSkepticBreakerProperties(Properties properties) {
+ this.skepticBreakerProperties = properties;
+ }
+
+ /**
+ * Find an existing {@link CircuitBreaker}
+ * @param name the value for the {@link CircuitBreaker}
+ * @return the found {@link CircuitBreaker}, or null if it is not found.
+ */
+ public CircuitBreaker findCircuitBreaker(String name) {
+ return circuitBreakerMap.get(name);
+ }
+
+ /**
+ * Find an existing {@link SkepticBreaker}
+ * @param name the value for the {@link SkepticBreaker}
+ * @return the found {@link SkepticBreaker}, or null if it is not found.
+ */
+ public SkepticBreaker findSkepticBreaker(String name) {
+ return skepticBreakerMap.get(name);
+ }
+
+ /**
+ * Get the {@link Set} of created {@link CircuitBreaker} names.
+ * @return the {@link Set} of names.
+ */
+ public Set getCircuitBreakerNames() {
+ return circuitBreakerMap.keySet();
+ }
+
+ /**
+ * Get the {@link Set} of created {@link SkepticBreaker} names.
+ * @return the {@link Set} of names.
+ */
+ public Set getSkepticBreakerNames() {
+ return skepticBreakerMap.keySet();
+ }
+
+ protected void configureCircuitBreaker(String name,
+ CircuitBreaker circuit,
+ CircuitBreakerConfig config) {
+
+ long resetMillis = config.getResetMillis();
+ Long resetMillisOverride = getCircuitBreakerLongPropertyOverrideValue(name, RESETMILLIS_KEY);
+ if (resetMillisOverride != null) {
+ resetMillis = resetMillisOverride;
+ }
+
+ FailureInterpreter fi = config.getFailureInterpreter();
+ circuit.setFailureInterpreter(fi);
+
+ if (resetMillis > 0) {
+ circuit.setResetMillis(resetMillis);
+ }
+
+ if (fi instanceof DefaultFailureInterpreter) {
+ configureCircuitBreakerDefaultFailureInterpreter(name, resetMillis, circuit);
+ }
+ else {
+ logger.info(
+ "Created CircuitBreaker '{}', resetMillis={}",
+ new Object[] {
+ name,
+ resetMillis
+ });
+ }
+ }
+
+ protected void configureSkepticBreaker(String name,
+ SkepticBreaker skeptic,
+ SkepticBreakerConfig config) {
+
+ long waitMult = config.getWaitMult();
+ Long waitMultOverride = getSkepticBreakerLongPropertyOverrideValue(name, WAITMULT_KEY);
+ if (waitMultOverride != null) {
+ waitMult = waitMultOverride;
+ }
+
+ long goodMult = config.getGoodMult();
+ Long goodMultOverride = getSkepticBreakerLongPropertyOverrideValue(name, GOODMULT_KEY);
+ if (goodMultOverride != null) {
+ goodMult = goodMultOverride;
+ }
+
+ long waitBase = config.getWaitBase();
+ Long waitBaseOverride = getSkepticBreakerLongPropertyOverrideValue(name, WAITBASE_KEY);
+ if (waitBaseOverride != null) {
+ waitBase = waitBaseOverride;
+ }
+
+ long goodBase = config.getGoodBase();
+ Long goodBaseOverride = getSkepticBreakerLongPropertyOverrideValue(name, GOODBASE_KEY);
+ if (goodBaseOverride != null) {
+ goodBase = goodBaseOverride;
+ }
+
+ long maxLevel = config.getMaxLevel();
+ Long maxLevelOverride = getSkepticBreakerLongPropertyOverrideValue(name, MAXLEVEL_KEY);
+ if (maxLevelOverride != null) {
+ maxLevel = maxLevelOverride;
+ }
+
+ FailureInterpreter fi = config.getFailureInterpreter();
+ skeptic.setFailureInterpreter(fi);
+
+ if (waitMult > 0) {
+ skeptic.setWaitMult(waitMult);
+ }
+
+ if (goodMult > 0) {
+ skeptic.setGoodMult(goodMult);
+ }
+
+ if (waitBase > 0) {
+ skeptic.setWaitBase(waitBase);
+ }
+
+ if (goodBase > 0) {
+ skeptic.setGoodBase(goodBase);
+ }
+
+ if (maxLevel > 0) {
+ skeptic.setMaxLevel(maxLevel);
+ }
+
+ skeptic.updateTimers();
+
+ if (fi instanceof DefaultFailureInterpreter) {
+ configureSkepticBreakerDefaultFailureInterpreter(name, waitMult, goodMult, waitBase,
+ goodBase, maxLevel, skeptic);
+ }
+ else {
+ logger.info(
+ "Created SkepticBreaker '{}', waitMult={}, goodMult={}, waitBase={}, " +
+ "goodBase={}, maxLevel={}",
+ new Object[] {
+ name,
+ waitMult,
+ goodMult,
+ waitBase,
+ goodBase,
+ maxLevel
+ });
+ }
+ }
+
+ private void configureCircuitBreakerDefaultFailureInterpreter(String name, long resetMillis, CircuitBreaker circuit) {
+ DefaultFailureInterpreter fi = (DefaultFailureInterpreter) circuit.getFailureInterpreter();
+
+ Integer limitOverride = getCircuitBreakerIntegerPropertyOverrideValue(name, LIMIT_KEY);
+
+ if (limitOverride != null) {
+ fi.setLimit(limitOverride);
+ }
+
+ Long windowMillisOverride = getCircuitBreakerLongPropertyOverrideValue(name, WINDOWMILLIS_KEY);
+
+ if (windowMillisOverride != null) {
+ fi.setWindowMillis(windowMillisOverride);
+ }
+
+ logger.info(
+ "Created CircuitBreaker '{}', limit={}, windowMillis={}, resetMillis={}",
+ new Object[] {
+ name,
+ fi.getLimit(),
+ fi.getWindowMillis(),
+ resetMillis
+ });
+ }
+
+ private void configureSkepticBreakerDefaultFailureInterpreter(String name, long waitMult,
+ long goodMult, long waitBase, long goodBase, long maxLevel, SkepticBreaker skeptic) {
+ DefaultFailureInterpreter fi = (DefaultFailureInterpreter) skeptic.getFailureInterpreter();
+
+ Integer limitOverride = getSkepticBreakerIntegerPropertyOverrideValue(name, LIMIT_KEY);
+
+ if (limitOverride != null) {
+ fi.setLimit(limitOverride);
+ }
+
+ Long windowMillisOverride = getSkepticBreakerLongPropertyOverrideValue(name, WINDOWMILLIS_KEY);
+
+ if (windowMillisOverride != null) {
+ fi.setWindowMillis(windowMillisOverride);
+ }
+
+ logger.info(
+ "Created SkepticBreaker '{}', limit={}, windowMillis={}, waitMult={}, " +
+ "goodMult={}, waitBase={}, goodBase={}, maxLevel={}",
+ new Object[] {
+ name,
+ fi.getLimit(),
+ fi.getWindowMillis(),
+ waitMult,
+ goodMult,
+ waitBase,
+ goodBase,
+ maxLevel
+ });
+ }
+
+ /**
+ * Add a {@link CircuitBreaker} to the map.
+ * @param name the name for the {@link CircuitBreaker}
+ * @param circuitBreaker the {@link CircuitBreaker} to add.
+ */
+ protected void addCircuitBreakerToMap(String name, CircuitBreaker circuitBreaker) {
+ circuitBreakerMap.put(name, circuitBreaker);
+ }
+
+ /**
+ * Add a {@link SkepticBreaker} to the map.
+ * @param name the name for the {@link SkepticBreaker}
+ * @param skepticBreaker the {@link SkepticBreaker} to add.
+ */
+ protected void addSkepticBreakerToMap(String name, SkepticBreaker skepticBreaker) {
+ skepticBreakerMap.put(name, skepticBreaker);
+ }
+
+ /**
+ * Get the property name for a circuit name and key.
+ * @param name the circuit name.
+ * @param key the property key.
+ * @return the property name.
+ */
+ private String getCircuitBreakerPropertyName(String name, String key) {
+ return CIRCUIT_BREAKER_CONFIG_KEY_PREFIX + '.' + name + '.' + key;
+ }
+
+ /**
+ * Get the property name for a skeptic name and key.
+ * @param name the skeptic name.
+ * @param key the property key.
+ * @return the property name.
+ */
+ private String getSkepticBreakerPropertyName(String name, String key) {
+ return SKEPTIC_BREAKER_CONFIG_KEY_PREFIX + '.' + name + '.' + key;
+ }
+
+ /**
+ * Get an integer property override value.
+ * @param name the {@link CircuitBreaker} name.
+ * @param key the property override key.
+ * @return the property override value, or null if it is not found.
+ */
+ private Integer getCircuitBreakerIntegerPropertyOverrideValue(String name, String key) {
+ if (circuitBreakerProperties != null) {
+ String propertyName = getCircuitBreakerPropertyName(name, key);
+
+ String propertyOverrideValue = circuitBreakerProperties.getProperty(propertyName);
+
+ if (propertyOverrideValue != null) {
+ try {
+ return Integer.parseInt(propertyOverrideValue);
+ }
+ catch (NumberFormatException e) {
+ logger.error("Could not parse property override key={}, value={}",
+ key, propertyOverrideValue);
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get an integer property override value.
+ * @param name the {@link SkepticBreaker} name.
+ * @param key the property override key.
+ * @return the property override value, or null if it is not found.
+ */
+ private Integer getSkepticBreakerIntegerPropertyOverrideValue(String name, String key) {
+ if (skepticBreakerProperties != null) {
+ String propertyName = getSkepticBreakerPropertyName(name, key);
+
+ String propertyOverrideValue = skepticBreakerProperties.getProperty(propertyName);
+
+ if (propertyOverrideValue != null) {
+ try {
+ return Integer.parseInt(propertyOverrideValue);
+ }
+ catch (NumberFormatException e) {
+ logger.error("Could not parse property override key={}, value={}",
+ key, propertyOverrideValue);
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get an {@link Long} property override value.
+ * @param name the {@link CircuitBreaker} name.
+ * @param key the property override key.
+ * @return the property override value, or null if it is not found.
+ */
+ private Long getCircuitBreakerLongPropertyOverrideValue(String name, String key) {
+ if (circuitBreakerProperties != null) {
+ String propertyName = getCircuitBreakerPropertyName(name, key);
+
+ String propertyOverrideValue = circuitBreakerProperties.getProperty(propertyName);
+
+ if (propertyOverrideValue != null) {
+ try {
+ return Long.parseLong(propertyOverrideValue);
+ }
+ catch (NumberFormatException e) {
+ logger.error("Could not parse property override key={}, value={}",
+ key, propertyOverrideValue);
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get an {@link Long} property override value.
+ * @param name the {@link SkepticBreaker} name.
+ * @param key the property override key.
+ * @return the property override value, or null if it is not found.
+ */
+ private Long getSkepticBreakerLongPropertyOverrideValue(String name, String key) {
+ if (skepticBreakerProperties != null) {
+ String propertyName = getSkepticBreakerPropertyName(name, key);
+
+ String propertyOverrideValue = skepticBreakerProperties.getProperty(propertyName);
+
+ if (propertyOverrideValue != null) {
+ try {
+ return Long.parseLong(propertyOverrideValue);
+ }
+ catch (NumberFormatException e) {
+ logger.error("Could not parse property override key={}, value={}",
+ key, propertyOverrideValue);
+ }
+ }
+ }
+ return null;
+ }
+}
diff --git a/jrugged-core/src/main/java/org/fishwife/jrugged/CircuitBreakerNotificationCallback.java b/jrugged-core/src/main/java/org/fishwife/jrugged/BreakerNotificationCallback.java
similarity index 95%
rename from jrugged-core/src/main/java/org/fishwife/jrugged/CircuitBreakerNotificationCallback.java
rename to jrugged-core/src/main/java/org/fishwife/jrugged/BreakerNotificationCallback.java
index 80ca2e92..87d249fb 100644
--- a/jrugged-core/src/main/java/org/fishwife/jrugged/CircuitBreakerNotificationCallback.java
+++ b/jrugged-core/src/main/java/org/fishwife/jrugged/BreakerNotificationCallback.java
@@ -22,7 +22,7 @@
* the listeners to then take action based on what the state change
* was.
*/
-public abstract class CircuitBreakerNotificationCallback {
+public abstract class BreakerNotificationCallback {
/**
* The method needing an application specific implementation
diff --git a/jrugged-core/src/main/java/org/fishwife/jrugged/CircuitBreaker.java b/jrugged-core/src/main/java/org/fishwife/jrugged/CircuitBreaker.java
index 80194f0e..d6ce9e90 100644
--- a/jrugged-core/src/main/java/org/fishwife/jrugged/CircuitBreaker.java
+++ b/jrugged-core/src/main/java/org/fishwife/jrugged/CircuitBreaker.java
@@ -16,12 +16,6 @@
*/
package org.fishwife.jrugged;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicLong;
@@ -58,85 +52,12 @@ public String call() {
}
*
*/
-public class CircuitBreaker implements MonitoredService, ServiceWrapper {
- /**
- * Represents whether a {@link CircuitBreaker} is OPEN, HALF_CLOSED,
- * or CLOSED.
- */
- protected enum BreakerState {
- /** An OPEN breaker has tripped and will not allow requests
- through. */
- OPEN,
-
- /** A HALF_CLOSED breaker has completed its cooldown
- period and will allow one request through as a "test request." */
- HALF_CLOSED,
-
- /** A CLOSED breaker is operating normally and allowing
- requests through. */
- CLOSED
- }
-
- private Throwable tripException = null;
-
- /**
- * Returns the last exception that caused the breaker to trip, NULL if never tripped.
- *
- * @return Throwable
- */
- public Throwable getTripException() {
- return tripException;
- }
-
- /**
- * Returns the last exception that caused the breaker to trip, empty String
- * if never tripped.
- *
- * @return Throwable
- */
- public String getTripExceptionAsString() {
- if (tripException == null) {
- return "";
- } else {
- return getFullStackTrace(tripException);
-
- }
- }
-
- /** Current state of the breaker. */
- protected volatile BreakerState state = BreakerState.CLOSED;
-
- /** The time the breaker last tripped, in milliseconds since the
- epoch. */
- protected AtomicLong lastFailure = new AtomicLong(0L);
-
- /** How many times the breaker has tripped during its lifetime. */
- protected AtomicLong openCount = new AtomicLong(0L);
-
- /** How long the cooldown period is in milliseconds. */
- protected AtomicLong resetMillis = new AtomicLong(15 * 1000L);
-
- /** The {@link FailureInterpreter} to use to determine whether a
- given failure should cause the breaker to trip. */
- protected FailureInterpreter failureInterpreter =
- new DefaultFailureInterpreter();
-
- /** Helper class to allow throwing an application-specific
- * exception rather than the default {@link
- * CircuitBreakerException}. */
- protected CircuitBreakerExceptionMapper extends Exception> exceptionMapper;
-
- protected List cbNotifyList =
- Collections.synchronizedList(new ArrayList());
-
- private boolean isHardTrip;
-
- /**
- * Bypass this CircuitBreaker - used for testing, or other operational
- * situations where verification of the Break might be required.
- */
- protected boolean byPass = false;
-
+public class CircuitBreaker extends Breaker implements MonitoredService, ServiceWrapper {
+
+
+ /** How long the cooldown period is in milliseconds. */
+ protected AtomicLong resetMillis = new AtomicLong(15 * 1000L);
+
/**
* Whether the "test" attempt permitted in the HALF_CLOSED state
* is currently in-flight.
@@ -148,81 +69,78 @@ public String getTripExceptionAsString() {
/** The name for the CircuitBreaker. */
protected String name = DEFAULT_NAME;
-
+
/** Creates a {@link CircuitBreaker} with a {@link
* DefaultFailureInterpreter} and the default "tripped" exception
- * behavior (throwing a {@link CircuitBreakerException}). */
+ * behavior (throwing a {@link BreakerException}). */
public CircuitBreaker() {
}
-
+
/** Creates a {@link CircuitBreaker} with a {@link
* DefaultFailureInterpreter} and the default "tripped" exception
- * behavior (throwing a {@link CircuitBreakerException}).
+ * behavior (throwing a {@link BreakerException}).
* @param name the name for the {@link CircuitBreaker}.
*/
public CircuitBreaker(String name) {
- this.name = name;
+ super(name);
}
-
+
/** Creates a {@link CircuitBreaker} with the specified {@link
* FailureInterpreter} and the default "tripped" exception
- * behavior (throwing a {@link CircuitBreakerException}).
+ * behavior (throwing a {@link BreakerException}).
* @param fi the FailureInterpreter to use when
* determining whether a specific failure ought to cause the
* breaker to trip
*/
public CircuitBreaker(FailureInterpreter fi) {
- failureInterpreter = fi;
+ super(fi);
}
/** Creates a {@link CircuitBreaker} with the specified {@link
* FailureInterpreter} and the default "tripped" exception
- * behavior (throwing a {@link CircuitBreakerException}).
+ * behavior (throwing a {@link BreakerException}).
* @param name the name for the {@link CircuitBreaker}.
* @param fi the FailureInterpreter to use when
* determining whether a specific failure ought to cause the
* breaker to trip
*/
public CircuitBreaker(String name, FailureInterpreter fi) {
- this.name = name;
- failureInterpreter = fi;
+ super(name, fi);
}
/** Creates a {@link CircuitBreaker} with a {@link
* DefaultFailureInterpreter} and using the supplied {@link
- * CircuitBreakerExceptionMapper} when client calls are made
+ * BreakerExceptionMapper} when client calls are made
* while the breaker is tripped.
* @param name the name for the {@link CircuitBreaker}.
* @param mapper helper used to translate a {@link
- * CircuitBreakerException} into an application-specific one */
- public CircuitBreaker(String name, CircuitBreakerExceptionMapper extends Exception> mapper) {
- this.name = name;
- exceptionMapper = mapper;
+ * BreakerException} into an application-specific one */
+ public CircuitBreaker(String name, BreakerExceptionMapper extends Exception> mapper) {
+ super(name, mapper);
}
/** Creates a {@link CircuitBreaker} with the provided {@link
* FailureInterpreter} and using the provided {@link
- * CircuitBreakerExceptionMapper} when client calls are made
+ * BreakerExceptionMapper} when client calls are made
* while the breaker is tripped.
* @param name the name for the {@link CircuitBreaker}.
* @param fi the FailureInterpreter to use when
* determining whether a specific failure ought to cause the
* breaker to trip
* @param mapper helper used to translate a {@link
- * CircuitBreakerException} into an application-specific one */
+ * BreakerException} into an application-specific one */
public CircuitBreaker(String name,
FailureInterpreter fi,
- CircuitBreakerExceptionMapper extends Exception> mapper) {
- this.name = name;
- failureInterpreter = fi;
- exceptionMapper = mapper;
+ BreakerExceptionMapper extends Exception> mapper) {
+ super(name, fi, mapper);
}
+
/** Wrap the given service call with the {@link CircuitBreaker}
* protection logic.
* @param c the {@link Callable} to attempt
* @return whatever c would return on success
- * @throws CircuitBreakerException if the
+ * @throws BreakerException if the
* breaker was OPEN or HALF_CLOSED and this attempt wasn't the
* reset attempt
* @throws Exception if c throws one during
@@ -231,7 +149,7 @@ public CircuitBreaker(String name,
public V invoke(Callable c) throws Exception {
if (!byPass) {
if (!allowRequest()) {
- throw mapException(new CircuitBreakerException());
+ throw mapException(new BreakerException());
}
try {
@@ -252,7 +170,7 @@ public V invoke(Callable c) throws Exception {
/** Wrap the given service call with the {@link CircuitBreaker}
* protection logic.
* @param r the {@link Runnable} to attempt
- * @throws CircuitBreakerException if the
+ * @throws BreakerException if the
* breaker was OPEN or HALF_CLOSED and this attempt wasn't the
* reset attempt
* @throws Exception if c throws one during
@@ -261,7 +179,7 @@ public V invoke(Callable c) throws Exception {
public void invoke(Runnable r) throws Exception {
if (!byPass) {
if (!allowRequest()) {
- throw mapException(new CircuitBreakerException());
+ throw mapException(new BreakerException());
}
try {
@@ -284,7 +202,7 @@ public void invoke(Runnable r) throws Exception {
* @param r the {@link Runnable} to attempt
* @param result what to return after r succeeds
* @return result
- * @throws CircuitBreakerException if the
+ * @throws BreakerException if the
* breaker was OPEN or HALF_CLOSED and this attempt wasn't the
* reset attempt
* @throws Exception if c throws one during
@@ -293,7 +211,7 @@ public void invoke(Runnable r) throws Exception {
public V invoke(Runnable r, V result) throws Exception {
if (!byPass) {
if (!allowRequest()) {
- throw mapException(new CircuitBreakerException());
+ throw mapException(new BreakerException());
}
try {
@@ -312,28 +230,6 @@ public V invoke(Runnable r, V result) throws Exception {
}
}
- /**
- * When called with true - causes the {@link CircuitBreaker} to byPass
- * its functionality allowing requests to be executed unmolested
- * until the CircuitBreaker is reset or the byPass
- * is manually set to false.
- *
- * @param b Set this breaker into bypass mode
- */
- public void setByPassState(boolean b) {
- byPass = b;
- notifyBreakerStateChange(getStatus());
- }
-
- /**
- * Get the current state of the {@link CircuitBreaker} byPass
- *
- * @return boolean the byPass flag's current value
- */
- public boolean getByPassState() {
- return byPass;
- }
-
/**
* Causes the {@link CircuitBreaker} to trip and OPEN; no new
* requests will be allowed until the CircuitBreaker
@@ -350,32 +246,6 @@ public void trip() {
notifyBreakerStateChange(getStatus());
}
- /**
- * Manually trips the CircuitBreaker until {@link #reset()} is invoked.
- */
- public void tripHard() {
- this.trip();
- isHardTrip = true;
- }
-
- /**
- * Returns the last time the breaker tripped OPEN, measured in
- * milliseconds since the Epoch.
- * @return long the last failure time
- */
- public long getLastTripTime() {
- return lastFailure.get();
- }
-
- /**
- * Returns the number of times the breaker has tripped OPEN during
- * its lifetime.
- * @return long the number of times the circuit breaker tripped
- */
- public long getTripCount() {
- return openCount.get();
- }
-
/**
* Manually set the breaker to be reset and ready for use. This
* is only useful after a manual trip otherwise the breaker will
@@ -391,20 +261,6 @@ public void reset() {
notifyBreakerStateChange(getStatus());
}
- /**
- * Returns the current {@link org.fishwife.jrugged.Status} of the
- * {@link CircuitBreaker}. In this case, it really refers to the
- * status of the client service. If the
- * CircuitBreaker is CLOSED, we report that the
- * client is UP; if it is HALF_CLOSED, we report that the client
- * is DEGRADED; if it is OPEN, we report the client is DOWN.
- *
- * @return Status the current status of the breaker
- */
- public Status getStatus() {
- return getServiceStatus().getStatus();
- }
-
/**
* Get the current {@link ServiceStatus} of the
* {@link CircuitBreaker}, including the name,
@@ -449,139 +305,6 @@ public void setResetMillis(long l) {
resetMillis.set(l);
}
- /** Returns a {@link String} representation of the breaker's
- * status; potentially useful for exposing to monitoring software.
- * @return String which is "GREEN" if
- * the breaker is CLOSED; "YELLOW" if the breaker
- * is HALF_CLOSED; and "RED" if the breaker is
- * OPEN (tripped). */
- public String getHealthCheck() {
- return getStatus().getSignal();
- }
-
- /**
- * Specifies the failure tolerance limit for the {@link
- * DefaultFailureInterpreter} that comes with a {@link
- * CircuitBreaker} by default.
- * @see DefaultFailureInterpreter
- * @param limit the number of tolerated failures in a window
- */
- public void setLimit(int limit) {
- FailureInterpreter fi = getFailureInterpreter();
- if (!(fi instanceof DefaultFailureInterpreter)) {
- throw new IllegalStateException("setLimit() not supported: this CircuitBreaker's FailureInterpreter isn't a DefaultFailureInterpreter.");
- }
- ((DefaultFailureInterpreter)fi).setLimit(limit);
- }
-
- /**
- * Specifies a set of {@link Throwable} classes that should not
- * be considered failures by the {@link CircuitBreaker}.
- * @see DefaultFailureInterpreter
- * @param ignore a {@link java.util.Collection} of {@link Throwable}
- * classes
- */
- public void setIgnore(Collection> ignore) {
- FailureInterpreter fi = getFailureInterpreter();
- if (!(fi instanceof DefaultFailureInterpreter)) {
- throw new IllegalStateException("setIgnore() not supported: this CircuitBreaker's FailureInterpreter isn't a DefaultFailureInterpreter.");
- }
-
- @SuppressWarnings("unchecked")
- Class extends Throwable>[] classes = new Class[ignore.size()];
- int i = 0;
- for(Class extends Throwable> c : ignore) {
- classes[i] = c;
- i++;
- }
- ((DefaultFailureInterpreter)fi).setIgnore(classes);
- }
-
- /**
- * Specifies the tolerance window in milliseconds for the {@link
- * DefaultFailureInterpreter} that comes with a {@link
- * CircuitBreaker} by default.
- * @see DefaultFailureInterpreter
- * @param windowMillis length of the window in milliseconds
- */
- public void setWindowMillis(long windowMillis) {
- FailureInterpreter fi = getFailureInterpreter();
- if (!(fi instanceof DefaultFailureInterpreter)) {
- throw new IllegalStateException("setWindowMillis() not supported: this CircuitBreaker's FailureInterpreter isn't a DefaultFailureInterpreter.");
- }
- ((DefaultFailureInterpreter)fi).setWindowMillis(windowMillis);
- }
-
- /**
- * Specifies a helper that determines whether a given failure will
- * cause the breaker to trip or not.
- *
- * @param failureInterpreter the {@link FailureInterpreter} to use
- */
- public void setFailureInterpreter(FailureInterpreter failureInterpreter) {
- this.failureInterpreter = failureInterpreter;
- }
-
- /**
- * Get the failure interpreter for this instance. The failure
- * interpreter provides the configuration for determining which
- * exceptions trip the circuit breaker, in what time interval,
- * etc.
- *
- * @return {@link FailureInterpreter} for this instance or null if no
- * failure interpreter was set.
- */
- public FailureInterpreter getFailureInterpreter() {
- return this.failureInterpreter;
- }
-
- /**
- * A helper that converts CircuitBreakerExceptions into a known
- * 'application' exception.
- *
- * @param mapper my converter object
- */
- public void setExceptionMapper(CircuitBreakerExceptionMapper extends Exception> mapper) {
- this.exceptionMapper = mapper;
- }
-
- /**
- * Add an interested party for {@link CircuitBreaker} events, like up,
- * down, degraded status state changes.
- *
- * @param listener an interested party for {@link CircuitBreaker} status events.
- */
- public void addListener(CircuitBreakerNotificationCallback listener) {
- cbNotifyList.add(listener);
- }
-
- /**
- * Set a list of interested parties for {@link CircuitBreaker} events, like up,
- * down, degraded status state changes.
- *
- * @param listeners a list of interested parties for {@link CircuitBreaker} status events.
- */
- public void setListeners(ArrayList listeners) {
- cbNotifyList = Collections.synchronizedList(listeners);
- }
-
- /**
- * Get the helper that converts {@link CircuitBreakerException}s into
- * application-specific exceptions.
- * @return {@link CircuitBreakerExceptionMapper} my converter object, or
- * null if one is not currently set.
- */
- public CircuitBreakerExceptionMapper extends Exception> getExceptionMapper(){
- return this.exceptionMapper;
- }
-
- protected Exception mapException(CircuitBreakerException cbe) {
- if (exceptionMapper == null)
- return cbe;
-
- return exceptionMapper.map(this, cbe);
- }
-
protected void handleFailure(Throwable cause) throws Exception {
if (failureInterpreter == null || failureInterpreter.shouldTrip(cause)) {
this.tripException = cause;
@@ -618,14 +341,6 @@ private synchronized boolean canAttempt() {
return true;
}
- private void notifyBreakerStateChange(Status status) {
- if (cbNotifyList != null && cbNotifyList.size() >= 1) {
- for (CircuitBreakerNotificationCallback notifyObject : cbNotifyList) {
- notifyObject.notify(status);
- }
- }
- }
-
/**
* @return boolean whether the breaker will allow a request
* through or not.
@@ -647,9 +362,4 @@ else if (BreakerState.CLOSED == state) {
}
- private String getFullStackTrace(Throwable t) {
- StringWriter sw = new StringWriter();
- t.printStackTrace(new PrintWriter(sw));
- return sw.toString();
- }
}
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 bd94d6d7..f61339dc 100644
--- a/jrugged-core/src/main/java/org/fishwife/jrugged/CircuitBreakerConfig.java
+++ b/jrugged-core/src/main/java/org/fishwife/jrugged/CircuitBreakerConfig.java
@@ -18,7 +18,7 @@
* The CircuitBreakerConfig class holds a
* {@link org.fishwife.jrugged.CircuitBreaker} configuration.
*/
-public class CircuitBreakerConfig {
+public class CircuitBreakerConfig implements BreakerConfig{
private FailureInterpreter failureInterpreter;
private long resetMillis;
diff --git a/jrugged-core/src/main/java/org/fishwife/jrugged/CircuitBreakerFactory.java b/jrugged-core/src/main/java/org/fishwife/jrugged/CircuitBreakerFactory.java
deleted file mode 100644
index eb93dd22..00000000
--- a/jrugged-core/src/main/java/org/fishwife/jrugged/CircuitBreakerFactory.java
+++ /dev/null
@@ -1,218 +0,0 @@
-/* Copyright 2009-2015 Comcast Interactive Media, LLC.
-
- 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.fishwife.jrugged;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.Properties;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * Factory to create new {@link CircuitBreaker} instances and keep track of
- * them.
- */
-public class CircuitBreakerFactory {
-
- public static final String CONFIG_KEY_PREFIX = "circuit";
- public static final String LIMIT_KEY = "limit";
- public static final String WINDOWMILLIS_KEY = "windowMillis";
- public static final String RESETMILLIS_KEY = "resetMillis";
-
- private final ConcurrentHashMap circuitBreakerMap =
- new ConcurrentHashMap();
-
- private Properties properties;
-
- private final Logger logger = LoggerFactory.getLogger(getClass());
-
- /**
- * Create a new {@link CircuitBreaker} and map it to the provided name.
- * If the CircuitBreaker already exists, then the existing instance is
- * returned.
- * @param name the name of the {@link CircuitBreaker}
- * @param config the {@link CircuitBreakerConfig} with the configuration
- * values.
- * @return the created {@link CircuitBreaker}
- */
- public synchronized CircuitBreaker createCircuitBreaker(String name,
- CircuitBreakerConfig config) {
- CircuitBreaker circuitBreaker = findCircuitBreaker(name);
-
- if (circuitBreaker == null) {
- circuitBreaker = new CircuitBreaker(name);
-
- configureCircuitBreaker(name, circuitBreaker, config);
- addCircuitBreakerToMap(name, circuitBreaker);
- }
-
- return circuitBreaker;
- }
-
- /**
- * Set the {@link Properties} object to search for {@link CircuitBreaker}
- * property override values. The override values can be specified as:
- * circuit.{circuit_name}.limit
- * circuit.{circuit_name}.resetmillis
- * circuit.{circuit_name}.windowmillis
- * @param properties the {@link Properties} object to search.
- */
- public void setProperties(Properties properties) {
- this.properties = properties;
- }
-
- /**
- * Find an existing {@link CircuitBreaker}
- * @param name the value for the {@link CircuitBreaker}
- * @return the found {@link CircuitBreaker}, or null if it is not found.
- */
- public CircuitBreaker findCircuitBreaker(String name) {
- return circuitBreakerMap.get(name);
- }
-
- /**
- * Get the {@link Set} of created {@link CircuitBreaker} names.
- * @return the {@link Set} of names.
- */
- public Set getCircuitBreakerNames() {
- return circuitBreakerMap.keySet();
- }
-
- protected void configureCircuitBreaker(String name,
- CircuitBreaker circuit,
- CircuitBreakerConfig config) {
-
- long resetMillis = config.getResetMillis();
- Long resetMillisOverride = getLongPropertyOverrideValue(name, RESETMILLIS_KEY);
- if (resetMillisOverride != null) {
- resetMillis = resetMillisOverride;
- }
-
- FailureInterpreter fi = config.getFailureInterpreter();
- circuit.setFailureInterpreter(fi);
-
- if (resetMillis > 0) {
- circuit.setResetMillis(resetMillis);
- }
-
- if (fi instanceof DefaultFailureInterpreter) {
- configureDefaultFailureInterpreter(name, resetMillis, circuit);
- }
- else {
- logger.info(
- "Created CircuitBreaker '{}', resetMillis={}",
- new Object[] {
- name,
- resetMillis
- });
- }
- }
-
- private void configureDefaultFailureInterpreter(String name, long resetMillis, CircuitBreaker circuit) {
- DefaultFailureInterpreter fi = (DefaultFailureInterpreter) circuit.getFailureInterpreter();
-
- Integer limitOverride = getIntegerPropertyOverrideValue(name, LIMIT_KEY);
-
- if (limitOverride != null) {
- fi.setLimit(limitOverride);
- }
-
- Long windowMillisOverride = getLongPropertyOverrideValue(name, WINDOWMILLIS_KEY);
-
- if (windowMillisOverride != null) {
- fi.setWindowMillis(windowMillisOverride);
- }
-
- logger.info(
- "Created CircuitBreaker '{}', limit={}, windowMillis={}, resetMillis={}",
- new Object[] {
- name,
- fi.getLimit(),
- fi.getWindowMillis(),
- resetMillis
- });
- }
-
- /**
- * Add a {@link CircuitBreaker} to the map.
- * @param name the name for the {@link CircuitBreaker}
- * @param circuitBreaker the {@link CircuitBreaker} to add.
- */
- protected void addCircuitBreakerToMap(String name, CircuitBreaker circuitBreaker) {
- circuitBreakerMap.put(name, circuitBreaker);
- }
-
- /**
- * Get the property name for a circuit name and key.
- * @param name the circuit name.
- * @param key the property key.
- * @return the property name.
- */
- private String getPropertyName(String name, String key) {
- return CONFIG_KEY_PREFIX + '.' + name + '.' + key;
- }
-
- /**
- * Get an integer property override value.
- * @param name the {@link CircuitBreaker} name.
- * @param key the property override key.
- * @return the property override value, or null if it is not found.
- */
- private Integer getIntegerPropertyOverrideValue(String name, String key) {
- if (properties != null) {
- String propertyName = getPropertyName(name, key);
-
- String propertyOverrideValue = properties.getProperty(propertyName);
-
- if (propertyOverrideValue != null) {
- try {
- return Integer.parseInt(propertyOverrideValue);
- }
- catch (NumberFormatException e) {
- logger.error("Could not parse property override key={}, value={}",
- key, propertyOverrideValue);
- }
- }
- }
- return null;
- }
-
- /**
- * Get an {@link Long} property override value.
- * @param name the {@link CircuitBreaker} name.
- * @param key the property override key.
- * @return the property override value, or null if it is not found.
- */
- private Long getLongPropertyOverrideValue(String name, String key) {
- if (properties != null) {
- String propertyName = getPropertyName(name, key);
-
- String propertyOverrideValue = properties.getProperty(propertyName);
-
- if (propertyOverrideValue != null) {
- try {
- return Long.parseLong(propertyOverrideValue);
- }
- catch (NumberFormatException e) {
- logger.error("Could not parse property override key={}, value={}",
- key, propertyOverrideValue);
- }
- }
- }
- return null;
- }
-}
-
diff --git a/jrugged-core/src/main/java/org/fishwife/jrugged/DefaultFailureInterpreter.java b/jrugged-core/src/main/java/org/fishwife/jrugged/DefaultFailureInterpreter.java
index 5f45e4cc..93ff85f3 100644
--- a/jrugged-core/src/main/java/org/fishwife/jrugged/DefaultFailureInterpreter.java
+++ b/jrugged-core/src/main/java/org/fishwife/jrugged/DefaultFailureInterpreter.java
@@ -21,175 +21,192 @@
import java.util.Set;
/**
- * Trips a {@link CircuitBreaker} if the number of failures in a given
- * time window exceed a specified tolerance. By default, all {@link
- * Throwable} occurrences will be considered failures.
+ * Trips a {@link CircuitBreaker} if the number of failures in a given time
+ * window exceed a specified tolerance. By default, all {@link Throwable}
+ * occurrences will be considered failures.
*/
public final class DefaultFailureInterpreter implements FailureInterpreter {
- private Set> ignore = new HashSet>();
- private int limit = 0;
- private long windowMillis = 0;
-
- private WindowedEventCounter counter;
-
- @SuppressWarnings("unchecked")
- private static Class extends Throwable>[] defaultIgnore =
- new Class[0];
-
- /**
- * Default constructor. Any {@link Throwable} will cause the breaker to trip.
- */
- public DefaultFailureInterpreter() {
- setIgnore(defaultIgnore);
- }
-
- /**
- * Constructor that allows a tolerance for a certain number of
- * failures within a given window of time without tripping.
- * @param limit the number of failures that will be tolerated
- * (i.e. the number of failures has to be strictly greater
- * than this number in order to trip the breaker). For
- * example, if the limit is 3, the fourth failure during
- * the window will cause the breaker to trip.
- * @param windowMillis length of the window in milliseconds
- */
- public DefaultFailureInterpreter(int limit, long windowMillis) {
- setIgnore(defaultIgnore);
- setLimit(limit);
- setWindowMillis(windowMillis);
- initCounter();
- }
-
- /**
- * Constructor where we specify certain {@link Throwable} classes
- * that will be ignored by the breaker and not be treated as
- * failures (they will be passed through transparently without
- * causing the breaker to trip).
- * @param ignore an array of {@link Throwable} classes that will
- * be ignored. Any given Throwable that is a
- * subclass of one of these classes will be ignored.
- */
- public DefaultFailureInterpreter(Class extends Throwable>[] ignore) {
- setIgnore(ignore);
- }
-
- /**
- * Constructor where we specify tolerance and a set of ignored failures.
- *
- * @param ignore an array of {@link Throwable} classes that will
- * be ignored. Any given Throwable that is a
- * subclass of one of these classes will be ignored.
- * @param limit the number of failures that will be tolerated
- * (i.e. the number of failures has to be strictly greater
- * than this number in order to trip the breaker). For
- * example, if the limit is 3, the fourth failure during
- * the window will cause the breaker to trip.
- * @param windowMillis length of the window in milliseconds
- */
- public DefaultFailureInterpreter(Class extends Throwable>[] ignore,
- int limit, long windowMillis) {
- setIgnore(ignore);
- setLimit(limit);
- setWindowMillis(windowMillis);
- initCounter();
- }
-
- private boolean hasWindowConditions() {
- return this.limit > 0 && this.windowMillis > 0;
- }
-
- public boolean shouldTrip(Throwable cause) {
- for(Class> clazz : ignore) {
- if (clazz.isInstance(cause)) {
- return false;
- }
- }
-
- // if Exception is of specified type, and window conditions exist,
- // keep circuit open unless exception threshold has passed
- if (hasWindowConditions()) {
- counter.mark();
- // Trip if the exception count has passed the limit
- return (counter.tally() > limit);
- }
-
- return true;
- }
-
- private void initCounter() {
- if (hasWindowConditions()) {
- int capacity = limit + 1;
- if (counter == null) {
- this.counter = new WindowedEventCounter(capacity,windowMillis);
- } else {
- if (capacity != counter.getCapacity()) {
- counter.setCapacity(capacity);
- }
-
- if (windowMillis != counter.getWindowMillis()) {
- counter.setWindowMillis(windowMillis);
- }
- }
- } else {
- // we're not under windowConditions, no counter needed
- counter = null;
- }
- }
-
- /**
- * Returns the set of currently ignored {@link Throwable} classes.
- * @return {@link Set}
- */
- public Set> getIgnore(){
- return this.ignore;
- }
-
- /**
- * Specifies an array of {@link Throwable} classes to ignore. These will not
- * be considered failures.
- * @param ignore array of {@link Class} objects
- */
- public synchronized void setIgnore(Class extends Throwable>[] ignore) {
- this.ignore = new HashSet>(Arrays.asList(ignore));
- }
-
- /**
- * Returns the current number of failures within the window that will be tolerated
- * without tripping the breaker.
- * @return int
- */
- public int getLimit(){
- return this.limit;
- }
-
- /**
- * Specifies the number of tolerated failures within the
- * configured time window. If limit is set to n then the
- * (n+1) th failure will trip the breaker. Mutating the
- * limit at runtime can reset previous failure counts.
- * @param limit int
- */
- public void setLimit(int limit) {
- this.limit=limit;
- initCounter();
- }
-
- /**
- * Returns the length of the currently configured tolerance window
- * in milliseconds.
- * @return long
- */
- public long getWindowMillis(){
- return this.windowMillis;
- }
-
- /**
- * Specifies the length of the tolerance window in milliseconds.
- * @param windowMillis long
- */
- public void setWindowMillis(long windowMillis) {
- this.windowMillis=windowMillis;
- initCounter();
- }
+ private Set> ignore = new HashSet>();
+ private int limit = 0;
+ private long windowMillis = 0;
+
+ private WindowedEventCounter counter;
+
+ @SuppressWarnings("unchecked")
+ private static Class extends Throwable>[] defaultIgnore = new Class[0];
+
+ /**
+ * Default constructor. Any {@link Throwable} will cause the breaker to
+ * trip.
+ */
+ public DefaultFailureInterpreter() {
+ setIgnore(defaultIgnore);
+ }
+
+ /**
+ * Constructor that allows a tolerance for a certain number of failures
+ * within a given window of time without tripping.
+ *
+ * @param limit
+ * the number of failures that will be tolerated (i.e. the number
+ * of failures has to be strictly greater
+ * than this number in order to trip the breaker). For example, if
+ * the limit is 3, the fourth failure during the window will
+ * cause the breaker to trip.
+ * @param windowMillis
+ * length of the window in milliseconds
+ */
+ public DefaultFailureInterpreter(int limit, long windowMillis) {
+ setIgnore(defaultIgnore);
+ setLimit(limit);
+ setWindowMillis(windowMillis);
+ initCounter();
+ }
+
+ /**
+ * Constructor where we specify certain {@link Throwable} classes that will
+ * be ignored by the breaker and not be treated as failures (they will be
+ * passed through transparently without causing the breaker to trip).
+ *
+ * @param ignore
+ * an array of {@link Throwable} classes that will be ignored.
+ * Any given Throwable that is a subclass of one of
+ * these classes will be ignored.
+ */
+ public DefaultFailureInterpreter(Class extends Throwable>[] ignore) {
+ setIgnore(ignore);
+ }
+
+ /**
+ * Constructor where we specify tolerance and a set of ignored failures.
+ *
+ * @param ignore
+ * an array of {@link Throwable} classes that will be ignored.
+ * Any given Throwable that is a subclass of one of
+ * these classes will be ignored.
+ * @param limit
+ * the number of failures that will be tolerated (i.e. the number
+ * of failures has to be strictly greater
+ * than this number in order to trip the breaker). For example, if
+ * the limit is 3, the fourth failure during the window will
+ * cause the breaker to trip.
+ * @param windowMillis
+ * length of the window in milliseconds
+ */
+ public DefaultFailureInterpreter(Class extends Throwable>[] ignore,
+ int limit, long windowMillis) {
+ setIgnore(ignore);
+ setLimit(limit);
+ setWindowMillis(windowMillis);
+ initCounter();
+ }
+
+ private boolean hasWindowConditions() {
+ return this.limit > 0 && this.windowMillis > 0;
+ }
+
+ public boolean shouldTrip(Throwable cause) {
+ for (Class> clazz : ignore) {
+ if (clazz.isInstance(cause)) {
+ return false;
+ }
+ }
+
+ // if Exception is of specified type, and window conditions exist,
+ // keep circuit open unless exception threshold has passed
+ if (hasWindowConditions()) {
+ counter.mark();
+ // Trip if the exception count has passed the limit
+ return (counter.tally() > limit);
+ }
+
+ return true;
+ }
+
+ private void initCounter() {
+ if (hasWindowConditions()) {
+ int capacity = limit + 1;
+ if (counter == null) {
+ this.counter = new WindowedEventCounter(capacity, windowMillis);
+ } else {
+ if (capacity != counter.getCapacity()) {
+ counter.setCapacity(capacity);
+ }
+
+ if (windowMillis != counter.getWindowMillis()) {
+ counter.setWindowMillis(windowMillis);
+ }
+ }
+ } else {
+ // we're not under windowConditions, no counter needed
+ counter = null;
+ }
+ }
+
+ /**
+ * Returns the set of currently ignored {@link Throwable} classes.
+ *
+ * @return {@link Set}
+ */
+ public Set> getIgnore() {
+ return this.ignore;
+ }
+
+ /**
+ * Specifies an array of {@link Throwable} classes to ignore. These will not
+ * be considered failures.
+ *
+ * @param ignore
+ * array of {@link Class} objects
+ */
+ public synchronized void setIgnore(Class extends Throwable>[] ignore) {
+ this.ignore = new HashSet>(
+ Arrays.asList(ignore));
+ }
+
+ /**
+ * Returns the current number of failures within the window that will be
+ * tolerated without tripping the breaker.
+ *
+ * @return int
+ */
+ public int getLimit() {
+ return this.limit;
+ }
+
+ /**
+ * Specifies the number of tolerated failures within the configured time
+ * window. If limit is set to n then the (n+1) th failure
+ * will trip the breaker. Mutating the limit at runtime can reset previous
+ * failure counts.
+ *
+ * @param limit
+ * int
+ */
+ public void setLimit(int limit) {
+ this.limit = limit;
+ initCounter();
+ }
+
+ /**
+ * Returns the length of the currently configured tolerance window in
+ * milliseconds.
+ *
+ * @return long
+ */
+ public long getWindowMillis() {
+ return this.windowMillis;
+ }
+
+ /**
+ * Specifies the length of the tolerance window in milliseconds.
+ *
+ * @param windowMillis
+ * long
+ */
+ public void setWindowMillis(long windowMillis) {
+ this.windowMillis = windowMillis;
+ initCounter();
+ }
}
diff --git a/jrugged-core/src/main/java/org/fishwife/jrugged/SkepticBreaker.java b/jrugged-core/src/main/java/org/fishwife/jrugged/SkepticBreaker.java
new file mode 100644
index 00000000..8344e99b
--- /dev/null
+++ b/jrugged-core/src/main/java/org/fishwife/jrugged/SkepticBreaker.java
@@ -0,0 +1,471 @@
+/* SkepticBreaker.java
+ *
+ * Copyright 2009-2015 Comcast Interactive Media, LLC.
+ *
+ * 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.fishwife.jrugged;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.fishwife.jrugged.Breaker.BreakerState;
+import static org.easymock.EasyMock.createMock;
+
+/** A {@link SkepticBreaker} can be used with a service to throttle traffic
+ * to a failed subsystem (particularly one we might not be able to monitor,
+ * such as a peer system which must be accessed over the network). Service
+ * calls are wrapped by the SkepticBreaker.
+ *
+ * When everything is operating normally, the SkepticBreaker
+ * is CLOSED and the calls are allowed through. If the 'good timer' runs out
+ * while in CLOSED state, Skepticism level decreases.
+ *
+ * When a call fails, however, the SkepticBreaker "trips" and
+ * moves to an OPEN state. Skepticism level increases. Client calls are not
+ * allowed through while the SkepticBreaker is OPEN.
+ *
+ * After a certain "cooldown" period where calls are not allowed to go through
+ * but internal sample calls are made, if no bad calls are encountered after the
+ * cooldown timer runs out (wait time), the SkepticBreaker will
+ * transition back to a CLOSED state. If bad calls are made, the timer restarts.
+ *
+ * Sample usage:
+ *
+ public class Service implements Monitorable {
+ private SkepticBreaker cb = new SkepticBreaker();
+ public String doSomething(final Object arg) throws Exception {
+ return cb.invoke(new Callable<String>() {
+ public String call() {
+ // make the call ...
+ }
+ });
+ }
+ public Status getStatus() { return cb.getStatus(); }
+ }
+ *
+ */
+public class SkepticBreaker extends Breaker implements MonitoredService, ServiceWrapper {
+
+ /** The time the breaker last switched into good/CLOSED state. */
+ protected AtomicLong lastCloseTime = new AtomicLong(System.currentTimeMillis());
+
+ /** Variables added to determine Wait Timer and Good Timer */
+ protected AtomicLong waitBase = new AtomicLong(1000L);
+ protected AtomicLong waitMult = new AtomicLong(100L);
+ protected AtomicLong goodBase = new AtomicLong(600000L);
+ protected AtomicLong goodMult = new AtomicLong(100L);
+ protected AtomicLong skepticLevel = new AtomicLong(0L);
+ protected AtomicLong maxLevel = new AtomicLong(20L);
+
+ /** How long the cooldown period is in milliseconds during Wait Phase. */
+ protected AtomicLong waitTime = new AtomicLong((long) (waitBase.get() +
+ waitMult.get() * Math.pow(2, skepticLevel.get())));
+
+ /** How long the cooldown period is in milliseconds during Good Phase. */
+ protected AtomicLong goodTime = new AtomicLong((long) (goodBase.get() +
+ goodMult.get() * Math.pow(2, skepticLevel.get())));
+
+ /** The default name if none is provided. */
+ private static final String DEFAULT_NAME = "SkepticBreaker";
+
+ /** The name for the SkepticBreaker. */
+ protected String name = DEFAULT_NAME;
+
+ /** Creates a {@link SkepticBreaker} with a {@link
+ * DefaultFailureInterpreter} and the default "tripped" exception
+ * behavior (throwing a {@link BreakerException}). */
+ public SkepticBreaker() {
+ }
+
+ /** Creates a {@link SkepticBreaker} with a {@link
+ * DefaultFailureInterpreter} and the default "tripped" exception
+ * behavior (throwing a {@link BreakerException}).
+ * @param name the name for the {@link SkepticBreaker}.
+ */
+ public SkepticBreaker(String name) {
+ super(name);
+ }
+
+ /** Creates a {@link SkepticBreaker} with the specified {@link
+ * FailureInterpreter} and the default "tripped" exception
+ * behavior (throwing a {@link BreakerException}).
+ * @param fi the FailureInterpreter to use when
+ * determining whether a specific failure ought to cause the
+ * breaker to trip
+ */
+ public SkepticBreaker(FailureInterpreter fi) {
+ super(fi);
+ }
+
+ /** Creates a {@link SkepticBreaker} with the specified {@link
+ * FailureInterpreter} and the default "tripped" exception
+ * behavior (throwing a {@link BreakerException}).
+ * @param name the name for the {@link SkepticBreaker}.
+ * @param fi the FailureInterpreter to use when
+ * determining whether a specific failure ought to cause the
+ * breaker to trip
+ */
+ public SkepticBreaker(String name, FailureInterpreter fi) {
+ super(name, fi);
+ }
+
+ /** Creates a {@link SkepticBreaker} with a {@link
+ * DefaultFailureInterpreter} and using the supplied {@link
+ * BreakerExceptionMapper} when client calls are made
+ * while the breaker is tripped.
+ * @param name the name for the {@link SkepticBreaker}.
+ * @param mapper helper used to translate a {@link
+ * BreakerException} into an application-specific one */
+ public SkepticBreaker(String name, BreakerExceptionMapper extends Exception> mapper) {
+ super(name, mapper);
+ }
+
+ /** Creates a {@link SkepticBreaker} with the provided {@link
+ * FailureInterpreter} and using the provided {@link
+ * BreakerExceptionMapper} when client calls are made
+ * while the breaker is tripped.
+ * @param name the name for the {@link SkepticBreaker}.
+ * @param fi the FailureInterpreter to use when
+ * determining whether a specific failure ought to cause the
+ * breaker to trip
+ * @param mapper helper used to translate a {@link
+ * BreakerException} into an application-specific one */
+ public SkepticBreaker(String name,
+ FailureInterpreter fi,
+ BreakerExceptionMapper extends Exception> mapper) {
+ super(name, fi, mapper);
+ }
+
+ /** Wrap the given service call with the {@link SkepticBreaker}
+ * protection logic.
+ * @param c the {@link Callable} to attempt
+ * @return whatever c would return on success
+ * @throws BreakerException if the
+ * breaker was OPEN and this attempt wasn't the
+ * reset attempt
+ * @throws Exception if c throws one during
+ * execution
+ */
+ public V invoke(Callable c) throws Exception {
+ if (!byPass) {
+ if (!allowRequest()) {
+ if (!isHardTrip) {
+ try {
+ Callable mockCallable = createMock(Callable.class);
+ mockCallable.call();
+ }
+ catch (Throwable cause) {
+ handleFailure(cause);
+ }
+ }
+ throw mapException(new BreakerException());
+ }
+
+ try {
+ V result = c.call();
+ close();
+ return result;
+ } catch (Throwable cause) {
+ handleFailure(cause);
+ }
+ throw new IllegalStateException("not possible");
+ }
+ else {
+ return c.call();
+ }
+ }
+
+ /** Wrap the given service call with the {@link SkepticBreaker}
+ * protection logic.
+ * @param r the {@link Runnable} to attempt
+ * @throws BreakerException if the
+ * breaker was OPEN and this attempt wasn't the
+ * reset attempt
+ * @throws Exception if c throws one during
+ * execution
+ */
+ public void invoke(Runnable r) throws Exception {
+ if (!byPass) {
+ if (!allowRequest()) {
+ if(!isHardTrip){
+ try{
+ Runnable mockRunnable = createMock(Runnable.class);
+ mockRunnable.run();
+ }
+ catch (Throwable cause){
+ handleFailure(cause);
+ }
+ }
+ throw mapException(new BreakerException());
+ }
+
+ try {
+ r.run();
+ close();
+ return;
+ } catch (Throwable cause) {
+ handleFailure(cause);
+ }
+ throw new IllegalStateException("not possible");
+ }
+ else {
+ r.run();
+ }
+ }
+
+ /** Wrap the given service call with the {@link SkepticBreaker}
+ * protection logic.
+ * @param r the {@link Runnable} to attempt
+ * @param result what to return after r succeeds
+ * @return result
+ * @throws BreakerException if the
+ * breaker was OPEN and this attempt wasn't the
+ * reset attempt
+ * @throws Exception if c throws one during
+ * execution
+ */
+ public V invoke(Runnable r, V result) throws Exception {
+ if (!byPass) {
+ if (!allowRequest()) {
+ if(!isHardTrip){
+ try{
+ Runnable mockRunnable = createMock(Runnable.class);
+ mockRunnable.run();
+ }
+ catch (Throwable cause){
+ handleFailure(cause);
+ }
+ }
+ throw mapException(new BreakerException());
+ }
+
+ try {
+ r.run();
+ close();
+ return result;
+ } catch (Throwable cause) {
+ handleFailure(cause);
+ }
+ throw new IllegalStateException("not possible");
+ }
+ else {
+ r.run();
+ return result;
+ }
+ }
+
+ /**
+ * Causes the {@link SkepticBreaker} to trip and OPEN; no new
+ * requests will be allowed until the SkepticBreaker
+ * resets. Skeptic level increases and timers are updated accordingly.
+ */
+ public void trip() {
+ if (state != BreakerState.OPEN) {
+ openCount.getAndIncrement();
+ }
+ if (skepticLevel.get() < maxLevel.get()) {
+ increaseSkepticLevel();
+ }
+ state = BreakerState.OPEN;
+ lastFailure.set(System.currentTimeMillis());
+
+ notifyBreakerStateChange(getStatus());
+ }
+
+ /**
+ * Increments the skeptic level and updates timers; occurs when the
+ * {@link SkepticBreaker} trips and moves from CLOSED to OPEN
+ */
+ public void increaseSkepticLevel() {
+ skepticLevel.set(skepticLevel.incrementAndGet());
+ updateTimers();
+ }
+
+ /**
+ * Updates wait timer and good timer based on current skeptic level.
+ */
+ public void updateTimers() {
+ waitTime.set((long) (waitBase.get() + waitMult.get() * Math.pow(2, skepticLevel.get())));
+ goodTime.set((long) (goodBase.get() + goodMult.get() * Math.pow(2, skepticLevel.get())));
+ }
+
+ /**
+ * Manually set the breaker to be reset and ready for use. This
+ * is only useful after a manual trip otherwise the breaker will
+ * trip automatically again if the service is still unavailable.
+ * Just like a real breaker. WOOT!!!
+ */
+ public void reset() {
+ state = BreakerState.CLOSED;
+ isHardTrip = false;
+ byPass = false;
+ skepticLevel.set(0);
+ updateTimers();
+
+ notifyBreakerStateChange(getStatus());
+ }
+
+ /**
+ * Get the current {@link ServiceStatus} of the
+ * {@link SkepticBreaker}, including the name,
+ * {@link org.fishwife.jrugged.Status}, and reason.
+ * @return the {@link ServiceStatus}.
+ */
+ public ServiceStatus getServiceStatus() {
+ if (byPass) {
+ return new ServiceStatus(name, Status.DEGRADED, "Bypassed");
+ }
+
+ return (state == BreakerState.CLOSED || (lastFailure.get() > 0
+ && moveToClosed() && !isHardTrip) ?
+ new ServiceStatus(name, Status.UP) :
+ new ServiceStatus(name, Status.DOWN, "Open"));
+ }
+
+ protected void handleFailure(Throwable cause) throws Exception {
+ if (failureInterpreter == null || failureInterpreter.shouldTrip(cause)) {
+ if (state == BreakerState.CLOSED) {
+ this.tripException = cause;
+ trip();// also updates skeptic level
+ }
+ else {
+ //update last failure - this is the case where we send 'ping' that fails
+ lastFailure.set(System.currentTimeMillis());
+ }
+ }
+
+ if (cause instanceof Exception) {
+ throw (Exception)cause;
+ } else if (cause instanceof Error) {
+ throw (Error)cause;
+ } else {
+ throw (RuntimeException)cause;
+ }
+ }
+
+ /**
+ * Reports a successful service call to the {@link SkepticBreaker},
+ * putting the SkepticBreaker back into the CLOSED
+ * state serving requests.
+ */
+ protected void close() {
+ state = BreakerState.CLOSED;
+ notifyBreakerStateChange(getStatus());
+ }
+
+ /**
+ * @return boolean whether the breaker will allow a request
+ * through or not.
+ */
+ protected boolean allowRequest() {
+ if (this.isHardTrip) {
+ return false;
+ }
+ else if (BreakerState.CLOSED == state) {
+ decreaseSkepticLevel();
+ return true;
+ }
+
+ return moveToClosed();
+ }
+
+ private boolean moveToClosed() {
+ if (BreakerState.OPEN == state &&
+ System.currentTimeMillis() - lastFailure.get() >= waitTime.get()) {
+
+ state = BreakerState.CLOSED;
+ notifyBreakerStateChange(getStatus());
+
+ lastCloseTime.set(lastFailure.get() + waitTime.get());
+ decreaseSkepticLevel();
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks to see if SkepticLevel needs to be updated (if Good Timer has
+ * expired). If necessary, decrements the skeptic level and updates timers;
+ * occurs when the {@link SkepticBreaker} is in the CLOSED state
+ */
+ protected void decreaseSkepticLevel() {
+ long currTime = System.currentTimeMillis();
+ if (currTime - lastCloseTime.get() >= goodTime.get()) {
+ if(skepticLevel.get() > 0){
+ skepticLevel.set(skepticLevel.decrementAndGet());
+ lastCloseTime.set(goodTime.get() + lastCloseTime.get());
+ updateTimers();
+ decreaseSkepticLevel();
+ }
+ }
+ }
+
+ public long getWaitBase() {
+ return waitBase.get();
+ }
+
+ public void setWaitBase(long wBase) {
+ waitBase.set(wBase);
+ }
+
+ public long getWaitMult() {
+ return waitMult.get();
+ }
+
+ public void setWaitMult(long wMult) {
+ waitMult.set(wMult);
+ }
+
+ public long getGoodBase() {
+ return goodBase.get();
+ }
+
+ public void setGoodBase(long gBase) {
+ goodBase.set(gBase);
+ }
+
+ public long getGoodMult() {
+ return goodMult.get();
+ }
+
+ public void setGoodMult(long gMult) {
+ goodMult.set(gMult);
+ }
+
+ public long getMaxLevel() {
+ return maxLevel.get();
+ }
+
+ public void setMaxLevel(long mLevel) {
+ maxLevel.set(mLevel);
+ }
+
+ public long getSkepticLevel() {
+ return skepticLevel.get();
+ }
+
+ public long getWaitTime() {
+ return waitTime.get();
+ }
+
+ public void setWaitTime(long l) {
+ waitTime.set(l);
+ }
+
+ public long getGoodTime() {
+ return goodTime.get();
+ }
+}
diff --git a/jrugged-core/src/main/java/org/fishwife/jrugged/SkepticBreakerConfig.java b/jrugged-core/src/main/java/org/fishwife/jrugged/SkepticBreakerConfig.java
new file mode 100644
index 00000000..61ace818
--- /dev/null
+++ b/jrugged-core/src/main/java/org/fishwife/jrugged/SkepticBreakerConfig.java
@@ -0,0 +1,69 @@
+/* Copyright 2009-2015 Comcast Interactive Media, LLC.
+
+ 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.fishwife.jrugged;
+
+/**
+ * The SkepticBreakerConfig class holds a
+ * {@link org.fishwife.jrugged.SkepticBreaker} configuration.
+ */
+public class SkepticBreakerConfig implements BreakerConfig {
+
+ private FailureInterpreter failureInterpreter;
+
+ private long waitMult;
+ private long waitBase;
+
+ private long goodMult;
+ private long goodBase;
+
+ private long maxLevel;
+
+ public SkepticBreakerConfig(long goodBase, long goodMult, long waitBase, long waitMult,
+ long maxLevel, FailureInterpreter failureInterpreter) {
+
+ //ASSUMPTION: start level = 0
+ this.waitMult = waitMult;
+ this.waitBase = waitBase;
+ this.goodMult = goodMult;
+ this.goodBase = goodBase;
+ this.maxLevel = maxLevel;
+
+ this.failureInterpreter = failureInterpreter;
+ }
+
+ public FailureInterpreter getFailureInterpreter() {
+ return failureInterpreter;
+ }
+
+ public long getWaitMult() {
+ return waitMult;
+ }
+
+ public long getWaitBase() {
+ return waitBase;
+ }
+
+ public long getGoodMult() {
+ return goodMult;
+ }
+
+ public long getGoodBase() {
+ return goodBase;
+ }
+
+ public long getMaxLevel() {
+ return maxLevel;
+ }
+}
diff --git a/jrugged-core/src/test/java/org/fishwife/jrugged/TestBreakerFactory.java b/jrugged-core/src/test/java/org/fishwife/jrugged/TestBreakerFactory.java
new file mode 100644
index 00000000..172875f3
--- /dev/null
+++ b/jrugged-core/src/test/java/org/fishwife/jrugged/TestBreakerFactory.java
@@ -0,0 +1,344 @@
+/* Copyright 2009-2015 Comcast Interactive Media, LLC.
+
+ 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.fishwife.jrugged;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.HashSet;
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicLong;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertSame;
+
+public class TestBreakerFactory {
+
+ private BreakerFactory factory;
+ private CircuitBreakerConfig circuitBreakerConfig;
+ private SkepticBreakerConfig skepticBreakerConfig;
+
+ private static final int TEST_LIMIT = 5;
+ private static final long TEST_WINDOW_MILLIS = 30000L;
+ private static final long TEST_RESET_MILLIS = 10000L;
+
+ private static final long TEST_WAIT_BASE = 2000L;
+ private static final long TEST_WAIT_MULT = 200L;
+ private static final long TEST_GOOD_BASE = 300000L;
+ private static final long TEST_GOOD_MULT = 50L;
+ private static final long TEST_MAX_LEVEL = 20L;
+
+ @Before
+ public void setUp() {
+ factory = new BreakerFactory();
+ circuitBreakerConfig = new CircuitBreakerConfig(TEST_RESET_MILLIS,
+ new DefaultFailureInterpreter(TEST_LIMIT, TEST_WINDOW_MILLIS));
+ skepticBreakerConfig = new SkepticBreakerConfig(TEST_GOOD_BASE,
+ TEST_GOOD_MULT, TEST_WAIT_BASE, TEST_WAIT_MULT, TEST_MAX_LEVEL,
+ new DefaultFailureInterpreter(TEST_LIMIT, TEST_WINDOW_MILLIS));
+ }
+
+ @Test
+ public void testCreateCircuitBreaker() {
+ CircuitBreaker breaker = factory.createCircuitBreaker("testCreate", circuitBreakerConfig);
+ checkCircuitBreaker(breaker, TEST_LIMIT, TEST_WINDOW_MILLIS, TEST_RESET_MILLIS);
+ }
+
+ @Test
+ public void testCreateSkepticBreaker() {
+ SkepticBreaker breaker = factory.createSkepticBreaker("testCreate", skepticBreakerConfig);
+ checkSkepticBreaker(breaker, TEST_LIMIT, TEST_WINDOW_MILLIS, TEST_GOOD_BASE,
+ TEST_GOOD_MULT, TEST_WAIT_BASE, TEST_WAIT_MULT, TEST_MAX_LEVEL);
+ }
+
+ @Test
+ public void testCreateDuplicateCircuitBreaker() {
+ String name = "testCreate";
+ CircuitBreaker createdBreaker = factory.createCircuitBreaker(name, circuitBreakerConfig);
+ CircuitBreaker secondBreaker = factory.createCircuitBreaker(name, circuitBreakerConfig);
+
+ assertSame(createdBreaker, secondBreaker);
+ checkCircuitBreaker(createdBreaker, TEST_LIMIT, TEST_WINDOW_MILLIS, TEST_RESET_MILLIS);
+ }
+
+ @Test
+ public void testCreateDuplicateSkepticBreaker() {
+ String name = "testCreate";
+ SkepticBreaker createdBreaker = factory.createSkepticBreaker(name, skepticBreakerConfig);
+ SkepticBreaker secondBreaker = factory.createSkepticBreaker(name, skepticBreakerConfig);
+
+ assertSame(createdBreaker, secondBreaker);
+ checkSkepticBreaker(createdBreaker, TEST_LIMIT, TEST_WINDOW_MILLIS, TEST_GOOD_BASE,
+ TEST_GOOD_MULT, TEST_WAIT_BASE, TEST_WAIT_MULT, TEST_MAX_LEVEL);
+ }
+
+ @Test
+ public void testCreateCircuitBreakerEmptyConfig() {
+ CircuitBreakerConfig emptyConfig =
+ new CircuitBreakerConfig(-1, new DefaultFailureInterpreter());
+ CircuitBreaker breaker = factory.createCircuitBreaker("testCreateEmpty", emptyConfig);
+
+ checkCircuitBreaker(breaker, 0, 0, 15000L); // These are the CircuitBreaker Defaults.
+ }
+
+ @Test
+ public void testCreateSkepticBreakerEmptyConfig() {
+ SkepticBreakerConfig emptyConfig =
+ new SkepticBreakerConfig(-1, -1, -1, -1, -1,
+ new DefaultFailureInterpreter());
+ SkepticBreaker breaker = factory.createSkepticBreaker("testCreateEmpty", emptyConfig);
+
+ checkSkepticBreaker(breaker, 0, 0, 600000L,
+ 100L, 1000L, 100L, 20L); // These are the SkepticBreaker Defaults.
+ }
+
+ @Test
+ public void testCreateCircuitBreakerNullFailureInterpreter() {
+ CircuitBreakerConfig emptyConfig =
+ new CircuitBreakerConfig(-1, null);
+ CircuitBreaker breaker = factory.createCircuitBreaker("testCreateEmpty", emptyConfig);
+
+ checkCircuitBreakerNoFailureInterpreter(breaker, 15000L); // These are the CircuitBreaker Defaults.
+ }
+
+ @Test
+ public void testCreateSkepticBreakerNullFailureInterpreter() {
+ SkepticBreakerConfig emptyConfig =
+ new SkepticBreakerConfig(-1, -1, -1, -1, -1, null);
+ SkepticBreaker breaker = factory.createSkepticBreaker("testCreateEmpty", emptyConfig);
+
+ checkSkepticBreakerNoFailureInterpreter(breaker, 600000L,
+ 100L, 1000L, 100L, 20L); // These are the SkepticBreaker Defaults.
+ }
+
+ @Test
+ public void testFindANamedCircuitBreaker() {
+ String monitorName = "testFind";
+ CircuitBreaker createdBreaker = factory.createCircuitBreaker(monitorName, circuitBreakerConfig);
+ CircuitBreaker foundBreaker = factory.findCircuitBreaker(monitorName);
+ assertEquals(createdBreaker, foundBreaker);
+ }
+
+ @Test
+ public void testFindANamedSkepticBreaker() {
+ String monitorName = "testFind";
+ SkepticBreaker createdBreaker = factory.createSkepticBreaker(monitorName, skepticBreakerConfig);
+ SkepticBreaker foundBreaker = factory.findSkepticBreaker(monitorName);
+ assertEquals(createdBreaker, foundBreaker);
+ }
+
+ @Test
+ public void testFindNonExistentCircuitBreaker() {
+ CircuitBreaker foundBreaker = factory.findCircuitBreaker("testNonExistent");
+ assertNull(foundBreaker);
+ }
+
+ @Test
+ public void testFindNonExistentSkepticBreaker() {
+ SkepticBreaker foundBreaker = factory.findSkepticBreaker("testNonExistent");
+ assertNull(foundBreaker);
+ }
+
+ @Test
+ public void testGetCircuitBreakerNames() {
+ Set testSet = new HashSet();
+ testSet.add("one");
+ testSet.add("two");
+ testSet.add("three");
+ testSet.add("four");
+
+ factory.createCircuitBreaker("one", circuitBreakerConfig);
+ factory.createCircuitBreaker("two", circuitBreakerConfig);
+ factory.createCircuitBreaker("three", circuitBreakerConfig);
+ factory.createCircuitBreaker("four", circuitBreakerConfig);
+ Set monitorNames = factory.getCircuitBreakerNames();
+ assertEquals(testSet, monitorNames);
+ }
+
+ @Test
+ public void testGetSkepticBreakerNames() {
+ Set testSet = new HashSet();
+ testSet.add("one");
+ testSet.add("two");
+ testSet.add("three");
+ testSet.add("four");
+
+ factory.createSkepticBreaker("one", skepticBreakerConfig);
+ factory.createSkepticBreaker("two", skepticBreakerConfig);
+ factory.createSkepticBreaker("three", skepticBreakerConfig);
+ factory.createSkepticBreaker("four", skepticBreakerConfig);
+ Set monitorNames = factory.getSkepticBreakerNames();
+ assertEquals(testSet, monitorNames);
+ }
+
+ @Test
+ public void testEmptyCircuitBreakerPropertyOverrides() {
+ Properties overrideProperties = new Properties();
+ factory.setCircuitBreakerProperties(overrideProperties);
+ CircuitBreaker breaker = factory.createCircuitBreaker("emptyOverrides", circuitBreakerConfig);
+ checkCircuitBreaker(breaker, TEST_LIMIT, TEST_WINDOW_MILLIS, TEST_RESET_MILLIS);
+ }
+
+ @Test
+ public void testEmptySkepticBreakerPropertyOverrides() {
+ Properties overrideProperties = new Properties();
+ factory.setSkepticBreakerProperties(overrideProperties);
+ SkepticBreaker breaker = factory.createSkepticBreaker("emptyOverrides", skepticBreakerConfig);
+ checkSkepticBreaker(breaker, TEST_LIMIT, TEST_WINDOW_MILLIS, TEST_GOOD_BASE,
+ TEST_GOOD_MULT, TEST_WAIT_BASE, TEST_WAIT_MULT, TEST_MAX_LEVEL);
+ }
+
+ @Test
+ public void testCircuitBreakerPropertyOverrides() {
+ Properties overrideProperties = new Properties();
+ Integer overrideLimit = 10;
+ Long overrideResetMillis = 50000L;
+ Long overrideWindowMillis = 500000L;
+ String name = "testOverrides";
+
+ overrideProperties.put("circuit." + name + ".limit", overrideLimit.toString());
+ overrideProperties.put("circuit." + name + ".resetMillis", overrideResetMillis.toString());
+ overrideProperties.put("circuit." + name + ".windowMillis", overrideWindowMillis.toString());
+ factory.setCircuitBreakerProperties(overrideProperties);
+
+ CircuitBreaker breaker = factory.createCircuitBreaker(name, circuitBreakerConfig);
+ checkCircuitBreaker(breaker, overrideLimit, overrideWindowMillis, overrideResetMillis);
+ }
+
+ @Test
+ public void testSkepticBreakerPropertyOverrides() {
+ Properties overrideProperties = new Properties();
+ Integer overrideLimit = 10;
+ Long overrideWindowMillis = 500000L;
+ Long overrideGoodBase = 50000L;
+ Long overrideGoodMult = 500000L;
+ Long overrideWaitBase = 50000L;
+ Long overrideWaitMult = 500000L;
+ Long overrideMaxLevel = 25L;
+ String name = "testOverrides";
+
+ overrideProperties.put("skeptic." + name + ".limit", overrideLimit.toString());
+ overrideProperties.put("skeptic." + name + ".windowMillis", overrideWindowMillis.toString());
+ overrideProperties.put("skeptic." + name + ".goodBase", overrideGoodBase.toString());
+ overrideProperties.put("skeptic." + name + ".goodMult", overrideGoodMult.toString());
+ overrideProperties.put("skeptic." + name + ".waitBase", overrideWaitBase.toString());
+ overrideProperties.put("skeptic." + name + ".waitMult", overrideWaitMult.toString());
+ overrideProperties.put("skeptic." + name + ".maxLevel", overrideMaxLevel.toString());
+ factory.setSkepticBreakerProperties(overrideProperties);
+
+ SkepticBreaker breaker = factory.createSkepticBreaker(name, skepticBreakerConfig);
+ checkSkepticBreaker(breaker, overrideLimit, overrideWindowMillis, overrideGoodBase,
+ overrideGoodMult, overrideWaitBase, overrideWaitMult, overrideMaxLevel);
+ }
+
+ @Test
+ public void testInvalidCircuitBreakerPropertyOverrides() {
+ Properties overrideProperties = new Properties();
+ String name = "testOverrides";
+
+ overrideProperties.put("circuit." + name + ".limit", "badLimit");
+ overrideProperties.put("circuit." + name + ".resetMillis", "badResetMillis");
+ overrideProperties.put("circuit." + name + ".windowMillis", "badWindowMillis");
+ factory.setCircuitBreakerProperties(overrideProperties);
+
+ CircuitBreakerConfig emptyConfig =
+ new CircuitBreakerConfig(-1, new DefaultFailureInterpreter());
+ CircuitBreaker breaker = factory.createCircuitBreaker(name, emptyConfig);
+ checkCircuitBreaker(breaker, 0, 0L, 15000L); // These are the CircuitBreaker defaults.
+ assertNotNull(breaker);
+ }
+
+ @Test
+ public void testInvalidSkepticBreakerPropertyOverrides() {
+ Properties overrideProperties = new Properties();
+ String name = "testOverrides";
+
+ overrideProperties.put("skeptic." + name + ".limit", "badLimit");
+ overrideProperties.put("skeptic." + name + ".windowMillis", "badWindowMillis");
+ overrideProperties.put("skeptic." + name + ".goodBase", "badGoodBase");
+ overrideProperties.put("skeptic." + name + ".goodMult", "badGoodMult");
+ overrideProperties.put("skeptic." + name + ".waitBase", "badWaitBase");
+ overrideProperties.put("skeptic." + name + ".waitMult", "badWaitMult");
+ overrideProperties.put("skeptic." + name + ".maxLevel", "badMaxLevel");
+ factory.setSkepticBreakerProperties(overrideProperties);
+
+ SkepticBreakerConfig emptyConfig =
+ new SkepticBreakerConfig(-1, -1, -1, -1, -1,
+ new DefaultFailureInterpreter());
+ SkepticBreaker breaker = factory.createSkepticBreaker(name, emptyConfig);
+ checkSkepticBreaker(breaker, 0, 0, 600000L,
+ 100L, 1000L, 100L, 20L); // These are the SkepticBreaker Defaults.
+ assertNotNull(breaker);
+ }
+
+ private void checkCircuitBreaker(CircuitBreaker breaker,
+ int expectedLimit,
+ long expectedWindowMillis,
+ long expectedResetMillis) {
+
+ assertNotNull(breaker);
+
+ DefaultFailureInterpreter failureInterpreter = (DefaultFailureInterpreter)breaker.getFailureInterpreter();
+
+ if (failureInterpreter != null) {
+ assertEquals(failureInterpreter.getLimit(), expectedLimit);
+ assertEquals(failureInterpreter.getWindowMillis(), expectedWindowMillis);
+ }
+
+ assertEquals(breaker.getResetMillis(), expectedResetMillis);
+ }
+
+ private void checkSkepticBreaker(SkepticBreaker breaker, int expectedLimit,
+ long expectedWindowMillis, long expectedGoodBase, long expectedGoodMult,
+ long expectedWaitBase, long expectedWaitMult, long expectedMaxLevel) {
+
+ assertNotNull(breaker);
+
+ DefaultFailureInterpreter failureInterpreter = (DefaultFailureInterpreter)breaker.getFailureInterpreter();
+
+ if (failureInterpreter != null) {
+ assertEquals(failureInterpreter.getLimit(), expectedLimit);
+ assertEquals(failureInterpreter.getWindowMillis(), expectedWindowMillis);
+ }
+
+ assertEquals(breaker.getGoodBase(), expectedGoodBase);
+ assertEquals(breaker.getGoodMult(), expectedGoodMult);
+ assertEquals(breaker.getWaitBase(), expectedWaitBase);
+ assertEquals(breaker.getWaitMult(), expectedWaitMult);
+ assertEquals(breaker.getMaxLevel(), expectedMaxLevel);
+ }
+
+ private void checkCircuitBreakerNoFailureInterpreter(CircuitBreaker breaker,
+ long expectedResetMillis) {
+
+ assertNotNull(breaker);
+ assertEquals(breaker.getResetMillis(), expectedResetMillis);
+ }
+
+ private void checkSkepticBreakerNoFailureInterpreter(SkepticBreaker breaker,
+ long expectedGoodBase, long expectedGoodMult, long expectedWaitBase,
+ long expectedWaitMult, long expectedMaxLevel) {
+ assertNotNull(breaker);
+
+ assertEquals(breaker.getGoodBase(), expectedGoodBase);
+ assertEquals(breaker.getGoodMult(), expectedGoodMult);
+ assertEquals(breaker.getWaitBase(), expectedWaitBase);
+ assertEquals(breaker.getWaitMult(), expectedWaitMult);
+ assertEquals(breaker.getMaxLevel(), expectedMaxLevel);
+ }
+}
\ No newline at end of file
diff --git a/jrugged-core/src/test/java/org/fishwife/jrugged/TestCircuitBreaker.java b/jrugged-core/src/test/java/org/fishwife/jrugged/TestCircuitBreaker.java
index bcf3dbc0..24a00a32 100644
--- a/jrugged-core/src/test/java/org/fishwife/jrugged/TestCircuitBreaker.java
+++ b/jrugged-core/src/test/java/org/fishwife/jrugged/TestCircuitBreaker.java
@@ -76,7 +76,7 @@ public void testInvokeWithRunnableResultAndByPassReturnsResult() throws Exceptio
assertSame(result, theReturned);
}
- @Test(expected = CircuitBreakerException.class)
+ @Test(expected = BreakerException.class)
public void testInvokeWithRunnableResultAndTripHardReturnsException() throws Exception {
final Object result = new Object();
impl.tripHard();
@@ -111,7 +111,7 @@ public void testInvokeWithRunnableAndByPassDoesNotError() throws Exception {
verify(mockRunnable);
}
- @Test(expected = CircuitBreakerException.class)
+ @Test(expected = BreakerException.class)
public void testInvokeWithRunnableAndTripHardReturnsException() throws Exception {
impl.tripHard();
@@ -169,7 +169,7 @@ public void testOpenDuringCooldownThrowsCBException()
try {
impl.invoke(mockCallable);
fail("should have thrown an exception");
- } catch (CircuitBreakerException expected) {
+ } catch (BreakerException expected) {
}
verify(mockCallable);
@@ -241,7 +241,7 @@ public void testManualTripAndReset() throws Exception {
try {
impl.invoke(mockCallable);
fail("Manual trip method failed.");
- } catch (CircuitBreakerException e) {
+ } catch (BreakerException e) {
}
impl.reset();
@@ -263,7 +263,7 @@ public void testTripHard() throws Exception {
try {
impl.invoke(mockCallable);
fail("exception expected after CircuitBreaker.tripHard()");
- } catch (CircuitBreakerException e) {
+ } catch (BreakerException e) {
}
assertEquals(CircuitBreaker.BreakerState.OPEN, impl.state);
@@ -354,7 +354,7 @@ public void testByPassIgnoresBreakerStateAndCallsWrappedMethod() throws Exceptio
try {
impl.invoke(mockCallable);
- } catch (CircuitBreakerException e) {
+ } catch (BreakerException e) {
fail("exception not expected when CircuitBreaker is bypassed.");
}
assertEquals(CircuitBreaker.BreakerState.OPEN, impl.state);
@@ -371,7 +371,7 @@ public void testByPassIgnoresBreakerStateAndCallsWrappedMethod() throws Exceptio
@Test
public void testNotificationCallback() throws Exception {
- CircuitBreakerNotificationCallback cb = new CircuitBreakerNotificationCallback() {
+ BreakerNotificationCallback cb = new BreakerNotificationCallback() {
public void notify(Status s) {
theStatus = s;
}
diff --git a/jrugged-core/src/test/java/org/fishwife/jrugged/TestCircuitBreakerFactory.java b/jrugged-core/src/test/java/org/fishwife/jrugged/TestCircuitBreakerFactory.java
deleted file mode 100644
index 97fa4f2e..00000000
--- a/jrugged-core/src/test/java/org/fishwife/jrugged/TestCircuitBreakerFactory.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/* Copyright 2009-2015 Comcast Interactive Media, LLC.
-
- 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.fishwife.jrugged;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.HashSet;
-import java.util.Properties;
-import java.util.Set;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.assertNull;
-import static junit.framework.Assert.assertSame;
-
-public class TestCircuitBreakerFactory {
-
- private CircuitBreakerFactory factory;
- private CircuitBreakerConfig config;
-
- private static final int TEST_LIMIT = 5;
- private static final long TEST_WINDOW_MILLIS = 30000L;
- private static final long TEST_RESET_MILLIS = 10000L;
-
- @Before
- public void setUp() {
- factory = new CircuitBreakerFactory();
- config = new CircuitBreakerConfig(TEST_RESET_MILLIS,
- new DefaultFailureInterpreter(TEST_LIMIT, TEST_WINDOW_MILLIS));
- }
-
- @Test
- public void testCreateCircuitBreaker() {
- CircuitBreaker breaker = factory.createCircuitBreaker("testCreate", config);
- checkBreaker(breaker, TEST_LIMIT, TEST_WINDOW_MILLIS, TEST_RESET_MILLIS);
- }
-
- @Test
- public void testCreateDuplicateCircuitBreaker() {
- String name = "testCreate";
- CircuitBreaker createdBreaker = factory.createCircuitBreaker(name, config);
- CircuitBreaker secondBreaker = factory.createCircuitBreaker(name, config);
-
- assertSame(createdBreaker, secondBreaker);
- checkBreaker(createdBreaker, TEST_LIMIT, TEST_WINDOW_MILLIS, TEST_RESET_MILLIS);
- }
-
- @Test
- public void testCreateCircuitBreakerEmptyConfig() {
- CircuitBreakerConfig emptyConfig =
- new CircuitBreakerConfig(-1, new DefaultFailureInterpreter());
- CircuitBreaker breaker = factory.createCircuitBreaker("testCreateEmpty", emptyConfig);
-
- checkBreaker(breaker, 0, 0, 15000L); // These are the CircuitBreaker Defaults.
- }
-
- @Test
- public void testCreateCircuitBreakerNullFailureInterpreter() {
- CircuitBreakerConfig emptyConfig =
- new CircuitBreakerConfig(-1, null);
- CircuitBreaker breaker = factory.createCircuitBreaker("testCreateEmpty", emptyConfig);
-
- checkBreakerNoFailureInterpreter(breaker, 15000L); // These are the CircuitBreaker Defaults.
- }
-
- @Test
- public void testFindANamedCircuitBreaker() {
- String monitorName = "testFind";
- CircuitBreaker createdBreaker = factory.createCircuitBreaker(monitorName, config);
- CircuitBreaker foundBreaker = factory.findCircuitBreaker(monitorName);
- assertEquals(createdBreaker, foundBreaker);
- }
-
- @Test
- public void testFindNonExistentCircuitBreaker() {
- CircuitBreaker foundBreaker = factory.findCircuitBreaker("testNonExistent");
- assertNull(foundBreaker);
- }
-
- @Test
- public void testGetCircuitBreakerNames() {
- Set testSet = new HashSet();
- testSet.add("one");
- testSet.add("two");
- testSet.add("three");
- testSet.add("four");
-
- factory.createCircuitBreaker("one", config);
- factory.createCircuitBreaker("two", config);
- factory.createCircuitBreaker("three", config);
- factory.createCircuitBreaker("four", config);
- Set monitorNames = factory.getCircuitBreakerNames();
- assertEquals(testSet, monitorNames);
- }
-
- @Test
- public void testEmptyPropertyOverrides() {
- Properties overrideProperties = new Properties();
- factory.setProperties(overrideProperties);
- CircuitBreaker breaker = factory.createCircuitBreaker("emptyOverrides", config);
- checkBreaker(breaker, TEST_LIMIT, TEST_WINDOW_MILLIS, TEST_RESET_MILLIS);
- }
-
- @Test
- public void testPropertyOverrides() {
- Properties overrideProperties = new Properties();
- Integer overrideLimit = 10;
- Long overrideResetMillis = 50000L;
- Long overrideWindowMillis = 500000L;
- String name = "testOverrides";
-
- overrideProperties.put("circuit." + name + ".limit", overrideLimit.toString());
- overrideProperties.put("circuit." + name + ".resetMillis", overrideResetMillis.toString());
- overrideProperties.put("circuit." + name + ".windowMillis", overrideWindowMillis.toString());
- factory.setProperties(overrideProperties);
-
- CircuitBreaker breaker = factory.createCircuitBreaker(name, config);
- checkBreaker(breaker, overrideLimit, overrideWindowMillis, overrideResetMillis);
- }
-
- @Test
- public void testInvalidPropertyOverrides() {
- Properties overrideProperties = new Properties();
- String name = "testOverrides";
-
- overrideProperties.put("circuit." + name + ".limit", "badLimit");
- overrideProperties.put("circuit." + name + ".resetMillis", "badResetMillis");
- overrideProperties.put("circuit." + name + ".windowMillis", "badWindowMillis");
- factory.setProperties(overrideProperties);
-
- CircuitBreakerConfig emptyConfig =
- new CircuitBreakerConfig(-1, new DefaultFailureInterpreter());
- CircuitBreaker breaker = factory.createCircuitBreaker(name, emptyConfig);
- checkBreaker(breaker, 0, 0L, 15000L); // These are the CircuitBreaker defaults.
- assertNotNull(breaker);
- }
-
- private void checkBreaker(CircuitBreaker breaker,
- int expectedLimit,
- long expectedWindowMillis,
- long expectedResetMillis) {
-
- assertNotNull(breaker);
-
- DefaultFailureInterpreter failureInterpreter = (DefaultFailureInterpreter)breaker.getFailureInterpreter();
-
- if (failureInterpreter != null) {
- assertEquals(failureInterpreter.getLimit(), expectedLimit);
- assertEquals(failureInterpreter.getWindowMillis(), expectedWindowMillis);
- }
-
- assertEquals(breaker.getResetMillis(), expectedResetMillis);
- }
-
- private void checkBreakerNoFailureInterpreter(CircuitBreaker breaker,
- long expectedResetMillis) {
-
- assertNotNull(breaker);
- assertEquals(breaker.getResetMillis(), expectedResetMillis);
- }
-}
diff --git a/jrugged-core/src/test/java/org/fishwife/jrugged/TestSkepticBreaker.java b/jrugged-core/src/test/java/org/fishwife/jrugged/TestSkepticBreaker.java
new file mode 100644
index 00000000..a94dafe7
--- /dev/null
+++ b/jrugged-core/src/test/java/org/fishwife/jrugged/TestSkepticBreaker.java
@@ -0,0 +1,543 @@
+/* TestSkepticBreaker.java
+ *
+ * Copyright 2009-2015 Comcast Interactive Media, LLC.
+ *
+ * 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.fishwife.jrugged;
+
+import java.util.concurrent.Callable;
+
+import junit.framework.Assert;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static junit.framework.Assert.assertNull;
+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.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class TestSkepticBreaker {
+ private SkepticBreaker impl;
+ private Callable mockCallable;
+ private Runnable mockRunnable;
+
+ Status theStatus;
+
+ @SuppressWarnings("unchecked")
+ @Before
+ public void setUp() {
+ impl = new SkepticBreaker();
+ mockCallable = createMock(Callable.class);
+ mockRunnable = createMock(Runnable.class);
+ }
+
+ @Test
+ public void testInvokeWithRunnableResultAndResultReturnsResult() throws Exception {
+ final Object result = new Object();
+
+ mockRunnable.run();
+ replay(mockRunnable);
+
+ Object theReturned = impl.invoke(mockRunnable, result);
+
+ verify(mockRunnable);
+ assertSame(result, theReturned);
+ }
+
+ @Test
+ public void testInvokeWithRunnableResultAndByPassReturnsResult() throws Exception {
+ final Object result = new Object();
+ impl.setByPassState(true);
+
+ mockRunnable.run();
+ replay(mockRunnable);
+
+ Object theReturned = impl.invoke(mockRunnable, result);
+
+ verify(mockRunnable);
+ assertSame(result, theReturned);
+ }
+
+ @Test(expected = BreakerException.class)
+ public void testInvokeWithRunnableResultAndTripHardReturnsException() throws Exception {
+ final Object result = new Object();
+ impl.tripHard();
+
+ mockRunnable.run();
+ replay(mockRunnable);
+
+ impl.invoke(mockRunnable, result);
+
+ verify(mockRunnable);
+ }
+
+ @Test
+ public void testInvokeWithRunnableDoesNotError() throws Exception {
+ mockRunnable.run();
+ replay(mockRunnable);
+
+ impl.invoke(mockRunnable);
+
+ verify(mockRunnable);
+ }
+
+ @Test
+ public void testInvokeWithRunnableAndByPassDoesNotError() throws Exception {
+ impl.setByPassState(true);
+
+ mockRunnable.run();
+ replay(mockRunnable);
+
+ impl.invoke(mockRunnable);
+
+ verify(mockRunnable);
+ }
+
+ @Test(expected = BreakerException.class)
+ public void testInvokeWithRunnableAndTripHardReturnsException() throws Exception {
+ impl.tripHard();
+
+ mockRunnable.run();
+ replay(mockRunnable);
+
+ impl.invoke(mockRunnable);
+
+ verify(mockRunnable);
+ }
+
+ @Test
+ public void testStaysClosedOnSuccess() throws Exception {
+ impl.state = SkepticBreaker.BreakerState.CLOSED;
+ final Object obj = new Object();
+ expect(mockCallable.call()).andReturn(obj);
+ replay(mockCallable);
+
+ Object result = impl.invoke(mockCallable);
+
+ verify(mockCallable);
+ assertSame(obj, result);
+ assertEquals(SkepticBreaker.BreakerState.CLOSED, impl.state);
+ }
+
+ @Test
+ //Add check on timers here as well
+ public void testOpensOnFailure() throws Exception {
+ long start = System.currentTimeMillis();
+ impl.state = SkepticBreaker.BreakerState.OPEN;
+ expect(mockCallable.call()).andThrow(new RuntimeException());
+ replay(mockCallable);
+
+ try {
+ impl.invoke(mockCallable);
+ fail("should have thrown an exception");
+ } catch (RuntimeException expected) {
+ }
+
+ long end = System.currentTimeMillis();
+
+ verify(mockCallable);
+ assertEquals(SkepticBreaker.BreakerState.OPEN, impl.state);
+ assertTrue(impl.lastFailure.get() >= start);
+ assertTrue(impl.lastFailure.get() <= end);
+ }
+
+ @Test
+ public void testOpenDuringCooldownThrowsCBException()
+ throws Exception {
+
+ impl.state = SkepticBreaker.BreakerState.OPEN;
+ impl.lastFailure.set(System.currentTimeMillis());
+ replay(mockCallable);
+
+ try {
+ impl.invoke(mockCallable);
+ fail("should have thrown an exception");
+ } catch (BreakerException expected) {
+ }
+
+ verify(mockCallable);
+ assertEquals(SkepticBreaker.BreakerState.OPEN, impl.state);
+ }
+
+ @Test
+ public void testClosedAfterWaitTimeExpires() //testOpenAfterCooldownGoesClosed()
+ throws Exception {
+ impl.state = SkepticBreaker.BreakerState.OPEN;
+ impl.waitTime.set(1000);// waitTime is 1100 by default. set to 1000 in case default changes
+ impl.lastFailure.set(System.currentTimeMillis() - 2000);
+
+ assertEquals(Status.UP, impl.getStatus());
+ assertEquals(SkepticBreaker.BreakerState.CLOSED, impl.state);
+ }
+
+ @Test
+ public void testOpenFailureOpensAgain()
+ throws Exception {
+
+ impl.state = SkepticBreaker.BreakerState.OPEN;
+ impl.waitTime.set(1000);
+ impl.lastFailure.set(System.currentTimeMillis() - 2000);
+
+ long start = System.currentTimeMillis();
+
+ expect(mockCallable.call()).andThrow(new RuntimeException());
+ replay(mockCallable);
+
+ try {
+ impl.invoke(mockCallable);
+ fail("should have thrown exception");
+ } catch (RuntimeException expected) {
+ }
+
+ long end = System.currentTimeMillis();
+
+ verify(mockCallable);
+ assertEquals(SkepticBreaker.BreakerState.OPEN, impl.state);
+ assertTrue(impl.lastFailure.get() >= start);
+ assertTrue(impl.lastFailure.get() <= end);
+ }
+
+ @Test
+ public void testManualTripAndReset() throws Exception {
+ impl.state = SkepticBreaker.BreakerState.OPEN;
+ final Object obj = new Object();
+ expect(mockCallable.call()).andReturn(obj);
+ replay(mockCallable);
+
+ impl.trip();
+ try {
+ impl.invoke(mockCallable);
+ fail("Manual trip method failed.");
+ } catch (BreakerException e) {
+ }
+
+ impl.reset();
+
+ Object result = impl.invoke(mockCallable);
+
+ verify(mockCallable);
+ assertSame(obj, result);
+ assertEquals(SkepticBreaker.BreakerState.CLOSED, impl.state);
+ }
+
+ @Test
+ public void testTripHard() throws Exception {
+ expect(mockCallable.call()).andReturn("hi");
+
+ replay(mockCallable);
+
+ impl.tripHard();
+ try {
+ impl.invoke(mockCallable);
+ fail("exception expected after SkepticBreaker.tripHard()");
+ } catch (BreakerException e) {
+ }
+ assertEquals(SkepticBreaker.BreakerState.OPEN, impl.state);
+
+ impl.reset();
+ impl.invoke(mockCallable);
+ assertEquals(SkepticBreaker.BreakerState.CLOSED, impl.state);
+
+ verify(mockCallable);
+ }
+
+ @Test
+ public void testGetTripCount() throws Exception {
+ long tripCount1 = impl.getTripCount();
+
+ impl.tripHard();
+ long tripCount2 = impl.getTripCount();
+ assertEquals(tripCount1 + 1, tripCount2);
+
+ impl.tripHard();
+ assertEquals(tripCount2, impl.getTripCount());
+ }
+
+ @Test
+ public void testGetStatusWhenOpen() {
+ impl.state = SkepticBreaker.BreakerState.OPEN;
+ Assert.assertEquals(Status.DOWN, impl.getStatus());
+ }
+
+ @Test
+ //CHANGED THIS (title), must change test to reflect
+ public void testGetStatusWhenClosedBeforeReset() {
+ impl.state = SkepticBreaker.BreakerState.CLOSED;
+ impl.waitTime.set(1000);
+ impl.lastFailure.set(System.currentTimeMillis() - 50);
+
+ assertEquals(Status.UP, impl.getStatus());
+ }
+
+ @Test
+ public void testGetStatusWhenOpenAfterExpiration() {
+ impl.state = SkepticBreaker.BreakerState.OPEN;
+ impl.waitTime.set(1000);
+ impl.lastFailure.set(System.currentTimeMillis() - 2000);
+
+ assertEquals(Status.UP, impl.getStatus());
+ }
+
+ @Test
+ public void testGetStatusAfterHardTrip() {
+ impl.tripHard();
+ impl.waitTime.set(1000);
+ impl.lastFailure.set(System.currentTimeMillis() - 2000);
+
+ assertEquals(Status.DOWN, impl.getStatus());
+ }
+
+ @Test
+ public void testStatusIsByPassWhenSet() {
+ impl.setByPassState(true);
+ assertEquals(Status.DEGRADED, impl.getStatus());
+ }
+
+ @Test
+ public void testByPassIgnoresCurrentBreakerStateWhenSet() {
+ impl.state = SkepticBreaker.BreakerState.OPEN;
+ assertEquals(Status.DOWN, impl.getStatus());
+
+ impl.setByPassState(true);
+ assertEquals(Status.DEGRADED, impl.getStatus());
+
+ impl.setByPassState(false);
+ assertEquals(Status.DOWN, impl.getStatus());
+ }
+
+ @Test
+ public void testByPassIgnoresBreakerStateAndCallsWrappedMethod() throws Exception {
+ expect(mockCallable.call()).andReturn("hi").anyTimes();
+
+ replay(mockCallable);
+
+ impl.tripHard();
+ impl.setByPassState(true);
+
+ try {
+ impl.invoke(mockCallable);
+ } catch (BreakerException e) {
+ fail("exception not expected when SkepticBreaker is bypassed.");
+ }
+ assertEquals(SkepticBreaker.BreakerState.OPEN, impl.state);
+ assertEquals(Status.DEGRADED, impl.getStatus());
+
+ impl.reset();
+ impl.setByPassState(false);
+ impl.invoke(mockCallable);
+ assertEquals(SkepticBreaker.BreakerState.CLOSED, impl.state);
+
+ verify(mockCallable);
+ }
+
+ @Test
+ public void testNotificationCallback() throws Exception {
+
+ BreakerNotificationCallback cb = new BreakerNotificationCallback() {
+ public void notify(Status s) {
+ theStatus = s;
+ }
+ };
+
+ impl.addListener(cb);
+ impl.trip();
+
+ assertNotNull(theStatus);
+ assertEquals(Status.DOWN, theStatus);
+ }
+
+ @Test(expected = Throwable.class)
+ public void SkepticBreakerKeepsExceptionThatTrippedIt() throws Throwable {
+
+ try {
+ impl.invoke(new FailingCallable("broken"));
+ } catch (Exception e) {
+
+ }
+
+ Throwable tripException = impl.getTripException();
+ assertEquals("broken", tripException.getMessage());
+ throw tripException;
+ }
+
+ @Test(expected = Throwable.class)
+ public void resetSkepticBreakerStillHasTripException() throws Throwable {
+
+ try {
+ impl.invoke(new FailingCallable("broken"));
+ } catch (Exception e) {
+
+ }
+ impl.reset();
+
+ Throwable tripException = impl.getTripException();
+ assertEquals("broken", tripException.getMessage());
+ throw tripException;
+ }
+
+ @Test
+ public void skepticBreakerReturnsExceptionAsString() {
+
+ try {
+ impl.invoke(new FailingCallable("broken"));
+ } catch (Exception e) {
+
+ }
+
+ String s = impl.getTripExceptionAsString();
+ assertTrue(s.startsWith("java.lang.Exception: broken"));
+ assertTrue(s.contains("at org.fishwife.jrugged.TestSkepticBreaker$FailingCallable.call"));
+ assertTrue(s.contains("Caused by: java.lang.Exception: The Cause"));
+ }
+
+ @Test
+ public void neverTrippedSkepticBreakerReturnsNullForTripException() throws Exception {
+
+ impl.invoke(mockCallable);
+
+ Throwable tripException = impl.getTripException();
+
+ assertNull(tripException);
+ }
+
+ @Test
+ public void testResetsToInitialTimerValues() throws Exception {
+ long expectedWaitTime = impl.getWaitTime();
+ long expectedGoodTime = impl.getGoodTime();
+ long expectedSkepticLevel = impl.getSkepticLevel();
+
+ try {
+ impl.invoke(new FailingCallable("broken"));
+ } catch (Exception e) {
+
+ }
+ impl.reset();
+
+ assertEquals(expectedWaitTime, impl.getWaitTime());
+ assertEquals(expectedGoodTime, impl.getGoodTime());
+ assertEquals(expectedSkepticLevel, impl.getSkepticLevel());
+ }
+
+ @Test
+ public void testTimerValuesAndSkepticLevelAfterOneTrip() throws Exception {
+ long expectedWaitTime = (long) (impl.getWaitBase() +
+ impl.getWaitMult() * Math.pow(2, impl.getSkepticLevel() + 1));
+ long expectedGoodTime = (long) (impl.getGoodBase() +
+ impl.getGoodMult() * Math.pow(2, impl.getSkepticLevel() + 1));
+ long expectedSkepticLevel = impl.getSkepticLevel() + 1;
+
+ try {
+ impl.invoke(new FailingCallable("broken"));
+ } catch (Exception e) {
+
+ }
+
+ assertEquals(expectedWaitTime, impl.getWaitTime());
+ assertEquals(expectedGoodTime, impl.getGoodTime());
+ assertEquals(expectedSkepticLevel, impl.getSkepticLevel());
+ }
+
+ @Test
+ public void testTimerValuesAndSkepticLevelAfterThreeTrips() throws Exception {
+ int numTrips = 3;
+ long expectedWaitTime = (long) (impl.getWaitBase() +
+ impl.getWaitMult() * Math.pow(2, impl.getSkepticLevel() + numTrips));
+ long expectedGoodTime = (long) (impl.getGoodBase() +
+ impl.getGoodMult() * Math.pow(2, impl.getSkepticLevel() + numTrips));
+ long expectedSkepticLevel = impl.getSkepticLevel() + numTrips;
+
+ for (int i = 0; i < numTrips; i++) {
+ try {
+ impl.invoke(new FailingCallable("broken"));
+ } catch (Exception e) {
+
+ }
+ Thread.sleep(impl.getWaitTime() + 100);
+ }
+
+ assertEquals(expectedWaitTime, impl.getWaitTime());
+ assertEquals(expectedGoodTime, impl.getGoodTime());
+ assertEquals(expectedSkepticLevel, impl.getSkepticLevel());
+ }
+
+ @Test
+ public void testSkepticLevelStopsAtMaxLevel() throws Exception {
+ long expectedWaitTime = (long) (impl.getWaitBase() +
+ impl.getWaitMult() * Math.pow(2, impl.getMaxLevel()));
+ long expectedGoodTime = (long) (impl.getGoodBase() +
+ impl.getGoodMult() * Math.pow(2, impl.getMaxLevel()));
+ long expectedSkepticLevel = impl.getMaxLevel();
+ impl.skepticLevel.set(impl.getMaxLevel());
+ impl.updateTimers();
+
+ try {
+ impl.invoke(new FailingCallable("broken"));
+ } catch (Exception e) {
+
+ }
+
+ assertEquals(expectedWaitTime, impl.getWaitTime());
+ assertEquals(expectedGoodTime, impl.getGoodTime());
+ assertEquals(expectedSkepticLevel, impl.getSkepticLevel());
+ }
+
+ @Test
+ public void testDecreaseSkepticLevel() throws Exception {
+ impl.setGoodBase(100L);
+ impl.setWaitBase(100L);
+ impl.updateTimers();
+
+ long expectedWaitTime = impl.getWaitTime();
+ long expectedGoodTime = impl.getGoodTime();
+ long expectedSkepticLevel = impl.getSkepticLevel();
+
+ try {
+ impl.invoke(new FailingCallable("broken"));
+ } catch (Exception e) {
+
+ }
+ Thread.sleep(impl.getWaitTime() + 100);
+ Thread.sleep(impl.getGoodTime() + 100);
+
+ assertEquals(Status.UP, impl.getStatus());
+ assertEquals(expectedWaitTime, impl.getWaitTime());
+ assertEquals(expectedGoodTime, impl.getGoodTime());
+ assertEquals(expectedSkepticLevel, impl.getSkepticLevel());
+ }
+
+ // test bad ping - not sure how?
+
+ private class FailingCallable implements Callable {
+
+ private final String exceptionMessage;
+
+ public FailingCallable(String exceptionMessage) {
+ this.exceptionMessage = exceptionMessage;
+ }
+
+ Exception causeException = new Exception("The Cause");
+
+ public Object call() throws Exception {
+ throw new Exception(exceptionMessage, causeException);
+ }
+ }
+
+}
diff --git a/jrugged-examples/src/main/java/org/fishwife/jrugged/examples/webapp/InterceptCircuitBreakerExample.java b/jrugged-examples/src/main/java/org/fishwife/jrugged/examples/webapp/InterceptCircuitBreakerExample.java
index 45f6cb57..cc59b861 100644
--- a/jrugged-examples/src/main/java/org/fishwife/jrugged/examples/webapp/InterceptCircuitBreakerExample.java
+++ b/jrugged-examples/src/main/java/org/fishwife/jrugged/examples/webapp/InterceptCircuitBreakerExample.java
@@ -50,7 +50,7 @@ public void setResponseTweaker(BreakerResponseTweaker breakerResponseTweaker) {
@RequestMapping("/interceptCircuitBreaker")
public ModelAndView viewMain(HttpServletRequest request, HttpServletResponse response) throws Exception {
int delayedFor = breakerResponseTweaker.delay();
- ModelAndView view = new ModelAndView("interceptBreaker");
+ ModelAndView view = new ModelAndView("interceptCircuitBreaker");
view.addObject("delay", new Integer(delayedFor));
return view;
}
diff --git a/jrugged-examples/src/main/java/org/fishwife/jrugged/examples/webapp/InterceptSkepticBreakerExample.java b/jrugged-examples/src/main/java/org/fishwife/jrugged/examples/webapp/InterceptSkepticBreakerExample.java
new file mode 100644
index 00000000..2eba5e63
--- /dev/null
+++ b/jrugged-examples/src/main/java/org/fishwife/jrugged/examples/webapp/InterceptSkepticBreakerExample.java
@@ -0,0 +1,83 @@
+/* InterceptSkepticBreakerExample.java
+ *
+ * Copyright 2009-2015 Comcast Interactive Media, LLC.
+ *
+ * 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.fishwife.jrugged.examples.webapp;
+
+import org.fishwife.jrugged.examples.BreakerResponseTweaker;
+import org.fishwife.jrugged.spring.SkepticBreakerBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.servlet.ModelAndView;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.lang.reflect.Method;
+
+@Controller()
+public class InterceptSkepticBreakerExample {
+
+ @Autowired
+ private SkepticBreakerBean skepticBreakerBean;
+ public SkepticBreakerBean getSkepticBreakerBean() {
+ return skepticBreakerBean;
+ }
+ public void setSkepticBreakerBean(SkepticBreakerBean skepticBreakerBean) {
+ this.skepticBreakerBean = skepticBreakerBean;
+ }
+
+ @Autowired
+ private BreakerResponseTweaker breakerResponseTweaker;
+ public BreakerResponseTweaker getResponseTweaker() {
+ return breakerResponseTweaker;
+ }
+ public void setResponseTweaker(BreakerResponseTweaker breakerResponseTweaker) {
+ this.breakerResponseTweaker = breakerResponseTweaker;
+ }
+
+ @RequestMapping("/interceptSkepticBreaker")
+ public ModelAndView viewMain(HttpServletRequest request, HttpServletResponse response) throws Exception {
+ int delayedFor = breakerResponseTweaker.delay();
+ ModelAndView view = new ModelAndView("interceptSkepticBreaker");
+ view.addObject("delay", new Integer(delayedFor));
+ return view;
+ }
+
+ @RequestMapping("/interceptSkepticBreaker/stats")
+ public ModelAndView viewPerformanceMonitor(HttpServletRequest request,
+ HttpServletResponse response) throws Exception {
+ final StringBuilder sb = new StringBuilder();
+
+ // Go through all methods and invoke those with ManagedAttribute
+ // marker annotations
+ Method[] methods = skepticBreakerBean.getClass().getMethods();
+ for (Method monitorMethod : methods) {
+ if (monitorMethod.getName().startsWith("get")) {
+ sb.append(
+ String.format("\t%s: %s\n",
+ monitorMethod.getName().substring(3),
+ monitorMethod.invoke(skepticBreakerBean, new Object[] {})
+ )
+ );
+ }
+ }
+ sb.append("\n");
+
+ response.setContentType("text/plain");
+ response.getWriter().println(sb.toString());
+ return null;
+ }
+}
diff --git a/jrugged-examples/src/main/resources/applicationContext.xml b/jrugged-examples/src/main/resources/applicationContext.xml
index b9600aa4..42b3fbee 100644
--- a/jrugged-examples/src/main/resources/applicationContext.xml
+++ b/jrugged-examples/src/main/resources/applicationContext.xml
@@ -86,15 +86,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
circuitBreakerInterceptor
+
+
+
+
+
+ skepticBreakerInterceptor
+
+
+
+
+
+ JRugged Examples - Interceptor SkepticBreaker
+
+
+
+
+ JRugged Examples - Interceptor SkepticBreaker
+
+ The response for this page was delayed by <%= request.getAttribute("delay") %> ms.
+
+ After loading this page a few times take a look at the breaker stats .
+
+
+
+
diff --git a/jrugged-httpclient/src/main/java/org/fishwife/jrugged/httpclient/FailureHandlingHttpClient.java b/jrugged-httpclient/src/main/java/org/fishwife/jrugged/httpclient/FailureHandlingHttpClient.java
index b3ccd133..6eebde30 100644
--- a/jrugged-httpclient/src/main/java/org/fishwife/jrugged/httpclient/FailureHandlingHttpClient.java
+++ b/jrugged-httpclient/src/main/java/org/fishwife/jrugged/httpclient/FailureHandlingHttpClient.java
@@ -23,7 +23,7 @@
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.protocol.HttpContext;
-import org.fishwife.jrugged.CircuitBreakerException;
+import org.fishwife.jrugged.BreakerException;
public class FailureHandlingHttpClient extends AbstractHttpClientDecorator {
@@ -37,7 +37,7 @@ public HttpResponse execute(HttpHost host, HttpRequest req, HttpContext ctx)
return backend.execute(host, req, ctx);
} catch (UnsuccessfulResponseException ure) {
return ure.getResponse();
- } catch (CircuitBreakerException cbe) {
+ } catch (BreakerException cbe) {
throw new IOException(cbe);
}
}
diff --git a/jrugged-httpclient/src/test/java/org/fishwife/jrugged/httpclient/TestFailureHandlingHttpClient.java b/jrugged-httpclient/src/test/java/org/fishwife/jrugged/httpclient/TestFailureHandlingHttpClient.java
index 0d417cbf..e0993366 100644
--- a/jrugged-httpclient/src/test/java/org/fishwife/jrugged/httpclient/TestFailureHandlingHttpClient.java
+++ b/jrugged-httpclient/src/test/java/org/fishwife/jrugged/httpclient/TestFailureHandlingHttpClient.java
@@ -31,7 +31,7 @@
import org.apache.http.message.BasicHttpResponse;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;
-import org.fishwife.jrugged.CircuitBreakerException;
+import org.fishwife.jrugged.BreakerException;
import org.junit.Before;
import org.junit.Test;
@@ -79,7 +79,7 @@ public void returnsEnclosedResponseOnUnsuccessfulException() throws Exception {
@Test
public void throwsIOExceptionForCircuitBreakerException() throws Exception {
expect(mockBackend.execute(host, req, ctx))
- .andThrow(new CircuitBreakerException());
+ .andThrow(new BreakerException());
replay(mockBackend);
try {
impl.execute(host, req, ctx);
diff --git a/jrugged-spring/src/main/java/org/fishwife/jrugged/spring/AsyncCircuitBreaker.java b/jrugged-spring/src/main/java/org/fishwife/jrugged/spring/AsyncCircuitBreaker.java
index 2853ba52..de246e3f 100644
--- a/jrugged-spring/src/main/java/org/fishwife/jrugged/spring/AsyncCircuitBreaker.java
+++ b/jrugged-spring/src/main/java/org/fishwife/jrugged/spring/AsyncCircuitBreaker.java
@@ -2,8 +2,8 @@
import java.util.concurrent.Callable;
-import org.fishwife.jrugged.CircuitBreakerException;
-import org.fishwife.jrugged.CircuitBreakerExceptionMapper;
+import org.fishwife.jrugged.BreakerException;
+import org.fishwife.jrugged.BreakerExceptionMapper;
import org.fishwife.jrugged.DefaultFailureInterpreter;
import org.fishwife.jrugged.FailureInterpreter;
import org.springframework.util.concurrent.ListenableFuture;
@@ -14,13 +14,13 @@ public class AsyncCircuitBreaker extends org.fishwife.jrugged.CircuitBreaker {
/** Creates a {@link AsyncCircuitBreaker} with a {@link
* DefaultFailureInterpreter} and the default "tripped" exception
- * behavior (throwing a {@link CircuitBreakerException}). */
+ * behavior (throwing a {@link BreakerException}). */
public AsyncCircuitBreaker() {
}
/** Creates a {@link AsyncCircuitBreaker} with a {@link
* DefaultFailureInterpreter} and the default "tripped" exception
- * behavior (throwing a {@link CircuitBreakerException}).
+ * behavior (throwing a {@link BreakerException}).
* @param name the name for the {@link AsyncCircuitBreaker}.
*/
public AsyncCircuitBreaker(String name) {
@@ -29,7 +29,7 @@ public AsyncCircuitBreaker(String name) {
/** Creates a {@link AsyncCircuitBreaker} with the specified {@link
* FailureInterpreter} and the default "tripped" exception
- * behavior (throwing a {@link CircuitBreakerException}).
+ * behavior (throwing a {@link BreakerException}).
* @param fi the FailureInterpreter to use when
* determining whether a specific failure ought to cause the
* breaker to trip
@@ -40,7 +40,7 @@ public AsyncCircuitBreaker(FailureInterpreter fi) {
/** Creates a {@link AsyncCircuitBreaker} with the specified {@link
* FailureInterpreter} and the default "tripped" exception
- * behavior (throwing a {@link CircuitBreakerException}).
+ * behavior (throwing a {@link BreakerException}).
* @param name the name for the {@link AsyncCircuitBreaker}.
* @param fi the FailureInterpreter to use when
* determining whether a specific failure ought to cause the
@@ -53,29 +53,29 @@ public AsyncCircuitBreaker(String name, FailureInterpreter fi) {
/** Creates a {@link AsyncCircuitBreaker} with a {@link
* DefaultFailureInterpreter} and using the supplied {@link
- * CircuitBreakerExceptionMapper} when client calls are made
+ * BreakerExceptionMapper} when client calls are made
* while the breaker is tripped.
* @param name the name for the {@link AsyncCircuitBreaker}.
* @param mapper helper used to translate a {@link
- * CircuitBreakerException} into an application-specific one */
- public AsyncCircuitBreaker(String name, CircuitBreakerExceptionMapper extends Exception> mapper) {
+ * BreakerException} into an application-specific one */
+ public AsyncCircuitBreaker(String name, BreakerExceptionMapper extends Exception> mapper) {
this.name = name;
exceptionMapper = mapper;
}
/** Creates a {@link AsyncCircuitBreaker} with the provided {@link
* FailureInterpreter} and using the provided {@link
- * CircuitBreakerExceptionMapper} when client calls are made
+ * BreakerExceptionMapper} when client calls are made
* while the breaker is tripped.
* @param name the name for the {@link AsyncCircuitBreaker}.
* @param fi the FailureInterpreter to use when
* determining whether a specific failure ought to cause the
* breaker to trip
* @param mapper helper used to translate a {@link
- * CircuitBreakerException} into an application-specific one */
+ * BreakerException} into an application-specific one */
public AsyncCircuitBreaker(String name,
FailureInterpreter fi,
- CircuitBreakerExceptionMapper extends Exception> mapper) {
+ BreakerExceptionMapper extends Exception> mapper) {
this.name = name;
failureInterpreter = fi;
exceptionMapper = mapper;
@@ -84,7 +84,7 @@ public AsyncCircuitBreaker(String name,
/** Wrap the given service call with the {@link AsyncCircuitBreaker} protection logic.
* @param callable the {@link java.util.concurrent.Callable} to attempt
* @return {@link ListenableFuture} of whatever callable would return
- * @throws org.fishwife.jrugged.CircuitBreakerException if the
+ * @throws org.fishwife.jrugged.BreakerException if the
* breaker was OPEN or HALF_CLOSED and this attempt wasn't the
* reset attempt
* @throws Exception if the {@link org.fishwife.jrugged.CircuitBreaker} is in OPEN state
@@ -111,7 +111,7 @@ public void onFailure(Throwable ex) {
if (!byPass) {
if (!allowRequest()) {
- throw mapException(new CircuitBreakerException());
+ throw mapException(new BreakerException());
}
try {
diff --git a/jrugged-spring/src/main/java/org/fishwife/jrugged/spring/AsyncSkepticBreaker.java b/jrugged-spring/src/main/java/org/fishwife/jrugged/spring/AsyncSkepticBreaker.java
new file mode 100644
index 00000000..c8cdf9b0
--- /dev/null
+++ b/jrugged-spring/src/main/java/org/fishwife/jrugged/spring/AsyncSkepticBreaker.java
@@ -0,0 +1,131 @@
+package org.fishwife.jrugged.spring;
+
+import java.util.concurrent.Callable;
+
+import org.fishwife.jrugged.BreakerException;
+import org.fishwife.jrugged.BreakerExceptionMapper;
+import org.fishwife.jrugged.DefaultFailureInterpreter;
+import org.fishwife.jrugged.FailureInterpreter;
+import org.springframework.util.concurrent.ListenableFuture;
+import org.springframework.util.concurrent.ListenableFutureCallback;
+import org.springframework.util.concurrent.SettableListenableFuture;
+
+public class AsyncSkepticBreaker extends org.fishwife.jrugged.SkepticBreaker {
+
+ /** Creates a {@link AsyncSkepticBreaker} with a {@link
+ * DefaultFailureInterpreter} and the default "tripped" exception
+ * behavior (throwing a {@link BreakerException}). */
+ public AsyncSkepticBreaker() {
+ }
+
+ /** Creates a {@link AsyncSkepticBreaker} with a {@link
+ * DefaultFailureInterpreter} and the default "tripped" exception
+ * behavior (throwing a {@link BreakerException}).
+ * @param name the name for the {@link AsyncSkepticBreaker}.
+ */
+ public AsyncSkepticBreaker(String name) {
+ this.name = name;
+ }
+
+ /** Creates a {@link AsyncSkepticBreaker} with the specified {@link
+ * FailureInterpreter} and the default "tripped" exception
+ * behavior (throwing a {@link BreakerException}).
+ * @param fi the FailureInterpreter to use when
+ * determining whether a specific failure ought to cause the
+ * breaker to trip
+ */
+ public AsyncSkepticBreaker(FailureInterpreter fi) {
+ failureInterpreter = fi;
+ }
+
+ /** Creates a {@link AsyncSkepticBreaker} with the specified {@link
+ * FailureInterpreter} and the default "tripped" exception
+ * behavior (throwing a {@link BreakerException}).
+ * @param name the name for the {@link AsyncSkepticBreaker}.
+ * @param fi the FailureInterpreter to use when
+ * determining whether a specific failure ought to cause the
+ * breaker to trip
+ */
+ public AsyncSkepticBreaker(String name, FailureInterpreter fi) {
+ this.name = name;
+ failureInterpreter = fi;
+ }
+
+ /** Creates a {@link AsyncSkepticBreaker} with a {@link
+ * DefaultFailureInterpreter} and using the supplied {@link
+ * BreakerExceptionMapper} when client calls are made
+ * while the breaker is tripped.
+ * @param name the name for the {@link AsyncSkepticBreaker}.
+ * @param mapper helper used to translate a {@link
+ * BreakerException} into an application-specific one */
+ public AsyncSkepticBreaker(String name, BreakerExceptionMapper extends Exception> mapper) {
+ this.name = name;
+ exceptionMapper = mapper;
+ }
+
+ /** Creates a {@link AsyncSkepticBreaker} with the provided {@link
+ * FailureInterpreter} and using the provided {@link
+ * BreakerExceptionMapper} when client calls are made
+ * while the breaker is tripped.
+ * @param name the name for the {@link AsyncSkepticBreaker}.
+ * @param fi the FailureInterpreter to use when
+ * determining whether a specific failure ought to cause the
+ * breaker to trip
+ * @param mapper helper used to translate a {@link
+ * BreakerException} into an application-specific one */
+ public AsyncSkepticBreaker(String name,
+ FailureInterpreter fi,
+ BreakerExceptionMapper extends Exception> mapper) {
+ this.name = name;
+ failureInterpreter = fi;
+ exceptionMapper = mapper;
+ }
+
+ /** Wrap the given service call with the {@link AsyncSkepticBreaker} protection logic.
+ * @param callable the {@link java.util.concurrent.Callable} to attempt
+ * @return {@link ListenableFuture} of whatever callable would return
+ * @throws org.fishwife.jrugged.BreakerException if the
+ * breaker was OPEN or HALF_CLOSED and this attempt wasn't the
+ * reset attempt
+ * @throws Exception if the {@link org.fishwife.jrugged.CircuitBreaker} is in OPEN state
+ */
+ public ListenableFuture invokeAsync(Callable> callable) throws Exception {
+
+ final SettableListenableFuture response = new SettableListenableFuture();
+ ListenableFutureCallback callback = new ListenableFutureCallback() {
+ @Override
+ public void onSuccess(T result) {
+ close();
+ response.set(result);
+ }
+
+ @Override
+ public void onFailure(Throwable ex) {
+ try {
+ handleFailure(ex);
+ } catch (Exception e) {
+ response.setException(e);
+ }
+ }
+ };
+
+ if (!byPass) {
+ if (!allowRequest()) {
+ throw mapException(new BreakerException());
+ }
+
+ try {
+ callable.call().addCallback(callback);
+ return response;
+ } catch (Throwable cause) {
+ // This shouldn't happen because Throwables are handled in the async onFailure callback
+ handleFailure(cause);
+ }
+ throw new IllegalStateException("not possible");
+ }
+ else {
+ callable.call().addCallback(callback);
+ return response;
+ }
+ }
+}
diff --git a/jrugged-spring/src/main/java/org/fishwife/jrugged/spring/CircuitBreakerBean.java b/jrugged-spring/src/main/java/org/fishwife/jrugged/spring/CircuitBreakerBean.java
index 5b177261..c8ac3220 100644
--- a/jrugged-spring/src/main/java/org/fishwife/jrugged/spring/CircuitBreakerBean.java
+++ b/jrugged-spring/src/main/java/org/fishwife/jrugged/spring/CircuitBreakerBean.java
@@ -15,7 +15,7 @@
package org.fishwife.jrugged.spring;
import org.fishwife.jrugged.CircuitBreaker;
-import org.fishwife.jrugged.CircuitBreakerExceptionMapper;
+import org.fishwife.jrugged.BreakerExceptionMapper;
import org.fishwife.jrugged.DefaultFailureInterpreter;
import org.fishwife.jrugged.FailureInterpreter;
import org.springframework.beans.factory.InitializingBean;
@@ -43,14 +43,14 @@ public void afterPropertiesSet() throws Exception {
/**
* Creates a {@link CircuitBreakerBean} with a
* {@link DefaultFailureInterpreter} and the default "tripped" exception
- * behavior (throwing a {@link org.fishwife.jrugged.CircuitBreakerException}).
+ * behavior (throwing a {@link org.fishwife.jrugged.BreakerException}).
*/
public CircuitBreakerBean() { super(); }
/**
* Creates a {@link CircuitBreakerBean} with a
* {@link DefaultFailureInterpreter} and the default "tripped" exception
- * behavior (throwing a {@link org.fishwife.jrugged.CircuitBreakerException}).
+ * behavior (throwing a {@link org.fishwife.jrugged.BreakerException}).
* @param name the name for the {@link CircuitBreakerBean}
*/
public CircuitBreakerBean(String name) { super(name); }
@@ -58,7 +58,7 @@ public void afterPropertiesSet() throws Exception {
/**
* Creates a {@link CircuitBreakerBean} with the specified
* {@link FailureInterpreter} and the default "tripped" exception
- * behavior (throwing a {@link org.fishwife.jrugged.CircuitBreakerException}).
+ * behavior (throwing a {@link org.fishwife.jrugged.BreakerException}).
* @param fi the FailureInterpreter to use when
* determining whether a specific failure ought to cause the
* breaker to trip
@@ -70,7 +70,7 @@ public CircuitBreakerBean(FailureInterpreter fi) {
/**
* Creates a {@link CircuitBreakerBean} with the specified
* {@link FailureInterpreter} and the default "tripped" exception
- * behavior (throwing a {@link org.fishwife.jrugged.CircuitBreakerException}).
+ * behavior (throwing a {@link org.fishwife.jrugged.BreakerException}).
* @param name the name for the {@link CircuitBreakerBean}
* @param fi the FailureInterpreter to use when
* determining whether a specific failure ought to cause the
@@ -83,20 +83,20 @@ public CircuitBreakerBean(String name, FailureInterpreter fi) {
/**
* Creates a {@link CircuitBreaker} with a {@link
* DefaultFailureInterpreter} and using the supplied {@link
- * CircuitBreakerExceptionMapper} when client calls are made
+ * BreakerExceptionMapper} when client calls are made
* while the breaker is tripped.
* @param name the name for the {@link CircuitBreakerBean}
* @param mapper helper used to translate a {@link
- * org.fishwife.jrugged.CircuitBreakerException} into an application-specific one
+ * org.fishwife.jrugged.BreakerException} into an application-specific one
*/
- public CircuitBreakerBean(String name, CircuitBreakerExceptionMapper extends Exception> mapper) {
+ public CircuitBreakerBean(String name, BreakerExceptionMapper extends Exception> mapper) {
super(name, mapper);
}
/**
* Creates a {@link CircuitBreaker} with the provided {@link
* FailureInterpreter} and using the provided {@link
- * CircuitBreakerExceptionMapper} when client calls are made
+ * BreakerExceptionMapper} when client calls are made
* while the breaker is tripped.
*
* @param name the name for the {@link CircuitBreakerBean}
@@ -104,10 +104,10 @@ public CircuitBreakerBean(String name, CircuitBreakerExceptionMapper extends E
* determining whether a specific failure ought to cause the
* breaker to trip
* @param mapper helper used to translate a {@link
- * org.fishwife.jrugged.CircuitBreakerException} into an application-specific one
+ * org.fishwife.jrugged.BreakerException} into an application-specific one
*/
public CircuitBreakerBean(String name, FailureInterpreter fi,
- CircuitBreakerExceptionMapper extends Exception> mapper) {
+ BreakerExceptionMapper extends Exception> mapper) {
super(name, fi, mapper);
}
diff --git a/jrugged-spring/src/main/java/org/fishwife/jrugged/spring/CircuitBreakerBeanFactory.java b/jrugged-spring/src/main/java/org/fishwife/jrugged/spring/CircuitBreakerBeanFactory.java
index 9a0a4377..0410435a 100644
--- a/jrugged-spring/src/main/java/org/fishwife/jrugged/spring/CircuitBreakerBeanFactory.java
+++ b/jrugged-spring/src/main/java/org/fishwife/jrugged/spring/CircuitBreakerBeanFactory.java
@@ -21,7 +21,7 @@
import org.fishwife.jrugged.CircuitBreaker;
import org.fishwife.jrugged.CircuitBreakerConfig;
-import org.fishwife.jrugged.CircuitBreakerFactory;
+import org.fishwife.jrugged.BreakerFactory;
import org.fishwife.jrugged.DefaultFailureInterpreter;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
@@ -33,7 +33,7 @@
* them. If a {@link MBeanExportOperations} is set, then the CircuitBreakerBean will be
* automatically exported as a JMX MBean.
*/
-public class CircuitBreakerBeanFactory extends CircuitBreakerFactory implements InitializingBean {
+public class CircuitBreakerBeanFactory extends BreakerFactory implements InitializingBean {
@Autowired(required = false)
private MBeanExportOperations mBeanExportOperations;
diff --git a/jrugged-spring/src/main/java/org/fishwife/jrugged/spring/SkepticBreakerBean.java b/jrugged-spring/src/main/java/org/fishwife/jrugged/spring/SkepticBreakerBean.java
new file mode 100644
index 00000000..527e3e0f
--- /dev/null
+++ b/jrugged-spring/src/main/java/org/fishwife/jrugged/spring/SkepticBreakerBean.java
@@ -0,0 +1,344 @@
+/* Copyright 2009-2015 Comcast Interactive Media, LLC.
+
+ 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.fishwife.jrugged.spring;
+
+import org.fishwife.jrugged.CircuitBreaker;
+import org.fishwife.jrugged.BreakerExceptionMapper;
+import org.fishwife.jrugged.DefaultFailureInterpreter;
+import org.fishwife.jrugged.FailureInterpreter;
+import org.fishwife.jrugged.SkepticBreaker;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.jmx.export.annotation.ManagedAttribute;
+import org.springframework.jmx.export.annotation.ManagedOperation;
+import org.springframework.jmx.export.annotation.ManagedResource;
+
+/**
+ * This is basically a {@link CircuitBreaker} that adds JMX
+ * annotations to some of the methods so that the core library
+ * doesn't have to depend on spring-context.
+ */
+@ManagedResource
+public class SkepticBreakerBean extends SkepticBreaker implements InitializingBean {
+
+ private boolean disabledAtStart = false;
+
+ /**
+ * {@inheritDoc}
+ */
+ public void afterPropertiesSet() throws Exception {
+ if (disabledAtStart) tripHard();
+ }
+
+ /**
+ * Creates a {@link SkepticBreakerBean} with a
+ * {@link DefaultFailureInterpreter} and the default "tripped" exception
+ * behavior (throwing a {@link org.fishwife.jrugged.BreakerException}).
+ */
+ public SkepticBreakerBean() { super(); }
+
+ /**
+ * Creates a {@link SkepticBreakerBean} with a
+ * {@link DefaultFailureInterpreter} and the default "tripped" exception
+ * behavior (throwing a {@link org.fishwife.jrugged.BreakerException}).
+ * @param name the name for the {@link SkepticBreakerBean}
+ */
+ public SkepticBreakerBean(String name) { super(name); }
+
+ /**
+ * Creates a {@link SkepticBreakerBean} with the specified
+ * {@link FailureInterpreter} and the default "tripped" exception
+ * behavior (throwing a {@link org.fishwife.jrugged.BreakerException}).
+ * @param fi the FailureInterpreter to use when
+ * determining whether a specific failure ought to cause the
+ * breaker to trip
+ */
+ public SkepticBreakerBean(FailureInterpreter fi) {
+ super(fi);
+ }
+
+ /**
+ * Creates a {@link SkepticBreakerBean} with the specified
+ * {@link FailureInterpreter} and the default "tripped" exception
+ * behavior (throwing a {@link org.fishwife.jrugged.BreakerException}).
+ * @param name the name for the {@link SkepticBreakerBean}
+ * @param fi the FailureInterpreter to use when
+ * determining whether a specific failure ought to cause the
+ * breaker to trip
+ */
+ public SkepticBreakerBean(String name, FailureInterpreter fi) {
+ super(name, fi);
+ }
+
+ /**
+ * Creates a {@link CircuitBreaker} with a {@link
+ * DefaultFailureInterpreter} and using the supplied {@link
+ * BreakerExceptionMapper} when client calls are made
+ * while the breaker is tripped.
+ * @param name the name for the {@link SkepticBreakerBean}
+ * @param mapper helper used to translate a {@link
+ * org.fishwife.jrugged.BreakerException} into an application-specific one
+ */
+ public SkepticBreakerBean(String name, BreakerExceptionMapper extends Exception> mapper) {
+ super(name, mapper);
+ }
+
+ /**
+ * Creates a {@link CircuitBreaker} with the provided {@link
+ * FailureInterpreter} and using the provided {@link
+ * BreakerExceptionMapper} when client calls are made
+ * while the breaker is tripped.
+ *
+ * @param name the name for the {@link SkepticBreakerBean}
+ * @param fi the FailureInterpreter to use when
+ * determining whether a specific failure ought to cause the
+ * breaker to trip
+ * @param mapper helper used to translate a {@link
+ * org.fishwife.jrugged.BreakerException} into an application-specific one
+ */
+ public SkepticBreakerBean(String name, FailureInterpreter fi,
+ BreakerExceptionMapper extends Exception> mapper) {
+ super(name, fi, mapper);
+ }
+
+ /**
+ * Manually trips the CircuitBreaker until {@link #reset()} is invoked.
+ */
+ @ManagedOperation
+ @Override
+ public void tripHard() {
+ super.tripHard();
+ }
+
+ /**
+ * Manually trips the CircuitBreaker until {@link #reset()} is invoked.
+ */
+ @ManagedOperation
+ @Override
+ public void trip() {
+ super.trip();
+ }
+
+ /**
+ * Returns the last time the breaker tripped OPEN, measured in
+ * milliseconds since the Epoch.
+ * @return long the last failure time
+ */
+ @ManagedAttribute
+ @Override
+ public long getLastTripTime() {
+ return super.getLastTripTime();
+ }
+
+ /**
+ * Returns the number of times the breaker has tripped OPEN during
+ * its lifetime.
+ * @return long the number of times the circuit breaker tripped
+ */
+ @ManagedAttribute
+ @Override
+ public long getTripCount() {
+ return super.getTripCount();
+ }
+
+ /**
+ * When called with true - causes the {@link CircuitBreaker} to byPass
+ * its functionality allowing requests to be executed unmolested
+ * until the CircuitBreaker is reset or the byPass
+ * is manually set to false.
+ */
+ @ManagedAttribute
+ @Override
+ public void setByPassState(boolean b) {
+ super.setByPassState(b);
+ }
+
+ /**
+ * Get the current state of the {@link CircuitBreaker} byPass
+ *
+ * @return boolean the byPass flag's current value
+ */
+ @ManagedAttribute
+ @Override
+ public boolean getByPassState() {
+ return super.getByPassState();
+ }
+
+ /**
+ * Manually set the breaker to be reset and ready for use. This
+ * is only useful after a manual trip otherwise the breaker will
+ * trip automatically again if the service is still unavailable.
+ * Just like a real breaker. WOOT!!!
+ */
+ @ManagedOperation
+ @Override
+ public void reset() {
+ super.reset();
+ }
+
+
+ @ManagedAttribute
+ @Override
+ public long getWaitMult() {
+ return super.getWaitMult();
+ }
+
+ @ManagedAttribute
+ @Override
+ public void setWaitMult(long l) {
+ super.setWaitMult(l);
+ }
+
+ @ManagedAttribute
+ @Override
+ public long getGoodMult() {
+ return super.getGoodMult();
+ }
+
+ @ManagedAttribute
+ @Override
+ public void setGoodMult(long l) {
+ super.setGoodMult(l);
+ }
+
+ @ManagedAttribute
+ @Override
+ public long getWaitBase() {
+ return super.getWaitBase();
+ }
+
+ @ManagedAttribute
+ @Override
+ public void setWaitBase(long l) {
+ super.setWaitBase(l);
+ }
+
+ @ManagedAttribute
+ @Override
+ public long getGoodBase() {
+ return super.getGoodBase();
+ }
+
+ @ManagedAttribute
+ @Override
+ public void setGoodBase(long l) {
+ super.setGoodBase(l);
+ }
+
+ @ManagedAttribute
+ @Override
+ public long getMaxLevel() {
+ return super.getMaxLevel();
+ }
+
+ @ManagedAttribute
+ @Override
+ public void setMaxLevel(long l) {
+ super.setMaxLevel(l);
+ }
+
+ /**
+ * Returns a {@link String} representation of the breaker's
+ * status; potentially useful for exposing to monitoring software.
+ *
+ * @return String which is "GREEN" if
+ * the breaker is CLOSED; "YELLOW" if the breaker
+ * is HALF_CLOSED; and "RED" if the breaker is
+ * OPEN (tripped).
+ */
+ @ManagedAttribute
+ @Override
+ public String getHealthCheck() { return super.getHealthCheck(); }
+
+ /**
+ * Gets the failure tolerance limit for the {@link DefaultFailureInterpreter} that
+ * comes with a {@link CircuitBreaker} by default.
+ *
+ * @see DefaultFailureInterpreter
+ *
+ * @return the number of tolerated failures in a window
+ */
+ @ManagedAttribute
+ public int getLimit() {
+ return ((DefaultFailureInterpreter) super.getFailureInterpreter()).getLimit();
+ }
+
+ /**
+ * Specifies the failure tolerance limit for the {@link
+ * DefaultFailureInterpreter} that comes with a {@link
+ * CircuitBreaker} by default.
+ *
+ * @see DefaultFailureInterpreter
+ *
+ * @param limit the number of tolerated failures in a window
+ */
+ @ManagedAttribute
+ @Override
+ public void setLimit(int limit) {
+ ((DefaultFailureInterpreter) super.getFailureInterpreter()).setLimit(limit);
+ }
+
+ /**
+ * Gets the tolerance window in milliseconds for the {@link DefaultFailureInterpreter}
+ * that comes with a {@link CircuitBreaker} by default.
+ *
+ * @see DefaultFailureInterpreter
+ *
+ * @return length of the window in milliseconds
+ */
+ @ManagedAttribute
+ public long getWindowMillis() {
+ return ((DefaultFailureInterpreter) super.getFailureInterpreter()).getWindowMillis();
+ }
+
+ /**
+ * Specifies the tolerance window in milliseconds for the {@link
+ * DefaultFailureInterpreter} that comes with a {@link
+ * CircuitBreaker} by default.
+ *
+ * @see DefaultFailureInterpreter
+ *
+ * @param windowMillis length of the window in milliseconds
+ */
+ @ManagedAttribute
+ @Override
+ public void setWindowMillis(long windowMillis) {
+ super.setWindowMillis(windowMillis);
+ }
+
+ /**
+ * returns a {@link String} representation of the breaker's
+ * last known exception that caused it to OPEN (i.e. when the breaker
+ * opens, it will record the specific exception that caused it to open)
+ *
+ * @return String which is the full stack trace.
+ */
+ @ManagedAttribute
+ @Override
+ public String getTripExceptionAsString() {
+ return super.getTripExceptionAsString();
+ }
+
+ /**
+ * Specifies whether the associated CircuitBreaker should be tripped
+ * at startup time.
+ *
+ * @param b true if the CircuitBreaker should start
+ * open (tripped); false if the CircuitBreaker should start
+ * closed (not tripped).
+ */
+ public void setDisabled(boolean b) {
+ disabledAtStart = b;
+ }
+}
diff --git a/jrugged-spring/src/main/java/org/fishwife/jrugged/spring/SkepticBreakerBeanFactory.java b/jrugged-spring/src/main/java/org/fishwife/jrugged/spring/SkepticBreakerBeanFactory.java
new file mode 100644
index 00000000..2af8ce49
--- /dev/null
+++ b/jrugged-spring/src/main/java/org/fishwife/jrugged/spring/SkepticBreakerBeanFactory.java
@@ -0,0 +1,149 @@
+/* Copyright 2009-2015 Comcast Interactive Media, LLC.
+
+ 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.fishwife.jrugged.spring;
+
+import java.lang.reflect.Method;
+
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+
+import org.fishwife.jrugged.SkepticBreaker;
+import org.fishwife.jrugged.SkepticBreakerConfig;
+import org.fishwife.jrugged.BreakerFactory;
+import org.fishwife.jrugged.DefaultFailureInterpreter;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jmx.export.MBeanExportOperations;
+import org.springframework.jmx.export.MBeanExporter;
+
+/**
+ * Factory to create new {@link SkepticBreakerBean} instances and keep track of
+ * them. If a {@link MBeanExportOperations} is set, then the SkepticBreakerBean will be
+ * automatically exported as a JMX MBean.
+ */
+public class SkepticBreakerBeanFactory extends BreakerFactory implements InitializingBean {
+
+ @Autowired(required = false)
+ private MBeanExportOperations mBeanExportOperations;
+
+ private String packageScanBase;
+
+ /**
+ * {@inheritDoc}
+ */
+ public void afterPropertiesSet() {
+ buildAnnotatedSkepticBreakers();
+ }
+
+ /**
+ * Set the {@link MBeanExporter} to use to export {@link SkepticBreakerBean}
+ * instances as JMX MBeans.
+ * @param mBeanExporter the {@link MBeanExporter} to set.
+ */
+ @Deprecated
+ public void setMBeanExporter(MBeanExporter mBeanExporter) {
+ setMBeanExportOperations(mBeanExporter);
+ }
+
+ /**
+ * Set the {@link MBeanExportOperations} to use to export {@link SkepticBreakerBean}
+ * instances as JMX MBeans.
+ * @param mBeanExportOperations the {@link MBeanExportOperations} to set.
+ */
+ public void setMBeanExportOperations(MBeanExportOperations mBeanExportOperations) {
+ this.mBeanExportOperations = mBeanExportOperations;
+ }
+
+ /**
+ * If specified, SkepticBreakerBeanFactory will scan all classes
+ * under packageScanBase for methods with the
+ * {@link org.fishwife.jrugged.aspects.SkepticBreaker} annotation
+ * and initialize skepticbreakers for them.
+ *
+ * @param packageScanBase Where should the scan for annotations begin
+ */
+ public void setPackageScanBase(String packageScanBase) {
+ this.packageScanBase = packageScanBase;
+ }
+
+ /**
+ * If packageScanBase is defined will search packages for {@link org.fishwife.jrugged.aspects.SkepticBreaker}
+ * annotations and create skepticbreakers for them.
+ */
+ public void buildAnnotatedSkepticBreakers() {
+ if (packageScanBase != null) {
+ AnnotatedMethodScanner methodScanner = new AnnotatedMethodScanner();
+ for (Method m : methodScanner.findAnnotatedMethods(packageScanBase, org.fishwife.jrugged.aspects.SkepticBreaker.class)) {
+ org.fishwife.jrugged.aspects.SkepticBreaker skepticBreakerAnnotation = m.getAnnotation(org.fishwife.jrugged.aspects.SkepticBreaker.class);
+ DefaultFailureInterpreter dfi = new DefaultFailureInterpreter(skepticBreakerAnnotation.ignore(), skepticBreakerAnnotation.limit(), skepticBreakerAnnotation.windowMillis());
+ SkepticBreakerConfig config = new SkepticBreakerConfig(skepticBreakerAnnotation.goodBase(), skepticBreakerAnnotation.goodMult(), skepticBreakerAnnotation.waitBase(),
+ skepticBreakerAnnotation.waitMult(), skepticBreakerAnnotation.maxLevel(), dfi);
+ createSkepticBreaker(skepticBreakerAnnotation.name(), config);
+ }
+ }
+
+ }
+
+ /**
+ * Create a new {@link SkepticBreakerBean} and map it to the provided value.
+ * If the {@link MBeanExportOperations} is set, then the SkepticBreakerBean will be
+ * exported as a JMX MBean.
+ * If the SkepticBreaker already exists, then the existing instance is
+ * returned.
+ * @param name the value for the {@link org.fishwife.jrugged.SkepticBreaker}
+ * @param config the {@link org.fishwife.jrugged.SkepticBreakerConfig}
+ */
+ public synchronized SkepticBreaker createSkepticBreaker(String name, SkepticBreakerConfig config) {
+
+ SkepticBreaker skepticBreaker = findSkepticBreaker(name);
+
+ if (skepticBreaker == null) {
+ skepticBreaker = new SkepticBreakerBean(name);
+
+ configureSkepticBreaker(name, skepticBreaker, config);
+
+ if (mBeanExportOperations != null) {
+ ObjectName objectName;
+
+ try {
+ objectName = new ObjectName("org.fishwife.jrugged.spring:type=SkepticBreakerBean," + "name=" + name);
+ } catch (MalformedObjectNameException e) {
+ throw new IllegalArgumentException("Invalid MBean Name " + name, e);
+
+ }
+
+ mBeanExportOperations.registerManagedResource(skepticBreaker, objectName);
+ }
+
+ addSkepticBreakerToMap(name, skepticBreaker);
+ }
+
+ return skepticBreaker;
+ }
+
+ /**
+ * Find an existing {@link SkepticBreakerBean}
+ * @param name the value for the {@link SkepticBreakerBean}
+ * @return the found {@link SkepticBreakerBean}, or null if it is not found.
+ */
+ public SkepticBreakerBean findSkepticBreakerBean(String name) {
+ SkepticBreaker skepticBreaker = findSkepticBreaker(name);
+
+ if (skepticBreaker instanceof SkepticBreakerBean) {
+ return (SkepticBreakerBean) skepticBreaker;
+ }
+ return null;
+ }
+}
diff --git a/jrugged-spring/src/test/java/org/fishwife/jrugged/spring/TestCircuitBreakerBean.java b/jrugged-spring/src/test/java/org/fishwife/jrugged/spring/TestCircuitBreakerBean.java
index 98b0038c..72b81027 100644
--- a/jrugged-spring/src/test/java/org/fishwife/jrugged/spring/TestCircuitBreakerBean.java
+++ b/jrugged-spring/src/test/java/org/fishwife/jrugged/spring/TestCircuitBreakerBean.java
@@ -20,7 +20,7 @@
import java.util.concurrent.Callable;
-import org.fishwife.jrugged.CircuitBreakerException;
+import org.fishwife.jrugged.BreakerException;
import org.junit.Before;
import org.junit.Test;
@@ -59,7 +59,7 @@ public void canBeDisabled() throws Exception {
try {
impl.invoke(call);
fail("Should have thrown CircuitBreakerException");
- } catch (CircuitBreakerException cbe) {
+ } catch (BreakerException cbe) {
}
}
}