001/** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.camel.processor; 018 019import java.util.concurrent.ScheduledExecutorService; 020import java.util.concurrent.atomic.AtomicLong; 021 022import org.apache.camel.AsyncCallback; 023import org.apache.camel.CamelContext; 024import org.apache.camel.Exchange; 025import org.apache.camel.Expression; 026import org.apache.camel.Processor; 027import org.apache.camel.RuntimeExchangeException; 028import org.apache.camel.Traceable; 029import org.apache.camel.util.ObjectHelper; 030 031/** 032 * A <a href="http://camel.apache.org/throttler.html">Throttler</a> 033 * will set a limit on the maximum number of message exchanges which can be sent 034 * to a processor within a specific time period. <p/> This pattern can be 035 * extremely useful if you have some external system which meters access; such 036 * as only allowing 100 requests per second; or if huge load can cause a 037 * particular system to malfunction or to reduce its throughput you might want 038 * to introduce some throttling. 039 * 040 * @version 041 */ 042public class Throttler extends DelayProcessorSupport implements Traceable { 043 private volatile long maximumRequestsPerPeriod; 044 private Expression maxRequestsPerPeriodExpression; 045 private AtomicLong timePeriodMillis = new AtomicLong(1000); 046 private volatile TimeSlot slot; 047 private boolean rejectExecution; 048 049 public Throttler(CamelContext camelContext, Processor processor, Expression maxRequestsPerPeriodExpression, long timePeriodMillis, 050 ScheduledExecutorService executorService, boolean shutdownExecutorService, boolean rejectExecution) { 051 super(camelContext, processor, executorService, shutdownExecutorService); 052 this.rejectExecution = rejectExecution; 053 054 ObjectHelper.notNull(maxRequestsPerPeriodExpression, "maxRequestsPerPeriodExpression"); 055 this.maxRequestsPerPeriodExpression = maxRequestsPerPeriodExpression; 056 057 if (timePeriodMillis <= 0) { 058 throw new IllegalArgumentException("TimePeriodMillis should be a positive number, was: " + timePeriodMillis); 059 } 060 this.timePeriodMillis.set(timePeriodMillis); 061 } 062 063 @Override 064 public String toString() { 065 return "Throttler[requests: " + maxRequestsPerPeriodExpression + " per: " + timePeriodMillis + " (ms) to: " 066 + getProcessor() + "]"; 067 } 068 069 public String getTraceLabel() { 070 return "throttle[" + maxRequestsPerPeriodExpression + " per: " + timePeriodMillis + "]"; 071 } 072 073 // Properties 074 // ----------------------------------------------------------------------- 075 076 /** 077 * Sets the maximum number of requests per time period expression 078 */ 079 public void setMaximumRequestsPerPeriodExpression(Expression maxRequestsPerPeriodExpression) { 080 this.maxRequestsPerPeriodExpression = maxRequestsPerPeriodExpression; 081 } 082 083 public Expression getMaximumRequestsPerPeriodExpression() { 084 return maxRequestsPerPeriodExpression; 085 } 086 087 public long getTimePeriodMillis() { 088 return timePeriodMillis.get(); 089 } 090 091 /** 092 * Gets the current maximum request per period value. 093 */ 094 public long getCurrentMaximumRequestsPerPeriod() { 095 return maximumRequestsPerPeriod; 096 } 097 098 /** 099 * Sets the time period during which the maximum number of requests apply 100 */ 101 public void setTimePeriodMillis(long timePeriodMillis) { 102 this.timePeriodMillis.set(timePeriodMillis); 103 } 104 105 // Implementation methods 106 // ----------------------------------------------------------------------- 107 108 protected long calculateDelay(Exchange exchange) { 109 // evaluate as Object first to see if we get any result at all 110 Object result = maxRequestsPerPeriodExpression.evaluate(exchange, Object.class); 111 if (maximumRequestsPerPeriod == 0 && result == null) { 112 throw new RuntimeExchangeException("The max requests per period expression was evaluated as null: " + maxRequestsPerPeriodExpression, exchange); 113 } 114 115 // then must convert value to long 116 Long longValue = exchange.getContext().getTypeConverter().convertTo(Long.class, result); 117 if (longValue != null) { 118 // log if we changed max period after initial setting 119 if (maximumRequestsPerPeriod > 0 && longValue.longValue() != maximumRequestsPerPeriod) { 120 log.debug("Throttler changed maximum requests per period from {} to {}", maximumRequestsPerPeriod, longValue); 121 } 122 if (maximumRequestsPerPeriod > longValue) { 123 slot.capacity = 0; 124 } 125 maximumRequestsPerPeriod = longValue; 126 } 127 128 if (maximumRequestsPerPeriod <= 0) { 129 throw new IllegalStateException("The maximumRequestsPerPeriod must be a positive number, was: " + maximumRequestsPerPeriod); 130 } 131 132 TimeSlot slot = nextSlot(); 133 if (!slot.isActive()) { 134 long delay = slot.startTime - currentSystemTime(); 135 return delay; 136 } else { 137 return 0; 138 } 139 } 140 141 /* 142 * Determine what the next available time slot is for handling an Exchange 143 */ 144 protected synchronized TimeSlot nextSlot() { 145 if (slot == null) { 146 slot = new TimeSlot(); 147 } 148 if (slot.isFull() || !slot.isPast()) { 149 slot = slot.next(); 150 } 151 slot.assign(); 152 return slot; 153 } 154 155 /* 156 * A time slot is capable of handling a number of exchanges within a certain period of time. 157 */ 158 protected class TimeSlot { 159 160 private volatile long capacity = Throttler.this.maximumRequestsPerPeriod; 161 private final long duration = Throttler.this.timePeriodMillis.get(); 162 private final long startTime; 163 164 protected TimeSlot() { 165 this(System.currentTimeMillis()); 166 } 167 168 protected TimeSlot(long startTime) { 169 this.startTime = startTime; 170 } 171 172 protected void assign() { 173 capacity--; 174 } 175 176 /* 177 * Start the next time slot either now or in the future 178 * (no time slots are being created in the past) 179 */ 180 protected TimeSlot next() { 181 return new TimeSlot(Math.max(System.currentTimeMillis(), this.startTime + this.duration)); 182 } 183 184 protected boolean isPast() { 185 long current = System.currentTimeMillis(); 186 return current < (startTime + duration); 187 } 188 189 protected boolean isActive() { 190 long current = System.currentTimeMillis(); 191 return startTime <= current && current < (startTime + duration); 192 } 193 194 protected boolean isFull() { 195 return capacity <= 0; 196 } 197 } 198 199 TimeSlot getSlot() { 200 return this.slot; 201 } 202 203 public boolean isRejectExecution() { 204 return rejectExecution; 205 } 206 207 public void setRejectExecution(boolean rejectExecution) { 208 this.rejectExecution = rejectExecution; 209 } 210 211 @Override 212 protected boolean processDelay(Exchange exchange, AsyncCallback callback, long delay) { 213 if (isRejectExecution() && delay > 0) { 214 exchange.setException(new ThrottlerRejectedExecutionException("Exceed the max request limit!")); 215 callback.done(true); 216 return true; 217 } else { 218 return super.processDelay(exchange, callback, delay); 219 } 220 } 221}