/*
* Copyright 2016 JBoss Inc
*
* 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 io.apiman.plugins.circuit_breaker;
import io.apiman.gateway.engine.beans.ApiRequest;
import io.apiman.gateway.engine.beans.ApiResponse;
import io.apiman.gateway.engine.beans.PolicyFailure;
import io.apiman.gateway.engine.beans.PolicyFailureType;
import io.apiman.gateway.engine.components.IPolicyFailureFactoryComponent;
import io.apiman.gateway.engine.policies.AbstractMappedPolicy;
import io.apiman.gateway.engine.policy.IPolicyChain;
import io.apiman.gateway.engine.policy.IPolicyContext;
import io.apiman.plugins.circuit_breaker.beans.CircuitBreakerConfigBean;
import java.util.HashMap;
import java.util.Map;
/**
* A policy that implements basic circuit breaker functionality.
*
* @author eric.wittmann@gmail.com
*/
public class CircuitBreakerPolicy extends AbstractMappedPolicy<CircuitBreakerConfigBean> {
private static final int BROKEN_CIRCUIT_FAILURE_CODE = 20001;
private final Map<CircuitKey, Circuit> circuits = new HashMap<CircuitKey, Circuit>();
/**
* Constructor.
*/
public CircuitBreakerPolicy() {
}
/**
* @see io.apiman.gateway.engine.policies.AbstractMappedPolicy#getConfigurationClass()
*/
@Override
protected Class<CircuitBreakerConfigBean> getConfigurationClass() {
return CircuitBreakerConfigBean.class;
}
/**
* @see io.apiman.gateway.engine.policies.AbstractMappedPolicy#doApply(io.apiman.gateway.engine.beans.ApiRequest, io.apiman.gateway.engine.policy.IPolicyContext, java.lang.Object, io.apiman.gateway.engine.policy.IPolicyChain)
*/
@Override
protected void doApply(ApiRequest request, IPolicyContext context, CircuitBreakerConfigBean config,
IPolicyChain<ApiRequest> chain) {
CircuitKey ckey = new CircuitKey(request.getApiOrgId(), request.getApiId(), request.getApiVersion());
Circuit circuit = circuits.get(ckey);
if (circuit == null) {
circuit = new Circuit(config.getLimit(), config.getWindow(), config.getReset());
circuits.put(ckey, circuit);
}
context.setAttribute(CircuitBreakerPolicy.class.getName() + ".circuit", circuit); //$NON-NLS-1$
// Is the circuit broken? If so, either immediately send a failure, or else
// if the circuit is ready to be reset (the reset time has elapsed) then try
// to reset the circuit by letting through the request and seeing what happens.
if (circuit.isBroken()) {
// Can the circuit possibly be reset? If so, try...otherwise fail.
if (circuit.isResettable()) {
circuit.startReset();
super.doApply(request, context, config, chain);
} else {
IPolicyFailureFactoryComponent failureFactory = context.getComponent(IPolicyFailureFactoryComponent.class);
PolicyFailure failure = failureFactory.createFailure(PolicyFailureType.Other, BROKEN_CIRCUIT_FAILURE_CODE, "Circuit broken."); //$NON-NLS-1$
failure.setResponseCode(config.getFailureCode());
chain.doFailure(failure);
}
} else {
super.doApply(request, context, config, chain);
}
}
/**
* @see io.apiman.gateway.engine.policies.AbstractMappedPolicy#doApply(io.apiman.gateway.engine.beans.ApiResponse, io.apiman.gateway.engine.policy.IPolicyContext, java.lang.Object, io.apiman.gateway.engine.policy.IPolicyChain)
*/
@Override
protected void doApply(ApiResponse response, IPolicyContext context, CircuitBreakerConfigBean config,
IPolicyChain<ApiResponse> chain) {
Circuit circuit = context.getAttribute(CircuitBreakerPolicy.class.getName() + ".circuit", (Circuit) null); //$NON-NLS-1$
boolean isFault = isCircuitFault(response, config);
if (circuit.isResetting()) {
if (isFault) {
circuit.trip();
} else {
circuit.reset();
}
} else {
if (isFault) {
circuit.addFault();
}
}
super.doApply(response, context, config, chain);
}
/**
* Returns true if the API response represents a circuit fault. This depends on
* the return code from the back end API as well as the configuration of the policy.
* @param response
* @param config
*/
protected static boolean isCircuitFault(ApiResponse response, CircuitBreakerConfigBean config) {
int code = response.getCode();
for (String codePattern : config.getErrorCodes()) {
if (isMatch(code, codePattern)) {
return true;
}
}
return false;
}
/**
* Returns true if the code matches the given pattern.
* @param code
* @param codePattern
*/
protected static boolean isMatch(int code, String codePattern) {
String scode = String.valueOf(code);
byte [] c = scode.getBytes();
byte [] cp = codePattern.getBytes();
if (c.length != cp.length) {
return false;
}
for (int idx = 0; idx < c.length; idx++) {
byte cb = c[idx];
byte cpb = cp[idx];
if (cb != cpb && cpb != '*') {
return false;
}
}
return true;
}
}