/* ConstantFlowRegulator.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; public class ConstantFlowRegulator implements ServiceWrapper { /** * By default a value of -1 allows all requests to go through * unmolested. */ private int requestPerSecondThreshold = -1; /** * */ private long deltaWaitTimeMillis = 0; /** * */ private long lastRequestOccurance = 0; ConstantFlowRegulatorExceptionMapper<? extends Exception> exceptionMapper; public ConstantFlowRegulator() { } public ConstantFlowRegulator(int requestsPerSecond) { requestPerSecondThreshold = requestsPerSecond; calculateDeltaWaitTime(); } public ConstantFlowRegulator(int requestsPerSecond, ConstantFlowRegulatorExceptionMapper<? extends Exception> mapper) { requestPerSecondThreshold = requestsPerSecond; calculateDeltaWaitTime(); exceptionMapper = mapper; } /** * Wrap the given service call with the {@link ConstantFlowRegulator} * protection logic. * @param c the {@link Callable} to attempt * * @return whatever c would return on success * * @throws FlowRateExceededException if the total requests per second * through the flow regulator exceeds the configured value * @throws Exception if <code>c</code> throws one during * execution */ public <T> T invoke(Callable<T> c) throws Exception { if (canProceed()) { return c.call(); } else { throw mapException(new FlowRateExceededException()); } } /** Wrap the given service call with the {@link ConstantFlowRegulator} * protection logic. * @param r the {@link Runnable} to attempt * * @throws FlowRateExceededException if the total requests per second * through the flow regulator exceeds the configured value * @throws Exception if <code>c</code> throws one during * execution */ public void invoke(Runnable r) throws Exception { if (canProceed()) { r.run(); } else { throw mapException(new FlowRateExceededException()); } } /** Wrap the given service call with the {@link ConstantFlowRegulator} * protection logic. * @param r the {@link Runnable} to attempt * @param result what to return after <code>r</code> succeeds * * @return result * * @throws FlowRateExceededException if the total requests per second * through the flow regulator exceeds the configured value * @throws Exception if <code>c</code> throws one during * execution */ public <T> T invoke(Runnable r, T result) throws Exception { if (canProceed()) { r.run(); return result; } else { throw mapException(new FlowRateExceededException()); } } protected synchronized boolean canProceed() { if (requestPerSecondThreshold == -1) { return true; } if (lastRequestOccurance == 0) { lastRequestOccurance = System.currentTimeMillis(); return true; } if ((System.currentTimeMillis() - lastRequestOccurance) > deltaWaitTimeMillis) { lastRequestOccurance = System.currentTimeMillis(); return true; } return false; } /** * Configures number of requests per second to allow through this flow regulator * onto a configured back end service. * * @param i the requests per second threshold for this flow regulator */ public void setRequestPerSecondThreshold(int i) { this.requestPerSecondThreshold = i; calculateDeltaWaitTime(); } /** * Returns the currently configured number of requests per second to allow * through this flow regulator onto a configured back end service. * * @return int the currently configured requests per second threshold */ public int getRequestPerSecondThreshold() { return requestPerSecondThreshold; } /** * Get the helper that converts {@link FlowRateExceededException}s into * application-specific exceptions. * * @return {@link ConstantFlowRegulatorExceptionMapper} my converter object, or * <code>null</code> if one is not currently set. */ public ConstantFlowRegulatorExceptionMapper<? extends Exception> getExceptionMapper(){ return this.exceptionMapper; } /** * A helper that converts {@link FlowRateExceededException} into a known * 'application' exception. * * @param mapper my converter object */ public void setExceptionMapper(ConstantFlowRegulatorExceptionMapper<? extends Exception> mapper) { this.exceptionMapper = mapper; } private void calculateDeltaWaitTime() { if (requestPerSecondThreshold > 0) { deltaWaitTimeMillis = 1000L / requestPerSecondThreshold; } } private Exception mapException(FlowRateExceededException e) { if (exceptionMapper == null) return e; return exceptionMapper.map(this, e); } }