You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2010/03/14 11:37:10 UTC

svn commit: r922808 - in /camel/trunk/camel-core/src: main/java/org/apache/camel/builder/ main/java/org/apache/camel/language/bean/ main/java/org/apache/camel/language/simple/ main/java/org/apache/camel/util/ test/java/org/apache/camel/language/ test/j...

Author: davsclaus
Date: Sun Mar 14 10:37:10 2010
New Revision: 922808

URL: http://svn.apache.org/viewvc?rev=922808&view=rev
Log:
CAMEL-2546: Added OGNL notation to simple/bean language. Work in progress.

Added:
    camel/trunk/camel-core/src/main/java/org/apache/camel/util/OgnlHelper.java   (with props)
    camel/trunk/camel-core/src/test/java/org/apache/camel/language/BeanLanguageOGNLTest.java   (with props)
Modified:
    camel/trunk/camel-core/src/main/java/org/apache/camel/builder/ExpressionBuilder.java
    camel/trunk/camel-core/src/main/java/org/apache/camel/language/bean/BeanExpression.java
    camel/trunk/camel-core/src/main/java/org/apache/camel/language/simple/SimpleLanguage.java
    camel/trunk/camel-core/src/main/java/org/apache/camel/util/StringHelper.java
    camel/trunk/camel-core/src/test/java/org/apache/camel/language/SimpleTest.java
    camel/trunk/camel-core/src/test/java/org/apache/camel/util/StringHelperTest.java

Modified: camel/trunk/camel-core/src/main/java/org/apache/camel/builder/ExpressionBuilder.java
URL: http://svn.apache.org/viewvc/camel/trunk/camel-core/src/main/java/org/apache/camel/builder/ExpressionBuilder.java?rev=922808&r1=922807&r2=922808&view=diff
==============================================================================
--- camel/trunk/camel-core/src/main/java/org/apache/camel/builder/ExpressionBuilder.java (original)
+++ camel/trunk/camel-core/src/main/java/org/apache/camel/builder/ExpressionBuilder.java Sun Mar 14 10:37:10 2010
@@ -22,7 +22,6 @@ import java.util.Collections;
 import java.util.Comparator;
 import java.util.Date;
 import java.util.List;
-import java.util.Map;
 import java.util.Scanner;
 import java.util.regex.Pattern;
 
@@ -33,16 +32,17 @@ import org.apache.camel.Expression;
 import org.apache.camel.InvalidPayloadException;
 import org.apache.camel.Message;
 import org.apache.camel.NoSuchEndpointException;
-import org.apache.camel.NoTypeConversionAvailableException;
 import org.apache.camel.Producer;
 import org.apache.camel.component.bean.BeanInvocation;
 import org.apache.camel.component.properties.PropertiesComponent;
 import org.apache.camel.impl.ExpressionAdapter;
 import org.apache.camel.language.bean.BeanLanguage;
+import org.apache.camel.model.language.MethodCallExpression;
 import org.apache.camel.spi.Language;
 import org.apache.camel.util.ExchangeHelper;
 import org.apache.camel.util.FileUtil;
 import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.util.OgnlHelper;
 
 /**
  * A helper class for working with <a href="http://camel.apache.org/expression.html">expressions</a>.
@@ -102,32 +102,37 @@ public final class ExpressionBuilder {
     }
 
     /**
-     * Returns an expression to lookup a key in the header which should be Map based.
+     * Returns the expression for the exchanges inbound message header invoking methods defined
+     * in a simple OGNL notation
      *
-     * @param headerName the name of the header the expression will return
-     * @param keyName    the name of the key to lookup in the header value
-     * @return an expression object which will return the key value looked up in the header
+     * @param ognl  methods to invoke on the header in a simple OGNL syntax
      */
