Calling methods with arguments with mvel

1,825 views
Skip to first unread message

Jon Stevens

unread,
Sep 12, 2011, 4:41:18 PM9/12/11
to cambridge...@googlegroups.com
So, I've switched to mvel, primarily because I'd like to be able to call methods with arguments.

I've got a bean that looks like this:

public class Model {
    List<Long> bars;

    public boolean containsBar(Long id) {
         return bars.contains(id);
    }
}

And a template like this:

<div a:if="model.containsBar(1)"></div>

It seems like Cambridge is trying to execute things with PropertyUtils instead of just passing the data to mvel to process. The exception I get looks like this:

Caused by: java.lang.IllegalArgumentException: wrong number of arguments
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at com.google.appengine.tools.development.agent.runtime.Runtime.invoke(Runtime.java:100)
at cambridge.runtime.PropertyUtils.getBeanProperty(PropertyUtils.java:83)
at cambridge.parser.expressions.VarExpression.eval(VarExpression.java:70)
at cambridge.parser.expressions.VarExpression.asBoolean(VarExpression.java:90)
at cambridge.behaviors.ConditionalTagBehavior.conditionMet(ConditionalTagBehavior.java:36)
at cambridge.model.TagNode.conditionsMet(TagNode.java:823)
at cambridge.model.TagNode.eval(TagNode.java:632)
at cambridge.DynamicTemplate.printTo(DynamicTemplate.java:53)

Also, in code, how can I set mvel as the default template language? Right now, I have it set with <!--$expressionLanguage mvel --> at the top of my _body.html base template, but I'd rather just set it in java code.

You might want to add to the documentation that you need to register mvel in code like this: 
Expressions.registerExpressionLanguage("mvel", MvelExpressionLanguage.class);

jon

Jon Stevens

unread,
Sep 12, 2011, 5:05:29 PM9/12/11
to cambridge...@googlegroups.com
Ok, nevermind. I found the problem. Basically, mvel wasn't being executed because I had only defined it at the top of my _body.html page. It still needed to be 'turned on' on every template page. Template inheritance doesn't count. ;-)

I also found the MvelExpressionLanguage.register() method.

Now, where is the MvelExpressionLanguage.ALWAYSON()? ;-)

jon

Jon Stevens

unread,
Sep 12, 2011, 5:07:19 PM9/12/11
to cambridge...@googlegroups.com
I just noticed that in Expressions, you are doing new CambridgeExpressionLanguage() 2x by accident.

jon

Jon Stevens

unread,
Sep 12, 2011, 5:23:41 PM9/12/11
to cambridge...@googlegroups.com
Sorry for all of the postings, but it appears as though it is impossible to change the default template language used across the board.

I've tried:

MvelExpressionLanguage.register();
Expressions.registerExpressionLanguage("cambridge", MvelExpressionLanguage.class);

From what I can tell, the call to Expressions.getDefaultExpressionLanguage() in TemplateParser is what the problem is and since there is no Expressions.setDefaultExpressionLanguage(), I'm kind of out of luck without modifying the Cambridge source code.

jon

Erdinc Yilmazel

unread,
Sep 12, 2011, 5:40:37 PM9/12/11
to cambridge...@googlegroups.com
Hi Jon,

I will look into this and your original issue about the map literals sometime tonight and get back to you. You are using your own build right? So a fix in trunk probably be good for you?

Thanks

Jeff Schnitzer

unread,
Sep 12, 2011, 5:59:07 PM9/12/11
to cambridge...@googlegroups.com
Yes, a fix in trunk is fine for us. The main issue for us at this
point is just a good way to set the default EL to mvel.

Thanks,
Jeff

Jon Stevens

unread,
Sep 12, 2011, 6:05:24 PM9/12/11
to cambridge...@googlegroups.com
Another issue we just ran into is that we were using your Cambridge.registerFunction() method, but now with mvel, we have to do that on our own by creating a 'tool' class with static methods and putting that into the template context. Not a huge deal, but it'd be nice if the registerFunction() method just worked with the mvel integration.

jon

On Mon, Sep 12, 2011 at 2:40 PM, Erdinc Yilmazel <erd...@yilmazel.com> wrote:

Erdinc Yilmazel

unread,
Sep 12, 2011, 7:24:49 PM9/12/11
to cambridge...@googlegroups.com
Point me to some information on how to create a custom function for mvel and I will look into it.

Jon Stevens

unread,
Sep 12, 2011, 7:28:08 PM9/12/11
to cambridge...@googlegroups.com
It is actually fairly easy...


What we are doing right now is we have a static class that looks like this:

/**
 * This is where we declare our 'functions' which are made available within our templates.
 * For example you'd write ${tool.jsonify(model.foo)}
 */
public class TemplateTool {
/** default instance of the jackson ObjectMapper */
private static final ObjectMapper mapper = new ObjectMapper();

/** */
public static String jsonify(Object obj) throws JsonGenerationException, JsonMappingException, IOException {
return mapper.writeValueAsString(obj);
}
}

Then, we just Template.setProperty("tool", TemplateTool.class);

This actually doesn't instantiate the object, which is great. It just allows mvel to call the static methods on the class.

cheers,

jon

Erdinc Yilmazel

unread,
Sep 12, 2011, 10:30:49 PM9/12/11
to cambridge...@googlegroups.com
Ok this is now in trunk. You can call Expressions.setDefaultExpressionLanguage("mvel", new MvelExperssionLanguage());
When you do this it will both register as an expression language and set it as default. You don't need to call MvelExpressionLanguage.register() anymore after making the call above, but it will not hurt as well.

