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 }