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