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