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.component.ibatis;
018    
019    import java.util.LinkedList;
020    import java.util.List;
021    import java.util.Queue;
022    
023    import org.apache.camel.BatchConsumer;
024    import org.apache.camel.Exchange;
025    import org.apache.camel.ExchangePattern;
026    import org.apache.camel.Message;
027    import org.apache.camel.Processor;
028    import org.apache.camel.ShutdownRunningTask;
029    import org.apache.camel.impl.ScheduledPollConsumer;
030    import org.apache.camel.spi.ShutdownAware;
031    import org.apache.camel.util.CastUtils;
032    import org.apache.camel.util.ObjectHelper;
033    import org.apache.commons.logging.Log;
034    import org.apache.commons.logging.LogFactory;
035    
036    /**
037     * <pre>
038     *  Ibatis Camel Component used to read data from a database.
039     * 
040     *  Example Configuration :
041     *  &lt;route&gt;
042     *   &lt;from uri=&quot;ibatis:selectRecords&quot; /&gt;
043     *   &lt;to uri=&quot;jms:destinationQueue&quot; /&gt;
044     *  &lt;/route&gt;
045     * 
046     * 
047     *  This also can be configured to treat a table as a logical queue by defining
048     *  an &quot;onConsume&quot; statement.
049     * 
050     *  Example Configuration :
051     *  &lt;route&gt;
052     *   &lt;from uri=&quot;ibatis:selectRecords?consumer.onConsume=updateRecord&quot; /&gt;
053     *   &lt;to uri=&quot;jms:destinationQueue&quot; /&gt;
054     *  &lt;/route&gt;
055     * 
056     *  By default, if the select statement contains multiple rows, it will
057     *  iterate over the set and deliver each row to the route.  If this is not the
058     *  desired behavior then set &quot;useIterator=false&quot;.  This will deliver the entire
059     *  set to the route as a list.
060     * </pre>
061     *
062     * <b>URI Options</b>
063     * <table border="1">
064     * <thead>
065     * <th>Name</th>
066     * <th>Default Value</th>
067     * <th>description</th>
068     * </thead>
069     * <tbody>
070     * <tr>
071     * <td>initialDelay</td>
072     * <td>1000 ms</td>
073     * <td>time before polling starts</td>
074     * </tr>
075     * <tr>
076     * <td>delay</td>
077     * <td>500 ms</td>
078     * <td>time before the next poll</td>
079     * </tr>
080     * <tr>
081     * <td>timeUnit</td>
082     * <td>MILLISECONDS</td>
083     * <td>Time unit to use for delay properties (NANOSECONDS, MICROSECONDS,
084     * MILLISECONDS, SECONDS)</td>
085     * </tr>
086     * <tr>
087     * <td>useIterator</td>
088     * <td>true</td>
089     * <td>If true, processes one exchange per row. If false processes one exchange
090     * for all rows</td>
091     * </tr>
092     * <tr>
093     * <td>onConsume</td>
094     * <td>null</td>
095     * <td>statement to run after data has been processed</td>
096     * </tr>
097     * <tbody> </table>
098     *
099     * @see org.apache.camel.component.ibatis.strategy.IBatisProcessingStrategy
100     */
101    public class IBatisPollingConsumer extends ScheduledPollConsumer implements BatchConsumer, ShutdownAware {
102    
103        private static final Log LOG = LogFactory.getLog(IBatisPollingConsumer.class);
104    
105        private final class DataHolder {
106            private Exchange exchange;
107            private Object data;
108            private DataHolder() {
109            }
110        }
111        
112        protected volatile ShutdownRunningTask shutdownRunningTask;
113        protected volatile int pendingExchanges;
114    
115        /**
116         * Statement to run after data has been processed in the route
117         */
118        private String onConsume;
119    
120        /**
121         * Process resultset individually or as a list
122         */
123        private boolean useIterator = true;
124    
125        /**
126         * Whether allow empty resultset to be routed to the next hop
127         */
128        private boolean routeEmptyResultSet;
129    
130        private int maxMessagesPerPoll;
131        
132    
133        public IBatisPollingConsumer(IBatisEndpoint endpoint, Processor processor) throws Exception {
134            super(endpoint, processor);
135        }
136    
137        public IBatisEndpoint getEndpoint() {
138            return (IBatisEndpoint) super.getEndpoint();
139        }
140    
141        /**
142         * Polls the database
143         */
144        @Override
145        protected void poll() throws Exception {
146    
147            // must reset for each poll
148            shutdownRunningTask = null;
149            pendingExchanges = 0;
150    
151            // poll data from the database
152            IBatisEndpoint endpoint = getEndpoint();
153            if (LOG.isTraceEnabled()) {
154                LOG.trace("Polling: " + endpoint);
155            }
156            List<Object> data = CastUtils.cast(endpoint.getProcessingStrategy().poll(this, getEndpoint()));
157    
158            // create a list of exchange objects with the data
159            Queue<DataHolder> answer = new LinkedList<DataHolder>();
160            if (useIterator) {
161                for (Object item : data) {
162                    Exchange exchange = createExchange(item);
163                    DataHolder holder = new DataHolder();
164                    holder.exchange = exchange;
165                    holder.data = item;
166                    answer.add(holder);
167                }
168            } else {
169                if (!data.isEmpty() || routeEmptyResultSet) {
170                    Exchange exchange = createExchange(data);
171                    DataHolder holder = new DataHolder();
172                    holder.exchange = exchange;
173                    holder.data = data;
174                    answer.add(holder);
175                }
176            }
177    
178            // process all the exchanges in this batch
179            processBatch(CastUtils.cast(answer));
180        }
181    
182        public void setMaxMessagesPerPoll(int maxMessagesPerPoll) {
183            this.maxMessagesPerPoll = maxMessagesPerPoll;
184        }
185    
186        public void processBatch(Queue<Object> exchanges) throws Exception {
187            final IBatisEndpoint endpoint = getEndpoint();
188    
189            int total = exchanges.size();
190    
191            // limit if needed
192            if (maxMessagesPerPoll > 0 && total > maxMessagesPerPoll) {
193                LOG.debug("Limiting to maximum messages to poll " + maxMessagesPerPoll + " as there was " + total + " messages in this poll.");
194                total = maxMessagesPerPoll;
195            }
196    
197            for (int index = 0; index < total && isBatchAllowed(); index++) {
198                // only loop if we are started (allowed to run)
199                DataHolder holder = ObjectHelper.cast(DataHolder.class, exchanges.poll());
200                Exchange exchange = holder.exchange;
201                Object data = holder.data;
202    
203                // add current index and total as properties
204                exchange.setProperty(Exchange.BATCH_INDEX, index);
205                exchange.setProperty(Exchange.BATCH_SIZE, total);
206                exchange.setProperty(Exchange.BATCH_COMPLETE, index == total - 1);
207    
208                // update pending number of exchanges
209                pendingExchanges = total - index - 1;
210    
211                // process the current exchange
212                if (LOG.isDebugEnabled()) {
213                    LOG.debug("Processing exchange: " + exchange);
214                }
215                getProcessor().process(exchange);
216    
217                try {
218                    if (onConsume != null) {
219                        endpoint.getProcessingStrategy().commit(endpoint, exchange, data, onConsume);
220                    }
221                } catch (Exception e) {
222                    handleException(e);
223                }
224            }
225        }
226    
227        public boolean deferShutdown(ShutdownRunningTask shutdownRunningTask) {
228            // store a reference what to do in case when shutting down and we have pending messages
229            this.shutdownRunningTask = shutdownRunningTask;
230            // do not defer shutdown
231            return false;
232        }
233    
234        public int getPendingExchangesSize() {
235            // only return the real pending size in case we are configured to complete all tasks
236            if (ShutdownRunningTask.CompleteAllTasks == shutdownRunningTask) {
237                return pendingExchanges;
238            } else {
239                return 0;
240            }
241        }
242    
243        public boolean isBatchAllowed() {
244            // stop if we are not running
245            boolean answer = isRunAllowed();
246            if (!answer) {
247                return false;
248            }
249    
250            if (shutdownRunningTask == null) {
251                // we are not shutting down so continue to run
252                return true;
253            }
254    
255            // we are shutting down so only continue if we are configured to complete all tasks
256            return ShutdownRunningTask.CompleteAllTasks == shutdownRunningTask;
257        }
258    
259        private Exchange createExchange(Object data) {
260            final IBatisEndpoint endpoint = getEndpoint();
261            final Exchange exchange = endpoint.createExchange(ExchangePattern.InOnly);
262    
263            Message msg = exchange.getIn();
264            msg.setBody(data);
265            msg.setHeader(IBatisConstants.IBATIS_STATEMENT_NAME, endpoint.getStatement());
266    
267            return exchange;
268        }
269    
270        /**
271         * Gets the statement(s) to run after successful processing.
272         * Use comma to separate multiple statements.
273         */
274        public String getOnConsume() {
275            return onConsume;
276        }
277    
278        /**
279         * Sets the statement to run after successful processing.
280         * Use comma to separate multiple statements.
281         */
282        public void setOnConsume(String onConsume) {
283            this.onConsume = onConsume;
284        }
285    
286        /**
287         * Indicates how resultset should be delivered to the route
288         */
289        public boolean isUseIterator() {
290            return useIterator;
291        }
292    
293        /**
294         * Sets how resultset should be delivered to route.
295         * Indicates delivery as either a list or individual object.
296         * defaults to true.
297         */
298        public void setUseIterator(boolean useIterator) {
299            this.useIterator = useIterator;
300        }
301    
302        /**
303         * Indicates whether empty resultset should be allowed to be sent to the next hop or not
304         */
305        public boolean isRouteEmptyResultSet() {
306            return routeEmptyResultSet;
307        }
308    
309        /**
310         * Sets whether empty resultset should be allowed to be sent to the next hop.
311         * defaults to false. So the empty resultset will be filtered out.
312         */
313        public void setRouteEmptyResultSet(boolean routeEmptyResultSet) {
314            this.routeEmptyResultSet = routeEmptyResultSet;
315        }
316    }