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.spi.IdAware; 030import org.apache.camel.util.ObjectHelper; 031 032/** 033 * A <a href="http://camel.apache.org/throttler.html">Throttler</a> 034 * will set a limit on the maximum number of message exchanges which can be sent 035 * to a processor within a specific time period. <p/> This pattern can be 036 * extremely useful if you have some external system which meters access; such 037 * as only allowing 100 requests per second; or if huge load can cause a 038 * particular system to malfunction or to reduce its throughput you might want 039 * to introduce some throttling. 040 * 041 * @version 042 */ 043public class Throttler extends DelayProcessorSupport implements Traceable, IdAware { 044 private String id; 045 private volatile long maximumRequestsPerPeriod; 046 private Expression maxRequestsPerPeriodExpression; 047 private AtomicLong timePeriodMillis = new AtomicLong(1000); 048 private volatile TimeSlot slot; 049 private boolean rejectExecution; 050 051 public Throttler(CamelContext camelContext, Processor processor, Expression maxRequestsPerPeriodExpression, long timePeriodMillis, 052 ScheduledExecutorService executorService, boolean shutdownExecutorService, boolean rejectExecution) { 053 super(camelContext, processor, executorService, shutdownExecutorService); 054 this.rejectExecution = rejectExecution; 055 056 ObjectHelper.notNull(maxRequestsPerPeriodExpression, "maxRequestsPerPeriodExpression"); 057 this.maxRequestsPerPeriodExpression = maxRequestsPerPeriodExpression; 058 059 if (timePeriodMillis <= 0) { 060 throw new IllegalArgumentException("TimePeriodMillis should be a positive number, was: " + timePeriodMillis); 061 } 062 this.timePeriodMillis.set(timePeriodMillis); 063 } 064 065 @Override 066 public String toString() { 067 return "Throttler[requests: " + maxRequestsPerPeriodExpression + " per: " + timePeriodMillis + " (ms) to: " 068 + getProcessor() + "]"; 069 } 070 071 public String getTraceLabel() { 072 return "throttle[" + maxRequestsPerPeriodExpression + " per: " + timePeriodMillis + "]"; 073 } 074 075 public String getId() { 076 return id; 077 } 078 079 public void setId(String id) { 080 this.id = id; 081 } 082 083 // Properties 084 // ----------------------------------------------------------------------- 085 086 /** 087 * Sets the maximum number of requests per time period expression 088 */ 089 public void setMaximumRequestsPerPeriodExpression(Expression maxRequestsPerPeriodExpression) { 090 this.maxRequestsPerPeriodExpression = maxRequestsPerPeriodExpression; 091 } 092 093 public Expression getMaximumRequestsPerPeriodExpression() { 094 return maxRequestsPerPeriodExpression; 095 } 096 097 public long getTimePeriodMillis() { 098 return timePeriodMillis.get(); 099 } 100 101 /** 102 * Gets the current maximum request per period value. 103 */ 104 public long getCurrentMaximumRequestsPerPeriod() { 105 return maximumRequestsPerPeriod; 106 } 107 108 /** 109 * Sets the time period during which the maximum number of requests apply 110 */ 111 public void setTimePeriodMillis(long timePeriodMillis) { 112 this.timePeriodMillis.set(timePeriodMillis); 113 } 114 115 // Implementation methods 116 // ----------------------------------------------------------------------- 117 118 protected long calculateDelay(Exchange exchange) { 119 // evaluate as Object first to see if we get any result at all 120 Object result = maxRequestsPerPeriodExpression.evaluate(exchange, Object.class); 121 if (maximumRequestsPerPeriod == 0 && result == null) { 122 throw new RuntimeExchangeException("The max requests per period expression was evaluated as null: " + maxRequestsPerPeriodExpression, exchange); 123 } 124 125 // then must convert value to long 126 Long longValue = exchange.getContext().getTypeConverter().convertTo(Long.class, result); 127 if (longValue != null) { 128 // log if we changed max period after initial setting 129 if (maximumRequestsPerPeriod > 0 && longValue.longValue() != maximumRequestsPerPeriod) { 130 log.debug("Throttler changed maximum requests per period from {} to {}", maximumRequestsPerPeriod, longValue); 131 } 132 if (maximumRequestsPerPeriod > longValue) { 133 slot.capacity = 0; 134 } 135 maximumRequestsPerPeriod = longValue; 136 } 137 138 if (maximumRequestsPerPeriod <= 0) { 139 throw new IllegalStateException("The maximumRequestsPerPeriod must be a positive number, was: " + maximumRequestsPerPeriod); 140 } 141 142 TimeSlot slot = nextSlot(); 143 if (!slot.isActive()) { 144 long delay = slot.startTime - currentSystemTime(); 145 return delay; 146 } else { 147 return 0; 148 } 149 } 150 151 /* 152 * Determine what the next available time slot is for handling an Exchange 153 */ 154 protected synchronized TimeSlot nextSlot() throws ThrottlerRejectedExecutionException { 155 if (slot == null) { 156 slot = new TimeSlot(); 157 } else { 158 if (rejectExecution && slot.isFull() && !slot.isPast()) { 159 throw new ThrottlerRejectedExecutionException("Exceed the max request limit!"); 160 } 161 if (slot.isFull() || slot.isPast()) { 162 slot = slot.next(); 163 } 164 } 165 slot.assign(); 166 return slot; 167 } 168 169 /* 170 * A time slot is capable of handling a number of exchanges within a certain period of time. 171 */ 172 protected class TimeSlot { 173 174 private volatile long capacity = Throttler.this.maximumRequestsPerPeriod; 175 private final long duration = Throttler.this.timePeriodMillis.get(); 176 private final long startTime; 177 178 protected TimeSlot() { 179 this(System.currentTimeMillis()); 180 } 181 182 protected TimeSlot(long startTime) { 183 this.startTime = startTime; 184 } 185 186 protected void assign() { 187 capacity--; 188 } 189 190 /* 191 * Start the next time slot either now or in the future 192 * (no time slots are being created in the past) 193 */ 194 protected TimeSlot next() { 195 return new TimeSlot(Math.max(System.currentTimeMillis(), this.startTime + this.duration)); 196 } 197 198 protected boolean isPast() { 199 long current = System.currentTimeMillis(); 200 return current > (startTime + duration); 201 } 202 203 protected boolean isActive() { 204 long current = System.currentTimeMillis(); 205 return startTime <= current && current < (startTime + duration); 206 } 207 208 protected boolean isFull() { 209 return capacity <= 0; 210 } 211 } 212 213 TimeSlot getSlot() { 214 return this.slot; 215 } 216 217 public boolean isRejectExecution() { 218 return rejectExecution; 219 } 220 221 public void setRejectExecution(boolean rejectExecution) { 222 this.rejectExecution = rejectExecution; 223 } 224 225 @Override 226 protected boolean processDelay(Exchange exchange, AsyncCallback callback, long delay) { 227 if (isRejectExecution() && delay > 0) { 228 exchange.setException(new ThrottlerRejectedExecutionException("Exceed the max request limit!")); 229 callback.done(true); 230 return true; 231 } else { 232 return super.processDelay(exchange, callback, delay); 233 } 234 } 235}