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    }