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
022 import org.apache.camel.Endpoint;
023 import org.apache.camel.Exchange;
024 import org.apache.camel.PollingConsumer;
025 import org.apache.camel.Processor;
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.ServiceHelper;
030 import org.apache.commons.logging.Log;
031 import org.apache.commons.logging.LogFactory;
032
033 /**
034 * A base class for any kind of {@link Processor} which implements some kind of
035 * batch processing.
036 *
037 * @version $Revision: 53337 $
038 */
039 public class BatchProcessor extends ServiceSupport implements Runnable, Processor {
040 public static final long DEFAULT_BATCH_TIMEOUT = 1000L;
041 public static final int DEFAULT_BATCH_SIZE = 100;
042
043 private static final transient Log LOG = LogFactory.getLog(BatchProcessor.class);
044 private Endpoint endpoint;
045 private Processor processor;
046 private Collection<Exchange> collection;
047 private long batchTimeout = DEFAULT_BATCH_TIMEOUT;
048 private int batchSize = DEFAULT_BATCH_SIZE;
049 private int outBatchSize;
050 private PollingConsumer consumer;
051 private ExceptionHandler exceptionHandler;
052
053 public BatchProcessor(Endpoint endpoint, Processor processor, Collection<Exchange> collection) {
054 this.endpoint = endpoint;
055 this.processor = processor;
056 this.collection = collection;
057 }
058
059 @Override
060 public String toString() {
061 return "BatchProcessor[to: " + processor + "]";
062 }
063
064 public void run() {
065 LOG.debug("Starting thread for " + this);
066 while (isRunAllowed()) {
067 try {
068 processBatch();
069 } catch (Exception e) {
070 getExceptionHandler().handleException(e);
071 }
072 }
073 collection.clear();
074 }
075
076 // Properties
077 // -------------------------------------------------------------------------
078 public ExceptionHandler getExceptionHandler() {
079 if (exceptionHandler == null) {
080 exceptionHandler = new LoggingExceptionHandler(getClass());
081 }
082 return exceptionHandler;
083 }
084
085 public void setExceptionHandler(ExceptionHandler exceptionHandler) {
086 this.exceptionHandler = exceptionHandler;
087 }
088
089 public int getBatchSize() {
090 return batchSize;
091 }
092
093 /**
094 * Sets the <b>in</b> batch size. This is the number of incoming exchanges that this batch processor
095 * will process before its completed. The default value is {@link #DEFAULT_BATCH_SIZE}.
096 *
097 * @param batchSize the size
098 */
099 public void setBatchSize(int batchSize) {
100 this.batchSize = batchSize;
101 }
102
103 public int getOutBatchSize() {
104 return outBatchSize;
105 }
106
107 /**
108 * Sets the <b>out</b> batch size. If the batch processor holds more exchanges than this out size then
109 * the completion is triggered. Can for instance be used to ensure that this batch is completed when
110 * a certain number of exchanges has been collected. By default this feature is <b>not</b> enabled.
111 *
112 * @param outBatchSize the size
113 */
114 public void setOutBatchSize(int outBatchSize) {
115 this.outBatchSize = outBatchSize;
116 }
117
118 public long getBatchTimeout() {
119 return batchTimeout;
120 }
121
122 public void setBatchTimeout(long batchTimeout) {
123 this.batchTimeout = batchTimeout;
124 }
125
126 public Endpoint getEndpoint() {
127 return endpoint;
128 }
129
130 public Processor getProcessor() {
131 return processor;
132 }
133
134 /**
135 * A transactional method to process a batch of messages up to a timeout
136 * period or number of messages reached.
137 */
138 protected synchronized void processBatch() throws Exception {
139 long start = System.currentTimeMillis();
140 long end = start + batchTimeout;
141 for (int i = 0; !isBatchCompleted(i); i++) {
142 long timeout = end - System.currentTimeMillis();
143 if (timeout < 0L) {
144 if (LOG.isTraceEnabled()) {
145 LOG.trace("batch timeout expired at batch index: " + i);
146 }
147 break;
148 }
149 Exchange exchange = consumer.receive(timeout);
150 if (exchange == null) {
151 if (LOG.isTraceEnabled()) {
152 LOG.trace("receive with timeout: " + timeout + " expired at batch index: " + i);
153 }
154 break;
155 }
156 collection.add(exchange);
157 }
158
159 // we should NOT log the collection directly as it will invoke a toString() on collection
160 // and it will call collection.iterator() where end-users might do stuff that would break
161 // calling the iterator a 2nd time as below
162
163 // lets send the batch
164 Iterator<Exchange> iter = collection.iterator();
165 while (iter.hasNext()) {
166 Exchange exchange = iter.next();
167 iter.remove();
168 processExchange(exchange);
169 }
170 }
171
172 /**
173 * A strategy method to decide if the batch is completed the resulting exchanges should be sent
174 */
175 protected boolean isBatchCompleted(int index) {
176 // out batch size is optional and we should only check it if its enabled (= >0)
177 if (outBatchSize > 0 && collection.size() >= outBatchSize) {
178 return true;
179 }
180 // fallback to regular batch size check
181 return index >= batchSize;
182 }
183
184 /**
185 * Strategy Method to process an exchange in the batch. This method allows
186 * derived classes to perform custom processing before or after an
187 * individual exchange is processed
188 */
189 protected void processExchange(Exchange exchange) throws Exception {
190 processor.process(exchange);
191 }
192
193 protected void doStart() throws Exception {
194 consumer = endpoint.createPollingConsumer();
195
196 ServiceHelper.startServices(processor, consumer);
197
198 Thread thread = new Thread(this, this + " Polling Thread");
199 thread.start();
200 }
201
202 protected void doStop() throws Exception {
203 ServiceHelper.stopServices(consumer, processor);
204 collection.clear();
205 }
206
207 protected Collection<Exchange> getCollection() {
208 return collection;
209 }
210
211 public void process(Exchange exchange) throws Exception {
212 // empty since exchanges come from endpoint's polling consumer
213 }
214 }