/* ConstantFlowRegulator.java * * Copyright 2009-2012 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(final int requestsPerSecond) { requestPerSecondThreshold = requestsPerSecond; calculateDeltaWaitTime(); } public ConstantFlowRegulator(final int requestsPerSecond, final 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 */ @Override public <T> T invoke(final Callable<T> c) throws Exception { if (canProceed()) { return c.call(); } 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 */ @Override public void invoke(final 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 */ @Override public <T> T invoke(final Runnable r, final T result) throws Exception { if (canProceed()) { r.run(); return result; } 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(final int i) { 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 exceptionMapper; } /** * A helper that converts {@link FlowRateExceededException} into a known 'application' * exception. * * @param mapper * my converter object */ public void setExceptionMapper( final ConstantFlowRegulatorExceptionMapper<? extends Exception> mapper) { exceptionMapper = mapper; } private void calculateDeltaWaitTime() { if (requestPerSecondThreshold > 0) { deltaWaitTimeMillis = 1000L / requestPerSecondThreshold; } } private Exception mapException(final FlowRateExceededException e) { if (exceptionMapper == null) { return e; } return exceptionMapper.map(this, e); } }