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