On Mon, Sep 12, 2011 at 5:23 PM, Jon Stevens <latc...@gmail.com> wrote:

Erdinc Yilmazel

unread,
Sep 12, 2011, 11:59:03 PM9/12/11
to cambridge...@googlegroups.com
This is also in trunk right now. I don't want to extend/modify Cambridge.registerFunction() method to do anything that is not related to Cambridge EL so I added support for this directly on MvelExpressionLanguage.java.

MvelExpressionLanguage now has a getParserConfiguration method which returns a ParserConfiguration object that you can use to import classes, static methods etc. Any change you make on the ParserConfiguration will be global and will have affect to all expressions that you use.

Here is an example:

            MvelExpressionLanguage mvel = new MvelExpressionLanguage();

            try {
                mvel.getParserConfiguration().addImport("format", String.class.getMethod("format", String.class, Object[].class));
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }

            Expressions.setDefaultExpressionLanguage("mvel", mvel);

Then you can use the following template:

${format("Hello %s", ["erdinc"])}

I also added static method support for Cambridge EL as well. You need to create a StaticMethodCaller and call Cambridge.registerFunction with that caller.

Here is an example:
Cambridge.registerFunction("format", new StaticMethodCaller(String.class.getMethod("format", String.class, Object[].class)));

There is a difference with the mvel implementation though. If the static method is a var-args method and if the last argument that you use does not evaluate to an array, then it wraps that argument within an array itself. So the template becomes:

${format("Hello %s", "erdinc")} -> Note that there are no brackets around "erdinc" this time.

Hope this helps.

Jon Stevens

unread,
Sep 13, 2011, 1:42:40 AM9/13/11
to cambridge...@googlegroups.com
Sweet. Thanks for the fast response.

I know this is an old design so it is probably pretty hard to change, but using static's for this is pretty evil. It'd be much better to configure an instance or at least provide a configuration api.

Also, I'm pretty happy with mvel at this point. If this were my project, I'd consider switching to that as the default EL for Cambridge because it is far more expressive. Don't get me wrong. I'm not knocking your EL. It is quite good, but it really isn't enough for modern development. Plus, it loses on that argument of 'learning' a new EL. Better to just go with the flow with mvel at this point. It is still being developed too, which is nice.

OGNL seems dead.

cheers,

jon

Jeff Schnitzer

unread,
Sep 13, 2011, 7:55:07 PM9/13/11
to cambridge...@googlegroups.com
Out of curiosity, is there a way to make method calls and pass
arguments in Cambidge EL?

ie, we want to call thing.method(otherThing) and have both thing and
otherThing resolved as expressions.

Jeff

Jon Stevens

unread,
Sep 13, 2011, 8:02:40 PM9/13/11
to cambridge...@googlegroups.com
Cool. I've got this working now.

One thing that also seems 'off' is the line numbers for mvel errors.

Caused by: [Error: unresolvable property or identifier: id]
[Near : {... ! model.checkMember(auth.me.id) ....}]
                                 ^
[Line: 1, Column: 21]
at org.mvel2.optimizers.impl.refl.ReflectiveAccessorOptimizer.getBeanProperty(ReflectiveAccessorOptimizer.java:674)
at org.mvel2.optimizers.impl.refl.ReflectiveAccessorOptimizer.compileGetChain(ReflectiveAccessorOptimizer.java:335)


This isn't on line 1. ;-)

jon

Erdinc Yilmazel

unread,
Sep 13, 2011, 9:39:38 PM9/13/11
to cambridge...@googlegroups.com
Unfortunately that isn't me reporting that line number, that is mvel. It is treating every expression differently. That mvel exception should be wrapped in another Cambridge expression, doesn't it report the right line number?

Erdinc Yilmazel

unread,
Sep 13, 2011, 9:43:51 PM9/13/11
to cambridge...@googlegroups.com
No it isn't supported yet. You can only call methods which don't accept any arguments. I guess I could make it work if the method you are calling is accepting primitive types but the purpose of Cambridge EL isn't trying to be a full featured scripting language. It is designed for read-only access to java objects. So for any advanced features that you need, use mvel.

Erdinc Yilmazel

unread,
Sep 13, 2011, 9:45:00 PM9/13/11
to cambridge...@googlegroups.com
Jon and Jeff, are you guys working together? Which project are you guys using Cambridge for?

Erdinc Yilmazel

unread,
Sep 13, 2011, 9:58:37 PM9/13/11
to cambridge...@googlegroups.com
Well I felt the same way at some point and I was thinking of making it the default EL, however after doing some benchmarks, I realized that the Cambridge EL is outperforming the other two supported expression languages while doing some basic things and it is why I decided to keep it. However I should definitely extend the support for MVEL. 

I agree on your thoughts about the static methods as well. However it is really hard to implement a lot of the things without those static methods and objects especially if you don't have a kind of dependency injection mechanism available to you. While parsing the template and creating the DOM like tree, a lot of objects need to access global data such as registered namespaces, expression languages, functions, filters etc, and having them available in an instance means passing that instance around all these different objects. I'm imagining that 99% of the time developers will not need to have more than one instance of Cambridge which are configured differently. 

What kind of an API do you think would be nice to have if there were configuration objects?

Thanks

Jeff Schnitzer

unread,
Sep 13, 2011, 10:18:53 PM9/13/11
to cambridge...@googlegroups.com
Yep. We can't really announce it yet - closer to the end of the year.
Jon has spent a little time talking about our technology stack here:
http://lookfirst.com/

Jeff

Reply all
Reply to author
Forward
0 new messages