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 java.util.Iterator;
020
021import org.apache.camel.AsyncCallback;
022import org.apache.camel.AsyncProcessor;
023import org.apache.camel.AsyncProducerCallback;
024import org.apache.camel.CamelContext;
025import org.apache.camel.Endpoint;
026import org.apache.camel.Exchange;
027import org.apache.camel.ExchangePattern;
028import org.apache.camel.Expression;
029import org.apache.camel.FailedToCreateProducerException;
030import org.apache.camel.Message;
031import org.apache.camel.Producer;
032import org.apache.camel.Traceable;
033import org.apache.camel.builder.ExpressionBuilder;
034import org.apache.camel.impl.DefaultExchange;
035import org.apache.camel.impl.EmptyProducerCache;
036import org.apache.camel.impl.ProducerCache;
037import org.apache.camel.support.ServiceSupport;
038import org.apache.camel.util.AsyncProcessorHelper;
039import org.apache.camel.util.ExchangeHelper;
040import org.apache.camel.util.MessageHelper;
041import org.apache.camel.util.ObjectHelper;
042import org.apache.camel.util.ServiceHelper;
043import org.slf4j.Logger;
044import org.slf4j.LoggerFactory;
045
046import static org.apache.camel.processor.PipelineHelper.continueProcessing;
047import static org.apache.camel.util.ObjectHelper.notNull;
048
049/**
050 * Implements a <a href="http://camel.apache.org/routing-slip.html">Routing Slip</a>
051 * pattern where the list of actual endpoints to send a message exchange to are
052 * dependent on the value of a message header.
053 * <p/>
054 * This implementation mirrors the logic from the {@link org.apache.camel.processor.Pipeline} in the async variation
055 * as the failover load balancer is a specialized pipeline. So the trick is to keep doing the same as the
056 * pipeline to ensure it works the same and the async routing engine is flawless.
057 */
058public class RoutingSlip extends ServiceSupport implements AsyncProcessor, Traceable {
059    protected final Logger log = LoggerFactory.getLogger(getClass());
060    protected ProducerCache producerCache;
061    protected int cacheSize;
062    protected boolean ignoreInvalidEndpoints;
063    protected String header;
064    protected Expression expression;
065    protected String uriDelimiter;
066    protected final CamelContext camelContext;
067
068    /**
069     * The iterator to be used for retrieving the next routing slip(s) to be used.
070     */
071    protected interface RoutingSlipIterator {
072
073        /**
074         * Are the more routing slip(s)?
075         *
076         * @param exchange the current exchange
077         * @return <tt>true</tt> if more slips, <tt>false</tt> otherwise.
078         */
079        boolean hasNext(Exchange exchange);
080
081        /**
082         * Returns the next routing slip(s).
083         *
084         * @param exchange the current exchange
085         * @return the slip(s).
086         */
087        Object next(Exchange exchange);
088
089    }
090
091    public RoutingSlip(CamelContext camelContext) {
092        notNull(camelContext, "camelContext");
093        this.camelContext = camelContext;
094    }
095
096    public RoutingSlip(CamelContext camelContext, Expression expression, String uriDelimiter) {
097        notNull(camelContext, "camelContext");
098        notNull(expression, "expression");
099        
100        this.camelContext = camelContext;
101        this.expression = expression;
102        this.uriDelimiter = uriDelimiter;
103        this.header = null;
104    }
105    
106    public void setDelimiter(String delimiter) {
107        this.uriDelimiter = delimiter;
108    }
109    
110    public boolean isIgnoreInvalidEndpoints() {
111        return ignoreInvalidEndpoints;
112    }
113    
114    public void setIgnoreInvalidEndpoints(boolean ignoreInvalidEndpoints) {
115        this.ignoreInvalidEndpoints = ignoreInvalidEndpoints;
116    }
117
118    public int getCacheSize() {
119        return cacheSize;
120    }
121
122    public void setCacheSize(int cacheSize) {
123        this.cacheSize = cacheSize;
124    }
125
126    @Override
127    public String toString() {
128        return "RoutingSlip[expression=" + expression + " uriDelimiter=" + uriDelimiter + "]";
129    }
130
131    public String getTraceLabel() {
132        return "routingSlip[" + expression + "]";
133    }
134
135    public void process(Exchange exchange) throws Exception {
136        AsyncProcessorHelper.process(this, exchange);
137    }
138
139    public boolean process(Exchange exchange, AsyncCallback callback) {
140        if (!isStarted()) {
141            exchange.setException(new IllegalStateException("RoutingSlip has not been started: " + this));
142            callback.done(true);
143            return true;
144        }
145
146        return doRoutingSlip(exchange, callback);
147    }
148
149    public boolean doRoutingSlip(Exchange exchange, Object routingSlip, AsyncCallback callback) {
150        if (routingSlip instanceof Expression) {
151            this.expression = (Expression) routingSlip;
152        } else {
153            this.expression = ExpressionBuilder.constantExpression(routingSlip);
154        }
155        return doRoutingSlip(exchange, callback);
156    }
157
158    /**
159     * Creates the route slip iterator to be used.
160     *
161     * @param exchange the exchange
162     * @return the iterator, should never be <tt>null</tt>
163     */
164    protected RoutingSlipIterator createRoutingSlipIterator(final Exchange exchange) throws Exception {
165        Object slip = expression.evaluate(exchange, Object.class);
166        if (exchange.getException() != null) {
167            // force any exceptions occurred during evaluation to be thrown
168            throw exchange.getException();
169        }
170
171        final Iterator<Object> delegate = ObjectHelper.createIterator(slip, uriDelimiter);
172
173        return new RoutingSlipIterator() {
174            public boolean hasNext(Exchange exchange) {
175                return delegate.hasNext();
176            }
177
178            public Object next(Exchange exchange) {
179                return delegate.next();
180            }
181        };
182    }
183
184    private boolean doRoutingSlip(final Exchange exchange, final AsyncCallback callback) {
185        Exchange current = exchange;
186        RoutingSlipIterator iter;
187        try {
188            iter = createRoutingSlipIterator(exchange);
189        } catch (Exception e) {
190            exchange.setException(e);
191            callback.done(true);
192            return true;
193        }
194
195        // ensure the slip is empty when we start
196        if (current.hasProperties()) {
197            current.setProperty(Exchange.SLIP_ENDPOINT, null);
198        }
199
200        while (iter.hasNext(current)) {
201            Endpoint endpoint;
202            try {
203                endpoint = resolveEndpoint(iter, exchange);
204                // if no endpoint was resolved then try the next
205                if (endpoint == null) {
206                    continue;
207                }
208            } catch (Exception e) {
209                // error resolving endpoint so we should break out
210                current.setException(e);
211                break;
212            }
213
214            //process and prepare the routing slip
215            boolean sync = processExchange(endpoint, current, exchange, callback, iter);
216            current = prepareExchangeForRoutingSlip(current, endpoint);
217            
218            if (!sync) {
219                log.trace("Processing exchangeId: {} is continued being processed asynchronously", exchange.getExchangeId());
220                // the remainder of the routing slip will be completed async
221                // so we break out now, then the callback will be invoked which then continue routing from where we left here
222                return false;
223            }
224
225            log.trace("Processing exchangeId: {} is continued being processed synchronously", exchange.getExchangeId());
226
227            // we ignore some kind of exceptions and allow us to continue
228            if (isIgnoreInvalidEndpoints()) {
229                FailedToCreateProducerException e = current.getException(FailedToCreateProducerException.class);
230                if (e != null) {
231                    if (log.isDebugEnabled()) {
232                        log.debug("Endpoint uri is invalid: " + endpoint + ". This exception will be ignored.", e);
233                    }
234                    current.setException(null);
235                }
236            }
237
238            // Decide whether to continue with the recipients or not; similar logic to the Pipeline
239            // check for error if so we should break out
240            if (!continueProcessing(current, "so breaking out of the routing slip", log)) {
241                break;
242            }
243        }
244
245        // logging nextExchange as it contains the exchange that might have altered the payload and since
246        // we are logging the completion if will be confusing if we log the original instead
247        // we could also consider logging the original and the nextExchange then we have *before* and *after* snapshots
248        log.trace("Processing complete for exchangeId: {} >>> {}", exchange.getExchangeId(), current);
249
250        // copy results back to the original exchange
251        ExchangeHelper.copyResults(exchange, current);
252
253        callback.done(true);
254        return true;
255    }
256
257    protected Endpoint resolveEndpoint(RoutingSlipIterator iter, Exchange exchange) throws Exception {
258        Object nextRecipient = iter.next(exchange);
259        Endpoint endpoint = null;
260        try {
261            endpoint = ExchangeHelper.resolveEndpoint(exchange, nextRecipient);
262        } catch (Exception e) {
263            if (isIgnoreInvalidEndpoints()) {
264                log.info("Endpoint uri is invalid: " + nextRecipient + ". This exception will be ignored.", e);
265            } else {
266                throw e;
267            }
268        }
269        return endpoint;
270    }
271
272    protected Exchange prepareExchangeForRoutingSlip(Exchange current, Endpoint endpoint) {
273        Exchange copy = new DefaultExchange(current);
274        // we must use the same id as this is a snapshot strategy where Camel copies a snapshot
275        // before processing the next step in the pipeline, so we have a snapshot of the exchange
276        // just before. This snapshot is used if Camel should do redeliveries (re try) using
277        // DeadLetterChannel. That is why it's important the id is the same, as it is the *same*
278        // exchange being routed.
279        copy.setExchangeId(current.getExchangeId());
280        copyOutToIn(copy, current);
281
282        // ensure stream caching is reset
283        MessageHelper.resetStreamCache(copy.getIn());
284
285        return copy;
286    }
287
288    protected boolean processExchange(final Endpoint endpoint, final Exchange exchange, final Exchange original,
289                                      final AsyncCallback callback, final RoutingSlipIterator iter) {
290
291        // this does the actual processing so log at trace level
292        log.trace("Processing exchangeId: {} >>> {}", exchange.getExchangeId(), exchange);
293
294        boolean sync = producerCache.doInAsyncProducer(endpoint, exchange, null, callback, new AsyncProducerCallback() {
295            public boolean doInAsyncProducer(Producer producer, AsyncProcessor asyncProducer, final Exchange exchange,
296                                             ExchangePattern exchangePattern, final AsyncCallback callback) {
297                // set property which endpoint we send to
298                exchange.setProperty(Exchange.TO_ENDPOINT, endpoint.getEndpointUri());
299                exchange.setProperty(Exchange.SLIP_ENDPOINT, endpoint.getEndpointUri());
300
301                return asyncProducer.process(exchange, new AsyncCallback() {
302                    public void done(boolean doneSync) {
303                        // we only have to handle async completion of the routing slip
304                        if (doneSync) {
305                            callback.done(doneSync);
306                            return;
307                        }
308
309                        // continue processing the routing slip asynchronously
310                        Exchange current = prepareExchangeForRoutingSlip(exchange, endpoint);
311
312                        while (iter.hasNext(current)) {
313
314                            // we ignore some kind of exceptions and allow us to continue
315                            if (isIgnoreInvalidEndpoints()) {
316                                FailedToCreateProducerException e = current.getException(FailedToCreateProducerException.class);
317                                if (e != null) {
318                                    if (log.isDebugEnabled()) {
319                                        log.debug("Endpoint uri is invalid: " + endpoint + ". This exception will be ignored.", e);
320                                    }
321                                    current.setException(null);
322                                }
323                            }
324
325                            // Decide whether to continue with the recipients or not; similar logic to the Pipeline
326                            // check for error if so we should break out
327                            if (!continueProcessing(current, "so breaking out of the routing slip", log)) {
328                                break;
329                            }
330
331                            Endpoint endpoint;
332                            try {
333                                endpoint = resolveEndpoint(iter, exchange);
334                                // if no endpoint was resolved then try the next
335                                if (endpoint == null) {
336                                    continue;
337                                }
338                            } catch (Exception e) {
339                                // error resolving endpoint so we should break out
340                                exchange.setException(e);
341                                break;
342                            }
343
344                            // prepare and process the routing slip
345                            boolean sync = processExchange(endpoint, current, original, callback, iter);
346                            current = prepareExchangeForRoutingSlip(current, endpoint);
347
348                            if (!sync) {
349                                log.trace("Processing exchangeId: {} is continued being processed asynchronously", original.getExchangeId());
350                                return;
351                            }
352                        }
353
354                        // logging nextExchange as it contains the exchange that might have altered the payload and since
355                        // we are logging the completion if will be confusing if we log the original instead
356                        // we could also consider logging the original and the nextExchange then we have *before* and *after* snapshots
357                        log.trace("Processing complete for exchangeId: {} >>> {}", original.getExchangeId(), current);
358
359                        // copy results back to the original exchange
360                        ExchangeHelper.copyResults(original, current);
361                        callback.done(false);
362                    }
363                });
364            }
365        });
366
367        return sync;
368    }
369
370    protected void doStart() throws Exception {
371        if (producerCache == null) {
372            if (cacheSize < 0) {
373                producerCache = new EmptyProducerCache(this, camelContext);
374                log.debug("RoutingSlip {} is not using ProducerCache", this);
375            } else if (cacheSize == 0) {
376                producerCache = new ProducerCache(this, camelContext);
377                log.debug("RoutingSlip {} using ProducerCache with default cache size", this);
378            } else {
379                producerCache = new ProducerCache(this, camelContext, cacheSize);
380                log.debug("RoutingSlip {} using ProducerCache with cacheSize={}", this, cacheSize);
381            }
382        }
383        ServiceHelper.startService(producerCache);
384    }
385
386    protected void doStop() throws Exception {
387        ServiceHelper.stopService(producerCache);
388    }
389
390    protected void doShutdown() throws Exception {
391        ServiceHelper.stopAndShutdownService(producerCache);
392    }
393
394    /**
395     * Returns the outbound message if available. Otherwise return the inbound message.
396     */
397    private Message getResultMessage(Exchange exchange) {
398        if (exchange.hasOut()) {
399            return exchange.getOut();
400        } else {
401            // if this endpoint had no out (like a mock endpoint) just take the in
402            return exchange.getIn();
403        }
404    }
405
406    /**
407     * Copy the outbound data in 'source' to the inbound data in 'result'.
408     */
409    private void copyOutToIn(Exchange result, Exchange source) {
410        result.setException(source.getException());
411
412        if (source.hasOut() && source.getOut().isFault()) {
413            result.getOut().copyFrom(source.getOut());
414        }
415
416        result.setIn(getResultMessage(source));
417
418        result.getProperties().clear();
419        result.getProperties().putAll(source.getProperties());
420    }
421}