Commits

musachy  committed 0e6dc9f
  • Participants
  • Parent commits 76778b7
  • Branches parameter-binder

Comments (0)

Files changed (11)

File core/src/main/java/com/opensymphony/xwork2/config/providers/XWorkConfigurationProvider.java

 import com.opensymphony.xwork2.DefaultUnknownHandlerManager;
 import com.opensymphony.xwork2.TextProvider;
 import com.opensymphony.xwork2.UnknownHandlerManager;
-import com.opensymphony.xwork2.parameters.XWorkParameterParserUtils;
 import com.opensymphony.xwork2.parameters.XWorkParametersBinder;
 import com.opensymphony.xwork2.config.Configuration;
 import com.opensymphony.xwork2.config.ConfigurationException;
                .factory(ActionProxyFactory.class, DefaultActionProxyFactory.class, Scope.SINGLETON)
                .factory(ObjectTypeDeterminer.class, DefaultObjectTypeDeterminer.class, Scope.SINGLETON)
                .factory(XWorkConverter.class, Scope.SINGLETON)
-               .factory(XWorkParameterParserUtils.class, Scope.SINGLETON)
                .factory(XWorkParametersBinder.class, Scope.SINGLETON) 
                .factory(ValueStackFactory.class, OgnlValueStackFactory.class, Scope.SINGLETON)
                .factory(ValidatorFactory.class, DefaultValidatorFactory.class, Scope.SINGLETON)
         props.setProperty("devMode", Boolean.FALSE.toString());
         props.setProperty("logMissingProperties", Boolean.FALSE.toString());
         props.setProperty("enableOGNLExpressionCache", Boolean.TRUE.toString());
-        props.setProperty("enableSimpleParametersBinder", Boolean.FALSE.toString());
+        props.setProperty("enableSimpleParametersBinder", Boolean.TRUE.toString());
     }
 
 }

File core/src/main/java/com/opensymphony/xwork2/interceptor/ParametersInterceptor.java

                     return true;
                 }
             }
-        }
-        return acceptedPattern.matcher(paramName).matches();
+
+            return false;
+        } else
+            return acceptedPattern.matcher(paramName).matches();
     }
 
     protected boolean isExcluded(String paramName) {

File core/src/main/java/com/opensymphony/xwork2/parameters/ParserUtils.java

+/*
+ * $Id$
+ *
+ * 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 com.opensymphony.xwork2.parameters;
+
+import org.apache.commons.lang.StringUtils;
+
+abstract class ParserUtils {
+    /**
+     * this should be in the parser, but javacc keeps scaping "\" into a unicode char
+     * and the end result doesn't compile, very annoying
+     */
+    public static String getStringLiteral(String str) {
+        return StringUtils.strip(str, "\"'");
+    }
+}

File core/src/main/java/com/opensymphony/xwork2/parameters/XWorkParameterParser.java

 import java.util.List;
 import java.util.ArrayList;
 import com.opensymphony.xwork2.parameters.nodes.*;
