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