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.util;
018    
019    import java.util.HashMap;
020    import java.util.Map;
021    import java.util.concurrent.ExecutionException;
022    import java.util.concurrent.Future;
023    import java.util.concurrent.TimeUnit;
024    import java.util.concurrent.TimeoutException;
025    
026    import org.apache.camel.CamelContext;
027    import org.apache.camel.CamelExecutionException;
028    import org.apache.camel.Endpoint;
029    import org.apache.camel.Exchange;
030    import org.apache.camel.ExchangePattern;
031    import org.apache.camel.InvalidPayloadException;
032    import org.apache.camel.Message;
033    import org.apache.camel.NoSuchBeanException;
034    import org.apache.camel.NoSuchEndpointException;
035    import org.apache.camel.NoSuchHeaderException;
036    import org.apache.camel.NoSuchPropertyException;
037    import org.apache.camel.NoTypeConversionAvailableException;
038    import org.apache.camel.TypeConverter;
039    import org.apache.camel.spi.UnitOfWork;
040    
041    /**
042     * Some helper methods for working with {@link Exchange} objects
043     *
044     * @version $Revision: 893110 $
045     */
046    public final class ExchangeHelper {
047    
048        /**
049         * Utility classes should not have a public constructor.
050         */
051        private ExchangeHelper() {
052        }
053    
054        /**
055         * Extracts the Exchange.BINDING of the given type or null if not present
056         *
057         * @param exchange the message exchange
058         * @param type the expected binding type
059         * @return the binding object of the given type or null if it could not be found or converted
060         */
061        public static <T> T getBinding(Exchange exchange, Class<T> type) {
062            return exchange != null ? exchange.getProperty(Exchange.BINDING, type) : null;
063        }
064    
065        /**
066         * Attempts to resolve the endpoint for the given value
067         *
068         * @param exchange the message exchange being processed
069         * @param value the value which can be an {@link Endpoint} or an object
070         *                which provides a String representation of an endpoint via
071         *                {@link #toString()}
072         *
073         * @return the endpoint
074         * @throws NoSuchEndpointException if the endpoint cannot be resolved
075         */
076        public static Endpoint resolveEndpoint(Exchange exchange, Object value) throws NoSuchEndpointException {
077            Endpoint endpoint;
078            if (value instanceof Endpoint) {
079                endpoint = (Endpoint)value;
080            } else {
081                String uri = value.toString();
082                endpoint = CamelContextHelper.getMandatoryEndpoint(exchange.getContext(), uri);
083            }
084            return endpoint;
085        }
086    
087        public static <T> T getMandatoryProperty(Exchange exchange, String propertyName, Class<T> type) throws NoSuchPropertyException {
088            T result = exchange.getProperty(propertyName, type);
089            if (result != null) {
090                return result;
091            }
092            throw new NoSuchPropertyException(exchange, propertyName, type);
093        }
094    
095        public static <T> T getMandatoryHeader(Exchange exchange, String propertyName, Class<T> type) throws NoSuchHeaderException {
096            T answer = exchange.getIn().getHeader(propertyName, type);
097            if (answer == null) {
098                throw new NoSuchHeaderException(exchange, propertyName, type);
099            }
100            return answer;
101        }
102    
103        /**
104         * Returns the mandatory inbound message body of the correct type or throws
105         * an exception if it is not present
106         */
107        public static Object getMandatoryInBody(Exchange exchange) throws InvalidPayloadException {
108            return exchange.getIn().getMandatoryBody();
109        }
110    
111        /**
112         * Returns the mandatory inbound message body of the correct type or throws
113         * an exception if it is not present
114         */
115        public static <T> T getMandatoryInBody(Exchange exchange, Class<T> type) throws InvalidPayloadException {
116            return exchange.getIn().getMandatoryBody(type);
117        }
118    
119        /**
120         * Returns the mandatory outbound message body of the correct type or throws
121         * an exception if it is not present
122         */
123        public static Object getMandatoryOutBody(Exchange exchange) throws InvalidPayloadException {
124            return exchange.getOut().getMandatoryBody();
125        }
126    
127        /**
128         * Returns the mandatory outbound message body of the correct type or throws
129         * an exception if it is not present
130         */
131        public static <T> T getMandatoryOutBody(Exchange exchange, Class<T> type) throws InvalidPayloadException {
132            return exchange.getOut().getMandatoryBody(type);
133        }
134    
135        /**
136         * Converts the value to the given expected type or throws an exception
137         */
138        public static <T> T convertToMandatoryType(Exchange exchange, Class<T> type, Object value) throws NoTypeConversionAvailableException {
139            CamelContext camelContext = exchange.getContext();
140            TypeConverter converter = camelContext.getTypeConverter();
141            if (converter != null) {
142                return converter.mandatoryConvertTo(type, exchange, value);
143            }
144            throw new NoTypeConversionAvailableException(value, type);
145        }
146    
147        /**
148         * Converts the value to the given expected type returning null if it could
149         * not be converted
150         */
151        public static <T> T convertToType(Exchange exchange, Class<T> type, Object value) {
152            CamelContext camelContext = exchange.getContext();
153            TypeConverter converter = camelContext.getTypeConverter();
154            if (converter != null) {
155                return converter.convertTo(type, exchange, value);
156            }
157            return null;
158        }
159    
160        /**
161         * Creates a new instance and copies from the current message exchange so that it can be
162         * forwarded to another destination as a new instance. Unlike regular copy this operation
163         * will not share the same {@link org.apache.camel.spi.UnitOfWork} so its should be used
164         * for async messaging, where the original and copied exchange are independent.
165         *
166         * @param exchange original copy of the exchange
167         * @param handover whether the on completion callbacks should be handed over to the new copy.
168         */
169        public static Exchange createCorrelatedCopy(Exchange exchange, boolean handover) {
170            Exchange copy = exchange.copy();
171            // do not share the unit of work
172            copy.setUnitOfWork(null);
173            // hand over on completion to the copy if we got any
174            UnitOfWork uow = exchange.getUnitOfWork();
175            if (handover && uow != null) {
176                uow.handoverSynchronization(copy);
177            }
178            // set a correlation id so we can track back the original exchange
179            copy.setProperty(Exchange.CORRELATION_ID, exchange.getExchangeId());
180            return copy;
181        }
182    
183        /**
184         * Copies the results of a message exchange from the source exchange to the result exchange
185         * which will copy the out and fault message contents and the exception
186         *
187         * @param result the result exchange which will have the output and error state added
188         * @param source the source exchange which is not modified
189         */
190        public static void copyResults(Exchange result, Exchange source) {
191    
192            // --------------------------------------------------------------------
193            //  TODO: merge logic with that of copyResultsPreservePattern()
194            // --------------------------------------------------------------------
195            
196            if (result != source) {
197                result.setException(source.getException());
198                if (source.hasOut()) {
199                    result.getOut().copyFrom(source.getOut());
200                } else if (result.getPattern() == ExchangePattern.InOptionalOut) {
201                    // special case where the result is InOptionalOut and with no OUT response
202                    // so we should return null to indicate this fact
203                    result.setOut(null);
204                } else {
205                    // no results so lets copy the last input
206                    // as the final processor on a pipeline might not
207                    // have created any OUT; such as a mock:endpoint
208                    // so lets assume the last IN is the OUT
209                    if (result.getPattern().isOutCapable()) {
210                        // only set OUT if its OUT capable
211                        result.getOut().copyFrom(source.getIn());
212                    } else {
213                        // if not replace IN instead to keep the MEP
214                        result.getIn().copyFrom(source.getIn());
215                    }
216                }
217    
218                if (source.hasProperties()) {
219                    result.getProperties().putAll(source.getProperties());
220                }
221            }
222        }
223    
224        /**
225         * Copies the <code>source</code> exchange to <code>target</code> exchange
226         * preserving the {@link ExchangePattern} of <code>target</code>.  
227         * 
228         * @param source source exchange.
229         * @param result target exchange.
230         */
231        public static void copyResultsPreservePattern(Exchange result, Exchange source) {
232    
233            // --------------------------------------------------------------------
234            //  TODO: merge logic with that of copyResults()
235            // --------------------------------------------------------------------
236            
237            if (source == result) {
238                // no need to copy
239                return;
240            }
241    
242            // copy in message
243            result.getIn().copyFrom(source.getIn());
244        
245            // copy out message
246            if (source.hasOut()) {
247                // exchange pattern sensitive
248                Message resultMessage = source.getOut().isFault() ? result.getOut() : getResultMessage(result);
249                resultMessage.copyFrom(source.getOut());
250            }
251    
252            // copy exception
253            result.setException(source.getException());
254            
255            // copy properties
256            if (source.hasProperties()) {
257                result.getProperties().putAll(source.getProperties());
258            }
259        }
260    
261        /**
262         * Returns the message where to write results in an
263         * exchange-pattern-sensitive way.
264         * 
265         * @param exchange message exchange.
266         * @return result message.
267         */
268        public static Message getResultMessage(Exchange exchange) {
269            if (exchange.getPattern().isOutCapable()) {
270                return exchange.getOut();
271            } else {
272                return exchange.getIn();
273            }
274        }
275    
276        /**
277         * Returns true if the given exchange pattern (if defined) can support OUT messages
278         *
279         * @param exchange the exchange to interrogate
280         * @return true if the exchange is defined as an {@link ExchangePattern} which supports
281         * OUT messages
282         */
283        public static boolean isOutCapable(Exchange exchange) {
284            ExchangePattern pattern = exchange.getPattern();
285            return pattern != null && pattern.isOutCapable();
286        }
287    
288        /**
289         * Creates a new instance of the given type from the injector
290         */
291        public static <T> T newInstance(Exchange exchange, Class<T> type) {
292            return exchange.getContext().getInjector().newInstance(type);
293        }
294    
295        /**
296         * Creates a Map of the variables which are made available to a script or template
297         *
298         * @param exchange the exchange to make available
299         * @return a Map populated with the require variables
300         */
301        public static Map<String, Object> createVariableMap(Exchange exchange) {
302            Map<String, Object> answer = new HashMap<String, Object>();
303            populateVariableMap(exchange, answer);
304            return answer;
305        }
306    
307        /**
308         * Populates the Map with the variables which are made available to a script or template
309         *
310         * @param exchange the exchange to make available
311         * @param map      the map to populate
312         */
313        public static void populateVariableMap(Exchange exchange, Map<String, Object> map) {
314            map.put("exchange", exchange);
315            Message in = exchange.getIn();
316            map.put("in", in);
317            map.put("request", in);
318            map.put("headers", in.getHeaders());
319            map.put("body", in.getBody());
320            if (isOutCapable(exchange)) {
321                Message out = exchange.getOut();
322                map.put("out", out);
323                map.put("response", out);
324            }
325            map.put("camelContext", exchange.getContext());
326        }
327    
328        /**
329         * Returns the MIME content type on the input message or null if one is not defined
330         */
331        public static String getContentType(Exchange exchange) {
332            return MessageHelper.getContentType(exchange.getIn());        
333        }
334    
335        /**
336         * Returns the MIME content encoding on the input message or null if one is not defined
337         */
338        public static String getContentEncoding(Exchange exchange) {
339            return MessageHelper.getContentEncoding(exchange.getIn());
340        }
341    
342        /**
343         * Performs a lookup in the registry of the mandatory bean name and throws an exception if it could not be found
344         */
345        public static Object lookupMandatoryBean(Exchange exchange, String name) {
346            Object value = lookupBean(exchange, name);
347            if (value == null) {
348                throw new NoSuchBeanException(name);
349            }
350            return value;
351        }
352    
353        /**
354         * Performs a lookup in the registry of the mandatory bean name and throws an exception if it could not be found
355         */
356        public static <T> T lookupMandatoryBean(Exchange exchange, String name, Class<T> type) {
357            T value = lookupBean(exchange, name, type);
358            if (value == null) {
359                throw new NoSuchBeanException(name);
360            }
361            return value;
362        }
363    
364        /**
365         * Performs a lookup in the registry of the bean name
366         */
367        public static Object lookupBean(Exchange exchange, String name) {
368            return exchange.getContext().getRegistry().lookup(name);
369        }
370    
371        /**
372         * Performs a lookup in the registry of the bean name and type
373         */
374        public static <T> T lookupBean(Exchange exchange, String name, Class<T> type) {
375            return exchange.getContext().getRegistry().lookup(name, type);
376        }
377    
378        /**
379         * Returns the first exchange in the given collection of exchanges which has the same exchange ID as the one given
380         * or null if none could be found
381         */
382        public static Exchange getExchangeById(Iterable<Exchange> exchanges, String exchangeId) {
383            for (Exchange exchange : exchanges) {
384                String id = exchange.getExchangeId();
385                if (id != null && id.equals(exchangeId)) {
386                    return exchange;
387                }
388            }
389            return null;
390        }
391    
392        /**
393         * Prepares the exchanges for aggregation.
394         * <p/>
395         * This implementation will copy the OUT body to the IN body so when you do
396         * aggregation the body is <b>only</b> in the IN body to avoid confusing end users.
397         *
398         * @param oldExchange  the old exchange
399         * @param newExchange  the new exchange
400         */
401        public static void prepareAggregation(Exchange oldExchange, Exchange newExchange) {
402            // move body/header from OUT to IN
403            if (oldExchange != null) {
404                if (oldExchange.hasOut()) {
405                    oldExchange.setIn(oldExchange.getOut());
406                    oldExchange.setOut(null);
407                }
408            }
409    
410            if (newExchange != null) {
411                if (newExchange.hasOut()) {
412                    newExchange.setIn(newExchange.getOut());
413                    newExchange.setOut(null);
414                }
415            }
416        }
417    
418        public static boolean isFailureHandled(Exchange exchange) {
419            return exchange.getProperty(Exchange.FAILURE_HANDLED, false, Boolean.class);
420        }
421    
422        public static void setFailureHandled(Exchange exchange) {
423            exchange.setProperty(Exchange.FAILURE_HANDLED, Boolean.TRUE);
424            // clear exception since its failure handled
425            exchange.setException(null);
426        }
427    
428        public static boolean isRedelieryExhausted(Exchange exchange) {
429            return exchange.getProperty(Exchange.REDELIVERY_EXHAUSTED, false, Boolean.class);
430        }
431    
432        /**
433         * Extracts the body from the given exchange.
434         * <p/>
435         * If the exchange pattern is provided it will try to honor it and retrive the body
436         * from either IN or OUT according to the pattern.
437         *
438         * @param exchange   the exchange
439         * @param pattern    exchange pattern if given, can be <tt>null</tt>
440         * @return the result body, can be <tt>null</tt>.
441         * @throws CamelExecutionException if the processing of the exchange failed
442         */
443        public static Object extractResultBody(Exchange exchange, ExchangePattern pattern) {
444            Object answer = null;
445            if (exchange != null) {
446                // rethrow if there was an exception during execution
447                if (exchange.getException() != null) {
448                    throw ObjectHelper.wrapCamelExecutionException(exchange, exchange.getException());
449                }
450    
451                // result could have a fault message
452                if (hasFaultMessage(exchange)) {
453                    return exchange.getOut().getBody();
454                }
455    
456                // okay no fault then return the response according to the pattern
457                // try to honor pattern if provided
458                boolean notOut = pattern != null && !pattern.isOutCapable();
459                boolean hasOut = exchange.hasOut();
460                if (hasOut && !notOut) {
461                    // we have a response in out and the pattern is out capable
462                    answer = exchange.getOut().getBody();
463                } else if (!hasOut && exchange.getPattern() == ExchangePattern.InOptionalOut) {
464                    // special case where the result is InOptionalOut and with no OUT response
465                    // so we should return null to indicate this fact
466                    answer = null;
467                } else {
468                    // use IN as the response
469                    answer = exchange.getIn().getBody();
470                }
471            }
472            return answer;
473        }
474    
475        /**
476         * Tests whether the exchange has a fault message set and that its not null.
477         *
478         * @param exchange  the exchange
479         * @return <tt>true</tt> if fault message exists
480         */
481        public static boolean hasFaultMessage(Exchange exchange) {
482            return exchange.hasOut() && exchange.getOut().isFault() && exchange.getOut().getBody() != null;
483        }
484    
485        /**
486         * Extracts the body from the given future, that represents a handle to an asynchronous exchange.
487         * <p/>
488         * Will wait until the future task is complete.
489         *
490         * @param context the camel context
491         * @param future the future handle
492         * @param type the expected body response type
493         * @return the result body, can be <tt>null</tt>.
494         * @throws CamelExecutionException if the processing of the exchange failed
495         */
496        public static <T> T extractFutureBody(CamelContext context, Future<Object> future, Class<T> type) {
497            try {
498                return doExtractFutureBody(context, future.get(), type);
499            } catch (InterruptedException e) {
500                throw ObjectHelper.wrapRuntimeCamelException(e);
501            } catch (ExecutionException e) {
502                // execution failed due to an exception so rethrow the cause
503                throw ObjectHelper.wrapCamelExecutionException(null, e.getCause());
504            } finally {
505                // its harmless to cancel if task is already completed
506                // and in any case we do not want to get hold of the task a 2nd time
507                // and its recommended to cancel according to Brian Goetz in his Java Concurrency in Practice book
508                future.cancel(true);
509            }
510        }
511    
512        /**
513         * Extracts the body from the given future, that represents a handle to an asynchronous exchange.
514         * <p/>
515         * Will wait for the future task to complete, but waiting at most the timeout value.
516         *
517         * @param context the camel context
518         * @param future the future handle
519         * @param timeout timeout value
520         * @param unit    timeout unit
521         * @param type the expected body response type
522         * @return the result body, can be <tt>null</tt>.
523         * @throws CamelExecutionException if the processing of the exchange failed
524         * @throws java.util.concurrent.TimeoutException is thrown if a timeout triggered
525         */
526        public static <T> T extractFutureBody(CamelContext context, Future<Object> future, long timeout, TimeUnit unit, Class<T> type) throws TimeoutException {
527            try {
528                if (timeout > 0) {
529                    return doExtractFutureBody(context, future.get(timeout, unit), type);
530                } else {
531                    return doExtractFutureBody(context, future.get(), type);
532                }
533            } catch (InterruptedException e) {
534                throw ObjectHelper.wrapRuntimeCamelException(e);
535            } catch (ExecutionException e) {
536                // execution failed due to an exception so rethrow the cause
537                throw ObjectHelper.wrapCamelExecutionException(null, e.getCause());
538            } finally {
539                // its harmless to cancel if task is already completed
540                // and in any case we do not want to get hold of the task a 2nd time
541                // and its recommended to cancel according to Brian Goetz in his Java Concurrency in Practice book
542                future.cancel(true);
543            }
544        }
545    
546        private static <T> T doExtractFutureBody(CamelContext context, Object result, Class<T> type) {
547            if (result == null) {
548                return null;
549            }
550            if (type.isAssignableFrom(result.getClass())) {
551                return type.cast(result);
552            }
553            if (result instanceof Exchange) {
554                Exchange exchange = (Exchange) result;
555                Object answer = ExchangeHelper.extractResultBody(exchange, exchange.getPattern());
556                return context.getTypeConverter().convertTo(type, answer);
557            }
558            return context.getTypeConverter().convertTo(type, result);
559        }
560    
561        /**
562         * Creates an exception message with the provided details.
563         * <p/>
564         * All fields is optional so you can pass in only an exception, or just a message etc. or any combination.
565         *
566         * @param message the message
567         * @param exchange the exchange
568         * @param cause the caused exception
569         * @return an error message (without stacktrace from exception)
570         */
571        public static String createExceptionMessage(String message, Exchange exchange, Throwable cause) {
572            StringBuilder sb = new StringBuilder();
573            if (message != null) {
574                sb.append(message);
575            }
576            if (exchange != null) {
577                if (sb.length() > 0) {
578                    sb.append(". ");
579                }
580                sb.append(exchange);
581            }
582            if (cause != null) {
583                if (sb.length() > 0) {
584                    sb.append(". ");
585                }
586                sb.append("Caused by: [" + cause.getClass().getName() + " - " + cause.getMessage() + "]");
587            }
588            return sb.toString().trim();
589        }
590    
591    }