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 */
017 package org.apache.camel.processor;
018
019 import java.util.Collection;
020 import java.util.Iterator;
021 import java.util.concurrent.LinkedBlockingQueue;
022
023 import org.apache.camel.Exchange;
024 import org.apache.camel.Processor;
025 import org.apache.camel.impl.GroupedExchange;
026 import org.apache.camel.impl.LoggingExceptionHandler;
027 import org.apache.camel.impl.ServiceSupport;
028 import org.apache.camel.spi.ExceptionHandler;
029 import org.apache.camel.util.ObjectHelper;
030 import org.apache.camel.util.ServiceHelper;
031
032 /**
033 * A base class for any kind of {@link Processor} which implements some kind of
034 * batch processing.
035 *
036 * @version $Revision: 745139 $
037 */
038 public class BatchProcessor extends ServiceSupport implements Processor {
039
040 public static final long DEFAULT_BATCH_TIMEOUT = 1000L;
041 public static final int DEFAULT_BATCH_SIZE = 100;
042
043 private long batchTimeout = DEFAULT_BATCH_TIMEOUT;
044 private int batchSize = DEFAULT_BATCH_SIZE;
045 private int outBatchSize;
046 private boolean groupExchanges;
047
048 private Processor processor;
049 private Collection<Exchange> collection;
050 private ExceptionHandler exceptionHandler;
051
052 private BatchSender sender;
053
054 public BatchProcessor(Processor processor, Collection<Exchange> collection) {
055 ObjectHelper.notNull(processor, "processor");
056 ObjectHelper.notNull(collection, "collection");
057 this.processor = processor;
058 this.collection = collection;
059 this.sender = new BatchSender();
060 }
061
062 @Override
063 public String toString() {
064 return "BatchProcessor[to: " + processor + "]";
065 }
066
067 // Properties
068 // -------------------------------------------------------------------------
069 public ExceptionHandler getExceptionHandler() {
070 if (exceptionHandler == null) {
071 exceptionHandler = new LoggingExceptionHandler(getClass());
072 }
073 return exceptionHandler;
074 }
075
076 public void setExceptionHandler(ExceptionHandler exceptionHandler) {
077 this.exceptionHandler = exceptionHandler;
078 }
079
080 public int getBatchSize() {
081 return batchSize;
082 }
083
084 /**
085 * Sets the <b>in</b> batch size. This is the number of incoming exchanges that this batch processor
086 * will process before its completed. The default value is {@link #DEFAULT_BATCH_SIZE}.
087 *
088 * @param batchSize the size
089 */
090 public void setBatchSize(int batchSize) {
091 this.batchSize = batchSize;
092 }
093
094 public int getOutBatchSize() {
095 return outBatchSize;
096 }
097
098 /**
099 * Sets the <b>out</b> batch size. If the batch processor holds more exchanges than this out size then
100 * the completion is triggered. Can for instance be used to ensure that this batch is completed when
101 * a certain number of exchanges has been collected. By default this feature is <b>not</b> enabled.
102 *
103 * @param outBatchSize the size
104 */
105 public void setOutBatchSize(int outBatchSize) {
106 this.outBatchSize = outBatchSize;
107 }
108
109 public long getBatchTimeout() {
110 return batchTimeout;
111 }
112
113 public void setBatchTimeout(long batchTimeout) {
114 this.batchTimeout = batchTimeout;
115 }
116
117 public boolean isGroupExchanges() {
118 return groupExchanges;
119 }
120
121 public void setGroupExchanges(boolean groupExchanges) {
122 this.groupExchanges = groupExchanges;
123 }
124
125 public Processor getProcessor() {
126 return processor;
127 }
128
129 /**
130 * A strategy method to decide if the "in" batch is completed. That is, whether the resulting
131 * exchanges in the in queue should be drained to the "out" collection.
132 */
133 protected boolean isInBatchCompleted(int num) {
134 return num >= batchSize;
135 }
136
137 /**
138 * A strategy method to decide if the "out" batch is completed. That is, whether the resulting
139 * exchange in the out collection should be sent.
140 */
141 protected boolean isOutBatchCompleted() {
142 if (outBatchSize == 0) {
143 // out batch is disabled, so go ahead and send.
144 return true;
145 }
146 return collection.size() > 0 && collection.size() >= outBatchSize;
147 }
148
149 /**
150 * Strategy Method to process an exchange in the batch. This method allows
151 * derived classes to perform custom processing before or after an
152 * individual exchange is processed
153 */
154 protected void processExchange(Exchange exchange) throws Exception {
155 processor.process(exchange);
156 }
157
158 protected void doStart() throws Exception {
159 ServiceHelper.startServices(processor);
160 sender.start();
161 }
162
163 protected void doStop() throws Exception {
164 sender.cancel();
165 ServiceHelper.stopServices(processor);
166 collection.clear();
167 }
168
169 protected Collection<Exchange> getCollection() {
170 return collection;
171 }
172
173 /**
174 * Enqueues an exchange for later batch processing.
175 */
176 public void process(Exchange exchange) throws Exception {
177 sender.enqueueExchange(exchange);
178 }
179
180 /**
181 * Sender thread for queued-up exchanges.
182 */
183 private class BatchSender extends Thread {
184
185 private volatile boolean cancelRequested;
186
187 private LinkedBlockingQueue<Exchange> queue;
188
189 public BatchSender() {
190 super("Batch Sender");
191 this.queue = new LinkedBlockingQueue<Exchange>();
192 }
193
194 @Override
195 public void run() {
196 while (true) {
197 try {
198 Thread.sleep(batchTimeout);
199 queue.drainTo(collection, batchSize);
200 } catch (InterruptedException e) {
201 if (cancelRequested) {
202 return;
203 }
204
205 while (isInBatchCompleted(queue.size())) {
206 queue.drainTo(collection, batchSize);
207 }
208
209 if (!isOutBatchCompleted()) {
210 continue;
211 }
212 }
213 try {
214 sendExchanges();
215 } catch (Exception e) {
216 getExceptionHandler().handleException(e);
217 }
218 }
219 }
220
221 public void cancel() {
222 cancelRequested = true;
223 interrupt();
224 }
225
226 public void enqueueExchange(Exchange exchange) {
227 queue.add(exchange);
228 interrupt();
229 }
230
231 private void sendExchanges() throws Exception {
232 GroupedExchange grouped = null;
233
234 Iterator<Exchange> iter = collection.iterator();
235 while (iter.hasNext()) {
236 Exchange exchange = iter.next();
237 iter.remove();
238 if (!groupExchanges) {
239 // non grouped so process the exchange one at a time
240 processExchange(exchange);
241 } else {
242 // grouped so add all exchanges into one group
243 if (grouped == null) {
244 grouped = new GroupedExchange(exchange.getContext());
245 }
246 grouped.addExchange(exchange);
247 }
248 }
249
250 // and after adding process the single grouped exchange
251 if (grouped != null) {
252 processExchange(grouped);
253 }
254 }
255 }
256
257 }