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 }