-    public static Expression headerAsMapExpression(final String headerName, final String keyName) {
+    public static Expression headersOgnlExpression(final String ognl) {
         return new ExpressionAdapter() {
             public Object evaluate(Exchange exchange) {
-                Object header = exchange.getIn().getHeader(headerName);
-                if (header == null) {
-                    return null;
+                // try with full name first
+                Object header = exchange.getIn().getHeader(ognl);
+                if (header != null) {
+                    return header;
                 }
 
-                try {
-                    Map map = exchange.getContext().getTypeConverter().mandatoryConvertTo(Map.class, header);
-                    Object answer = map.get(keyName);
-                    return answer;
-                } catch (NoTypeConversionAvailableException e) {
-                    throw new IllegalArgumentException("Header " + headerName + " cannot be converted to a Map on " + this, e);
+                // split into first name
+                List<String> methods = OgnlHelper.splitOgnl(ognl);
+                // remove any OGNL operators so we got the pure key name
+                String key = OgnlHelper.removeOperators(methods.get(0));
+
+                header = exchange.getIn().getHeader(key);
+                if (header == null) {
+                    return null;
                 }
+                // the remainder is the rest of the ognl without the key
+                String remainder = ObjectHelper.after(ognl, key);
+                return new MethodCallExpression(header, remainder).evaluate(exchange);
             }
 
             @Override
             public String toString() {
-                return "headerAsMap(" + headerName + ")[" + keyName + "]";
+                return "headerOgnl(" + ognl + ")";
             }
         };
     }
@@ -502,6 +507,29 @@ public final class ExpressionBuilder {
     }
 
     /**
+     * Returns the expression for the exchanges inbound message body invoking methods defined
+     * in a simple OGNL notation
+     *
+     * @param ognl  methods to invoke on the body in a simple OGNL syntax
+     */
+    public static Expression bodyOgnlExpression(final String ognl) {
+        return new ExpressionAdapter() {
+            public Object evaluate(Exchange exchange) {
+                Object body = exchange.getIn().getBody();
+                if (body == null) {
+                    return null;
+                }
+                return new MethodCallExpression(body, ognl).evaluate(exchange);
+            }
+
+            @Override
+            public String toString() {
+                return "bodyOgnl(" + ognl + ")";
+            }
+        };
+    }
+
+    /**
      * Returns the expression for the exchanges inbound message body converted
      * to the given type
      */

Modified: camel/trunk/camel-core/src/main/java/org/apache/camel/language/bean/BeanExpression.java
URL: http://svn.apache.org/viewvc/camel/trunk/camel-core/src/main/java/org/apache/camel/language/bean/BeanExpression.java?rev=922808&r1=922807&r2=922808&view=diff
==============================================================================
--- camel/trunk/camel-core/src/main/java/org/apache/camel/language/bean/BeanExpression.java (original)
+++ camel/trunk/camel-core/src/main/java/org/apache/camel/language/bean/BeanExpression.java Sun Mar 14 10:37:10 2010
@@ -16,15 +16,21 @@
  */
 package org.apache.camel.language.bean;
 
+import java.util.List;
+import java.util.Map;
+
 import org.apache.camel.Exchange;
 import org.apache.camel.ExchangePattern;
 import org.apache.camel.Expression;
 import org.apache.camel.Predicate;
+import org.apache.camel.Processor;
 import org.apache.camel.component.bean.BeanHolder;
 import org.apache.camel.component.bean.BeanProcessor;
 import org.apache.camel.component.bean.ConstantBeanHolder;
 import org.apache.camel.component.bean.RegistryBean;
+import org.apache.camel.util.KeyValueHolder;
 import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.util.OgnlHelper;
 
 /**
  * Evaluates an expression using a bean method invocation
@@ -59,22 +65,28 @@ public class BeanExpression implements E
         } else {
             holder = new ConstantBeanHolder(bean, exchange.getContext());
         }
+        
+        // invoking the bean can either be the easy way or using OGNL
 
-        BeanProcessor processor = new BeanProcessor(holder);
-        if (method != null) {
-            processor.setMethod(method);
-        }
-        try {
-            Exchange newExchange = exchange.copy();
-            // The BeanExpression always has a result regardless of the ExchangePattern,
-            // so I add a checker here to make sure we can get the result.
-            if (!newExchange.getPattern().isOutCapable()) {
-                newExchange.setPattern(ExchangePattern.InOut);
-            }
-            processor.process(newExchange);
-            return newExchange.getOut().getBody();
-        } catch (Exception e) {
-            throw new RuntimeBeanExpressionException(exchange, beanName, method, e);
+        if (OgnlHelper.isValidOgnlExpression(method)) {
+            // okay the method is an ognl expression
+            Object beanToCall = holder.getBean();
+            OgnlInvokeProcessor ognl = new OgnlInvokeProcessor(beanToCall, method);
+            try {
+                ognl.process(exchange);
+                return ognl.getResult();
+            } catch (Exception e) {
+                throw new RuntimeBeanExpressionException(exchange, beanName, method, e);
+            }
+        } else {
+            // regular non ognl invocation
+            InvokeProcessor invoke = new InvokeProcessor(holder, method);
+            try {
+                invoke.process(exchange);
+                return invoke.getResult();
+            } catch (Exception e) {
+                throw new RuntimeBeanExpressionException(exchange, beanName, method, e);
+            }
         }
     }
 
@@ -88,4 +100,156 @@ public class BeanExpression implements E
         return ObjectHelper.evaluateValuePredicate(value);
     }
 
+    /**
+     * Invokes a given bean holder. The method name is optional.
+     */
+    private final class InvokeProcessor implements Processor {
+
+        private BeanHolder beanHolder;
+        private String methodName;
+        private Object result;
+
+        private InvokeProcessor(BeanHolder beanHolder, String methodName) {
+            this.beanHolder = beanHolder;
+            this.methodName = methodName;
+        }
+
+        public void process(Exchange exchange) throws Exception {
+            BeanProcessor processor = new BeanProcessor(beanHolder);
+            if (methodName != null) {
+                processor.setMethod(methodName);
+            }
+            try {
+                // copy the original exchange to avoid side effects on it
+                Exchange resultExchange = exchange.copy();
+                // force to use InOut to retrieve the result on the OUT message
+                resultExchange.setPattern(ExchangePattern.InOut);
+                processor.process(resultExchange);
+                result = resultExchange.getOut().getBody();
+            } catch (Exception e) {
+                throw new RuntimeBeanExpressionException(exchange, beanName, methodName, e);
+            }
+        }
+
+        public Object getResult() {
+            return result;
+        }
+    }
+
+    /**
+     * To invoke a bean using a OGNL notation which denotes the chain of methods to invoke.
+     * <p/>
+     * For more advanced OGNL you may have to look for a real framework such as OGNL, Mvel or dynamic
+     * programming language such as Groovy, JuEL, JavaScript.
+     */
+    private final class OgnlInvokeProcessor implements Processor {
+
+        private final Object bean;
+        private final String ognl;
+        private Object result;
+        private boolean startElvis;
+
+        public OgnlInvokeProcessor(Object bean, String ognl) {
+            this.bean = bean;
+            this.ognl = ognl;
+            // we must start with having bean as the result
+            this.result = bean;
+        }
+
+
+        public void process(Exchange exchange) throws Exception {
+            // copy the original exchange to avoid side effects on it
+            Exchange resultExchange = exchange.copy();
+            // force to use InOut to retrieve the result on the OUT message
+            resultExchange.setPattern(ExchangePattern.InOut);
+
+            // loop and invoke each method
+            Object beanToCall = bean;
+
+            // current ognl path as we go along
+            String ognlPath = "";
+
+            List<String> methods = OgnlHelper.splitOgnl(ognl);
+            for (String methodName : methods) {
+                BeanHolder holder = new ConstantBeanHolder(beanToCall, exchange.getContext());
+
+                // support the elvis operator
+                boolean elvis = OgnlHelper.isElvis(methodName);
+                if (startElvis) {
+                    elvis = true;
+                    // flip flag to not apply elvis the next time
+                    startElvis = false;
+                }
+
+                // keep up with how far are we doing
+                ognlPath += methodName;
+
+                // get rid of leading ?. or . as we only needed that to determine if elvis was enabled or not
+                methodName = OgnlHelper.removeLeadingOperators(methodName);
+
+                // are we doing an index lookup (eg in Map/List/array etc)?
+                String key = null;
+                KeyValueHolder<String, String> index = OgnlHelper.isOgnlIndex(methodName);
+                if (index != null) {
+                    methodName = index.getKey();
+                    key = index.getValue();
+                }
+
+                // only invoke if we have a method name to use to invoke
+                if (methodName != null) {
+                    InvokeProcessor invoke = new InvokeProcessor(holder, methodName);
+                    invoke.process(resultExchange);
+                    result = invoke.getResult();
+                }
+
+                // if there was a key then we need to lookup using the key
+                if (key != null) {
+                    result = lookupResult(resultExchange, key, result, elvis, ognlPath, holder.getBean());
+                }
+
+                // check elvis for null results
+                if (result == null && elvis) {
+                    return;
+                }
+
+                // prepare for next bean to invoke
+                beanToCall = result;
+            }
+        }
+
+        private Object lookupResult(Exchange exchange, String key, Object result, boolean elvis, String ognlPath, Object bean) {
+            // try map first
+            Map map = exchange.getContext().getTypeConverter().convertTo(Map.class, result);
+            if (map != null) {
+                return map.get(key);
+            }
+
+            Integer num = exchange.getContext().getTypeConverter().convertTo(Integer.class, key);
+            if (num != null) {
+                List list = exchange.getContext().getTypeConverter().convertTo(List.class, result);
+                if (list != null) {
+                    if (list.size() > num - 1) {
+                        return list.get(num);
+                    } else if (!elvis) {
+                        // not elvis then its mandatory so thrown out of bounds exception
+                        throw new IndexOutOfBoundsException("Index: " + num + ", Size: " + list.size()
+                                + " out of bounds with List from bean: " + bean + "using OGNL path [" + ognlPath + "]");
+                    }
+                }
+            }
+
+            if (!elvis) {
+                throw new IndexOutOfBoundsException("Key: " + key + " not found in bean: " + bean + " of type: "
+                        + ObjectHelper.classCanonicalName(bean) + " using OGNL path [" + ognlPath + "]");
+            } else {
+                // elvis so we can return null
+                return null;
+            }
+        }
+
+        public Object getResult() {
+            return result;
+        }
+    }
+
 }

Modified: camel/trunk/camel-core/src/main/java/org/apache/camel/language/simple/SimpleLanguage.java
URL: http://svn.apache.org/viewvc/camel/trunk/camel-core/src/main/java/org/apache/camel/language/simple/SimpleLanguage.java?rev=922808&r1=922807&r2=922808&view=diff
==============================================================================
--- camel/trunk/camel-core/src/main/java/org/apache/camel/language/simple/SimpleLanguage.java (original)
+++ camel/trunk/camel-core/src/main/java/org/apache/camel/language/simple/SimpleLanguage.java Sun Mar 14 10:37:10 2010
@@ -16,13 +16,11 @@
  */
 package org.apache.camel.language.simple;
 
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
 import org.apache.camel.Expression;
 import org.apache.camel.ExpressionIllegalSyntaxException;
 import org.apache.camel.builder.ExpressionBuilder;
 import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.util.OgnlHelper;
 
 /**
  * A <a href="http://camel.apache.org/simple.html">simple language</a>
@@ -31,10 +29,12 @@ import org.apache.camel.util.ObjectHelpe
  * <ul>
  * <li>id to access the inbound message id</li>
  * <li>in.body or body to access the inbound body</li>
+ * <li>in.body.OGNL or body.OGNL to access the inbound body using an OGNL expression</li>
  * <li>bodyAs(&lt;classname&gt;) to convert the in body to the given type</li>
  * <li>out.body to access the inbound body</li>
  * <li>in.header.foo or header.foo to access an inbound header called 'foo'</li>
  * <li>in.header.foo[bar] or header.foo[bar] to access an inbound header called 'foo' as a Map and lookup the map with 'bar' as key</li>
+ * <li>in.header.foo.OGNL or header.OGNL to access an inbound header called 'foo' using an OGNL expression</li>
  * <li>out.header.foo to access an outbound header called 'foo'</li>
  * <li>property.foo to access the exchange property called 'foo'</li>
  * <li>sys.foo to access the system property called 'foo'</li>
@@ -53,6 +53,8 @@ import org.apache.camel.util.ObjectHelpe
  * </li>
 * </ul>
  * <p/>
+ * The simple language supports OGNL notation when accessing either body or header.
+ * <p/>
  * The simple language now also includes file language out of the box which means the following expression is also
  * supported:
  * <ul>
@@ -82,7 +84,6 @@ import org.apache.camel.util.ObjectHelpe
 public class SimpleLanguage extends SimpleLanguageSupport {
 
     private static final SimpleLanguage SIMPLE = new SimpleLanguage();
-    private static final Pattern HEADER_MAP = Pattern.compile("^(.*)\\[(.*)\\]$");
 
     public static Expression simple(String expression) {
         return SIMPLE.createExpression(expression);
@@ -109,28 +110,45 @@ public class SimpleLanguage extends Simp
             return ExpressionBuilder.bodyExpression(type);
         }
 
+        // body OGNL
+        remainder = ifStartsWithReturnRemainder("body", expression);
+        if (remainder == null) {
+            remainder = ifStartsWithReturnRemainder("in.body", expression);
+        }
+        if (remainder != null) {
+            boolean invalid = OgnlHelper.isInvalidValidOgnlExpression(remainder);
+            if (invalid) {
+                throw new ExpressionIllegalSyntaxException("Valid syntax: ${body.OGNL} was: " + expression);
+            }
+            return ExpressionBuilder.bodyOgnlExpression(remainder);
+        }
+
         // in header expression
-        remainder = ifStartsWithReturnRemainder("in.header.", expression);
+        remainder = ifStartsWithReturnRemainder("in.headers", expression);
         if (remainder == null) {
-            remainder = ifStartsWithReturnRemainder("header.", expression);
+            remainder = ifStartsWithReturnRemainder("in.header", expression);
         }
         if (remainder == null) {
-            remainder = ifStartsWithReturnRemainder("headers.", expression);
+            remainder = ifStartsWithReturnRemainder("headers", expression);
         }
         if (remainder == null) {
-            remainder = ifStartsWithReturnRemainder("in.headers.", expression);
+            remainder = ifStartsWithReturnRemainder("header", expression);
         }
         if (remainder != null) {
-            // could be a map based index
-            Matcher matcher = HEADER_MAP.matcher(remainder);
-            if (matcher.matches()) {
-                String name = matcher.group(1);
-                String key = matcher.group(2);
-                return ExpressionBuilder.headerAsMapExpression(name, key);
+            // remove leading dot
+            remainder = remainder.substring(1);
+
+            // validate syntax
+            boolean invalid = OgnlHelper.isInvalidValidOgnlExpression(remainder);
+            if (invalid) {
+                throw new ExpressionIllegalSyntaxException("Valid syntax: ${header.name[key]} was: " + expression);
+            }
+
+            if (OgnlHelper.isValidOgnlExpression(remainder)) {
+                // ognl based header
+                return ExpressionBuilder.headersOgnlExpression(remainder);
             } else {
-                if (remainder.contains("[") || remainder.contains("]")) {
-                    throw new ExpressionIllegalSyntaxException("Valid syntax: ${header.name[key]} was: " + expression);
-                }
+                // regular header
                 return ExpressionBuilder.headerExpression(remainder);
             }
         }

Added: camel/trunk/camel-core/src/main/java/org/apache/camel/util/OgnlHelper.java
URL: http://svn.apache.org/viewvc/camel/trunk/camel-core/src/main/java/org/apache/camel/util/OgnlHelper.java?rev=922808&view=auto
==============================================================================
--- camel/trunk/camel-core/src/main/java/org/apache/camel/util/OgnlHelper.java (added)
+++ camel/trunk/camel-core/src/main/java/org/apache/camel/util/OgnlHelper.java Sun Mar 14 10:37:10 2010
@@ -0,0 +1,212 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.util;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Helper for Camel OGNL expressions.
+ *
+ * @version $Revision$
+ */
+public final class OgnlHelper {
+
+    private static final Pattern INDEX_PATTERN = Pattern.compile("^(.*)\\[(.*)\\]$");
+
+    private OgnlHelper() {
+    }
+
+    /**
+     * Tests whether or not the given String is a Camel OGNL expression.
+     * <p/>
+     * Its consider Camel OGNL expression when it contains either one of the following chars: . or [
+     *
+     * @param expression  the String
+     * @return <tt>true</tt> if Camel OGNL expression, otherwise <tt>false</tt>. 
+     */
+    public static boolean isValidOgnlExpression(String expression) {
+        if (ObjectHelper.isEmpty(expression)) {
+            return false;
+        }
+
+        // TODO: if possible used regexp to parse if its valid/invalid
+
+        // the brackets should come in pair
+        int bracketBegin = StringHelper.countChar(expression, '[');
+        int bracketEnd = StringHelper.countChar(expression, ']');
+        if (bracketBegin > 0 && bracketEnd > 0) {
+            return bracketBegin == bracketEnd;
+        }
+
+        return expression.contains(".");
+    }
+
+    public static boolean isInvalidValidOgnlExpression(String expression) {
+        if (ObjectHelper.isEmpty(expression)) {
+            return false;
+        }
+
+        if (!expression.contains(".") && !expression.contains("[") && !expression.contains("]")) {
+            return false;
+        }
+
+        // TODO: if possible used regexp to parse if its valid/invalid
+
+        // the brackets should come in pair
+        int bracketBegin = StringHelper.countChar(expression, '[');
+        int bracketEnd = StringHelper.countChar(expression, ']');
+        if (bracketBegin > 0 || bracketEnd > 0) {
+            return bracketBegin != bracketEnd;
+        }
+        
+        // check for double dots
+        if (expression.contains("..")) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Tests whether or not the given Camel OGNL expression is using the elvis operator or not.
+     *
+     * @param ognlExpression the Camel OGNL expression
+     * @return <tt>true</tt> if the elvis operator is used, otherwise <tt>false</tt>.
+     */
+    public static boolean isElvis(String ognlExpression) {
+        if (ObjectHelper.isEmpty(ognlExpression)) {
+            return false;
+        }
+
+        return ognlExpression.startsWith("?");
+    }
+
+    /**
+     * Removes any leading operators from the Camel OGNL expression.
+     * <p/>
+     * Will remove any leading of the following chars: ? or .
+     *
+     * @param ognlExpression  the Camel OGNL expression
+     * @return the Camel OGNL expression without any leading operators.
+     */
+    public static String removeLeadingOperators(String ognlExpression) {
+        if (ObjectHelper.isEmpty(ognlExpression)) {
+            return ognlExpression;
+        }
+
+        if (ognlExpression.startsWith("?")) {
+            ognlExpression = ognlExpression.substring(1);
+        }
+        if (ognlExpression.startsWith(".")) {
+            ognlExpression = ognlExpression.substring(1);
+        }
+
+        return ognlExpression;
+    }
+
+    /**
+     * Removes any trailing operators from the Camel OGNL expression.
+     *
+     * @param ognlExpression  the Camel OGNL expression
+     * @return the Camel OGNL expression without any trailing operators.
+     */
+    public static String removeTrailingOperators(String ognlExpression) {
+        if (ObjectHelper.isEmpty(ognlExpression)) {
+            return ognlExpression;
+        }
+
+        if (ognlExpression.contains("[")) {
+            return ObjectHelper.before(ognlExpression, "[");
+        }
+        return ognlExpression;
+    }
+
+    public static String removeOperators(String ognlExpression) {
+        return removeLeadingOperators(removeTrailingOperators(ognlExpression));
+    }
+
+    public static KeyValueHolder<String, String> isOgnlIndex(String ognlExpression) {
+        Matcher matcher = INDEX_PATTERN.matcher(ognlExpression);
+        if (matcher.matches()) {
+
+            // to avoid empty strings as we want key/value to be null in such cases
+            String key = matcher.group(1);
+            if (ObjectHelper.isEmpty(key)) {
+                key = null;
+            }
+
+            // to avoid empty strings as we want key/value to be null in such cases
+            String value = matcher.group(2);
+            if (ObjectHelper.isEmpty(value)) {
+                value = null;
+            }
+
+            return new KeyValueHolder<String, String>(key, value);
+        }
+
+        return null;
+    }
+
+    /**
+     * Regular expression with repeating groups is a pain to get right
+     * and then nobody understands the reg exp afterwards.
+     * So use a bit ugly/low-level java code to split the ognl into methods.
+     */
+    public static List<String> splitOgnl(String ognl) {
+        // TODO: if possible use reg exp to split instead
+
+        List<String> methods = new ArrayList<String>();
+
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < ognl.length(); i++) {
+            char ch = ognl.charAt(i);
+            // special for starting
+            if (i == 0 || (i == 1 && ognl.charAt(0) == '?')
+                    || (ch != '.' && ch != '?')) {
+                sb.append(ch);
+            } else {
+                if (ch == '.') {
+                    String s = sb.toString();
+
+                    // reset sb
+                    sb.setLength(0);
+
+                    // pass over ? to the new method
+                    if (s.endsWith("?")) {
+                        sb.append("?");
+                        s = s.substring(0, s.length() - 1);
+                    }
+
+                    // add the method
+                    methods.add(s);
+                }
+                // and dont lose the char
+                sb.append(ch);
+            }
+        }
+        // add remainder in buffer
+        if (sb.length() > 0) {
+            methods.add(sb.toString());
+        }
+
+        return methods;
+    }
+
+}

Propchange: camel/trunk/camel-core/src/main/java/org/apache/camel/util/OgnlHelper.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: camel/trunk/camel-core/src/main/java/org/apache/camel/util/OgnlHelper.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date

Modified: camel/trunk/camel-core/src/main/java/org/apache/camel/util/StringHelper.java
URL: http://svn.apache.org/viewvc/camel/trunk/camel-core/src/main/java/org/apache/camel/util/StringHelper.java?rev=922808&r1=922807&r2=922808&view=diff
==============================================================================
--- camel/trunk/camel-core/src/main/java/org/apache/camel/util/StringHelper.java (original)
+++ camel/trunk/camel-core/src/main/java/org/apache/camel/util/StringHelper.java Sun Mar 14 10:37:10 2010
@@ -44,5 +44,28 @@ public final class StringHelper {
             .replace('/', '-')
             .replace('\\', '-');
     }
+
+    /**
+     * Counts the number of times the given char is in the string
+     *
+     * @param s  the string
+     * @param ch the char
+     * @return number of times char is located in the string
+     */
+    public static int countChar(String s, char ch) {
+        if (ObjectHelper.isEmpty(s)) {
+            return 0;
+        }
+
+        int matches = 0;
+        for (int i = 0; i < s.length(); i++) {
+            char c = s.charAt(i);
+            if (ch == c) {
+                matches++;
+            }
+        }
+
+        return matches;
+    }
     
 }

Added: camel/trunk/camel-core/src/test/java/org/apache/camel/language/BeanLanguageOGNLTest.java
URL: http://svn.apache.org/viewvc/camel/trunk/camel-core/src/test/java/org/apache/camel/language/BeanLanguageOGNLTest.java?rev=922808&view=auto
==============================================================================
--- camel/trunk/camel-core/src/test/java/org/apache/camel/language/BeanLanguageOGNLTest.java (added)
+++ camel/trunk/camel-core/src/test/java/org/apache/camel/language/BeanLanguageOGNLTest.java Sun Mar 14 10:37:10 2010
@@ -0,0 +1,60 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.language;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.builder.RouteBuilder;
+
+/**
+ * @version $Revision$
+ */
+public class BeanLanguageOGNLTest extends ContextTestSupport {
+
+    public void testBeanLanguageOGNL() throws Exception {
+        String reply = template.requestBody("direct:start", "World", String.class);
+        assertEquals("Hello World", reply);
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() throws Exception {
+        return new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:start")
+                    .transform().method(MyReallyCoolBean.class, "getOther.greet");
+            }
+        };
+    }
+
+    public static class MyReallyCoolBean {
+
+        private MyOtherReallyCoolBean other = new MyOtherReallyCoolBean();
+
+        public MyOtherReallyCoolBean getOther() {
+            return other;
+        }
+
+    }
+
+    public static class MyOtherReallyCoolBean {
+
+        public String greet(String name) {
+            return "Hello " + name;
+        }
+
+    }
+}

