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 org.apache.camel.Exchange;
020    import org.apache.camel.PollingConsumer;
021    import org.apache.camel.Processor;
022    import org.apache.camel.impl.ServiceSupport;
023    import org.apache.camel.processor.aggregate.AggregationStrategy;
024    import org.apache.camel.util.ExchangeHelper;
025    import org.apache.camel.util.ServiceHelper;
026    import org.apache.commons.logging.Log;
027    import org.apache.commons.logging.LogFactory;
028    
029    import static org.apache.camel.util.ExchangeHelper.copyResultsPreservePattern;
030    
031    /**
032     * A content enricher that enriches input data by first obtaining additional
033     * data from a <i>resource</i> represented by an endpoint <code>producer</code>
034     * and second by aggregating input data and additional data. Aggregation of
035     * input data and additional data is delegated to an {@link org.apache.camel.processor.aggregate.AggregationStrategy}
036     * object.
037     * <p/>
038     * Uses a {@link org.apache.camel.PollingConsumer} to obtain the additional data as opposed to {@link Enricher}
039     * that uses a {@link org.apache.camel.Producer}.
040     *
041     * @see Enricher
042     */
043    public class PollEnricher extends ServiceSupport implements Processor {
044    
045        private static final transient Log LOG = LogFactory.getLog(PollEnricher.class);
046        private AggregationStrategy aggregationStrategy;
047        private PollingConsumer consumer;
048        private long timeout;
049    
050        /**
051         * Creates a new {@link PollEnricher}. The default aggregation strategy is to
052         * copy the additional data obtained from the enricher's resource over the
053         * input data. When using the copy aggregation strategy the enricher
054         * degenerates to a normal transformer.
055         *
056         * @param consumer consumer to resource endpoint.
057         */
058        public PollEnricher(PollingConsumer consumer) {
059            this(defaultAggregationStrategy(), consumer, 0);
060        }
061    
062        /**
063         * Creates a new {@link PollEnricher}.
064         *
065         * @param aggregationStrategy  aggregation strategy to aggregate input data and additional data.
066         * @param consumer consumer to resource endpoint.
067         * @param timeout timeout in millis
068         */
069        public PollEnricher(AggregationStrategy aggregationStrategy, PollingConsumer consumer, long timeout) {
070            this.aggregationStrategy = aggregationStrategy;
071            this.consumer = consumer;
072            this.timeout = timeout;
073        }
074    
075        /**
076         * Sets the aggregation strategy for this poll enricher.
077         *
078         * @param aggregationStrategy the aggregationStrategy to set
079         */
080        public void setAggregationStrategy(AggregationStrategy aggregationStrategy) {
081            this.aggregationStrategy = aggregationStrategy;
082        }
083    
084        /**
085         * Sets the default aggregation strategy for this poll enricher.
086         */
087        public void setDefaultAggregationStrategy() {
088            this.aggregationStrategy = defaultAggregationStrategy();
089        }
090    
091        /**
092         * Sets the timeout to use when polling.
093         * <p/>
094         * Use 0 or negative to not use timeout and block until data is available.
095         *
096         * @param timeout timeout in millis.
097         */
098        public void setTimeout(long timeout) {
099            this.timeout = timeout;
100        }
101    
102        /**
103         * Enriches the input data (<code>exchange</code>) by first obtaining
104         * additional data from an endpoint represented by an endpoint
105         * <code>producer</code> and second by aggregating input data and additional
106         * data. Aggregation of input data and additional data is delegated to an
107         * {@link org.apache.camel.processor.aggregate.AggregationStrategy} object set at construction time. If the
108         * message exchange with the resource endpoint fails then no aggregation
109         * will be done and the failed exchange content is copied over to the
110         * original message exchange.
111         *
112         * @param exchange input data.
113         */
114        public void process(Exchange exchange) throws Exception {
115            preCheckPoll(exchange);
116    
117            Exchange resourceExchange;
118            if (timeout < 0) {
119                if (LOG.isDebugEnabled()) {
120                    LOG.debug("Consumer receive: " + consumer);
121                }
122                resourceExchange = consumer.receive();
123            } else if (timeout == 0) {
124                if (LOG.isDebugEnabled()) {
125                    LOG.debug("Consumer receiveNoWait: " + consumer);
126                }
127                resourceExchange = consumer.receiveNoWait();
128            } else {
129                if (LOG.isDebugEnabled()) {
130                    LOG.debug("Consumer receive with timeout: " + timeout + " ms. " + consumer);
131                }
132                resourceExchange = consumer.receive(timeout);
133            }
134    
135            if (LOG.isDebugEnabled()) {
136                if (resourceExchange == null) {
137                    LOG.debug("Consumer received no exchange");
138                } else {
139                    LOG.debug("Consumer received: " + resourceExchange);
140                }
141            }
142    
143            if (resourceExchange != null && resourceExchange.isFailed()) {
144                // copy resource exchange onto original exchange (preserving pattern)
145                copyResultsPreservePattern(exchange, resourceExchange);
146            } else {
147                prepareResult(exchange);
148    
149                // prepare the exchanges for aggregation
150                ExchangeHelper.prepareAggregation(exchange, resourceExchange);
151                Exchange aggregatedExchange = aggregationStrategy.aggregate(exchange, resourceExchange);
152                if (aggregatedExchange != null) {
153                    // copy aggregation result onto original exchange (preserving pattern)
154                    copyResultsPreservePattern(exchange, aggregatedExchange);
155                    // handover any synchronization
156                    if (resourceExchange != null) {
157                        resourceExchange.handoverCompletions(exchange);
158                    }
159                }
160            }
161        }
162    
163        /**
164         * Strategy to pre check polling.
165         * <p/>
166         * Is currently used to prevent doing poll enrich from a file based endpoint when the current route also
167         * started from a file based endpoint as that is not currently supported.
168         *
169         * @param exchange the current exchange
170         */
171        protected void preCheckPoll(Exchange exchange) throws Exception {
172            // noop
173        }
174    
175        private static void prepareResult(Exchange exchange) {
176            if (exchange.getPattern().isOutCapable()) {
177                exchange.getOut().copyFrom(exchange.getIn());
178            }
179        }
180    
181        private static AggregationStrategy defaultAggregationStrategy() {
182            return new CopyAggregationStrategy();
183        }
184    
185        @Override
186        public String toString() {
187            return "PollEnrich[" + consumer + "]";
188        }
189    
190        protected void doStart() throws Exception {
191            ServiceHelper.startService(consumer);
192        }
193    
194        protected void doStop() throws Exception {
195            ServiceHelper.stopService(consumer);
196        }
197    
198        private static class CopyAggregationStrategy implements AggregationStrategy {
199    
200            public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
201                if (newExchange != null) {
202                    copyResultsPreservePattern(oldExchange, newExchange);
203                }
204                return oldExchange;
205            }
206    
207        }
208    
209    }