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.Endpoint;
023import org.apache.camel.EndpointAware;
024import org.apache.camel.Exchange;
025import org.apache.camel.ExchangePattern;
026import org.apache.camel.Producer;
027import org.apache.camel.impl.DefaultExchange;
028import org.apache.camel.processor.aggregate.AggregationStrategy;
029import org.apache.camel.support.ServiceSupport;
030import org.apache.camel.util.AsyncProcessorConverterHelper;
031import org.apache.camel.util.AsyncProcessorHelper;
032import org.apache.camel.util.EventHelper;
033import org.apache.camel.util.ExchangeHelper;
034import org.apache.camel.util.ServiceHelper;
035import org.apache.camel.util.StopWatch;
036import org.slf4j.Logger;
037import org.slf4j.LoggerFactory;
038import static org.apache.camel.util.ExchangeHelper.copyResultsPreservePattern;
039
040/**
041 * A content enricher that enriches input data by first obtaining additional
042 * data from a <i>resource</i> represented by an endpoint <code>producer</code>
043 * and second by aggregating input data and additional data. Aggregation of
044 * input data and additional data is delegated to an {@link AggregationStrategy}
045 * object.
046 * <p/>
047 * Uses a {@link org.apache.camel.Producer} to obtain the additional data as opposed to {@link PollEnricher}
048 * that uses a {@link org.apache.camel.PollingConsumer}.
049 *
050 * @see PollEnricher
051 */
052public class Enricher extends ServiceSupport implements AsyncProcessor, EndpointAware {
053
054    private static final Logger LOG = LoggerFactory.getLogger(Enricher.class);
055    private AggregationStrategy aggregationStrategy;
056    private Producer producer;
057    private boolean aggregateOnException;
058
059    /**
060     * Creates a new {@link Enricher}. The default aggregation strategy is to
061     * copy the additional data obtained from the enricher's resource over the
062     * input data. When using the copy aggregation strategy the enricher
063     * degenerates to a normal transformer.
064     * 
065     * @param producer producer to resource endpoint.
066     */
067    public Enricher(Producer producer) {
068        this(defaultAggregationStrategy(), producer);
069    }
070
071    /**
072     * Creates a new {@link Enricher}.
073     * 
074     * @param aggregationStrategy  aggregation strategy to aggregate input data and additional data.
075     * @param producer producer to resource endpoint.
076     */
077    public Enricher(AggregationStrategy aggregationStrategy, Producer producer) {
078        this.aggregationStrategy = aggregationStrategy;
079        this.producer = producer;
080    }
081
082    /**
083     * Sets the aggregation strategy for this enricher.
084     *
085     * @param aggregationStrategy the aggregationStrategy to set
086     */
087    public void setAggregationStrategy(AggregationStrategy aggregationStrategy) {
088        this.aggregationStrategy = aggregationStrategy;
089    }
090
091    public AggregationStrategy getAggregationStrategy() {
092        return aggregationStrategy;
093    }
094
095    public boolean isAggregateOnException() {
096        return aggregateOnException;
097    }
098
099    /**
100     * Whether to call {@link org.apache.camel.processor.aggregate.AggregationStrategy#aggregate(org.apache.camel.Exchange, org.apache.camel.Exchange)} if
101     * an exception was thrown.
102     */
103    public void setAggregateOnException(boolean aggregateOnException) {
104        this.aggregateOnException = aggregateOnException;
105    }
106
107    /**
108     * Sets the default aggregation strategy for this enricher.
109     */
110    public void setDefaultAggregationStrategy() {
111        this.aggregationStrategy = defaultAggregationStrategy();
112    }
113
114    public Endpoint getEndpoint() {
115        return producer.getEndpoint();
116    }
117
118    public void process(Exchange exchange) throws Exception {
119        AsyncProcessorHelper.process(this, exchange);
120    }
121
122    /**
123     * Enriches the input data (<code>exchange</code>) by first obtaining
124     * additional data from an endpoint represented by an endpoint
125     * <code>producer</code> and second by aggregating input data and additional
126     * data. Aggregation of input data and additional data is delegated to an
127     * {@link AggregationStrategy} object set at construction time. If the
128     * message exchange with the resource endpoint fails then no aggregation
129     * will be done and the failed exchange content is copied over to the
130     * original message exchange.
131     *
132     * @param exchange input data.
133     */
134    public boolean process(final Exchange exchange, final AsyncCallback callback) {
135        final Exchange resourceExchange = createResourceExchange(exchange, ExchangePattern.InOut);
136        final Endpoint destination = producer.getEndpoint();
137        
138        EventHelper.notifyExchangeSending(exchange.getContext(), resourceExchange, destination);
139        // record timing for sending the exchange using the producer
140        final StopWatch watch = new StopWatch();
141        AsyncProcessor ap = AsyncProcessorConverterHelper.convert(producer);
142        boolean sync = ap.process(resourceExchange, new AsyncCallback() {
143            public void done(boolean doneSync) {
144                // we only have to handle async completion of the routing slip
145                if (doneSync) {
146                    return;
147                }
148
149                // emit event that the exchange was sent to the endpoint
150                long timeTaken = watch.stop();
151                EventHelper.notifyExchangeSent(resourceExchange.getContext(), resourceExchange, destination, timeTaken);
152                
153                if (!isAggregateOnException() && resourceExchange.isFailed()) {
154                    // copy resource exchange onto original exchange (preserving pattern)
155                    copyResultsPreservePattern(exchange, resourceExchange);
156                } else {
157                    prepareResult(exchange);
158                    try {
159                        // prepare the exchanges for aggregation
160                        ExchangeHelper.prepareAggregation(exchange, resourceExchange);
161
162                        Exchange aggregatedExchange = aggregationStrategy.aggregate(exchange, resourceExchange);
163                        if (aggregatedExchange != null) {
164                            // copy aggregation result onto original exchange (preserving pattern)
165                            copyResultsPreservePattern(exchange, aggregatedExchange);
166                        }
167                    } catch (Throwable e) {
168                        // if the aggregationStrategy threw an exception, set it on the original exchange
169                        exchange.setException(new CamelExchangeException("Error occurred during aggregation", exchange, e));
170                        callback.done(false);
171                        // we failed so break out now
172                        return;
173                    }
174                }
175
176                // set property with the uri of the endpoint enriched so we can use that for tracing etc
177                exchange.setProperty(Exchange.TO_ENDPOINT, producer.getEndpoint().getEndpointUri());
178
179                callback.done(false);
180            }
181        });
182
183        if (!sync) {
184            LOG.trace("Processing exchangeId: {} is continued being processed asynchronously", exchange.getExchangeId());
185            // the remainder of the routing slip will be completed async
186            // so we break out now, then the callback will be invoked which then continue routing from where we left here
187            return false;
188        }
189
190        LOG.trace("Processing exchangeId: {} is continued being processed synchronously", exchange.getExchangeId());
191
192        // emit event that the exchange was sent to the endpoint
193        long timeTaken = watch.stop();
194        EventHelper.notifyExchangeSent(resourceExchange.getContext(), resourceExchange, destination, timeTaken);
195        
196        if (!isAggregateOnException() && resourceExchange.isFailed()) {
197            // copy resource exchange onto original exchange (preserving pattern)
198            copyResultsPreservePattern(exchange, resourceExchange);
199        } else {
200            prepareResult(exchange);
201
202            try {
203                // prepare the exchanges for aggregation
204                ExchangeHelper.prepareAggregation(exchange, resourceExchange);
205
206                Exchange aggregatedExchange = aggregationStrategy.aggregate(exchange, resourceExchange);
207                if (aggregatedExchange != null) {
208                    // copy aggregation result onto original exchange (preserving pattern)
209                    copyResultsPreservePattern(exchange, aggregatedExchange);
210                }
211            } catch (Throwable e) {
212                // if the aggregationStrategy threw an exception, set it on the original exchange
213                exchange.setException(new CamelExchangeException("Error occurred during aggregation", exchange, e));
214                callback.done(true);
215                // we failed so break out now
216                return true;
217            }
218        }
219
220        // set property with the uri of the endpoint enriched so we can use that for tracing etc
221        exchange.setProperty(Exchange.TO_ENDPOINT, producer.getEndpoint().getEndpointUri());
222
223        callback.done(true);
224        return true;
225    }
226
227    /**
228     * Creates a new {@link DefaultExchange} instance from the given
229     * <code>exchange</code>. The resulting exchange's pattern is defined by
230     * <code>pattern</code>.
231     *
232     * @param source  exchange to copy from.
233     * @param pattern exchange pattern to set.
234     * @return created exchange.
235     */
236    protected Exchange createResourceExchange(Exchange source, ExchangePattern pattern) {
237        // copy exchange, and do not share the unit of work
238        Exchange target = ExchangeHelper.createCorrelatedCopy(source, false);
239        target.setPattern(pattern);
240        return target;
241    }
242
243    private static void prepareResult(Exchange exchange) {
244        if (exchange.getPattern().isOutCapable()) {
245            exchange.getOut().copyFrom(exchange.getIn());
246        }
247    }
248
249    private static AggregationStrategy defaultAggregationStrategy() {
250        return new CopyAggregationStrategy();
251    }
252
253    @Override
254    public String toString() {
255        return "Enrich[" + producer.getEndpoint() + "]";
256    }
257
258    protected void doStart() throws Exception {
259        ServiceHelper.startServices(aggregationStrategy, producer);
260    }
261
262    protected void doStop() throws Exception {
263        ServiceHelper.stopServices(producer, aggregationStrategy);
264    }
265
266    private static class CopyAggregationStrategy implements AggregationStrategy {
267
268        public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
269            if (newExchange != null) {
270                copyResultsPreservePattern(oldExchange, newExchange);
271            }
272            return oldExchange;
273        }
274
275    }
276
277}