Propchange: camel/trunk/camel-core/src/test/java/org/apache/camel/language/BeanLanguageOGNLTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: camel/trunk/camel-core/src/test/java/org/apache/camel/language/BeanLanguageOGNLTest.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date

Modified: camel/trunk/camel-core/src/test/java/org/apache/camel/language/SimpleTest.java
URL: http://svn.apache.org/viewvc/camel/trunk/camel-core/src/test/java/org/apache/camel/language/SimpleTest.java?rev=922808&r1=922807&r2=922808&view=diff
==============================================================================
--- camel/trunk/camel-core/src/test/java/org/apache/camel/language/SimpleTest.java (original)
+++ camel/trunk/camel-core/src/test/java/org/apache/camel/language/SimpleTest.java Sun Mar 14 10:37:10 2010
@@ -16,13 +16,17 @@
  */
 package org.apache.camel.language;
 
+import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.GregorianCalendar;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import org.apache.camel.ExpressionIllegalSyntaxException;
 import org.apache.camel.LanguageTestSupport;
+import org.apache.camel.component.bean.MethodNotFoundException;
+import org.apache.camel.language.bean.RuntimeBeanExpressionException;
 
 /**
  * @version $Revision$
@@ -161,7 +165,43 @@ public class SimpleTest extends Language
         }
     }
 
-    public void testHeaderMap() throws Exception {
+    public void testOGNLHeaderList() throws Exception {
+        List<String> lines = new ArrayList<String>();
+        lines.add("Camel in Action");
+        lines.add("ActiveMQ in Action");
+        exchange.getIn().setHeader("wicket", lines);
+
+        assertExpression("${header.wicket[0]}", "Camel in Action");
+        assertExpression("${header.wicket[1]}", "ActiveMQ in Action");
+        try {
+            assertExpression("${header.wicket[2]}", "");
+            fail("Should have thrown an exception");
+        } catch (Exception e) {
+            IndexOutOfBoundsException cause = assertIsInstanceOf(IndexOutOfBoundsException.class, e.getCause());
+            assertEquals("Index: 2, Size: 2", cause.getMessage());
+        }
+        assertExpression("${header.unknown[cool]}", "");
+    }
+
+    public void testOGNLHeaderLinesList() throws Exception {
+        List<OrderLine> lines = new ArrayList<OrderLine>();
+        lines.add(new OrderLine(123, "Camel in Action"));
+        lines.add(new OrderLine(456, "ActiveMQ in Action"));
+        exchange.getIn().setHeader("wicket", lines);
+
+        assertExpression("${header.wicket[0].getId}", 123);
+        assertExpression("${header.wicket[1].getName}", "ActiveMQ in Action");
+        try {
+            assertExpression("${header.wicket[2]}", "");
+            fail("Should have thrown an exception");
+        } catch (Exception e) {
+            IndexOutOfBoundsException cause = assertIsInstanceOf(IndexOutOfBoundsException.class, e.getCause());
+            assertEquals("Index: 2, Size: 2", cause.getMessage());
+        }
+        assertExpression("${header.unknown[cool]}", "");
+    }
+
+    public void testOGNLHeaderMap() throws Exception {
         Map map = new HashMap();
         map.put("cool", "Camel rocks");
         map.put("dude", "Hey dude");
@@ -172,19 +212,22 @@ public class SimpleTest extends Language
         assertExpression("${header.wicket[dude]}", "Hey dude");
         assertExpression("${header.wicket[unknown]}", "");
         assertExpression("${header.wicket[code]}", 4321);
+        // no header named unknown
+        assertExpression("${header?.unknown[cool]}", "");
         assertExpression("${header.unknown[cool]}", "");
     }
 
-    public void testHeaderMapNotMap() throws Exception {
+    public void testOGNLHeaderMapNotMap() throws Exception {
         try {
             assertExpression("${header.foo[bar]}", null);
             fail("Should have thrown an exception");
-        } catch (IllegalArgumentException e) {
-            assertEquals("Header foo cannot be converted to a Map on headerAsMap(foo)[bar]", e.getMessage());
+        } catch (RuntimeBeanExpressionException e) {
+            IndexOutOfBoundsException cause = assertIsInstanceOf(IndexOutOfBoundsException.class, e.getCause());
+            assertEquals("Key: bar not found in bean: abc of type: java.lang.String using OGNL path [[bar]]", cause.getMessage());
         }
     }
 
-    public void testHeaderMapIllegalSyntax() throws Exception {
+    public void testOGNLHeaderMapIllegalSyntax() throws Exception {
         try {
             assertExpression("${header.foo[bar}", null);
             fail("Should have thrown an exception");
@@ -193,8 +236,225 @@ public class SimpleTest extends Language
         }
     }
 
+    public void testBodyOGNLSimple() throws Exception {
+        Animal camel = new Animal("Camel", 6);
+        exchange.getIn().setBody(camel);
+
+        assertExpression("${in.body.getName}", "Camel");
+        assertExpression("${in.body.getAge}", 6);
+    }
+
+    public void testBodyOGNLNested() throws Exception {
+        Animal tiger = new Animal("Tony the Tiger", 13);
+        Animal camel = new Animal("Camel", 6);
+        camel.setFriend(tiger);
+
+        exchange.getIn().setBody(camel);
+
+        assertExpression("${in.body.getName}", "Camel");
+        assertExpression("${in.body.getAge}", 6);
+
+        assertExpression("${in.body.getFriend.getName}", "Tony the Tiger");
+        assertExpression("${in.body.getFriend.getAge}", "13");
+    }
+
+    public void testBodyOGNLOrderList() throws Exception {
+        List<OrderLine> lines = new ArrayList<OrderLine>();
+        lines.add(new OrderLine(123, "Camel in Action"));
+        lines.add(new OrderLine(456, "ActiveMQ in Action"));
+        Order order = new Order(lines);
+        
+        exchange.getIn().setBody(order);
+
+        assertExpression("${in.body.getLines[0].getId}", 123);
+        assertExpression("${in.body.getLines[0].getName}", "Camel in Action");
+
+        assertExpression("${in.body.getLines[1].getId}", 456);
+        assertExpression("${in.body.getLines[1].getName}", "ActiveMQ in Action");
+    }
+
+    public void testBodyOGNLList() throws Exception {
+        List<OrderLine> lines = new ArrayList<OrderLine>();
+        lines.add(new OrderLine(123, "Camel in Action"));
+        lines.add(new OrderLine(456, "ActiveMQ in Action"));
+
+        exchange.getIn().setBody(lines);
+
+        assertExpression("${in.body[0].getId}", 123);
+        assertExpression("${in.body[0].getName}", "Camel in Action");
+
+        assertExpression("${in.body[1].getId}", 456);
+        assertExpression("${in.body[1].getName}", "ActiveMQ in Action");
+    }
+
+    public void testBodyOGNLArray() throws Exception {
+        OrderLine[] lines = new OrderLine[2];
+        lines[0] = new OrderLine(123, "Camel in Action");
+        lines[1] = new OrderLine(456, "ActiveMQ in Action");
+
+        exchange.getIn().setBody(lines);
+
+        assertExpression("${in.body[0].getId}", 123);
+        assertExpression("${in.body[0].getName}", "Camel in Action");
+
+        assertExpression("${in.body[1].getId}", 456);
+        assertExpression("${in.body[1].getName}", "ActiveMQ in Action");
+    }
+
+    public void testBodyOGNLOrderListOutOfBounds() throws Exception {
+        List<OrderLine> lines = new ArrayList<OrderLine>();
+        lines.add(new OrderLine(123, "Camel in Action"));
+        lines.add(new OrderLine(456, "ActiveMQ in Action"));
+        Order order = new Order(lines);
+
+        exchange.getIn().setBody(order);
+
+        try {
+            assertExpression("${in.body.getLines[3].getId}", 123);
+            fail("Should have thrown an exception");
+        } catch (RuntimeBeanExpressionException e) {
+            IndexOutOfBoundsException cause = assertIsInstanceOf(IndexOutOfBoundsException.class, e.getCause());
+
+            assertTrue(cause.getMessage().startsWith("Index: 3, Size: 2 out of bounds with List from bean"));
+        }
+    }
+
+    public void testBodyOGNLOrderListOutOfBoundsWithElvis() throws Exception {
+        List<OrderLine> lines = new ArrayList<OrderLine>();
+        lines.add(new OrderLine(123, "Camel in Action"));
+        lines.add(new OrderLine(456, "ActiveMQ in Action"));
+        Order order = new Order(lines);
+
+        exchange.getIn().setBody(order);
+
+        assertExpression("${in.body?.getLines[3].getId}", "");
+    }
+
+    public void testBodyOGNLOrderListNoMethodNameWithElvis() throws Exception {
+        List<OrderLine> lines = new ArrayList<OrderLine>();
+        lines.add(new OrderLine(123, "Camel in Action"));
+        lines.add(new OrderLine(456, "ActiveMQ in Action"));
+        Order order = new Order(lines);
+
+        exchange.getIn().setBody(order);
+
+        try {
+            assertExpression("${in.body.getLines[0]?.getRating}", "");
+            fail("Should have thrown exception");
+        } catch (RuntimeBeanExpressionException e) {
+            MethodNotFoundException cause = assertIsInstanceOf(MethodNotFoundException.class, e.getCause().getCause());
+            assertEquals("getRating", cause.getMethodName());
+        }
+    }
+
+    public void testBodyOGNLElvisToAvoidNPE() throws Exception {
+        Animal tiger = new Animal("Tony the Tiger", 13);
+        Animal camel = new Animal("Camel", 6);
+        camel.setFriend(tiger);
+
+        exchange.getIn().setBody(camel);
+
+        assertExpression("${in.body.getName}", "Camel");
+        assertExpression("${in.body.getAge}", 6);
+
+        assertExpression("${in.body.getFriend.getName}", "Tony the Tiger");
+        assertExpression("${in.body.getFriend.getAge}", "13");
+
+        // using elvis to avoid the NPE
+        assertExpression("${in.body.getFriend?.getFriend.getName}", "");
+        try {
+            // without elvis we get an NPE
+            assertExpression("${in.body.getFriend.getFriend.getName}", "");
+            fail("Should have thrown exception");
+        } catch (RuntimeBeanExpressionException e) {
+            assertEquals("Failed to invoke method: .getFriend.getFriend.getName on null due to: java.lang.NullPointerException", e.getMessage());
+            assertIsInstanceOf(NullPointerException.class, e.getCause());
+        }
+    }
+
+    public void testBodyOGNLReentrant() throws Exception {
+        Animal camel = new Animal("Camel", 6);
+        Animal tiger = new Animal("Tony the Tiger", 13);
+        Animal elephant = new Animal("Big Ella", 48);
+
+        camel.setFriend(tiger);
+        tiger.setFriend(elephant);
+        elephant.setFriend(camel);
+
+        exchange.getIn().setBody(camel);
+
+        assertExpression("${body.getFriend.getFriend.getFriend.getName}", "Camel");
+        assertExpression("${body.getFriend.getFriend.getFriend.getFriend.getName}", "Tony the Tiger");
+        assertExpression("${body.getFriend.getFriend.getFriend.getFriend.getFriend.getName}", "Big Ella");
+    }
 
     protected String getLanguageName() {
         return "simple";
     }
+
+    public static final class Animal {
+        private String name;
+        private int age;
+        private Animal friend;
+
+        private Animal(String name, int age) {
+            this.name = name;
+            this.age = age;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public int getAge() {
+            return age;
+        }
+
+        public Animal getFriend() {
+            return friend;
+        }
+
+        public void setFriend(Animal friend) {
+            this.friend = friend;
+        }
+
+        @Override
+        public String toString() {
+            return name;
+        }
+    }
+
+    public static final class Order {
+        private List lines;
+
+        public Order(List lines) {
+            this.lines = lines;
+        }
+
+        public List getLines() {
+            return lines;
+        }
+
+        public void setLines(List lines) {
+            this.lines = lines;
+        }
+    }
+
+    public static final class OrderLine {
+        private int id;
+        private String name;
+
+        public OrderLine(int id, String name) {
+            this.id = id;
+            this.name = name;
+        }
+
+        public int getId() {
+            return id;
+        }
+
+        public String getName() {
+            return name;
+        }
+    }
 }
\ No newline at end of file

Modified: camel/trunk/camel-core/src/test/java/org/apache/camel/util/StringHelperTest.java
URL: http://svn.apache.org/viewvc/camel/trunk/camel-core/src/test/java/org/apache/camel/util/StringHelperTest.java?rev=922808&r1=922807&r2=922808&view=diff
==============================================================================
--- camel/trunk/camel-core/src/test/java/org/apache/camel/util/StringHelperTest.java (original)
+++ camel/trunk/camel-core/src/test/java/org/apache/camel/util/StringHelperTest.java Sun Mar 14 10:37:10 2010
@@ -35,4 +35,13 @@ public class StringHelperTest extends Te
         assertTrue("Should not contain . ", out.indexOf('.') == -1);
     }
 
+    public void testCountChar() {
+        assertEquals(0, StringHelper.countChar("Hello World", 'x'));
+        assertEquals(1, StringHelper.countChar("Hello World", 'e'));
+        assertEquals(3, StringHelper.countChar("Hello World", 'l'));
+        assertEquals(1, StringHelper.countChar("Hello World", ' '));
+        assertEquals(0, StringHelper.countChar("", ' '));
+        assertEquals(0, StringHelper.countChar(null, ' '));
+    }
+
 }