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.builder;
018
019import java.io.PrintWriter;
020import java.io.StringWriter;
021import java.text.SimpleDateFormat;
022import java.util.ArrayList;
023import java.util.Collection;
024import java.util.Comparator;
025import java.util.Date;
026import java.util.Iterator;
027import java.util.List;
028import java.util.Map;
029import java.util.Random;
030import java.util.Scanner;
031import java.util.Set;
032import java.util.TimeZone;
033import java.util.concurrent.atomic.AtomicReference;
034import java.util.function.BiFunction;
035import java.util.function.Function;
036import java.util.regex.Matcher;
037import java.util.regex.Pattern;
038import org.apache.camel.CamelContext;
039import org.apache.camel.Component;
040import org.apache.camel.Endpoint;
041import org.apache.camel.Exchange;
042import org.apache.camel.Expression;
043import org.apache.camel.InvalidPayloadException;
044import org.apache.camel.Message;
045import org.apache.camel.NoSuchEndpointException;
046import org.apache.camel.NoSuchLanguageException;
047import org.apache.camel.NoTypeConversionAvailableException;
048import org.apache.camel.Producer;
049import org.apache.camel.component.bean.BeanInvocation;
050import org.apache.camel.component.properties.PropertiesComponent;
051import org.apache.camel.language.bean.BeanLanguage;
052import org.apache.camel.language.simple.SimpleLanguage;
053import org.apache.camel.model.language.MethodCallExpression;
054import org.apache.camel.processor.DefaultExchangeFormatter;
055import org.apache.camel.spi.ExchangeFormatter;
056import org.apache.camel.spi.Language;
057import org.apache.camel.spi.RouteContext;
058import org.apache.camel.spi.UnitOfWork;
059import org.apache.camel.support.ExpressionAdapter;
060import org.apache.camel.support.TokenPairExpressionIterator;
061import org.apache.camel.support.TokenXMLExpressionIterator;
062import org.apache.camel.support.XMLTokenExpressionIterator;
063import org.apache.camel.util.CamelContextHelper;
064import org.apache.camel.util.ExchangeHelper;
065import org.apache.camel.util.FileUtil;
066import org.apache.camel.util.GroupIterator;
067import org.apache.camel.util.GroupTokenIterator;
068import org.apache.camel.util.IOHelper;
069import org.apache.camel.util.MessageHelper;
070import org.apache.camel.util.ObjectHelper;
071import org.apache.camel.util.OgnlHelper;
072import org.apache.camel.util.SkipIterator;
073import org.apache.camel.util.StringHelper;
074
075/**
076 * A helper class for working with <a href="http://camel.apache.org/expression.html">expressions</a>.
077 *
078 * @version
079 */
080public final class ExpressionBuilder {
081
082    private static final Pattern OFFSET_PATTERN = Pattern.compile("([+-])([^+-]+)");
083
084    /**
085     * Utility classes should not have a public constructor.
086     */
087    private ExpressionBuilder() {
088    }
089
090    /**
091     * Returns an expression for the inbound message attachments
092     *
093     * @return an expression object which will return the inbound message attachments
094     */
095    public static Expression attachmentObjectsExpression() {
096        return new ExpressionAdapter() {
097            public Object evaluate(Exchange exchange) {
098                return exchange.getIn().getAttachmentObjects();
099            }
100
101            @Override
102            public String toString() {
103                return "attachmentObjects";
104            }
105        };
106    }
107
108    /**
109     * Returns an expression for the inbound message attachments
110     *
111     * @return an expression object which will return the inbound message attachments
112     */
113    public static Expression attachmentObjectValuesExpression() {
114        return new ExpressionAdapter() {
115            public Object evaluate(Exchange exchange) {
116                return exchange.getIn().getAttachmentObjects().values();
117            }
118
119            @Override
120            public String toString() {
121                return "attachmentObjects";
122            }
123        };
124    }
125
126    /**
127     * Returns an expression for the inbound message attachments
128     *
129     * @return an expression object which will return the inbound message attachments
130     */
131    public static Expression attachmentsExpression() {
132        return new ExpressionAdapter() {
133            public Object evaluate(Exchange exchange) {
134                return exchange.getIn().getAttachments();
135            }
136
137            @Override
138            public String toString() {
139                return "attachments";
140            }
141        };
142    }
143
144    /**
145     * Returns an expression for the inbound message attachments
146     *
147     * @return an expression object which will return the inbound message attachments
148     */
149    public static Expression attachmentValuesExpression() {
150        return new ExpressionAdapter() {
151            public Object evaluate(Exchange exchange) {
152                return exchange.getIn().getAttachments().values();
153            }
154
155            @Override
156            public String toString() {
157                return "attachments";
158            }
159        };
160    }
161
162    /**
163     * Returns an expression for the header value with the given name
164     * <p/>
165     * Will fallback and look in properties if not found in headers.
166     *
167     * @param headerName the name of the header the expression will return
168     * @return an expression object which will return the header value
169     */
170    public static Expression headerExpression(final String headerName) {
171        return new ExpressionAdapter() {
172            public Object evaluate(Exchange exchange) {
173                String name = simpleExpression(headerName).evaluate(exchange, String.class);
174                Object header = exchange.getIn().getHeader(name);
175                if (header == null) {
176                    // fall back on a property
177                    header = exchange.getProperty(name);
178                }
179                return header;
180            }
181
182            @Override
183            public String toString() {
184                return "header(" + headerName + ")";
185            }
186        };
187    }
188
189    /**
190     * Returns an expression for the header value with the given name converted to the given type
191     * <p/>
192     * Will fallback and look in properties if not found in headers.
193     *
194     * @param headerName the name of the header the expression will return
195     * @param type the type to convert to
196     * @return an expression object which will return the header value
197     */
198    public static <T> Expression headerExpression(final String headerName, final Class<T> type) {
199        return new ExpressionAdapter() {
200            public Object evaluate(Exchange exchange) {
201                String name = simpleExpression(headerName).evaluate(exchange, String.class);
202                Object header = exchange.getIn().getHeader(name, type);
203                if (header == null) {
204                    // fall back on a property
205                    header = exchange.getProperty(name, type);
206                }
207                return header;
208            }
209
210            @Override
211            public String toString() {
212                return "headerAs(" + headerName + ", " + type + ")";
213            }
214        };
215    }
216
217    /**
218     * Returns an expression for the header value with the given name converted to the given type
219     * <p/>
220     * Will fallback and look in properties if not found in headers.
221     *
222     * @param headerName the name of the header the expression will return
223     * @param typeName the type to convert to as a FQN class name
224     * @return an expression object which will return the header value
225     */
226    public static Expression headerExpression(final String headerName, final String typeName) {
227        return new ExpressionAdapter() {
228            public Object evaluate(Exchange exchange) {
229                Class<?> type;
230                try {
231                    String text = simpleExpression(typeName).evaluate(exchange, String.class);
232                    type = exchange.getContext().getClassResolver().resolveMandatoryClass(text);
233                } catch (ClassNotFoundException e) {
234                    throw ObjectHelper.wrapCamelExecutionException(exchange, e);
235                }
236
237                String text = simpleExpression(headerName).evaluate(exchange, String.class);
238                Object header = exchange.getIn().getHeader(text, type);
239                if (header == null) {
240                    // fall back on a property
241                    header = exchange.getProperty(text, type);
242                }
243                return header;
244            }
245
246            @Override
247            public String toString() {
248                return "headerAs(" + headerName + ", " + typeName + ")";
249            }
250        };
251    }
252
253    /**
254     * Returns the expression for the exchanges inbound message header invoking methods defined
255     * in a simple OGNL notation
256     *
257     * @param ognl  methods to invoke on the header in a simple OGNL syntax
258     */
259    public static Expression headersOgnlExpression(final String ognl) {
260        return new KeyedOgnlExpressionAdapter(ognl, "headerOgnl(" + ognl + ")",
261            new KeyedOgnlExpressionAdapter.KeyedEntityRetrievalStrategy() {
262                public Object getKeyedEntity(Exchange exchange, String key) {
263                    String text = simpleExpression(key).evaluate(exchange, String.class);
264                    return exchange.getIn().getHeader(text);
265                }
266            });
267    }
268
269    /**
270     * Returns an expression for the inbound message headers
271     *
272     * @return an expression object which will return the inbound headers
273     */
274    public static Expression headersExpression() {
275        return new ExpressionAdapter() {
276            public Object evaluate(Exchange exchange) {
277                return exchange.getIn().getHeaders();
278            }
279
280            @Override
281            public String toString() {
282                return "headers";
283            }
284        };
285    }
286
287    /**
288     * Returns an expression for the out header value with the given name
289     * <p/>
290     * Will fallback and look in properties if not found in headers.
291     *
292     * @param headerName the name of the header the expression will return
293     * @return an expression object which will return the header value
294     */
295    public static Expression outHeaderExpression(final String headerName) {
296        return new ExpressionAdapter() {
297            public Object evaluate(Exchange exchange) {
298                if (!exchange.hasOut()) {
299                    return null;
300                }
301
302                String text = simpleExpression(headerName).evaluate(exchange, String.class);
303                Message out = exchange.getOut();
304                Object header = out.getHeader(text);
305                if (header == null) {
306                    // let's try the exchange header
307                    header = exchange.getProperty(text);
308                }
309                return header;
310            }
311
312            @Override
313            public String toString() {
314                return "outHeader(" + headerName + ")";
315            }
316        };
317    }
318
319    /**
320     * Returns an expression for the outbound message headers
321     *
322     * @return an expression object which will return the headers, will be <tt>null</tt> if the
323     * exchange is not out capable.
324     */
325    public static Expression outHeadersExpression() {
326        return new ExpressionAdapter() {
327            public Object evaluate(Exchange exchange) {
328                // only get out headers if the MEP is out capable
329                if (ExchangeHelper.isOutCapable(exchange)) {
330                    return exchange.getOut().getHeaders();
331                } else {
332                    return null;
333                }
334            }
335
336            @Override
337            public String toString() {
338                return "outHeaders";
339            }
340        };
341    }
342
343    /**
344     * Returns an expression for the exchange pattern
345     *
346     * @see org.apache.camel.Exchange#getPattern()
347     * @return an expression object which will return the exchange pattern
348     */
349    public static Expression exchangePatternExpression() {
350        return new ExpressionAdapter() {
351            public Object evaluate(Exchange exchange) {
352                return exchange.getPattern();
353            }
354
355            @Override
356            public String toString() {
357                return "exchangePattern";
358            }
359        };
360    }
361
362    /**
363     * Returns an expression for an exception set on the exchange
364     *
365     * @see Exchange#getException()
366     * @return an expression object which will return the exception set on the exchange
367     */
368    public static Expression exchangeExceptionExpression() {
369        return new ExpressionAdapter() {
370            public Object evaluate(Exchange exchange) {
371                Exception exception = exchange.getException();
372                if (exception == null) {
373                    exception = exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Exception.class);
374                }
375                return exception;
376            }
377
378            @Override
379            public String toString() {
380                return "exchangeException";
381            }
382        };
383    }
384
385    /**
386     * Returns an expression for an exception set on the exchange
387     * <p/>
388     * Is used to get the caused exception that typically have been wrapped in some sort
389     * of Camel wrapper exception
390     * @param type the exception type
391     * @see Exchange#getException(Class)
392     * @return an expression object which will return the exception set on the exchange
393     */
394    public static Expression exchangeExceptionExpression(final Class<Exception> type) {
395        return new ExpressionAdapter() {
396            public Object evaluate(Exchange exchange) {
397                Exception exception = exchange.getException(type);
398                if (exception == null) {
399                    exception = exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Exception.class);
400                    return ObjectHelper.getException(type, exception);
401                }
402                return exception;
403            }
404
405            @Override
406            public String toString() {
407                return "exchangeException[" + type + "]";
408            }
409        };
410    }
411
412    /**
413     * Returns the expression for the exchanges exception invoking methods defined
414     * in a simple OGNL notation
415     *
416     * @param ognl  methods to invoke on the body in a simple OGNL syntax
417     */
418    public static Expression exchangeExceptionOgnlExpression(final String ognl) {
419        return new ExpressionAdapter() {
420            public Object evaluate(Exchange exchange) {
421                Object exception = exchange.getException();
422                if (exception == null) {
423                    exception = exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Exception.class);
424                }
425
426                if (exception == null) {
427                    return null;
428                }
429
430                // ognl is able to evaluate method name if it contains nested functions
431                // so we should not eager evaluate ognl as a string
432                return new MethodCallExpression(exception, ognl).evaluate(exchange);
433            }
434
435            @Override
436            public String toString() {
437                return "exchangeExceptionOgnl(" + ognl + ")";
438            }
439        };
440    }
441
442    /**
443     * Returns an expression for the type converter
444     *
445     * @return an expression object which will return the type converter
446     */
447    public static Expression typeConverterExpression() {
448        return new ExpressionAdapter() {
449            public Object evaluate(Exchange exchange) {
450                return exchange.getContext().getTypeConverter();
451            }
452
453            @Override
454            public String toString() {
455                return "typeConverter";
456            }
457        };
458    }
459
460    /**
461     * Returns an expression for the {@link org.apache.camel.spi.Registry}
462     *
463     * @return an expression object which will return the registry
464     */
465    public static Expression registryExpression() {
466        return new ExpressionAdapter() {
467            public Object evaluate(Exchange exchange) {
468                return exchange.getContext().getRegistry();
469            }
470
471            @Override
472            public String toString() {
473                return "registry";
474            }
475        };
476    }
477
478    /**
479     * Returns an expression for lookup a bean in the {@link org.apache.camel.spi.Registry}
480     *
481     * @return an expression object which will return the bean
482     */
483    public static Expression refExpression(final String ref) {
484        return new ExpressionAdapter() {
485            public Object evaluate(Exchange exchange) {
486                String text = simpleExpression(ref).evaluate(exchange, String.class);
487                return exchange.getContext().getRegistry().lookupByName(text);
488            }
489
490            @Override
491            public String toString() {
492                return "ref(" + ref + ")";
493            }
494        };
495    }
496
497    /**
498     * Returns an expression for the {@link org.apache.camel.CamelContext}
499     *
500     * @return an expression object which will return the camel context
501     */
502    public static Expression camelContextExpression() {
503        return new ExpressionAdapter() {
504            public Object evaluate(Exchange exchange) {
505                return exchange.getContext();
506            }
507
508            @Override
509            public String toString() {
510                return "camelContext";
511            }
512        };
513    }
514
515    /**
516     * Returns an expression for the {@link org.apache.camel.CamelContext} name
517     *
518     * @return an expression object which will return the camel context name
519     */
520    public static Expression camelContextNameExpression() {
521        return new ExpressionAdapter() {
522            public Object evaluate(Exchange exchange) {
523                return exchange.getContext().getName();
524            }
525
526            @Override
527            public String toString() {
528                return "camelContextName";
529            }
530        };
531    }
532
533    /**
534     * Returns an expression for an exception message set on the exchange
535     *
536     * @see <tt>Exchange.getException().getMessage()</tt>
537     * @return an expression object which will return the exception message set on the exchange
538     */
539    public static Expression exchangeExceptionMessageExpression() {
540        return new ExpressionAdapter() {
541            public Object evaluate(Exchange exchange) {
542                Exception exception = exchange.getException();
543                if (exception == null) {
544                    exception = exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Exception.class);
545                }
546                return exception != null ? exception.getMessage() : null;
547            }
548
549            @Override
550            public String toString() {
551                return "exchangeExceptionMessage";
552            }
553        };
554    }
555
556    /**
557     * Returns an expression for an exception stacktrace set on the exchange
558     *
559     * @return an expression object which will return the exception stacktrace set on the exchange
560     */
561    public static Expression exchangeExceptionStackTraceExpression() {
562        return new ExpressionAdapter() {
563            public Object evaluate(Exchange exchange) {
564                Exception exception = exchange.getException();
565                if (exception == null) {
566                    exception = exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Exception.class);
567                }
568                if (exception != null) {
569                    StringWriter sw = new StringWriter();
570                    PrintWriter pw = new PrintWriter(sw);
571                    exception.printStackTrace(pw);
572                    IOHelper.close(pw, sw);
573                    return sw.toString();
574                } else {
575                    return null;
576                }
577            }
578
579            @Override
580            public String toString() {
581                return "exchangeExceptionStackTrace";
582            }
583        };
584    }
585
586    /**
587     * Returns an expression for the property value of exchange with the given name
588     *
589     * @param propertyName the name of the property the expression will return
590     * @return an expression object which will return the property value
591     * @deprecated use {@link #exchangePropertyExpression(String)} instead
592     */
593    @Deprecated
594    public static Expression propertyExpression(final String propertyName) {
595        return new ExpressionAdapter() {
596            public Object evaluate(Exchange exchange) {
597                String text = simpleExpression(propertyName).evaluate(exchange, String.class);
598                return exchange.getProperty(text);
599            }
600
601            @Override
602            public String toString() {
603                return "exchangeProperty(" + propertyName + ")";
604            }
605        };
606    }
607
608    /**
609     * Returns an expression for the property value of exchange with the given name
610     *
611     * @param propertyName the name of the property the expression will return
612     * @return an expression object which will return the property value
613     */
614    public static Expression exchangePropertyExpression(final String propertyName) {
615        return new ExpressionAdapter() {
616            public Object evaluate(Exchange exchange) {
617                String text = simpleExpression(propertyName).evaluate(exchange, String.class);
618                return exchange.getProperty(text);
619            }
620
621            @Override
622            public String toString() {
623                return "exchangeProperty(" + propertyName + ")";
624            }
625        };
626    }
627
628    /**
629     * Returns an expression for the property value of exchange with the given name invoking methods defined
630     * in a simple OGNL notation
631     *
632     * @param ognl  methods to invoke on the property in a simple OGNL syntax
633     */
634    public static Expression propertyOgnlExpression(final String ognl) {
635        return new KeyedOgnlExpressionAdapter(ognl, "propertyOgnl(" + ognl + ")",
636            new KeyedOgnlExpressionAdapter.KeyedEntityRetrievalStrategy() {
637                public Object getKeyedEntity(Exchange exchange, String key) {
638                    String text = simpleExpression(key).evaluate(exchange, String.class);
639                    return exchange.getProperty(text);
640                }
641            });
642    }
643
644    /**
645     * Returns an expression for the properties of exchange
646     *
647     * @return an expression object which will return the properties
648     * @deprecated use {@link #exchangeExceptionExpression()} instead
649     */
650    @Deprecated
651    public static Expression propertiesExpression() {
652        return exchangeExceptionExpression();
653    }
654
655    /**
656     * Returns an expression for the exchange properties of exchange
657     *
658     * @return an expression object which will return the exchange properties
659     */
660    public static Expression exchangePropertiesExpression() {
661        return new ExpressionAdapter() {
662            public Object evaluate(Exchange exchange) {
663                return exchange.getProperties();
664            }
665
666            @Override
667            public String toString() {
668                return "exchangeProperties";
669            }
670        };
671    }
672
673    /**
674     * Returns an expression for the properties of the camel context
675     *
676     * @return an expression object which will return the properties
677     */
678    public static Expression camelContextPropertiesExpression() {
679        return new ExpressionAdapter() {
680            public Object evaluate(Exchange exchange) {
681                return exchange.getContext().getGlobalOptions();
682            }
683
684            @Override
685            public String toString() {
686                return "camelContextProperties";
687            }
688        };
689    }
690
691    /**
692     * Returns an expression for the property value of the camel context with the given name
693     *
694     * @param propertyName the name of the property the expression will return
695     * @return an expression object which will return the property value
696     */
697    public static Expression camelContextPropertyExpression(final String propertyName) {
698        return new ExpressionAdapter() {
699            public Object evaluate(Exchange exchange) {
700                String text = simpleExpression(propertyName).evaluate(exchange, String.class);
701                return exchange.getContext().getGlobalOption(text);
702            }
703
704            @Override
705            public String toString() {
706                return "camelContextProperty(" + propertyName + ")";
707            }
708        };
709    }
710
711    /**
712     * Returns an expression for a system property value with the given name
713     *
714     * @param propertyName the name of the system property the expression will return
715     * @return an expression object which will return the system property value
716     */
717    public static Expression systemPropertyExpression(final String propertyName) {
718        return systemPropertyExpression(propertyName, null);
719    }
720
721    /**
722     * Returns an expression for a system property value with the given name
723     *
724     * @param propertyName the name of the system property the expression will return
725     * @param defaultValue default value to return if no system property exists
726     * @return an expression object which will return the system property value
727     */
728    public static Expression systemPropertyExpression(final String propertyName,
729                                                      final String defaultValue) {
730        return new ExpressionAdapter() {
731            public Object evaluate(Exchange exchange) {
732                String text = simpleExpression(propertyName).evaluate(exchange, String.class);
733                String text2 = simpleExpression(defaultValue).evaluate(exchange, String.class);
734                return System.getProperty(text, text2);
735            }
736
737            @Override
738            public String toString() {
739                return "systemProperty(" + propertyName + ")";
740            }
741        };
742    }
743
744    /**
745     * Returns an expression for a system environment value with the given name
746     *
747     * @param propertyName the name of the system environment the expression will return
748     * @return an expression object which will return the system property value
749     */
750    public static Expression systemEnvironmentExpression(final String propertyName) {
751        return systemEnvironmentExpression(propertyName, null);
752    }
753
754    /**
755     * Returns an expression for a system environment value with the given name
756     *
757     * @param propertyName the name of the system environment the expression will return
758     * @param defaultValue default value to return if no system environment exists
759     * @return an expression object which will return the system environment value
760     */
761    public static Expression systemEnvironmentExpression(final String propertyName,
762                                                         final String defaultValue) {
763        return new ExpressionAdapter() {
764            public Object evaluate(Exchange exchange) {
765                String text = simpleExpression(propertyName).evaluate(exchange, String.class);
766                String answer = System.getenv(text.toUpperCase());
767                if (answer == null) {
768                    String text2 = simpleExpression(defaultValue).evaluate(exchange, String.class);
769                    answer = text2;
770                }
771                return answer;
772            }
773
774            @Override
775            public String toString() {
776                return "systemEnvironment(" + propertyName + ")";
777            }
778        };
779    }
780
781    /**
782     * Returns an expression for the constant value
783     *
784     * @param value the value the expression will return
785     * @return an expression object which will return the constant value
786     */
787    public static Expression constantExpression(final Object value) {
788        return new ExpressionAdapter() {
789            public Object evaluate(Exchange exchange) {
790                return value;
791            }
792
793            @Override
794            public String toString() {
795                return "" + value;
796            }
797        };
798    }
799
800    /**
801     * Returns an expression for evaluating the expression/predicate using the given language
802     *
803     * @param expression  the expression or predicate
804     * @return an expression object which will evaluate the expression/predicate using the given language
805     */
806    public static Expression languageExpression(final String language, final String expression) {
807        return new ExpressionAdapter() {
808            public Object evaluate(Exchange exchange) {
809                Language lan = exchange.getContext().resolveLanguage(language);
810                if (lan != null) {
811                    return lan.createExpression(expression).evaluate(exchange, Object.class);
812                } else {
813                    throw new NoSuchLanguageException(language);
814                }
815            }
816
817            @Override
818            public boolean matches(Exchange exchange) {
819                Language lan = exchange.getContext().resolveLanguage(language);
820                if (lan != null) {
821                    return lan.createPredicate(expression).matches(exchange);
822                } else {
823                    throw new NoSuchLanguageException(language);
824                }
825            }
826
827            @Override
828            public String toString() {
829                return "language[" + language + ":" + expression + "]";
830            }
831        };
832    }
833
834    /**
835     * Returns an expression for a type value
836     *
837     * @param name the type name
838     * @return an expression object which will return the type value
839     */
840    public static Expression typeExpression(final String name) {
841        return new ExpressionAdapter() {
842            public Object evaluate(Exchange exchange) {
843                // it may refer to a class type
844                String text = simpleExpression(name).evaluate(exchange, String.class);
845                Class<?> type = exchange.getContext().getClassResolver().resolveClass(text);
846                if (type != null) {
847                    return type;
848                }
849
850                int pos = text.lastIndexOf(".");
851                if (pos > 0) {
852                    String before = text.substring(0, pos);
853                    String after = text.substring(pos + 1);
854                    type = exchange.getContext().getClassResolver().resolveClass(before);
855                    if (type != null) {
856                        return ObjectHelper.lookupConstantFieldValue(type, after);
857                    }
858                }
859
860                throw ObjectHelper.wrapCamelExecutionException(exchange, new ClassNotFoundException("Cannot find type " + text));
861            }
862
863            @Override
864            public String toString() {
865                return "type:" + name;
866            }
867        };
868    }
869
870    /**
871     * Returns an expression that caches the evaluation of another expression
872     * and returns the cached value, to avoid re-evaluating the expression.
873     *
874     * @param expression  the target expression to cache
875     * @return the cached value
876     */
877    public static Expression cacheExpression(final Expression expression) {
878        return new ExpressionAdapter() {
879            private final AtomicReference<Object> cache = new AtomicReference<Object>();
880
881            public Object evaluate(Exchange exchange) {
882                Object answer = cache.get();
883                if (answer == null) {
884                    answer = expression.evaluate(exchange, Object.class);
885                    cache.set(answer);
886                }
887                return answer;
888            }
889
890            @Override
891            public String toString() {
892                return expression.toString();
893            }
894        };
895    }
896
897    /**
898     * Returns the expression for the exchanges inbound message body
899     */
900    public static Expression bodyExpression() {
901        return new ExpressionAdapter() {
902            public Object evaluate(Exchange exchange) {
903                return exchange.getIn().getBody();
904            }
905
906            @Override
907            public String toString() {
908                return "body";
909            }
910        };
911    }
912
913    /**
914     * Returns a functional expression for the exchanges inbound message body
915     */
916    public static Expression bodyExpression(final Function<Object, Object> function) {
917        return new ExpressionAdapter() {
918            public Object evaluate(Exchange exchange) {
919                return function.apply(
920                    exchange.getIn().getBody()
921                );
922            }
923
924            @Override
925            public String toString() {
926                return "bodyExpression";
927            }
928        };
929    }
930
931    /**
932     * Returns a functional expression for the exchanges inbound message body and headers
933     */
934    public static Expression bodyExpression(final BiFunction<Object, Map<String, Object>, Object> function) {
935        return new ExpressionAdapter() {
936            public Object evaluate(Exchange exchange) {
937                return function.apply(
938                    exchange.getIn().getBody(),
939                    exchange.getIn().getHeaders()
940                );
941            }
942
943            @Override
944            public String toString() {
945                return "bodyExpression";
946            }
947        };
948    }
949
950    /**
951     * Returns a functional expression for the exchanges inbound message body converted to a desired type
952     */
953    public static <T> Expression bodyExpression(final Class<T> bodyType, final Function<T, Object> function) {
954        return new ExpressionAdapter() {
955            public Object evaluate(Exchange exchange) {
956                return function.apply(
957                    exchange.getIn().getBody(bodyType)
958                );
959            }
960
961            @Override
962            public String toString() {
963                return "bodyExpression (" + bodyType + ")";
964            }
965        };
966    }
967
968    /**
969     * Returns a functional expression for the exchanges inbound message body converted to a desired type and headers
970     */
971    public static <T> Expression bodyExpression(final Class<T> bodyType, final BiFunction<T, Map<String, Object>, Object> function) {
972        return new ExpressionAdapter() {
973            public Object evaluate(Exchange exchange) {
974                return function.apply(
975                    exchange.getIn().getBody(bodyType),
976                    exchange.getIn().getHeaders()
977                );
978            }
979
980            @Override
981            public String toString() {
982                return "bodyExpression (" + bodyType + ")";
983            }
984        };
985    }
986
987    /**
988     * Returns the expression for the exchanges inbound message body invoking methods defined
989     * in a simple OGNL notation
990     *
991     * @param ognl  methods to invoke on the body in a simple OGNL syntax
992     */
993    public static Expression bodyOgnlExpression(final String ognl) {
994        return new ExpressionAdapter() {
995            public Object evaluate(Exchange exchange) {
996                Object body = exchange.getIn().getBody();
997                if (body == null) {
998                    return null;
999                }
1000                // ognl is able to evaluate method name if it contains nested functions
1001                // so we should not eager evaluate ognl as a string
1002                return new MethodCallExpression(body, ognl).evaluate(exchange);
1003            }
1004
1005            @Override
1006            public String toString() {
1007                return "bodyOgnl(" + ognl + ")";
1008            }
1009        };
1010    }
1011
1012    /**
1013     * Returns the expression for invoking a method (support OGNL syntax) on the given expression
1014     *
1015     * @param exp   the expression to evaluate and invoke the method on its result
1016     * @param ognl  methods to invoke on the evaluated expression in a simple OGNL syntax
1017     */
1018    public static Expression ognlExpression(final Expression exp, final String ognl) {
1019        return new ExpressionAdapter() {
1020            public Object evaluate(Exchange exchange) {
1021                Object value = exp.evaluate(exchange, Object.class);
1022                if (value == null) {
1023                    return null;
1024                }
1025                // ognl is able to evaluate method name if it contains nested functions
1026                // so we should not eager evaluate ognl as a string
1027                return new MethodCallExpression(value, ognl).evaluate(exchange);
1028            }
1029
1030            @Override
1031            public String toString() {
1032                return "ognl(" + exp + ", " + ognl + ")";
1033            }
1034        };
1035    }
1036
1037    /**
1038     * Returns the expression for the exchanges camelContext invoking methods defined
1039     * in a simple OGNL notation
1040     *
1041     * @param ognl  methods to invoke on the context in a simple OGNL syntax
1042     */
1043    public static Expression camelContextOgnlExpression(final String ognl) {
1044        return new ExpressionAdapter() {
1045            public Object evaluate(Exchange exchange) {
1046                CamelContext context = exchange.getContext();
1047                if (context == null) {
1048                    return null;
1049                }
1050                // ognl is able to evaluate method name if it contains nested functions
1051                // so we should not eager evaluate ognl as a string
1052                return new MethodCallExpression(context, ognl).evaluate(exchange);
1053            }
1054
1055            @Override
1056            public String toString() {
1057                return "camelContextOgnl(" + ognl + ")";
1058            }
1059        };
1060    }
1061
1062    /**
1063     * Returns the expression for the exchange invoking methods defined
1064     * in a simple OGNL notation
1065     *
1066     * @param ognl  methods to invoke on the exchange in a simple OGNL syntax
1067     */
1068    public static Expression exchangeOgnlExpression(final String ognl) {
1069        return new ExpressionAdapter() {
1070            public Object evaluate(Exchange exchange) {
1071                // ognl is able to evaluate method name if it contains nested functions
1072                // so we should not eager evaluate ognl as a string
1073                return new MethodCallExpression(exchange, ognl).evaluate(exchange);
1074            }
1075
1076            @Override
1077            public String toString() {
1078                return "exchangeOgnl(" + ognl + ")";
1079            }
1080        };
1081    }
1082
1083    /**
1084     * Returns the expression for the exchanges inbound message body converted
1085     * to the given type
1086     */
1087    public static <T> Expression bodyExpression(final Class<T> type) {
1088        return new ExpressionAdapter() {
1089            public Object evaluate(Exchange exchange) {
1090                return exchange.getIn().getBody(type);
1091            }
1092
1093            @Override
1094            public String toString() {
1095                return "bodyAs[" + type.getName() + "]";
1096            }
1097        };
1098    }
1099
1100    /**
1101     * Returns the expression for the exchanges inbound message body converted
1102     * to the given type
1103     */
1104    public static Expression bodyExpression(final String name) {
1105        return new ExpressionAdapter() {
1106            public Object evaluate(Exchange exchange) {
1107                String text = simpleExpression(name).evaluate(exchange, String.class);
1108                Class<?> type;
1109                try {
1110                    type = exchange.getContext().getClassResolver().resolveMandatoryClass(text);
1111                } catch (ClassNotFoundException e) {
1112                    throw ObjectHelper.wrapCamelExecutionException(exchange, e);
1113                }
1114                return exchange.getIn().getBody(type);
1115            }
1116
1117            @Override
1118            public String toString() {
1119                return "bodyAs[" + name + "]";
1120            }
1121        };
1122    }
1123
1124    /**
1125     * Returns the expression for the exchanges inbound message body converted
1126     * to the given type and invoking methods on the converted body defined in a simple OGNL notation
1127     */
1128    public static Expression bodyOgnlExpression(final String name, final String ognl) {
1129        return new ExpressionAdapter() {
1130            public Object evaluate(Exchange exchange) {
1131                String text = simpleExpression(name).evaluate(exchange, String.class);
1132                Class<?> type;
1133                try {
1134                    type = exchange.getContext().getClassResolver().resolveMandatoryClass(text);
1135                } catch (ClassNotFoundException e) {
1136                    throw ObjectHelper.wrapCamelExecutionException(exchange, e);
1137                }
1138                Object body = exchange.getIn().getBody(type);
1139                if (body != null) {
1140                    // ognl is able to evaluate method name if it contains nested functions
1141                    // so we should not eager evaluate ognl as a string
1142                    MethodCallExpression call = new MethodCallExpression(exchange, ognl);
1143                    // set the instance to use
1144                    call.setInstance(body);
1145                    return call.evaluate(exchange);
1146                } else {
1147                    return null;
1148                }
1149            }
1150
1151            @Override
1152            public String toString() {
1153                return "bodyOgnlAs[" + name + "](" + ognl + ")";
1154            }
1155        };
1156    }
1157
1158    /**
1159     * Returns the expression for the exchanges inbound message body converted
1160     * to the given type
1161     */
1162    public static Expression mandatoryBodyExpression(final String name) {
1163        return new ExpressionAdapter() {
1164            public Object evaluate(Exchange exchange) {
1165                String text = simpleExpression(name).evaluate(exchange, String.class);
1166                Class<?> type;
1167                try {
1168                    type = exchange.getContext().getClassResolver().resolveMandatoryClass(text);
1169                } catch (ClassNotFoundException e) {
1170                    throw ObjectHelper.wrapCamelExecutionException(exchange, e);
1171                }
1172                try {
1173                    return exchange.getIn().getMandatoryBody(type);
1174                } catch (InvalidPayloadException e) {
1175                    throw ObjectHelper.wrapCamelExecutionException(exchange, e);
1176                }
1177            }
1178
1179            @Override
1180            public String toString() {
1181                return "mandatoryBodyAs[" + name + "]";
1182            }
1183        };
1184    }
1185
1186    /**
1187     * Returns the expression for the exchanges inbound message body converted
1188     * to the given type and invoking methods on the converted body defined in a simple OGNL notation
1189     */
1190    public static Expression mandatoryBodyOgnlExpression(final String name, final String ognl) {
1191        return new ExpressionAdapter() {
1192            public Object evaluate(Exchange exchange) {
1193                String text = simpleExpression(name).evaluate(exchange, String.class);
1194                Class<?> type;
1195                try {
1196                    type = exchange.getContext().getClassResolver().resolveMandatoryClass(text);
1197                } catch (ClassNotFoundException e) {
1198                    throw ObjectHelper.wrapCamelExecutionException(exchange, e);
1199                }
1200                Object body;
1201                try {
1202                    body = exchange.getIn().getMandatoryBody(type);
1203                } catch (InvalidPayloadException e) {
1204                    throw ObjectHelper.wrapCamelExecutionException(exchange, e);
1205                }
1206                // ognl is able to evaluate method name if it contains nested functions
1207                // so we should not eager evaluate ognl as a string
1208                MethodCallExpression call = new MethodCallExpression(exchange, ognl);
1209                // set the instance to use
1210                call.setInstance(body);
1211                return call.evaluate(exchange);
1212            }
1213
1214            @Override
1215            public String toString() {
1216                return "mandatoryBodyAs[" + name + "](" + ognl + ")";
1217            }
1218        };
1219    }
1220
1221    /**
1222     * Returns the expression for the current thread name
1223     */
1224    public static Expression threadNameExpression() {
1225        return new ExpressionAdapter() {
1226            public Object evaluate(Exchange exchange) {
1227                return Thread.currentThread().getName();
1228            }
1229
1230            @Override
1231            public String toString() {
1232                return "threadName";
1233            }
1234        };
1235    }
1236
1237    /**
1238     * Returns the expression for the {@code null} value
1239     */
1240    public static Expression nullExpression() {
1241        return new ExpressionAdapter() {
1242            public Object evaluate(Exchange exchange) {
1243                return null;
1244            }
1245
1246            @Override
1247            public String toString() {
1248                return "null";
1249            }
1250        };
1251    }
1252
1253    /**
1254     * Returns the expression for the exchanges inbound message body converted
1255     * to the given type.
1256     * <p/>
1257     * Does <b>not</b> allow null bodies.
1258     */
1259    public static <T> Expression mandatoryBodyExpression(final Class<T> type) {
1260        return mandatoryBodyExpression(type, false);
1261    }
1262
1263    /**
1264     * Returns the expression for the exchanges inbound message body converted
1265     * to the given type
1266     *
1267     * @param type the type
1268     * @param nullBodyAllowed whether null bodies is allowed and if so a null is returned,
1269     *                        otherwise an exception is thrown
1270     */
1271    public static <T> Expression mandatoryBodyExpression(final Class<T> type, final boolean nullBodyAllowed) {
1272        return new ExpressionAdapter() {
1273            public Object evaluate(Exchange exchange) {
1274                if (nullBodyAllowed) {
1275                    if (exchange.getIn().getBody() == null) {
1276                        return null;
1277                    }
1278
1279                    // if its a bean invocation then if it has no arguments then it should be threaded as null body allowed
1280                    if (exchange.getIn().getBody() instanceof BeanInvocation) {
1281                        // BeanInvocation would be stored directly as the message body
1282                        // do not force any type conversion attempts as it would just be unnecessary and cost a bit performance
1283                        // so a regular instanceof check is sufficient
1284                        BeanInvocation bi = (BeanInvocation) exchange.getIn().getBody();
1285                        if (bi.getArgs() == null || bi.getArgs().length == 0 || bi.getArgs()[0] == null) {
1286                            return null;
1287                        }
1288                    }
1289                }
1290
1291                try {
1292                    return exchange.getIn().getMandatoryBody(type);
1293                } catch (InvalidPayloadException e) {
1294                    throw ObjectHelper.wrapCamelExecutionException(exchange, e);
1295                }
1296            }
1297
1298            @Override
1299            public String toString() {
1300                return "mandatoryBodyAs[" + type.getName() + "]";
1301            }
1302        };
1303    }
1304
1305    /**
1306     * Returns the expression for the exchanges inbound message body type
1307     */
1308    public static Expression bodyTypeExpression() {
1309        return new ExpressionAdapter() {
1310            public Object evaluate(Exchange exchange) {
1311                return exchange.getIn().getBody().getClass();
1312            }
1313
1314            @Override
1315            public String toString() {
1316                return "bodyType";
1317            }
1318        };
1319    }
1320
1321    /**
1322     * Returns the expression for the out messages body
1323     */
1324    public static Expression outBodyExpression() {
1325        return new ExpressionAdapter() {
1326            public Object evaluate(Exchange exchange) {
1327                if (exchange.hasOut()) {
1328                    return exchange.getOut().getBody();
1329                } else {
1330                    return null;
1331                }
1332            }
1333
1334            @Override
1335            public String toString() {
1336                return "outBody";
1337            }
1338        };
1339    }
1340
1341    /**
1342     * Returns the expression for the exchanges outbound message body converted
1343     * to the given type
1344     */
1345    public static <T> Expression outBodyExpression(final Class<T> type) {
1346        return new ExpressionAdapter() {
1347            public Object evaluate(Exchange exchange) {
1348                if (exchange.hasOut()) {
1349                    return exchange.getOut().getBody(type);
1350                } else {
1351                    return null;
1352                }
1353            }
1354
1355            @Override
1356            public String toString() {
1357                return "outBodyAs[" + type.getName() + "]";
1358            }
1359        };
1360    }
1361
1362    /**
1363     * Returns the expression for the fault messages body
1364     */
1365    public static Expression faultBodyExpression() {
1366        return new ExpressionAdapter() {
1367            public Object evaluate(Exchange exchange) {
1368                Message msg = exchange.hasOut() ? exchange.getOut() : exchange.getIn();
1369                return msg.isFault() ? msg.getBody() : null;
1370            }
1371
1372            @Override
1373            public String toString() {
1374                return "faultBody";
1375            }
1376        };
1377    }
1378
1379    /**
1380     * Returns the expression for the exchanges fault message body converted
1381     * to the given type
1382     */
1383    public static <T> Expression faultBodyExpression(final Class<T> type) {
1384        return new ExpressionAdapter() {
1385            public Object evaluate(Exchange exchange) {
1386                Message msg = exchange.hasOut() ? exchange.getOut() : exchange.getIn();
1387                return msg.isFault() ? msg.getBody(type) : null;
1388            }
1389
1390            @Override
1391            public String toString() {
1392                return "faultBodyAs[" + type.getName() + "]";
1393            }
1394        };
1395    }
1396
1397    /**
1398     * Returns the expression for the exchange
1399     */
1400    public static Expression exchangeExpression() {
1401        return new ExpressionAdapter() {
1402            public Object evaluate(Exchange exchange) {
1403                return exchange;
1404            }
1405
1406            @Override
1407            public String toString() {
1408                return "exchange";
1409            }
1410        };
1411    }
1412
1413    /**
1414     * Returns a functional expression for the exchange
1415     */
1416    public static Expression exchangeExpression(final Function<Exchange, Object> function) {
1417        return new ExpressionAdapter() {
1418            public Object evaluate(Exchange exchange) {
1419                return function.apply(exchange);
1420            }
1421
1422            @Override
1423            public String toString() {
1424                return "exchangeExpression";
1425            }
1426        };
1427    }
1428
1429    /**
1430     * Returns the expression for the IN message
1431     */
1432    public static Expression messageExpression() {
1433        return inMessageExpression();
1434    }
1435
1436    /**
1437     * Returns a functional expression for the IN message
1438     */
1439    public static Expression messageExpression(final Function<Message, Object> function) {
1440        return inMessageExpression(function);
1441    }
1442
1443    /**
1444     * Returns the expression for the IN message
1445     */
1446    public static Expression inMessageExpression() {
1447        return new ExpressionAdapter() {
1448            public Object evaluate(Exchange exchange) {
1449                return exchange.getIn();
1450            }
1451
1452            @Override
1453            public String toString() {
1454                return "inMessage";
1455            }
1456        };
1457    }
1458
1459    /**
1460     * Returns a functional expression for the IN message
1461     */
1462    public static Expression inMessageExpression(final Function<Message, Object> function) {
1463        return new ExpressionAdapter() {
1464            public Object evaluate(Exchange exchange) {
1465                return function.apply(exchange.getIn());
1466            }
1467
1468            @Override
1469            public String toString() {
1470                return "inMessageExpression";
1471            }
1472        };
1473    }
1474
1475    /**
1476     * Returns the expression for the OUT message
1477     */
1478    public static Expression outMessageExpression() {
1479        return new ExpressionAdapter() {
1480            public Object evaluate(Exchange exchange) {
1481                return exchange.getOut();
1482            }
1483
1484            @Override
1485            public String toString() {
1486                return "outMessage";
1487            }
1488        };
1489    }
1490
1491    /**
1492     * Returns a functional expression for the OUT message
1493     */
1494    public static Expression outMessageExpression(final Function<Message, Object> function) {
1495        return new ExpressionAdapter() {
1496            public Object evaluate(Exchange exchange) {
1497                return function.apply(exchange.getOut());
1498            }
1499
1500            @Override
1501            public String toString() {
1502                return "outMessageExpression";
1503            }
1504        };
1505    }
1506
1507    /**
1508     * Returns an expression which converts the given expression to the given type
1509     */
1510    public static Expression convertToExpression(final Expression expression, final Class<?> type) {
1511        return new ExpressionAdapter() {
1512            public Object evaluate(Exchange exchange) {
1513                if (type != null) {
1514                    return expression.evaluate(exchange, type);
1515                } else {
1516                    return expression;
1517                }
1518            }
1519
1520            @Override
1521            public String toString() {
1522                return "" + expression;
1523            }
1524        };
1525    }
1526
1527    /**
1528     * Returns an expression which converts the given expression to the given type the type
1529     * expression is evaluated to
1530     */
1531    public static Expression convertToExpression(final Expression expression, final Expression type) {
1532        return new ExpressionAdapter() {
1533            public Object evaluate(Exchange exchange) {
1534                Object result = type.evaluate(exchange, Object.class);
1535                if (result != null) {
1536                    return expression.evaluate(exchange, result.getClass());
1537                } else {
1538                    return expression;
1539                }
1540            }
1541
1542            @Override
1543            public String toString() {
1544                return "" + expression;
1545            }
1546        };
1547    }
1548
1549    /**
1550     * Returns a tokenize expression which will tokenize the string with the
1551     * given token
1552     */
1553    public static Expression tokenizeExpression(final Expression expression,
1554                                                final String token) {
1555        return new ExpressionAdapter() {
1556            public Object evaluate(Exchange exchange) {
1557                String text = simpleExpression(token).evaluate(exchange, String.class);
1558                Object value = expression.evaluate(exchange, Object.class);
1559                Scanner scanner = ObjectHelper.getScanner(exchange, value);
1560                scanner.useDelimiter(text);
1561                return scanner;
1562            }
1563
1564            @Override
1565            public String toString() {
1566                return "tokenize(" + expression + ", " + token + ")";
1567            }
1568        };
1569    }
1570
1571    /**
1572     * Returns an expression that skips the first element
1573     */
1574    public static Expression skipFirstExpression(final Expression expression) {
1575        return new ExpressionAdapter() {
1576            public Object evaluate(Exchange exchange) {
1577                Object value = expression.evaluate(exchange, Object.class);
1578                Iterator it = exchange.getContext().getTypeConverter().tryConvertTo(Iterator.class, exchange, value);
1579                if (it != null) {
1580                    // skip first
1581                    it.next();
1582                    return it;
1583                } else {
1584                    return value;
1585                }
1586            }
1587
1588            @Override
1589            public String toString() {
1590                return "skipFirst(" + expression + ")";
1591            }
1592        };
1593    }
1594
1595    /**
1596     * Returns an {@link TokenPairExpressionIterator} expression
1597     */
1598    public static Expression tokenizePairExpression(String startToken, String endToken, boolean includeTokens) {
1599        return new TokenPairExpressionIterator(startToken, endToken, includeTokens);
1600    }
1601
1602    /**
1603     * Returns an {@link TokenXMLExpressionIterator} expression
1604     */
1605    public static Expression tokenizeXMLExpression(String tagName, String inheritNamespaceTagName) {
1606        ObjectHelper.notEmpty(tagName, "tagName");
1607        return new TokenXMLExpressionIterator(tagName, inheritNamespaceTagName);
1608    }
1609
1610    public static Expression tokenizeXMLAwareExpression(String path, char mode) {
1611        ObjectHelper.notEmpty(path, "path");
1612        return new XMLTokenExpressionIterator(path, mode);
1613    }
1614
1615    public static Expression tokenizeXMLAwareExpression(String path, char mode, int group) {
1616        ObjectHelper.notEmpty(path, "path");
1617        return new XMLTokenExpressionIterator(path, mode, group);
1618    }
1619
1620    /**
1621     * Returns a tokenize expression which will tokenize the string with the
1622     * given regex
1623     */
1624    public static Expression regexTokenizeExpression(final Expression expression,
1625                                                     final String regexTokenizer) {
1626        final Pattern pattern = Pattern.compile(regexTokenizer);
1627        return new ExpressionAdapter() {
1628            public Object evaluate(Exchange exchange) {
1629                Object value = expression.evaluate(exchange, Object.class);
1630                Scanner scanner = ObjectHelper.getScanner(exchange, value);
1631                scanner.useDelimiter(pattern);
1632                return scanner;
1633            }
1634
1635            @Override
1636            public String toString() {
1637                return "regexTokenize(" + expression + ", " + pattern.pattern() + ")";
1638            }
1639        };
1640    }
1641
1642    public static Expression groupXmlIteratorExpression(final Expression expression, final int group) {
1643        return new ExpressionAdapter() {
1644            public Object evaluate(Exchange exchange) {
1645                // evaluate expression as iterator
1646                Iterator<?> it = expression.evaluate(exchange, Iterator.class);
1647                ObjectHelper.notNull(it, "expression: " + expression + " evaluated on " + exchange + " must return an java.util.Iterator");
1648                // must use GroupTokenIterator in xml mode as we want to concat the xml parts into a single message
1649                return new GroupTokenIterator(exchange, it, null, group, false);
1650            }
1651
1652            @Override
1653            public String toString() {
1654                return "group " + expression + " " + group + " times";
1655            }
1656        };
1657    }
1658
1659    public static Expression groupIteratorExpression(final Expression expression, final String token, final int group, final boolean skipFirst) {
1660        return new ExpressionAdapter() {
1661            public Object evaluate(Exchange exchange) {
1662                // evaluate expression as iterator
1663                Iterator<?> it = expression.evaluate(exchange, Iterator.class);
1664                ObjectHelper.notNull(it, "expression: " + expression + " evaluated on " + exchange + " must return an java.util.Iterator");
1665                if (token != null) {
1666                    return new GroupTokenIterator(exchange, it, token, group, skipFirst);
1667                } else {
1668                    return new GroupIterator(exchange, it, group, skipFirst);
1669                }
1670            }
1671
1672            @Override
1673            public String toString() {
1674                return "group " + expression + " " + group + " times";
1675            }
1676        };
1677    }
1678
1679    public static Expression skipIteratorExpression(final Expression expression, final int skip) {
1680        return new ExpressionAdapter() {
1681            public Object evaluate(Exchange exchange) {
1682                // evaluate expression as iterator
1683                Iterator<?> it = expression.evaluate(exchange, Iterator.class);
1684                ObjectHelper.notNull(it, "expression: " + expression + " evaluated on " + exchange + " must return an java.util.Iterator");
1685                return new SkipIterator(exchange, it, skip);
1686            }
1687
1688            @Override
1689            public String toString() {
1690                return "skip " + expression + " " + skip + " times";
1691            }
1692        };
1693    }
1694
1695    /**
1696     * Returns a sort expression which will sort the expression with the given comparator.
1697     * <p/>
1698     * The expression is evaluated as a {@link List} object to allow sorting.
1699     */
1700    @SuppressWarnings({"unchecked", "rawtypes"})
1701    public static Expression sortExpression(final Expression expression, final Comparator comparator) {
1702        return new ExpressionAdapter() {
1703            public Object evaluate(Exchange exchange) {
1704                List<?> list = expression.evaluate(exchange, List.class);
1705                list.sort(comparator);
1706                return list;
1707            }
1708
1709            @Override
1710            public String toString() {
1711                return "sort(" + expression + " by: " + comparator + ")";
1712            }
1713        };
1714    }
1715
1716    /**
1717     * Transforms the expression into a String then performs the regex
1718     * replaceAll to transform the String and return the result
1719     */
1720    public static Expression regexReplaceAll(final Expression expression,
1721                                             final String regex, final String replacement) {
1722        final Pattern pattern = Pattern.compile(regex);
1723        return new ExpressionAdapter() {
1724            public Object evaluate(Exchange exchange) {
1725                String text = expression.evaluate(exchange, String.class);
1726                if (text == null) {
1727                    return null;
1728                }
1729                return pattern.matcher(text).replaceAll(replacement);
1730            }
1731
1732            @Override
1733            public String toString() {
1734                return "regexReplaceAll(" + expression + ", " + pattern.pattern() + ")";
1735            }
1736        };
1737    }
1738
1739    /**
1740     * Transforms the expression into a String then performs the regex
1741     * replaceAll to transform the String and return the result
1742     */
1743    public static Expression regexReplaceAll(final Expression expression,
1744                                             final String regex, final Expression replacementExpression) {
1745
1746        final Pattern pattern = Pattern.compile(regex);
1747        return new ExpressionAdapter() {
1748            public Object evaluate(Exchange exchange) {
1749                String text = expression.evaluate(exchange, String.class);
1750                String replacement = replacementExpression.evaluate(exchange, String.class);
1751                if (text == null || replacement == null) {
1752                    return null;
1753                }
1754                return pattern.matcher(text).replaceAll(replacement);
1755            }
1756
1757            @Override
1758            public String toString() {
1759                return "regexReplaceAll(" + expression + ", " + pattern.pattern() + ")";
1760            }
1761        };
1762    }
1763
1764    /**
1765     * Appends the String evaluations of the two expressions together
1766     */
1767    public static Expression append(final Expression left, final Expression right) {
1768        return new ExpressionAdapter() {
1769            public Object evaluate(Exchange exchange) {
1770                return left.evaluate(exchange, String.class) + right.evaluate(exchange, String.class);
1771            }
1772
1773            @Override
1774            public String toString() {
1775                return "append(" + left + ", " + right + ")";
1776            }
1777        };
1778    }
1779
1780    /**
1781     * Prepends the String evaluations of the two expressions together
1782     */
1783    public static Expression prepend(final Expression left, final Expression right) {
1784        return new ExpressionAdapter() {
1785            public Object evaluate(Exchange exchange) {
1786                return right.evaluate(exchange, String.class) + left.evaluate(exchange, String.class);
1787            }
1788
1789            @Override
1790            public String toString() {
1791                return "prepend(" + left + ", " + right + ")";
1792            }
1793        };
1794    }
1795
1796    /**
1797     * Returns an expression which returns the string concatenation value of the various
1798     * expressions
1799     *
1800     * @param expressions the expression to be concatenated dynamically
1801     * @return an expression which when evaluated will return the concatenated values
1802     */
1803    public static Expression concatExpression(final Collection<Expression> expressions) {
1804        return concatExpression(expressions, null);
1805    }
1806
1807    /**
1808     * Returns an expression which returns the string concatenation value of the various
1809     * expressions
1810     *
1811     * @param expressions the expression to be concatenated dynamically
1812     * @param desription the text description of the expression
1813     * @return an expression which when evaluated will return the concatenated values
1814     */
1815    public static Expression concatExpression(final Collection<Expression> expressions, final String desription) {
1816        return new ExpressionAdapter() {
1817            public Object evaluate(Exchange exchange) {
1818                StringBuilder buffer = new StringBuilder();
1819                for (Expression expression : expressions) {
1820                    String text = expression.evaluate(exchange, String.class);
1821                    if (text != null) {
1822                        buffer.append(text);
1823                    }
1824                }
1825                return buffer.toString();
1826            }
1827
1828            @Override
1829            public String toString() {
1830                if (desription != null) {
1831                    return desription;
1832                } else {
1833                    return "concat" + expressions;
1834                }
1835            }
1836        };
1837    }
1838
1839    /**
1840     * Returns an Expression for the inbound message id
1841     */
1842    public static Expression messageIdExpression() {
1843        return new ExpressionAdapter() {
1844            public Object evaluate(Exchange exchange) {
1845                return exchange.getIn().getMessageId();
1846            }
1847
1848            @Override
1849            public String toString() {
1850                return "messageId";
1851            }
1852        };
1853    }
1854
1855    /**
1856     * Returns an Expression for the exchange id
1857     */
1858    public static Expression exchangeIdExpression() {
1859        return new ExpressionAdapter() {
1860            public Object evaluate(Exchange exchange) {
1861                return exchange.getExchangeId();
1862            }
1863
1864            @Override
1865            public String toString() {
1866                return "exchangeId";
1867            }
1868        };
1869    }
1870
1871    /**
1872     * Returns an Expression for the route id
1873     */
1874    public static Expression routeIdExpression() {
1875        return new ExpressionAdapter() {
1876            public Object evaluate(Exchange exchange) {
1877                String answer = null;
1878                UnitOfWork uow = exchange.getUnitOfWork();
1879                RouteContext rc = uow != null ? uow.getRouteContext() : null;
1880                if (rc != null) {
1881                    answer = rc.getRoute().getId();
1882                }
1883                if (answer == null) {
1884                    // fallback and get from route id on the exchange
1885                    answer = exchange.getFromRouteId();
1886                }
1887                return answer;
1888            }
1889
1890            @Override
1891            public String toString() {
1892                return "routeId";
1893            }
1894        };
1895    }
1896
1897    public static Expression dateExpression(final String command) {
1898        return dateExpression(command, null, null);
1899    }
1900
1901    public static Expression dateExpression(final String command, final String pattern) {
1902        return dateExpression(command, null, pattern);
1903    }
1904
1905    public static Expression dateExpression(final String commandWithOffsets, final String timezone, final String pattern) {
1906        return new ExpressionAdapter() {
1907            public Object evaluate(Exchange exchange) {
1908                // Capture optional time offsets
1909                String command = commandWithOffsets.split("[+-]", 2)[0].trim();
1910                List<Long> offsets = new ArrayList<>();
1911                Matcher offsetMatcher = OFFSET_PATTERN.matcher(commandWithOffsets);
1912                while (offsetMatcher.find()) {
1913                    try {
1914                        long value = exchange.getContext().getTypeConverter().mandatoryConvertTo(long.class, exchange, offsetMatcher.group(2).trim());
1915                        offsets.add(offsetMatcher.group(1).equals("+") ? value : -value);
1916                    } catch (NoTypeConversionAvailableException e) {
1917                        throw ObjectHelper.wrapCamelExecutionException(exchange, e);
1918                    }
1919                }
1920
1921                Date date;
1922                if ("now".equals(command)) {
1923                    date = new Date();
1924                } else if (command.startsWith("header.") || command.startsWith("in.header.")) {
1925                    String key = command.substring(command.lastIndexOf('.') + 1);
1926                    date = exchange.getIn().getHeader(key, Date.class);
1927                    if (date == null) {
1928                        throw new IllegalArgumentException("Cannot find java.util.Date object at command: " + command);
1929                    }
1930                } else if (command.startsWith("out.header.")) {
1931                    String key = command.substring(command.lastIndexOf('.') + 1);
1932                    date = exchange.getOut().getHeader(key, Date.class);
1933                    if (date == null) {
1934                        throw new IllegalArgumentException("Cannot find java.util.Date object at command: " + command);
1935                    }
1936                } else if ("file".equals(command)) {
1937                    Long num = exchange.getIn().getHeader(Exchange.FILE_LAST_MODIFIED, Long.class);
1938                    if (num != null && num > 0) {
1939                        date = new Date(num);
1940                    } else {
1941                        date = exchange.getIn().getHeader(Exchange.FILE_LAST_MODIFIED, Date.class);
1942                        if (date == null) {
1943                            throw new IllegalArgumentException("Cannot find " + Exchange.FILE_LAST_MODIFIED + " header at command: " + command);
1944                        }
1945                    }
1946                } else {
1947                    throw new IllegalArgumentException("Command not supported for dateExpression: " + command);
1948                }
1949
1950                // Apply offsets
1951                long dateAsLong = date.getTime();
1952                for (long offset : offsets) {
1953                    dateAsLong += offset;
1954                }
1955                date = new Date(dateAsLong);
1956
1957                if (pattern != null && !pattern.isEmpty()) {
1958                    SimpleDateFormat df = new SimpleDateFormat(pattern);
1959                    if (timezone != null && !timezone.isEmpty()) {
1960                        df.setTimeZone(TimeZone.getTimeZone(timezone));
1961                    }
1962                    return df.format(date);
1963                } else {
1964                    return date;
1965                }
1966            }
1967
1968            @Override
1969            public String toString() {
1970                return "date(" + commandWithOffsets + ":" + pattern + ":" + timezone + ")";
1971            }
1972        };
1973    }
1974
1975    public static Expression simpleExpression(final String expression) {
1976        return new ExpressionAdapter() {
1977            public Object evaluate(Exchange exchange) {
1978                if (SimpleLanguage.hasSimpleFunction(expression)) {
1979                    // resolve language using context to have a clear separation of packages
1980                    // must call evaluate to return the nested language evaluate when evaluating
1981                    // stacked expressions
1982                    Language language = exchange.getContext().resolveLanguage("simple");
1983                    return language.createExpression(expression).evaluate(exchange, Object.class);
1984                } else {
1985                    return expression;
1986                }
1987            }
1988
1989            @Override
1990            public String toString() {
1991                return "simple(" + expression + ")";
1992            }
1993        };
1994    }
1995
1996    public static Expression beanExpression(final String expression) {
1997        return new ExpressionAdapter() {
1998            public Object evaluate(Exchange exchange) {
1999                // bean is able to evaluate method name if it contains nested functions
2000                // so we should not eager evaluate expression as a string
2001                // resolve language using context to have a clear separation of packages
2002                // must call evaluate to return the nested language evaluate when evaluating
2003                // stacked expressions
2004                Language language = exchange.getContext().resolveLanguage("bean");
2005                return language.createExpression(expression).evaluate(exchange, Object.class);
2006            }
2007
2008            @Override
2009            public String toString() {
2010                return "bean(" + expression + ")";
2011            }
2012        };
2013    }
2014
2015    public static Expression beanExpression(final Class<?> beanType, final String methodName) {
2016        return BeanLanguage.bean(beanType, methodName);
2017    }
2018
2019    public static Expression beanExpression(final Object bean, final String methodName) {
2020        return BeanLanguage.bean(bean, methodName);
2021    }
2022
2023    public static Expression beanExpression(final String beanRef, final String methodName) {
2024        String expression = methodName != null ? beanRef + "." + methodName : beanRef;
2025        return beanExpression(expression);
2026    }
2027
2028    /**
2029     * Returns an expression processing the exchange to the given endpoint uri
2030     *
2031     * @param uri endpoint uri to send the exchange to
2032     * @return an expression object which will return the OUT body
2033     */
2034    public static Expression toExpression(final String uri) {
2035        return new ExpressionAdapter() {
2036            public Object evaluate(Exchange exchange) {
2037                String text = simpleExpression(uri).evaluate(exchange, String.class);
2038                Endpoint endpoint = exchange.getContext().getEndpoint(text);
2039                if (endpoint == null) {
2040                    throw new NoSuchEndpointException(text);
2041                }
2042
2043                Producer producer;
2044                try {
2045                    producer = endpoint.createProducer();
2046                    producer.start();
2047                    producer.process(exchange);
2048                    producer.stop();
2049                } catch (Exception e) {
2050                    throw ObjectHelper.wrapRuntimeCamelException(e);
2051                }
2052
2053                // return the OUT body, but check for exchange pattern
2054                if (ExchangeHelper.isOutCapable(exchange)) {
2055                    return exchange.getOut().getBody();
2056                } else {
2057                    return exchange.getIn().getBody();
2058                }
2059            }
2060
2061            @Override
2062            public String toString() {
2063                return "to(" + uri + ")";
2064            }
2065        };
2066    }
2067
2068    public static Expression fileNameExpression() {
2069        return new ExpressionAdapter() {
2070            public Object evaluate(Exchange exchange) {
2071                return exchange.getIn().getHeader(Exchange.FILE_NAME, String.class);
2072            }
2073
2074            @Override
2075            public String toString() {
2076                return "file:name";
2077            }
2078        };
2079    }
2080
2081    public static Expression fileOnlyNameExpression() {
2082        return new ExpressionAdapter() {
2083            public Object evaluate(Exchange exchange) {
2084                String answer = exchange.getIn().getHeader(Exchange.FILE_NAME_ONLY, String.class);
2085                if (answer == null) {
2086                    answer = exchange.getIn().getHeader(Exchange.FILE_NAME, String.class);
2087                    answer = FileUtil.stripPath(answer);
2088                }
2089                return answer;
2090            }
2091
2092            @Override
2093            public String toString() {
2094                return "file:onlyname";
2095            }
2096        };
2097    }
2098
2099    public static Expression fileNameNoExtensionExpression() {
2100        return new ExpressionAdapter() {
2101            public Object evaluate(Exchange exchange) {
2102                String name = exchange.getIn().getHeader(Exchange.FILE_NAME, String.class);
2103                return FileUtil.stripExt(name);
2104            }
2105
2106            @Override
2107            public String toString() {
2108                return "file:name.noext";
2109            }
2110        };
2111    }
2112
2113    public static Expression fileNameNoExtensionSingleExpression() {
2114        return new ExpressionAdapter() {
2115            public Object evaluate(Exchange exchange) {
2116                String name = exchange.getIn().getHeader(Exchange.FILE_NAME, String.class);
2117                return FileUtil.stripExt(name, true);
2118            }
2119
2120            @Override
2121            public String toString() {
2122                return "file:name.noext.single";
2123            }
2124        };
2125    }
2126
2127    public static Expression fileOnlyNameNoExtensionExpression() {
2128        return new ExpressionAdapter() {
2129            public Object evaluate(Exchange exchange) {
2130                String name = fileOnlyNameExpression().evaluate(exchange, String.class);
2131                return FileUtil.stripExt(name);
2132            }
2133
2134            @Override
2135            public String toString() {
2136                return "file:onlyname.noext";
2137            }
2138        };
2139    }
2140
2141    public static Expression fileOnlyNameNoExtensionSingleExpression() {
2142        return new ExpressionAdapter() {
2143            public Object evaluate(Exchange exchange) {
2144                String name = fileOnlyNameExpression().evaluate(exchange, String.class);
2145                return FileUtil.stripExt(name, true);
2146            }
2147
2148            @Override
2149            public String toString() {
2150                return "file:onlyname.noext.single";
2151            }
2152        };
2153    }
2154
2155    public static Expression fileExtensionExpression() {
2156        return new ExpressionAdapter() {
2157            public Object evaluate(Exchange exchange) {
2158                String name = exchange.getIn().getHeader(Exchange.FILE_NAME, String.class);
2159                return FileUtil.onlyExt(name);
2160            }
2161
2162            @Override
2163            public String toString() {
2164                return "file:ext";
2165            }
2166        };
2167    }
2168
2169    public static Expression fileExtensionSingleExpression() {
2170        return new ExpressionAdapter() {
2171            public Object evaluate(Exchange exchange) {
2172                String name = exchange.getIn().getHeader(Exchange.FILE_NAME, String.class);
2173                return FileUtil.onlyExt(name, true);
2174            }
2175
2176            @Override
2177            public String toString() {
2178                return "file:ext.single";
2179            }
2180        };
2181    }
2182
2183    public static Expression fileParentExpression() {
2184        return new ExpressionAdapter() {
2185            public Object evaluate(Exchange exchange) {
2186                return exchange.getIn().getHeader("CamelFileParent", String.class);
2187            }
2188
2189            @Override
2190            public String toString() {
2191                return "file:parent";
2192            }
2193        };
2194    }
2195
2196    public static Expression filePathExpression() {
2197        return new ExpressionAdapter() {
2198            public Object evaluate(Exchange exchange) {
2199                return exchange.getIn().getHeader("CamelFilePath", String.class);
2200            }
2201
2202            @Override
2203            public String toString() {
2204                return "file:path";
2205            }
2206        };
2207    }
2208
2209    public static Expression fileAbsolutePathExpression() {
2210        return new ExpressionAdapter() {
2211            public Object evaluate(Exchange exchange) {
2212                return exchange.getIn().getHeader("CamelFileAbsolutePath", String.class);
2213            }
2214
2215            @Override
2216            public String toString() {
2217                return "file:absolute.path";
2218            }
2219        };
2220    }
2221
2222    public static Expression fileAbsoluteExpression() {
2223        return new ExpressionAdapter() {
2224            public Object evaluate(Exchange exchange) {
2225                return exchange.getIn().getHeader("CamelFileAbsolute", Boolean.class);
2226            }
2227
2228            @Override
2229            public String toString() {
2230                return "file:absolute";
2231            }
2232        };
2233    }
2234
2235    public static Expression fileSizeExpression() {
2236        return new ExpressionAdapter() {
2237            public Object evaluate(Exchange exchange) {
2238                return exchange.getIn().getHeader(Exchange.FILE_LENGTH, Long.class);
2239            }
2240
2241            @Override
2242            public String toString() {
2243                return "file:length";
2244            }
2245        };
2246    }
2247
2248    public static Expression fileLastModifiedExpression() {
2249        return new ExpressionAdapter() {
2250            public Object evaluate(Exchange exchange) {
2251                return exchange.getIn().getHeader(Exchange.FILE_LAST_MODIFIED, Long.class);
2252            }
2253
2254            @Override
2255            public String toString() {
2256                return "file:modified";
2257            }
2258        };
2259    }
2260
2261    /**
2262     * Returns Simple expression or fallback to Constant expression if expression str is not Simple expression.
2263     */
2264    public static Expression parseSimpleOrFallbackToConstantExpression(String str, CamelContext camelContext) {
2265        if (StringHelper.hasStartToken(str, "simple")) {
2266            return camelContext.resolveLanguage("simple").createExpression(str);
2267        }
2268        return constantExpression(str);
2269    }
2270
2271    public static Expression propertiesComponentExpression(final String key, final String locations, final String defaultValue) {
2272        return new ExpressionAdapter() {
2273            public Object evaluate(Exchange exchange) {
2274                String text = simpleExpression(key).evaluate(exchange, String.class);
2275                String text2 = simpleExpression(locations).evaluate(exchange, String.class);
2276                try {
2277                    if (text2 != null) {
2278                        // the properties component is optional as we got locations
2279                        // getComponent will create a new component if none already exists
2280                        Component component = exchange.getContext().getComponent("properties");
2281                        PropertiesComponent pc = exchange.getContext().getTypeConverter()
2282                            .mandatoryConvertTo(PropertiesComponent.class, component);
2283                        // enclose key with {{ }} to force parsing
2284                        String[] paths = text2.split(",");
2285                        return pc.parseUri(pc.getPrefixToken() + text + pc.getSuffixToken(), paths);
2286                    } else {
2287                        // the properties component is mandatory if no locations provided
2288                        Component component = exchange.getContext().hasComponent("properties");
2289                        if (component == null) {
2290                            throw new IllegalArgumentException("PropertiesComponent with name properties must be defined"
2291                                + " in CamelContext to support property placeholders in expressions");
2292                        }
2293                        PropertiesComponent pc = exchange.getContext().getTypeConverter()
2294                            .mandatoryConvertTo(PropertiesComponent.class, component);
2295                        // enclose key with {{ }} to force parsing
2296                        return pc.parseUri(pc.getPrefixToken() + text + pc.getSuffixToken());
2297                    }
2298                } catch (Exception e) {
2299                    // property with key not found, use default value if provided
2300                    if (defaultValue != null) {
2301                        return defaultValue;
2302                    }
2303                    throw ObjectHelper.wrapRuntimeCamelException(e);
2304                }
2305            }
2306
2307            @Override
2308            public String toString() {
2309                return "properties(" + key + ")";
2310            }
2311        };
2312    }
2313
2314    /**
2315     * Returns a random number between 0 and upperbound (exclusive)
2316     */
2317    public static Expression randomExpression(final int upperbound) {
2318        return randomExpression(0, upperbound);
2319    }
2320
2321    /**
2322     * Returns a random number between min and max
2323     */
2324    public static Expression randomExpression(final int min, final int max) {
2325        return new ExpressionAdapter() {
2326            public Object evaluate(Exchange exchange) {
2327                Random random = new Random();
2328                int randomNum = random.nextInt(max - min) + min;
2329                return randomNum;
2330            }
2331
2332            @Override
2333            public String toString() {
2334                return "random";
2335            }
2336        };
2337    }
2338
2339    /**
2340     * Returns a random number between min and max
2341     */
2342    public static Expression randomExpression(final String min, final String max) {
2343        return new ExpressionAdapter() {
2344            public Object evaluate(Exchange exchange) {
2345                int num1 = simpleExpression(min).evaluate(exchange, Integer.class);
2346                int num2 = simpleExpression(max).evaluate(exchange, Integer.class);
2347                Random random = new Random();
2348                int randomNum = random.nextInt(num2 - num1) + num1;
2349                return randomNum;
2350            }
2351
2352            @Override
2353            public String toString() {
2354                return "random";
2355            }
2356        };
2357    }
2358
2359    /**
2360     * Returns an iterator to skip (iterate) the given expression
2361     */
2362    public static Expression skipExpression(final String expression, final int number) {
2363        return new ExpressionAdapter() {
2364            public Object evaluate(Exchange exchange) {
2365                // use simple language
2366                Expression exp = exchange.getContext().resolveLanguage("simple").createExpression(expression);
2367                return ExpressionBuilder.skipIteratorExpression(exp, number).evaluate(exchange, Object.class);
2368            }
2369
2370            @Override
2371            public String toString() {
2372                return "skip(" + expression + "," + number + ")";
2373            }
2374        };
2375    }
2376
2377    /**
2378     * Returns an iterator to collate (iterate) the given expression
2379     */
2380    public static Expression collateExpression(final String expression, final int group) {
2381        return new ExpressionAdapter() {
2382            public Object evaluate(Exchange exchange) {
2383                // use simple language
2384                Expression exp = exchange.getContext().resolveLanguage("simple").createExpression(expression);
2385                return ExpressionBuilder.groupIteratorExpression(exp, null, group, false).evaluate(exchange, Object.class);
2386            }
2387
2388            @Override
2389            public String toString() {
2390                return "collate(" + expression + "," + group + ")";
2391            }
2392        };
2393    }
2394
2395    /**
2396     * Returns the message history (including exchange details or not)
2397     */
2398    public static Expression messageHistoryExpression(final boolean detailed) {
2399        return new ExpressionAdapter() {
2400
2401            private ExchangeFormatter formatter;
2402
2403            public Object evaluate(Exchange exchange) {
2404                ExchangeFormatter ef = null;
2405                if (detailed) {
2406                    // use the exchange formatter to log exchange details
2407                    ef = getOrCreateExchangeFormatter(exchange.getContext());
2408                }
2409                return MessageHelper.dumpMessageHistoryStacktrace(exchange, ef, false);
2410            }
2411
2412            private ExchangeFormatter getOrCreateExchangeFormatter(CamelContext camelContext) {
2413                if (formatter == null) {
2414                    Set<ExchangeFormatter> formatters = camelContext.getRegistry().findByType(ExchangeFormatter.class);
2415                    if (formatters != null && formatters.size() == 1) {
2416                        formatter = formatters.iterator().next();
2417                    } else {
2418                        // setup exchange formatter to be used for message history dump
2419                        DefaultExchangeFormatter def = new DefaultExchangeFormatter();
2420                        def.setShowExchangeId(true);
2421                        def.setMultiline(true);
2422                        def.setShowHeaders(true);
2423                        def.setStyle(DefaultExchangeFormatter.OutputStyle.Fixed);
2424                        try {
2425                            Integer maxChars = CamelContextHelper.parseInteger(camelContext, camelContext.getGlobalOption(Exchange.LOG_DEBUG_BODY_MAX_CHARS));
2426                            if (maxChars != null) {
2427                                def.setMaxChars(maxChars);
2428                            }
2429                        } catch (Exception e) {
2430                            throw ObjectHelper.wrapRuntimeCamelException(e);
2431                        }
2432                        formatter = def;
2433                    }
2434                }
2435                return formatter;
2436            }
2437
2438            @Override
2439            public String toString() {
2440                return "messageHistory(" + detailed + ")";
2441            }
2442        };
2443    }
2444
2445    /**
2446     * Expression adapter for OGNL expression from Message Header or Exchange property
2447     */
2448    private static class KeyedOgnlExpressionAdapter extends ExpressionAdapter {
2449        private final String ognl;
2450        private final String toStringValue;
2451        private final KeyedEntityRetrievalStrategy keyedEntityRetrievalStrategy;
2452
2453        KeyedOgnlExpressionAdapter(String ognl, String toStringValue,
2454                                   KeyedEntityRetrievalStrategy keyedEntityRetrievalStrategy) {
2455            this.ognl = ognl;
2456            this.toStringValue = toStringValue;
2457            this.keyedEntityRetrievalStrategy = keyedEntityRetrievalStrategy;
2458        }
2459
2460        public Object evaluate(Exchange exchange) {
2461            // try with full name first
2462            Object property = keyedEntityRetrievalStrategy.getKeyedEntity(exchange, ognl);
2463            if (property != null) {
2464                return property;
2465            }
2466
2467
2468            // Split ognl except when this is not a Map, Array
2469            // and we would like to keep the dots within the key name
2470            List<String> methods = OgnlHelper.splitOgnl(ognl);
2471
2472            String key = methods.get(0);
2473            String keySuffix = "";
2474            // if ognl starts with a key inside brackets (eg: [foo.bar])
2475            // remove starting and ending brackets from key
2476            if (key.startsWith("[") && key.endsWith("]")) {
2477                key = StringHelper.removeLeadingAndEndingQuotes(key.substring(1, key.length() - 1));
2478                keySuffix = StringHelper.after(methods.get(0), key);
2479            }
2480            // remove any OGNL operators so we got the pure key name
2481            key = OgnlHelper.removeOperators(key);
2482
2483
2484            property = keyedEntityRetrievalStrategy.getKeyedEntity(exchange, key);
2485            if (property == null) {
2486                return null;
2487            }
2488            // the remainder is the rest of the ognl without the key
2489            String remainder = ObjectHelper.after(ognl, key + keySuffix);
2490            return new MethodCallExpression(property, remainder).evaluate(exchange);
2491        }
2492
2493        @Override
2494        public String toString() {
2495            return toStringValue;
2496        }
2497
2498        /**
2499         * Strategy to retrieve the value based on the key
2500         */
2501        public interface KeyedEntityRetrievalStrategy {
2502            Object getKeyedEntity(Exchange exchange, String key);
2503        }
2504    }
2505
2506}