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