+import com.opensymphony.xwork2.parameters.ParserUtils;
+import org.apache.commons.lang.StringUtils;
 
 class XWorkParameterParser implements XWorkParameterParserConstants {
-    private XWorkParameterParserUtils parserUtils;
-
     public XWorkParameterParser(String paramName) {
         this((new StringReader(paramName)));
     }
     } else if (jj_2_4(2)) {
       id = jj_consume_token(IDENTIFIER);
         {if (true) return new IdentifierNode(id.image);}
+    } else if (jj_2_5(2)) {
+      jj_consume_token(LBRACKET);
+      id = jj_consume_token(STRING_LITERAL);
+      jj_consume_token(RBRACKET);
+        {if (true) return new IdentifierNode(ParserUtils.getStringLiteral(id.image));}
     } else {
       jj_consume_token(-1);
       throw new ParseException();
 
   final public Object value() throws ParseException {
     Token t;
-    if (jj_2_5(2)) {
+    if (jj_2_6(2)) {
       t = jj_consume_token(STRING_LITERAL);
-        {if (true) return t.image.substring(1, t.image.length() - 1);}
-    } else if (jj_2_6(2)) {
+        {if (true) return ParserUtils.getStringLiteral(t.image);}
+    } else if (jj_2_7(2)) {
       t = jj_consume_token(INTEGER_LITERAL);
         {if (true) return Integer.valueOf(t.image);}
-    } else if (jj_2_7(2)) {
+    } else if (jj_2_8(2)) {
       t = jj_consume_token(FLOATING_POINT_LITERAL);
         {if (true) return Double.valueOf(t.image);}
-    } else if (jj_2_8(2)) {
+    } else if (jj_2_9(2)) {
       jj_consume_token(TRUE);
         {if (true) return Boolean.TRUE;}
-    } else if (jj_2_9(2)) {
+    } else if (jj_2_10(2)) {
       jj_consume_token(FALSE);
         {if (true) return Boolean.FALSE;}
     } else {
     finally { jj_save(8, xla); }
   }
 
+  private boolean jj_2_10(int xla) {
+    jj_la = xla; jj_lastpos = jj_scanpos = token;
+    try { return !jj_3_10(); }
+    catch(LookaheadSuccess ls) { return true; }
+    finally { jj_save(9, xla); }
+  }
+
   private boolean jj_3_1() {
     if (jj_scan_token(DOT)) return true;
     if (jj_3R_2()) return true;
     return false;
   }
 
-  private boolean jj_3_4() {
-    if (jj_scan_token(IDENTIFIER)) return true;
+  private boolean jj_3_5() {
+    if (jj_scan_token(LBRACKET)) return true;
+    if (jj_scan_token(STRING_LITERAL)) return true;
     return false;
   }
 
-  private boolean jj_3_9() {
+  private boolean jj_3_10() {
     if (jj_scan_token(FALSE)) return true;
     return false;
   }
     return false;
   }
 
+  private boolean jj_3_4() {
+    if (jj_scan_token(IDENTIFIER)) return true;
+    return false;
+  }
+
+  private boolean jj_3_9() {
+    if (jj_scan_token(TRUE)) return true;
+    return false;
+  }
+
   private boolean jj_3_3() {
     if (jj_3R_4()) return true;
     return false;
   }
 
   private boolean jj_3_8() {
-    if (jj_scan_token(TRUE)) return true;
+    if (jj_scan_token(FLOATING_POINT_LITERAL)) return true;
     return false;
   }
 
     jj_scanpos = xsp;
     if (jj_3_3()) {
     jj_scanpos = xsp;
-    if (jj_3_4()) return true;
+    if (jj_3_4()) {
+    jj_scanpos = xsp;
+    if (jj_3_5()) return true;
+    }
     }
     }
     return false;
   }
 
   private boolean jj_3_7() {
-    if (jj_scan_token(FLOATING_POINT_LITERAL)) return true;
-    return false;
-  }
-
-  private boolean jj_3_6() {
     if (jj_scan_token(INTEGER_LITERAL)) return true;
     return false;
   }
     return false;
   }
 
-  private boolean jj_3_5() {
+  private boolean jj_3_6() {
     if (jj_scan_token(STRING_LITERAL)) return true;
     return false;
   }
    private static void jj_la1_init_0() {
       jj_la1_0 = new int[] {};
    }
-  final private JJCalls[] jj_2_rtns = new JJCalls[9];
+  final private JJCalls[] jj_2_rtns = new JJCalls[10];
   private boolean jj_rescan = false;
   private int jj_gc = 0;
 
 
   private void jj_rescan_token() {
     jj_rescan = true;
-    for (int i = 0; i < 9; i++) {
+    for (int i = 0; i < 10; i++) {
     try {
       JJCalls p = jj_2_rtns[i];
       do {
             case 6: jj_3_7(); break;
             case 7: jj_3_8(); break;
             case 8: jj_3_9(); break;
+            case 9: jj_3_10(); break;
           }
         }
         p = p.next;

File core/src/main/java/com/opensymphony/xwork2/parameters/XWorkParameterParserTokenManager.java

 import java.util.List;
 import java.util.ArrayList;
 import com.opensymphony.xwork2.parameters.nodes.*;
+import com.opensymphony.xwork2.parameters.ParserUtils;
+import org.apache.commons.lang.StringUtils;
 
 /** Token Manager. */
 public class XWorkParameterParserTokenManager implements XWorkParameterParserConstants

File core/src/main/java/com/opensymphony/xwork2/parameters/XWorkParameterParserUtils.java

-package com.opensymphony.xwork2.parameters;
-
-import com.opensymphony.xwork2.util.reflection.ReflectionProvider;
-import com.opensymphony.xwork2.util.reflection.ReflectionContextState;
-import com.opensymphony.xwork2.conversion.NullHandler;
-import com.opensymphony.xwork2.inject.Inject;
-
-import java.util.Map;
-
-public class XWorkParameterParserUtils {
-    private NullHandler nullHandler;
-    private ReflectionProvider reflectionProvider;
-
-    public Object get(String expression, Map<String, Object> context, Object root) {
-        Object value = reflectionProvider.getValue(expression, context, root);
-        if (value == null) {
-            //instantiate it and set it
-            boolean previousValue = ReflectionContextState.isCreatingNullObjects(context);
-            ReflectionContextState.setCreatingNullObjects(context, true);
-
-            try {
-                return nullHandler.nullPropertyValue(context, root, expression);
-            } finally {
-                ReflectionContextState.setCreatingNullObjects(context, previousValue);
-            }
-        }
-
-        return value;
-    }
-
-    @Inject
-    public void setReflectionProvider(ReflectionProvider reflectionProvider) {
-        this.reflectionProvider = reflectionProvider;
-    }
-
-    @Inject("java.lang.Object")
-    public void setNullHandler(NullHandler nullHandler) {
-        this.nullHandler = nullHandler;
-    }
-}

File core/src/main/java/com/opensymphony/xwork2/parameters/XWorkParametersBinder.java

 import ognl.OgnlContext;
 
 public class XWorkParametersBinder {
-    protected XWorkParameterParserUtils parameterParserUtils;
     protected ReflectionProvider reflectionProvider;
     protected NullHandler nullHandler;
     protected Container container;
+    private XWorkParametersMapPropertyAccessor mapAccessor;
 
     public void setProperty(Map<String, Object> context, Object action, String paramName, Object paramValue) {
         try {
                 if (node instanceof IdentifierNode) {
                     //A.B
                     String id = ((IdentifierNode) node).getIdentifier();
+
+                    if (StringUtils.isBlank(id))
+                        throw new ParseException("Expression '" + paramName + "' is invalid");
+
                     lastProperty = id;
 
                     //if this is not the last expression, create the object if it doesn't exist
 
                     lastProperty = index;
                     PropertyAccessor accessor = getPropertyAccessor(lastObject);
-                    lastObject = accessor.getProperty(ognlContext, lastObject, id);
+                    //the list or map
+                    Object container = accessor.getProperty(ognlContext, lastObject, id);
 
-                    //create the lastObject
-                    if (lastObject == null) {
-                        //create it
-                        lastObject = create(ognlContext, action, id);
+                    if (container == null) {
+                        //create the list or map
+                        container = create(ognlContext, lastObject, id);
+                    }
+
+                    lastObject = container;
+
+                    if (!lastNode) {
+                        //the expression goes on like A[B].C, so now create A[B]
+                        accessor = getPropertyAccessor(lastObject);
+                        lastObject = getAndCreate(ognlContext, lastObject, index, accessor);
                     }
                 } else if (node instanceof CollectionNode) {
                     //A(B)
         if (object instanceof CompoundRoot)
             return container.getInstance(PropertyAccessor.class, CompoundRoot.class.getName());
         if (object instanceof Map)
-            return container.getInstance(PropertyAccessor.class, Map.class.getName());
+            return mapAccessor;
         else if (object instanceof List)
             return container.getInstance(PropertyAccessor.class, List.class.getName());
         else if (object instanceof Collection)
         }
     }
 
-    @Inject
-    public void setParameterParserUtils(XWorkParameterParserUtils parameterParserUtils) {
-        this.parameterParserUtils = parameterParserUtils;
+    protected Object getAndCreate(Map<String, Object> context, Object root, Object property, PropertyAccessor accessor) throws OgnlException {
+        boolean originalValue = ReflectionContextState.isCreatingNullObjects(context);
+        try {
+            ReflectionContextState.setCreatingNullObjects(context, true);
+            return accessor.getProperty(context, root, property);
+        } finally {
+            ReflectionContextState.setCreatingNullObjects(context, originalValue);
+        }
     }
 
     @Inject
     @Inject
     public void setContainer(Container container) {
         this.container = container;
+        this.mapAccessor = new XWorkParametersMapPropertyAccessor();
+        container.inject(mapAccessor);
     }
 }

File core/src/main/java/com/opensymphony/xwork2/parameters/XWorkSimpleMapPropertyAccessor.java

+/*
+ * $Id$
+ *
+ * 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 com.opensymphony.xwork2.parameters;
+
+import ognl.MapPropertyAccessor;
+import ognl.OgnlException;
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+import com.opensymphony.xwork2.util.reflection.ReflectionContextState;
+import com.opensymphony.xwork2.conversion.impl.XWorkConverter;
+import com.opensymphony.xwork2.conversion.ObjectTypeDeterminer;
+import com.opensymphony.xwork2.ObjectFactory;
+import com.opensymphony.xwork2.ognl.accessor.XWorkMapPropertyAccessor;
+import com.opensymphony.xwork2.inject.Inject;
+
+import java.util.Map;
+
+/**
+ * Same code as XWorkMapPropertyAccessor, but all exceptions are catched from
+ * result = super.getProperty(context, target, name);
+ * because we didn't parse the expression as an ONGL expression, calling getProperty might fail in some
+ * scenarios. This class is not intended to be used outside this package
+ */
+class XWorkParametersMapPropertyAccessor extends MapPropertyAccessor {
+
+    private static final Logger LOG = LoggerFactory.getLogger(XWorkMapPropertyAccessor.class);
+
+    private static final String[] INDEX_ACCESS_PROPS = new String[]
+            {"size", "isEmpty", "keys", "values"};
+
+    private XWorkConverter xworkConverter;
+    private ObjectFactory objectFactory;
+    private ObjectTypeDeterminer objectTypeDeterminer;
+
+    @Inject
+    public void setXWorkConverter(XWorkConverter conv) {
+        this.xworkConverter = conv;
+    }
+
+    @Inject
+    public void setObjectFactory(ObjectFactory fac) {
+        this.objectFactory = fac;
+    }
+
+    @Inject
+    public void setObjectTypeDeterminer(ObjectTypeDeterminer ot) {
+        this.objectTypeDeterminer = ot;
+    }
+
+    @Override
+    public Object getProperty(Map context, Object target, Object name) throws OgnlException {
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("Entering getProperty ("+context+","+target+","+name+")");
+        }
+
+        ReflectionContextState.updateCurrentPropertyPath(context, name);
+        // if this is one of the regular index access
+        // properties then just let the superclass deal with the
+        // get.
+        if (name instanceof String && contains(INDEX_ACCESS_PROPS, (String) name)) {
+            return super.getProperty(context, target, name);
+        }
+
+        Object result = null;
+
+        try{
+            result = super.getProperty(context, target, name);
+        } catch(Exception ex){
+        }
+
+        if (result == null) {
+            //find the key class and convert the name to that class
+            Class lastClass = (Class) context.get(XWorkConverter.LAST_BEAN_CLASS_ACCESSED);
+
+            String lastProperty = (String) context.get(XWorkConverter.LAST_BEAN_PROPERTY_ACCESSED);
+            if (lastClass == null || lastProperty == null) {
+                return super.getProperty(context, target, name);
+            }
+            Class keyClass = objectTypeDeterminer
+                    .getKeyClass(lastClass, lastProperty);
+
+            if (keyClass == null) {
+
+                keyClass = java.lang.String.class;
+            }
+            Object key = getKey(context, name);
+            Map map = (Map) target;
+            result = map.get(key);
+
+            if (result == null &&
+                    context.get(ReflectionContextState.CREATE_NULL_OBJECTS) != null
+                    &&  objectTypeDeterminer.shouldCreateIfNew(lastClass,lastProperty,target,null,false)) {
+                Class valueClass = objectTypeDeterminer.getElementClass(lastClass, lastProperty, key);
+
+                try {
+                    result = objectFactory.buildBean(valueClass, context);
+                    map.put(key, result);
+                } catch (Exception exc) {
+
+                }
+
+            }
+        }
+        return result;
+    }
+
+    /**
+     * @param array
+     * @param name
+     */
+    private boolean contains(String[] array, String name) {
+        for (String anArray : array) {
+            if (anArray.equals(name)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    @Override
+    public void setProperty(Map context, Object target, Object name, Object value) throws OgnlException {
+        if (LOG.isDebugEnabled()) {
+     		LOG.debug("Entering setProperty("+context+","+target+","+name+","+value+")");
+     	}
+
+        Object key = getKey(context, name);
+        Map map = (Map) target;
+        map.put(key, getValue(context, value));
+     }
+
+     private Object getValue(Map context, Object value) {
+         Class lastClass = (Class) context.get(XWorkConverter.LAST_BEAN_CLASS_ACCESSED);
+         String lastProperty = (String) context.get(XWorkConverter.LAST_BEAN_PROPERTY_ACCESSED);
+         if (lastClass == null || lastProperty == null) {
+             return value;
+         }
+         Class elementClass = objectTypeDeterminer.getElementClass(lastClass, lastProperty, null);
+         if (elementClass == null) {
+             return value; // nothing is specified, we assume it will be the value passed in.
+         }
+         return xworkConverter.convertValue(context, value, elementClass);
+}
+
+    private Object getKey(Map context, Object name) {
+        Class lastClass = (Class) context.get(XWorkConverter.LAST_BEAN_CLASS_ACCESSED);
+        String lastProperty = (String) context.get(XWorkConverter.LAST_BEAN_PROPERTY_ACCESSED);
+        if (lastClass == null || lastProperty == null) {
+            // return java.lang.String.class;
+            // commented out the above -- it makes absolutely no sense for when setting basic maps!
+            return name;
+        }
+        Class keyClass = objectTypeDeterminer.getKeyClass(lastClass, lastProperty);
+        if (keyClass == null) {
+            keyClass = java.lang.String.class;
+        }
+
+        return xworkConverter.convertValue(context, name, keyClass);
+
+    }
+}
+

File core/src/main/java/com/opensymphony/xwork2/parameters/parameters.jj

 import java.util.List;
 import java.util.ArrayList;
 import com.opensymphony.xwork2.parameters.nodes.*;
+import com.opensymphony.xwork2.parameters.ParserUtils;
+import org.apache.commons.lang.StringUtils;
 
-class XWorkParameterParser {
-    private XWorkParameterParserUtils parserUtils;
 
+class XWorkParameterParser {
     public XWorkParameterParser(String paramName) {
         this((new StringReader(paramName)));
     }
     | id=<IDENTIFIER> {
         return new IdentifierNode(id.image);
       }
+    | <LBRACKET>id=<STRING_LITERAL><RBRACKET> {
+        return new IdentifierNode(ParserUtils.getStringLiteral(id.image));
+      }
 }
 
 
 }
 {
       t=<STRING_LITERAL> {
-        return t.image.substring(1, t.image.length() - 1);
+        return ParserUtils.getStringLiteral(t.image);
       }
     | t=<INTEGER_LITERAL> {
         return Integer.valueOf(t.image);

File core/src/test/java/com/opensymphony/xwork2/SimpleAction.java

     private int[] intArray = new int[5];
     private Collection<SimpleAction> someCollection = new ArrayList<SimpleAction>();
 
+    private Map<String,TestBean> otherMap = new HashMap<String,TestBean>();
+    private List<TestBean> otherList = new ArrayList<TestBean>();
+
     public static boolean resultCalled;
     private SimpleAction nestedAction;
 
         existingMap.put("existingKey", "value");
     }
 
+    public List<TestBean> getOtherList() {
+        return otherList;
+    }
+
+    public void setOtherList(List<TestBean> otherList) {
+        this.otherList = otherList;
+    }
+
+    public void setOtherMap(Map<String, TestBean> otherMap) {
+        this.otherMap = otherMap;
+    }
+
+    public Map<String, TestBean> getOtherMap() {
+        return otherMap;
+    }
+
     public Collection<SimpleAction> getSomeCollection() {
         return someCollection;
     }

File core/src/test/java/com/opensymphony/xwork2/parameters/XWorkParametersBinderTest.java

         assertEquals("Lex Luthor", action.getName());
     }
 
+    public void testPropertyAsIndex() throws ParseException, OgnlException {
+        String expr = "['name']";
+        SimpleAction action = new SimpleAction();
+
+        assertNull(action.getName());
+
+        Map<String, Object> context = new HashMap<String, Object>();
+        binder.setProperty(context, action, expr, "Lex Luthor");
+
+        assertEquals("Lex Luthor", action.getName());
+    }
+
+    public void testPropertyAsIndexEmptyString() throws ParseException, OgnlException {
+        String expr = "['']";
+        SimpleAction action = new SimpleAction();
+
+        assertNull(action.getName());
+
+        Map<String, Object> context = new HashMap<String, Object>();
+        try {
+            binder.setProperty(context, action, expr, "Lex Luthor");
+            fail("should have failed");
+        } catch (Exception e) {
+            //ok
+        }
+    }
+
     public void testNested() throws ParseException, OgnlException {
         String expr = "bean.name";
         SimpleAction action = new SimpleAction();
         assertEquals("Lex Luthor", action.getSomeMap().get("Name"));
     }
 
+     public void testSimplePropertyOnObjectInMap() throws ParseException, OgnlException {
+        String expr = "otherMap['my_hero'].name";
+        SimpleAction action = new SimpleAction();
+
+        assertNull(action.getOtherMap().get("my_hero"));
+
+        Map<String, Object> context = new HashMap<String, Object>();
+        binder.setProperty(context, action, expr, "Lex Luthor");
+
+        assertEquals("Lex Luthor", action.getOtherMap().get("my_hero").getName());
+    }
+
+    public void testSimplePropertyOnObjectInMapNull() throws ParseException, OgnlException {
+        String expr = "otherMap['my_hero'].name";
+        SimpleAction action = new SimpleAction();
+        action.setOtherMap(null);
+
+        assertNull(action.getOtherMap());
+
+        Map<String, Object> context = new HashMap<String, Object>();
+        binder.setProperty(context, action, expr, "Lex Luthor");
+
+        assertEquals("Lex Luthor", action.getOtherMap().get("my_hero").getName());
+    }
+
+    public void testSimplePropertyOnObjectInList() throws ParseException, OgnlException {
+        String expr = "otherList[0].name";
+        SimpleAction action = new SimpleAction();
+
+        assertEquals(0, action.getOtherList().size());
+
+        Map<String, Object> context = new HashMap<String, Object>();
+        binder.setProperty(context, action, expr, "Lex Luthor");
+
+        assertEquals("Lex Luthor", action.getOtherList().get(0).getName());
+    }
+
+    public void testSimplePropertyOnObjectInListNull() throws ParseException, OgnlException {
+        String expr = "otherList[0].name";
+        SimpleAction action = new SimpleAction();
+        action.setOtherList(null);
+
+        assertNull(action.getOtherList());
+
+        Map<String, Object> context = new HashMap<String, Object>();
+        binder.setProperty(context, action, expr, "Lex Luthor");
+
+        assertEquals("Lex Luthor", action.getOtherList().get(0).getName());
+    }
+
     //Lists
     public void testSimpleList() throws ParseException, OgnlException {
         String expr = "someList[0]";