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.util;
018
019import java.util.ArrayList;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023import java.util.concurrent.ConcurrentHashMap;
024import java.util.concurrent.ExecutionException;
025import java.util.concurrent.Future;
026import java.util.concurrent.TimeUnit;
027import java.util.concurrent.TimeoutException;
028
029import org.apache.camel.CamelContext;
030import org.apache.camel.CamelExchangeException;
031import org.apache.camel.CamelExecutionException;
032import org.apache.camel.Endpoint;
033import org.apache.camel.Exchange;
034import org.apache.camel.ExchangePattern;
035import org.apache.camel.InvalidPayloadException;
036import org.apache.camel.Message;
037import org.apache.camel.MessageHistory;
038import org.apache.camel.NoSuchBeanException;
039import org.apache.camel.NoSuchEndpointException;
040import org.apache.camel.NoSuchHeaderException;
041import org.apache.camel.NoSuchPropertyException;
042import org.apache.camel.NoTypeConversionAvailableException;
043import org.apache.camel.TypeConversionException;
044import org.apache.camel.TypeConverter;
045import org.apache.camel.impl.DefaultExchange;
046import org.apache.camel.impl.MessageSupport;
047import org.apache.camel.spi.UnitOfWork;
048
049/**
050 * Some helper methods for working with {@link Exchange} objects
051 *
052 * @version 
053 */
054public final class ExchangeHelper {
055
056    /**
057     * Utility classes should not have a public constructor.
058     */
059    private ExchangeHelper() {
060    }
061
062    /**
063     * Extracts the Exchange.BINDING of the given type or null if not present
064     *
065     * @param exchange the message exchange
066     * @param type     the expected binding type
067     * @return the binding object of the given type or null if it could not be found or converted
068     */
069    public static <T> T getBinding(Exchange exchange, Class<T> type) {
070        return exchange != null ? exchange.getProperty(Exchange.BINDING, type) : null;
071    }
072
073    /**
074     * Attempts to resolve the endpoint for the given value
075     *
076     * @param exchange the message exchange being processed
077     * @param value    the value which can be an {@link Endpoint} or an object
078     *                 which provides a String representation of an endpoint via
079     *                 {@link #toString()}
080     * @return the endpoint
081     * @throws NoSuchEndpointException if the endpoint cannot be resolved
082     */
083    public static Endpoint resolveEndpoint(Exchange exchange, Object value) throws NoSuchEndpointException {
084        Endpoint endpoint;
085        if (value instanceof Endpoint) {
086            endpoint = (Endpoint) value;
087        } else {
088            String uri = value.toString().trim();
089            endpoint = CamelContextHelper.getMandatoryEndpoint(exchange.getContext(), uri);
090        }
091        return endpoint;
092    }
093
094    /**
095     * Gets the mandatory property of the exchange of the correct type
096     *
097     * @param exchange      the exchange
098     * @param propertyName  the property name
099     * @param type          the type
100     * @return the property value
101     * @throws TypeConversionException is thrown if error during type conversion
102     * @throws NoSuchPropertyException is thrown if no property exists
103     */
104    public static <T> T getMandatoryProperty(Exchange exchange, String propertyName, Class<T> type) throws NoSuchPropertyException {
105        T result = exchange.getProperty(propertyName, type);
106        if (result != null) {
107            return result;
108        }
109        throw new NoSuchPropertyException(exchange, propertyName, type);
110    }
111
112    /**
113     * Gets the mandatory inbound header of the correct type
114     *
115     * @param exchange      the exchange
116     * @param headerName    the header name
117     * @param type          the type
118     * @return the header value
119     * @throws TypeConversionException is thrown if error during type conversion
120     * @throws NoSuchHeaderException is thrown if no headers exists
121     */
122    public static <T> T getMandatoryHeader(Exchange exchange, String headerName, Class<T> type) throws TypeConversionException, NoSuchHeaderException {
123        T answer = exchange.getIn().getHeader(headerName, type);
124        if (answer == null) {
125            throw new NoSuchHeaderException(exchange, headerName, type);
126        }
127        return answer;
128    }
129
130    /**
131     * Returns the mandatory inbound message body of the correct type or throws
132     * an exception if it is not present
133     *
134     * @param exchange the exchange
135     * @return the body, is never <tt>null</tt>
136     * @throws InvalidPayloadException Is thrown if the body being <tt>null</tt> or wrong class type
137     * @deprecated use {@link org.apache.camel.Message#getMandatoryBody()}
138     */
139    @Deprecated
140    public static Object getMandatoryInBody(Exchange exchange) throws InvalidPayloadException {
141        return exchange.getIn().getMandatoryBody();
142    }
143
144    /**
145     * Returns the mandatory inbound message body of the correct type or throws
146     * an exception if it is not present
147     * @deprecated use {@link org.apache.camel.Message#getMandatoryBody(Class)}
148     */
149    @Deprecated
150    public static <T> T getMandatoryInBody(Exchange exchange, Class<T> type) throws InvalidPayloadException {
151        return exchange.getIn().getMandatoryBody(type);
152    }
153
154    /**
155     * Returns the mandatory outbound message body of the correct type or throws
156     * an exception if it is not present
157     * @deprecated use {@link org.apache.camel.Message#getMandatoryBody()}
158     */
159    @Deprecated
160    public static Object getMandatoryOutBody(Exchange exchange) throws InvalidPayloadException {
161        return exchange.getOut().getMandatoryBody();
162    }
163
164    /**
165     * Returns the mandatory outbound message body of the correct type or throws
166     * an exception if it is not present
167     * @deprecated use {@link org.apache.camel.Message#getMandatoryBody(Class)}
168     */
169    @Deprecated
170    public static <T> T getMandatoryOutBody(Exchange exchange, Class<T> type) throws InvalidPayloadException {
171        return exchange.getOut().getMandatoryBody(type);
172    }
173
174    /**
175     * Converts the value to the given expected type or throws an exception
176     *
177     * @return the converted value
178     * @throws TypeConversionException is thrown if error during type conversion
179     * @throws NoTypeConversionAvailableException} if no type converters exists to convert to the given type
180     */
181    public static <T> T convertToMandatoryType(Exchange exchange, Class<T> type, Object value)
182        throws TypeConversionException, NoTypeConversionAvailableException {
183        CamelContext camelContext = exchange.getContext();
184        ObjectHelper.notNull(camelContext, "CamelContext of Exchange");
185        TypeConverter converter = camelContext.getTypeConverter();
186        if (converter != null) {
187            return converter.mandatoryConvertTo(type, exchange, value);
188        }
189        throw new NoTypeConversionAvailableException(value, type);
190    }
191
192    /**
193     * Converts the value to the given expected type
194     *
195     * @return the converted value
196     * @throws org.apache.camel.TypeConversionException is thrown if error during type conversion
197     */
198    public static <T> T convertToType(Exchange exchange, Class<T> type, Object value) throws TypeConversionException {
199        CamelContext camelContext = exchange.getContext();
200        ObjectHelper.notNull(camelContext, "CamelContext of Exchange");
201        TypeConverter converter = camelContext.getTypeConverter();
202        if (converter != null) {
203            return converter.convertTo(type, exchange, value);
204        }
205        return null;
206    }
207
208    /**
209     * Creates a new instance and copies from the current message exchange so that it can be
210     * forwarded to another destination as a new instance. Unlike regular copy this operation
211     * will not share the same {@link org.apache.camel.spi.UnitOfWork} so its should be used
212     * for async messaging, where the original and copied exchange are independent.
213     *
214     * @param exchange original copy of the exchange
215     * @param handover whether the on completion callbacks should be handed over to the new copy.
216     */
217    public static Exchange createCorrelatedCopy(Exchange exchange, boolean handover) {
218        return createCorrelatedCopy(exchange, handover, false);
219    }
220
221    /**
222     * Creates a new instance and copies from the current message exchange so that it can be
223     * forwarded to another destination as a new instance. Unlike regular copy this operation
224     * will not share the same {@link org.apache.camel.spi.UnitOfWork} so its should be used
225     * for async messaging, where the original and copied exchange are independent.
226     *
227     * @param exchange original copy of the exchange
228     * @param handover whether the on completion callbacks should be handed over to the new copy.
229     * @param useSameMessageId whether to use same message id on the copy message.
230     */
231    public static Exchange createCorrelatedCopy(Exchange exchange, boolean handover, boolean useSameMessageId) {
232        String id = exchange.getExchangeId();
233
234        Exchange copy = exchange.copy();
235        // do not reuse message id on copy
236        if (!useSameMessageId) {
237            if (copy.hasOut()) {
238                copy.getOut().setMessageId(null);
239            }
240            copy.getIn().setMessageId(null);
241        }
242        // do not share the unit of work
243        copy.setUnitOfWork(null);
244        // do not reuse the message id
245        // hand over on completion to the copy if we got any
246        UnitOfWork uow = exchange.getUnitOfWork();
247        if (handover && uow != null) {
248            uow.handoverSynchronization(copy);
249        }
250        // set a correlation id so we can track back the original exchange
251        copy.setProperty(Exchange.CORRELATION_ID, id);
252        return copy;
253    }
254
255    /**
256     * Creates a new instance and copies from the current message exchange so that it can be
257     * forwarded to another destination as a new instance.
258     *
259     * @param exchange original copy of the exchange
260     * @param preserveExchangeId whether or not the exchange id should be preserved
261     * @return the copy
262     */
263    public static Exchange createCopy(Exchange exchange, boolean preserveExchangeId) {
264        Exchange copy = exchange.copy();
265        if (preserveExchangeId) {
266            // must preserve exchange id
267            copy.setExchangeId(exchange.getExchangeId());
268        }
269        return copy;
270    }
271
272    /**
273     * Copies the results of a message exchange from the source exchange to the result exchange
274     * which will copy the message contents, exchange properties and the exception.
275     * Notice the {@link ExchangePattern} is <b>not</b> copied/altered.
276     *
277     * @param result the result exchange which will have the output and error state added
278     * @param source the source exchange which is not modified
279     */
280    public static void copyResults(Exchange result, Exchange source) {
281
282        // --------------------------------------------------------------------
283        //  TODO: merge logic with that of copyResultsPreservePattern()
284        // --------------------------------------------------------------------
285
286        if (result == source) {
287            // we just need to ensure MEP is as expected (eg copy result to OUT if out capable)
288            // and the result is not failed
289            if (result.getPattern() == ExchangePattern.InOptionalOut) {
290                // keep as is
291            } else if (result.getPattern().isOutCapable() && !result.hasOut() && !result.isFailed()) {
292                // copy IN to OUT as we expect a OUT response
293                result.getOut().copyFrom(source.getIn());
294            }
295            return;
296        }
297
298        if (result != source) {
299            result.setException(source.getException());
300            if (source.hasOut()) {
301                result.getOut().copyFrom(source.getOut());
302            } else if (result.getPattern() == ExchangePattern.InOptionalOut) {
303                // special case where the result is InOptionalOut and with no OUT response
304                // so we should return null to indicate this fact
305                result.setOut(null);
306            } else {
307                // no results so lets copy the last input
308                // as the final processor on a pipeline might not
309                // have created any OUT; such as a mock:endpoint
310                // so lets assume the last IN is the OUT
311                if (result.getPattern().isOutCapable()) {
312                    // only set OUT if its OUT capable
313                    result.getOut().copyFrom(source.getIn());
314                } else {
315                    // if not replace IN instead to keep the MEP
316                    result.getIn().copyFrom(source.getIn());
317                    // clear any existing OUT as the result is on the IN
318                    if (result.hasOut()) {
319                        result.setOut(null);
320                    }
321                }
322            }
323
324            if (source.hasProperties()) {
325                result.getProperties().putAll(source.getProperties());
326            }
327        }
328    }
329
330    /**
331     * Copies the <code>source</code> exchange to <code>target</code> exchange
332     * preserving the {@link ExchangePattern} of <code>target</code>.
333     *
334     * @param source source exchange.
335     * @param result target exchange.
336     */
337    public static void copyResultsPreservePattern(Exchange result, Exchange source) {
338
339        // --------------------------------------------------------------------
340        //  TODO: merge logic with that of copyResults()
341        // --------------------------------------------------------------------
342
343        if (result == source) {
344            // we just need to ensure MEP is as expected (eg copy result to OUT if out capable)
345            // and the result is not failed
346            if (result.getPattern() == ExchangePattern.InOptionalOut) {
347                // keep as is
348            } else if (result.getPattern().isOutCapable() && !result.hasOut() && !result.isFailed()) {
349                // copy IN to OUT as we expect a OUT response
350                result.getOut().copyFrom(source.getIn());
351            }
352            return;
353        }
354
355        // copy in message
356        result.getIn().copyFrom(source.getIn());
357
358        // copy out message
359        if (source.hasOut()) {
360            // exchange pattern sensitive
361            Message resultMessage = source.getOut().isFault() ? result.getOut() : getResultMessage(result);
362            resultMessage.copyFrom(source.getOut());
363        }
364
365        // copy exception
366        result.setException(source.getException());
367
368        // copy properties
369        if (source.hasProperties()) {
370            result.getProperties().putAll(source.getProperties());
371        }
372    }
373
374    /**
375     * Returns the message where to write results in an
376     * exchange-pattern-sensitive way.
377     *
378     * @param exchange message exchange.
379     * @return result message.
380     */
381    public static Message getResultMessage(Exchange exchange) {
382        if (exchange.getPattern().isOutCapable()) {
383            return exchange.getOut();
384        } else {
385            return exchange.getIn();
386        }
387    }
388
389    /**
390     * Returns true if the given exchange pattern (if defined) can support OUT messages
391     *
392     * @param exchange the exchange to interrogate
393     * @return true if the exchange is defined as an {@link ExchangePattern} which supports
394     *         OUT messages
395     */
396    public static boolean isOutCapable(Exchange exchange) {
397        ExchangePattern pattern = exchange.getPattern();
398        return pattern != null && pattern.isOutCapable();
399    }
400
401    /**
402     * Creates a new instance of the given type from the injector
403     *
404     * @param exchange the exchange
405     * @param type     the given type
406     * @return the created instance of the given type
407     */
408    public static <T> T newInstance(Exchange exchange, Class<T> type) {
409        return exchange.getContext().getInjector().newInstance(type);
410    }
411
412    /**
413     * Creates a Map of the variables which are made available to a script or template
414     *
415     * @param exchange the exchange to make available
416     * @return a Map populated with the require variables
417     */
418    public static Map<String, Object> createVariableMap(Exchange exchange) {
419        Map<String, Object> answer = new HashMap<String, Object>();
420        populateVariableMap(exchange, answer);
421        return answer;
422    }
423
424    /**
425     * Populates the Map with the variables which are made available to a script or template
426     *
427     * @param exchange the exchange to make available
428     * @param map      the map to populate
429     */
430    public static void populateVariableMap(Exchange exchange, Map<String, Object> map) {
431        map.put("exchange", exchange);
432        Message in = exchange.getIn();
433        map.put("in", in);
434        map.put("request", in);
435        map.put("headers", in.getHeaders());
436        map.put("body", in.getBody());
437        if (isOutCapable(exchange)) {
438            // if we are out capable then set out and response as well
439            // however only grab OUT if it exists, otherwise reuse IN
440            // this prevents side effects to alter the Exchange if we force creating an OUT message
441            Message msg = exchange.hasOut() ? exchange.getOut() : exchange.getIn();
442            map.put("out", msg);
443            map.put("response", msg);
444        }
445        map.put("camelContext", exchange.getContext());
446    }
447
448    /**
449     * Returns the MIME content type on the input message or null if one is not defined
450     *
451     * @param exchange the exchange
452     * @return the MIME content type
453     */
454    public static String getContentType(Exchange exchange) {
455        return MessageHelper.getContentType(exchange.getIn());
456    }
457
458    /**
459     * Returns the MIME content encoding on the input message or null if one is not defined
460     *
461     * @param exchange the exchange
462     * @return the MIME content encoding
463     */
464    public static String getContentEncoding(Exchange exchange) {
465        return MessageHelper.getContentEncoding(exchange.getIn());
466    }
467
468    /**
469     * Performs a lookup in the registry of the mandatory bean name and throws an exception if it could not be found
470     *
471     * @param exchange the exchange
472     * @param name     the bean name
473     * @return the bean
474     * @throws NoSuchBeanException if no bean could be found in the registry
475     */
476    public static Object lookupMandatoryBean(Exchange exchange, String name) throws NoSuchBeanException {
477        Object value = lookupBean(exchange, name);
478        if (value == null) {
479            throw new NoSuchBeanException(name);
480        }
481        return value;
482    }
483
484    /**
485     * Performs a lookup in the registry of the mandatory bean name and throws an exception if it could not be found
486     *
487     * @param exchange the exchange
488     * @param name     the bean name
489     * @param type     the expected bean type
490     * @return the bean
491     * @throws NoSuchBeanException if no bean could be found in the registry
492     */
493    public static <T> T lookupMandatoryBean(Exchange exchange, String name, Class<T> type) {
494        T value = lookupBean(exchange, name, type);
495        if (value == null) {
496            throw new NoSuchBeanException(name);
497        }
498        return value;
499    }
500
501    /**
502     * Performs a lookup in the registry of the bean name
503     *
504     * @param exchange the exchange
505     * @param name     the bean name
506     * @return the bean, or <tt>null</tt> if no bean could be found
507     */
508    public static Object lookupBean(Exchange exchange, String name) {
509        return exchange.getContext().getRegistry().lookupByName(name);
510    }
511
512    /**
513     * Performs a lookup in the registry of the bean name and type
514     *
515     * @param exchange the exchange
516     * @param name     the bean name
517     * @param type     the expected bean type
518     * @return the bean, or <tt>null</tt> if no bean could be found
519     */
520    public static <T> T lookupBean(Exchange exchange, String name, Class<T> type) {
521        return exchange.getContext().getRegistry().lookupByNameAndType(name, type);
522    }
523
524    /**
525     * Returns the first exchange in the given collection of exchanges which has the same exchange ID as the one given
526     * or null if none could be found
527     *
528     * @param exchanges  the exchanges
529     * @param exchangeId the exchangeId to find
530     * @return matching exchange, or <tt>null</tt> if none found
531     */
532    public static Exchange getExchangeById(Iterable<Exchange> exchanges, String exchangeId) {
533        for (Exchange exchange : exchanges) {
534            String id = exchange.getExchangeId();
535            if (id != null && id.equals(exchangeId)) {
536                return exchange;
537            }
538        }
539        return null;
540    }
541
542    /**
543     * Prepares the exchanges for aggregation.
544     * <p/>
545     * This implementation will copy the OUT body to the IN body so when you do
546     * aggregation the body is <b>only</b> in the IN body to avoid confusing end users.
547     *
548     * @param oldExchange the old exchange
549     * @param newExchange the new exchange
550     */
551    public static void prepareAggregation(Exchange oldExchange, Exchange newExchange) {
552        // move body/header from OUT to IN
553        if (oldExchange != null) {
554            if (oldExchange.hasOut()) {
555                oldExchange.setIn(oldExchange.getOut());
556                oldExchange.setOut(null);
557            }
558        }
559
560        if (newExchange != null) {
561            if (newExchange.hasOut()) {
562                newExchange.setIn(newExchange.getOut());
563                newExchange.setOut(null);
564            }
565        }
566    }
567
568    /**
569     * Checks whether the exchange has been failure handed
570     *
571     * @param exchange  the exchange
572     * @return <tt>true</tt> if failure handled, <tt>false</tt> otherwise
573     */
574    public static boolean isFailureHandled(Exchange exchange) {
575        return exchange.getProperty(Exchange.FAILURE_HANDLED, false, Boolean.class);
576    }
577
578    /**
579     * Checks whether the exchange {@link UnitOfWork} is exhausted
580     *
581     * @param exchange  the exchange
582     * @return <tt>true</tt> if exhausted, <tt>false</tt> otherwise
583     */
584    public static boolean isUnitOfWorkExhausted(Exchange exchange) {
585        return exchange.getProperty(Exchange.UNIT_OF_WORK_EXHAUSTED, false, Boolean.class);
586    }
587
588    /**
589     * Sets the exchange to be failure handled.
590     *
591     * @param exchange  the exchange
592     */
593    public static void setFailureHandled(Exchange exchange) {
594        exchange.setProperty(Exchange.FAILURE_HANDLED, Boolean.TRUE);
595        // clear exception since its failure handled
596        exchange.setException(null);
597    }
598
599    /**
600     * Checks whether the exchange is redelivery exhausted
601     *
602     * @param exchange  the exchange
603     * @return <tt>true</tt> if exhausted, <tt>false</tt> otherwise
604     */
605    public static boolean isRedeliveryExhausted(Exchange exchange) {
606        return exchange.getProperty(Exchange.REDELIVERY_EXHAUSTED, false, Boolean.class);
607    }
608
609    /**
610     * Checks whether the exchange {@link UnitOfWork} is redelivered
611     *
612     * @param exchange  the exchange
613     * @return <tt>true</tt> if redelivered, <tt>false</tt> otherwise
614     */
615    public static boolean isRedelivered(Exchange exchange) {
616        return exchange.getIn().hasHeaders() && exchange.getIn().getHeader(Exchange.REDELIVERED, false, Boolean.class);
617    }
618
619    /**
620     * Checks whether the exchange {@link UnitOfWork} has been interrupted during processing
621     *
622     * @param exchange  the exchange
623     * @return <tt>true</tt> if interrupted, <tt>false</tt> otherwise
624     */
625    public static boolean isInterrupted(Exchange exchange) {
626        return exchange.getException(InterruptedException.class) != null;
627    }
628
629    /**
630     * Extracts the body from the given exchange.
631     * <p/>
632     * If the exchange pattern is provided it will try to honor it and retrieve the body
633     * from either IN or OUT according to the pattern.
634     *
635     * @param exchange the exchange
636     * @param pattern  exchange pattern if given, can be <tt>null</tt>
637     * @return the result body, can be <tt>null</tt>.
638     * @throws CamelExecutionException is thrown if the processing of the exchange failed
639     */
640    public static Object extractResultBody(Exchange exchange, ExchangePattern pattern) {
641        Object answer = null;
642        if (exchange != null) {
643            // rethrow if there was an exception during execution
644            if (exchange.getException() != null) {
645                throw ObjectHelper.wrapCamelExecutionException(exchange, exchange.getException());
646            }
647
648            // result could have a fault message
649            if (hasFaultMessage(exchange)) {
650                Message msg = exchange.hasOut() ? exchange.getOut() : exchange.getIn();
651                answer = msg.getBody();
652                return answer;
653            }
654
655            // okay no fault then return the response according to the pattern
656            // try to honor pattern if provided
657            boolean notOut = pattern != null && !pattern.isOutCapable();
658            boolean hasOut = exchange.hasOut();
659            if (hasOut && !notOut) {
660                // we have a response in out and the pattern is out capable
661                answer = exchange.getOut().getBody();
662            } else if (!hasOut && exchange.getPattern() == ExchangePattern.InOptionalOut) {
663                // special case where the result is InOptionalOut and with no OUT response
664                // so we should return null to indicate this fact
665                answer = null;
666            } else {
667                // use IN as the response
668                answer = exchange.getIn().getBody();
669            }
670        }
671        return answer;
672    }
673
674    /**
675     * Tests whether the exchange has a fault message set and that its not null.
676     *
677     * @param exchange the exchange
678     * @return <tt>true</tt> if fault message exists
679     */
680    public static boolean hasFaultMessage(Exchange exchange) {
681        Message msg = exchange.hasOut() ? exchange.getOut() : exchange.getIn();
682        return msg.isFault() && msg.getBody() != null;
683    }
684
685    /**
686     * Tests whether the exchange has already been handled by the error handler
687     *
688     * @param exchange the exchange
689     * @return <tt>true</tt> if handled already by error handler, <tt>false</tt> otherwise
690     */
691    public static boolean hasExceptionBeenHandledByErrorHandler(Exchange exchange) {
692        return Boolean.TRUE.equals(exchange.getProperty(Exchange.ERRORHANDLER_HANDLED));
693    }
694
695    /**
696     * Extracts the body from the given future, that represents a handle to an asynchronous exchange.
697     * <p/>
698     * Will wait until the future task is complete.
699     *
700     * @param context the camel context
701     * @param future  the future handle
702     * @param type    the expected body response type
703     * @return the result body, can be <tt>null</tt>.
704     * @throws CamelExecutionException is thrown if the processing of the exchange failed
705     */
706    public static <T> T extractFutureBody(CamelContext context, Future<Object> future, Class<T> type) {
707        try {
708            return doExtractFutureBody(context, future.get(), type);
709        } catch (InterruptedException e) {
710            throw ObjectHelper.wrapRuntimeCamelException(e);
711        } catch (ExecutionException e) {
712            // execution failed due to an exception so rethrow the cause
713            throw ObjectHelper.wrapCamelExecutionException(null, e.getCause());
714        } finally {
715            // its harmless to cancel if task is already completed
716            // and in any case we do not want to get hold of the task a 2nd time
717            // and its recommended to cancel according to Brian Goetz in his Java Concurrency in Practice book
718            future.cancel(true);
719        }
720    }
721
722    /**
723     * Extracts the body from the given future, that represents a handle to an asynchronous exchange.
724     * <p/>
725     * Will wait for the future task to complete, but waiting at most the timeout value.
726     *
727     * @param context the camel context
728     * @param future  the future handle
729     * @param timeout timeout value
730     * @param unit    timeout unit
731     * @param type    the expected body response type
732     * @return the result body, can be <tt>null</tt>.
733     * @throws CamelExecutionException is thrown if the processing of the exchange failed
734     * @throws java.util.concurrent.TimeoutException is thrown if a timeout triggered
735     */
736    public static <T> T extractFutureBody(CamelContext context, Future<Object> future, long timeout, TimeUnit unit, Class<T> type) throws TimeoutException {
737        try {
738            if (timeout > 0) {
739                return doExtractFutureBody(context, future.get(timeout, unit), type);
740            } else {
741                return doExtractFutureBody(context, future.get(), type);
742            }
743        } catch (InterruptedException e) {
744            // execution failed due interruption so rethrow the cause
745            throw ObjectHelper.wrapCamelExecutionException(null, e);
746        } catch (ExecutionException e) {
747            // execution failed due to an exception so rethrow the cause
748            throw ObjectHelper.wrapCamelExecutionException(null, e.getCause());
749        } finally {
750            // its harmless to cancel if task is already completed
751            // and in any case we do not want to get hold of the task a 2nd time
752            // and its recommended to cancel according to Brian Goetz in his Java Concurrency in Practice book
753            future.cancel(true);
754        }
755    }
756
757    private static <T> T doExtractFutureBody(CamelContext context, Object result, Class<T> type) {
758        if (result == null) {
759            return null;
760        }
761        if (type.isAssignableFrom(result.getClass())) {
762            return type.cast(result);
763        }
764        if (result instanceof Exchange) {
765            Exchange exchange = (Exchange) result;
766            Object answer = ExchangeHelper.extractResultBody(exchange, exchange.getPattern());
767            return context.getTypeConverter().convertTo(type, exchange, answer);
768        }
769        return context.getTypeConverter().convertTo(type, result);
770    }
771
772    /**
773     * @deprecated use org.apache.camel.CamelExchangeException.createExceptionMessage instead
774     */
775    @Deprecated
776    public static String createExceptionMessage(String message, Exchange exchange, Throwable cause) {
777        return CamelExchangeException.createExceptionMessage(message, exchange, cause);
778    }
779
780    /**
781     * Strategy to prepare results before next iterator or when we are complete,
782     * which is done by copying OUT to IN, so there is only an IN as input
783     * for the next iteration.
784     *
785     * @param exchange the exchange to prepare
786     */
787    public static void prepareOutToIn(Exchange exchange) {
788        // we are routing using pipes and filters so we need to manually copy OUT to IN
789        if (exchange.hasOut()) {
790            exchange.getIn().copyFrom(exchange.getOut());
791            exchange.setOut(null);
792        }
793    }
794
795    /**
796     * Gets both the messageId and exchangeId to be used for logging purposes.
797     * <p/>
798     * Logging both ids, can help to correlate exchanges which may be redelivered messages
799     * from for example a JMS broker.
800     *
801     * @param exchange the exchange
802     * @return a log message with both the messageId and exchangeId
803     */
804    public static String logIds(Exchange exchange) {
805        String msgId = exchange.hasOut() ? exchange.getOut().getMessageId() : exchange.getIn().getMessageId();
806        return "(MessageId: " + msgId + " on ExchangeId: " + exchange.getExchangeId()  + ")";
807    }
808
809    /**
810     * Copies the exchange but the copy will be tied to the given context
811     *
812     * @param exchange  the source exchange
813     * @param context   the camel context
814     * @return a copy with the given camel context
815     */
816    public static Exchange copyExchangeAndSetCamelContext(Exchange exchange, CamelContext context) {
817        return copyExchangeAndSetCamelContext(exchange, context, true);
818    }
819
820    /**
821     * Copies the exchange but the copy will be tied to the given context
822     *
823     * @param exchange  the source exchange
824     * @param context   the camel context
825     * @param handover  whether to handover on completions from the source to the copy
826     * @return a copy with the given camel context
827     */
828    public static Exchange copyExchangeAndSetCamelContext(Exchange exchange, CamelContext context, boolean handover) {
829        DefaultExchange answer = new DefaultExchange(context, exchange.getPattern());
830        if (exchange.hasProperties()) {
831            answer.setProperties(safeCopy(exchange.getProperties()));
832        }
833        if (handover) {
834            // Need to hand over the completion for async invocation
835            exchange.handoverCompletions(answer);
836        }
837        answer.setIn(exchange.getIn().copy());
838        if (exchange.hasOut()) {
839            answer.setOut(exchange.getOut().copy());
840        }
841        answer.setException(exchange.getException());
842        return answer;
843    }
844
845    /**
846     * Replaces the existing message with the new message
847     *
848     * @param exchange  the exchange
849     * @param newMessage the new message
850     * @param outOnly    whether to replace the message as OUT message
851     */
852    public static void replaceMessage(Exchange exchange, Message newMessage, boolean outOnly) {
853        Message old = exchange.hasOut() ? exchange.getOut() : exchange.getIn();
854        if (outOnly || exchange.hasOut()) {
855            exchange.setOut(newMessage);
856        } else {
857            exchange.setIn(newMessage);
858        }
859
860        // need to de-reference old from the exchange so it can be GC
861        if (old instanceof MessageSupport) {
862            ((MessageSupport) old).setExchange(null);
863        }
864    }
865
866    @SuppressWarnings("unchecked")
867    private static Map<String, Object> safeCopy(Map<String, Object> properties) {
868        if (properties == null) {
869            return null;
870        }
871
872        Map<String, Object> answer = new ConcurrentHashMap<String, Object>(properties);
873
874        // safe copy message history using a defensive copy
875        List<MessageHistory> history = (List<MessageHistory>) answer.remove(Exchange.MESSAGE_HISTORY);
876        if (history != null) {
877            answer.put(Exchange.MESSAGE_HISTORY, new ArrayList<MessageHistory>(history));
878        }
879
880        return answer;
881    }
882}