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.ArrayList;
020    import java.util.List;
021    
022    import org.apache.camel.CamelExchangeException;
023    import org.apache.camel.Exchange;
024    import org.apache.camel.PollingConsumer;
025    import org.apache.camel.Processor;
026    import org.apache.camel.processor.aggregate.AggregationStrategy;
027    import org.apache.camel.support.ServiceSupport;
028    import org.apache.camel.util.ExchangeHelper;
029    import org.apache.camel.util.ServiceHelper;
030    import org.slf4j.Logger;
031    import org.slf4j.LoggerFactory;
032    
033    import static org.apache.camel.util.ExchangeHelper.copyResultsPreservePattern;
034    
035    /**
036     * A content enricher that enriches input data by first obtaining additional
037     * data from a <i>resource</i> represented by an endpoint <code>producer</code>
038     * and second by aggregating input data and additional data. Aggregation of
039     * input data and additional data is delegated to an {@link org.apache.camel.processor.aggregate.AggregationStrategy}
040     * object.
041     * <p/>
042     * Uses a {@link org.apache.camel.PollingConsumer} to obtain the additional data as opposed to {@link Enricher}
043     * that uses a {@link org.apache.camel.Producer}.
044     *
045     * @see Enricher
046     */
047    public class PollEnricher extends ServiceSupport implements Processor {
048    
049        private static final transient Logger LOG = LoggerFactory.getLogger(PollEnricher.class);
050        private AggregationStrategy aggregationStrategy;
051        private PollingConsumer consumer;
052        private long timeout;
053        private Boolean pollMultiple;
054    
055        /**
056         * Creates a new {@link PollEnricher}. The default aggregation strategy is to
057         * copy the additional data obtained from the enricher's resource over the
058         * input data. When using the copy aggregation strategy the enricher
059         * degenerates to a normal transformer.
060         *
061         * @param consumer consumer to resource endpoint.
062         */
063        public PollEnricher(PollingConsumer consumer) {
064            this(defaultAggregationStrategy(), consumer, 0, false);
065        }
066    
067        /**
068         * Creates a new {@link PollEnricher}.
069         *
070         * @param aggregationStrategy  aggregation strategy to aggregate input data and additional data.
071         * @param consumer consumer to resource endpoint.
072         * @param timeout timeout in millis
073         */
074        public PollEnricher(AggregationStrategy aggregationStrategy, PollingConsumer consumer, long timeout) {
075            this.aggregationStrategy = aggregationStrategy;
076            this.consumer = consumer;
077            this.timeout = timeout;
078        }
079    
080        /**
081         * Creates a new {@link PollEnricher}.
082         *
083         * @param aggregationStrategy  aggregation strategy to aggregate input data and additional data.
084         * @param consumer consumer to resource endpoint.
085         * @param timeout timeout in millis
086         * @param pollMultiple enabled building a List of multiple exchanges
087         */
088        public PollEnricher(AggregationStrategy aggregationStrategy, PollingConsumer consumer, long timeout, Boolean pollMultiple) {
089            this.aggregationStrategy = aggregationStrategy;
090            this.consumer = consumer;
091            this.timeout = timeout;
092            this.pollMultiple = pollMultiple;
093        }
094    
095        /**
096         * Sets the aggregation strategy for this poll enricher.
097         *
098         * @param aggregationStrategy the aggregationStrategy to set
099         */
100        public void setAggregationStrategy(AggregationStrategy aggregationStrategy) {
101            this.aggregationStrategy = aggregationStrategy;
102        }
103    
104        /**
105         * Sets the default aggregation strategy for this poll enricher.
106         */
107        public void setDefaultAggregationStrategy() {
108            this.aggregationStrategy = defaultAggregationStrategy();
109        }
110    
111        /**
112         * Sets the timeout to use when polling.
113         * <p/>
114         * Use 0 or negative to not use timeout and block until data is available.
115         *
116         * @param timeout timeout in millis.
117         */
118        public void setTimeout(long timeout) {
119            this.timeout = timeout;
120        }
121    
122        public void setPollMultiple(Boolean value) {
123            this.pollMultiple = value;
124        }
125        
126        /**
127         * Enriches the input data (<code>exchange</code>) by first obtaining
128         * additional data from an endpoint represented by an endpoint
129         * <code>producer</code> and second by aggregating input data and additional
130         * data. Aggregation of input data and additional data is delegated to an
131         * {@link org.apache.camel.processor.aggregate.AggregationStrategy} object set at construction time. If the
132         * message exchange with the resource endpoint fails then no aggregation
133         * will be done and the failed exchange content is copied over to the
134         * original message exchange.
135         *
136         * @param exchange input data.
137         */
138        public void process(Exchange exchange) throws Exception {
139    
140            preCheckPoll(exchange);
141    
142            if (pollMultiple != null && pollMultiple) {
143    
144                List<Exchange> exchangeList = new ArrayList<Exchange>();
145                Exchange receivedExchange;
146                while (true) {
147                    if (timeout == 0) {
148                        LOG.debug("Polling Consumer receiveNoWait: {}", consumer);
149                        receivedExchange = consumer.receiveNoWait();
150                    } else {
151                        LOG.debug("Polling Consumer receive with timeout: {} ms. {}", timeout, consumer);
152                        receivedExchange = consumer.receive(timeout);
153                    }
154    
155                    if (receivedExchange == null) {
156                        break;
157                    }
158                    exchangeList.add(receivedExchange);
159                }
160                exchange.getIn().setBody(exchangeList);
161            } else {
162            
163                Exchange resourceExchange;
164                if (timeout < 0) {
165                    LOG.debug("Consumer receive: {}", consumer);
166                    resourceExchange = consumer.receive();
167                } else if (timeout == 0) {
168                    LOG.debug("Consumer receiveNoWait: {}", consumer);
169                    resourceExchange = consumer.receiveNoWait();
170                } else {
171                    LOG.debug("Consumer receive with timeout: {} ms. {}", timeout, consumer);
172                    resourceExchange = consumer.receive(timeout);
173                }
174        
175                if (resourceExchange == null) {
176                    LOG.debug("Consumer received no exchange");
177                } else {
178                    LOG.debug("Consumer received: {}", resourceExchange);
179                }
180    
181                if (resourceExchange != null && resourceExchange.isFailed()) {
182                    // copy resource exchange onto original exchange (preserving pattern)
183                    copyResultsPreservePattern(exchange, resourceExchange);
184                } else {
185                    prepareResult(exchange);
186        
187                    // prepare the exchanges for aggregation
188                    ExchangeHelper.prepareAggregation(exchange, resourceExchange);
189                    // must catch any exception from aggregation
190                    Exchange aggregatedExchange;
191                    try {
192                        aggregatedExchange = aggregationStrategy.aggregate(exchange, resourceExchange);
193                    } catch (Throwable e) {
194                        throw new CamelExchangeException("Error occurred during aggregation", exchange, e);
195                    }
196                    if (aggregatedExchange != null) {
197                        // copy aggregation result onto original exchange (preserving pattern)
198                        copyResultsPreservePattern(exchange, aggregatedExchange);
199                        // handover any synchronization
200                        if (resourceExchange != null) {
201                            resourceExchange.handoverCompletions(exchange);
202                        }
203                    }
204                }
205            }
206    
207            // set header with the uri of the endpoint enriched so we can use that for tracing etc
208            if (exchange.hasOut()) {
209                exchange.getOut().setHeader(Exchange.TO_ENDPOINT, consumer.getEndpoint().getEndpointUri());
210            } else {
211                exchange.getIn().setHeader(Exchange.TO_ENDPOINT, consumer.getEndpoint().getEndpointUri());
212            }
213        }
214    
215        /**
216         * Strategy to pre check polling.
217         * <p/>
218         * Is currently used to prevent doing poll enrich from a file based endpoint when the current route also
219         * started from a file based endpoint as that is not currently supported.
220         *
221         * @param exchange the current exchange
222         */
223        protected void preCheckPoll(Exchange exchange) throws Exception {
224            // noop
225        }
226    
227        private static void prepareResult(Exchange exchange) {
228            if (exchange.getPattern().isOutCapable()) {
229                exchange.getOut().copyFrom(exchange.getIn());
230            }
231        }
232    
233        private static AggregationStrategy defaultAggregationStrategy() {
234            return new CopyAggregationStrategy();
235        }
236    
237        @Override
238        public String toString() {
239            return "PollEnrich[" + consumer + "]";
240        }
241    
242        protected void doStart() throws Exception {
243            ServiceHelper.startService(consumer);
244        }
245    
246        protected void doStop() throws Exception {
247            ServiceHelper.stopService(consumer);
248        }
249    
250        private static class CopyAggregationStrategy implements AggregationStrategy {
251    
252            public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
253                if (newExchange != null) {
254                    copyResultsPreservePattern(oldExchange, newExchange);
255                } else {
256                    // if no newExchange then there was no message from the external resource
257                    // and therefore we should set an empty body to indicate this fact
258                    // but keep headers/attachments as we want to propagate those
259                    oldExchange.getIn().setBody(null);
260                    oldExchange.setOut(null);
261                }
262                return oldExchange;
263            }
264    
265        }
266    
267    }