Manuel S.
Manuel S.

Reputation: 421

Get expression values in JEXL

Having the following JEXL expression:

String expression = "myVar >= 12345 && mySecondVar <= 56789";

I can call createScript and getVariables to get myVar and mySecondVar as values, such as:

Set<List<String>> expressionVars = JEXL.createScript(expression).getVariables();

What I would like to know is, if given the same expression, I could call some other method that would return the values for those variables. Reason being is that I would like to validate the input of some of those values. I checked the docs and played around with JexlScript class but can't find an elegant way of doing it. As JEXL is already doing the work of parsing my expression, it would be awesome to be able to retrieve this info, and not having to manually parse my expression to get this values.

Something in the line of script.getValue("myVar"); returning 12345

Upvotes: 0

Views: 8457

Answers (2)

henrib
henrib

Reputation: 364

With JEXL, you evaluate a script/expression in a given context (JexlContext) that holds the variables and their values. JexlContext exposes the 'has' and 'get' methods that respectively check for the existence and get the value of variables. In your case, you need to find out what your JexlContext is (or should be); from there, it is straightforward to iterate on your variables (extracted from your script) and check their values (from the context).

See: http://commons.apache.org/proper/commons-jexl/apidocs/org/apache/commons/jexl3/JexlContext.html http://commons.apache.org/proper/commons-jexl/apidocs/org/apache/commons/jexl3/JexlScript.html

For example (using JEXL 3.2 trunk from https://github.com/apache/commons-jexl):

/**
 * Collect the global variables of a script and their values from a context.
 * @param script the script
 * @param context the context
 * @return a map keyed by variable name of their contextual values
 */
Map<String, Object> collectVars(JexlScript script, JexlContext context) {
    Set<List<String>> sls = script.getVariables();
    Map<String, Object> vars = new TreeMap<String, Object>();
    for(List<String> ls : sls) {
        // build the 'antish' name by concatenating
        StringBuilder strb = new StringBuilder();
        for(String s : ls) {
            if (strb.length() > 0) {
                strb.append('.');
            }
            strb.append(s);
        }
        String name = strb.toString();
        vars.put(name, context.get(name));
    }
    return vars;
}

@Test
public void testStckOvrflw() throws Exception {
    JexlEngine jexl = new JexlBuilder().safe(false).create();
    // a context with some variables
    JexlContext context = new MapContext();
    context.set("low", 15000);
    context.set("high", 50000);
    context.set("mid", 35000);
    context.set("limit.low", 15042);
    context.set("limit.high", 35042);
    // an expression with 2 variables
    JexlScript expr = jexl.createScript("low >= 12345 && high <= 56789");
    // collecting the 2 variables, low and high
    Map<String, Object> vars = collectVars(expr, context);
    Assert.assertEquals(2, vars.size());
    Assert.assertEquals(15000, vars.get("low"));
    Assert.assertEquals(50000, vars.get("high"));

    expr = jexl.createScript("limit.low >= 12345 && limit.high <= 56789");
    vars = collectVars(expr, context);
    Assert.assertEquals(2, vars.size());
    Assert.assertEquals(15042, vars.get("limit.low"));
    Assert.assertEquals(35042, vars.get("limit.high"));
}

Upvotes: 2

Blessed Geek
Blessed Geek

Reputation: 21674

You should implement your own context:

public class ZenContext implements JexlContext {
  static private final Map<String, Object> reservedVars = new HashMap<String, Object>();
  private final Map<String, Object> scriptDefinedVars  = new HashMap<String, Object>();
  static {
    reservedVars.put("math", java.lang.Math.class);
    reservedVars.put("stats", apache.commons.math3.stats.Stats);
    // whatever else ...
  }

  public boolean has(String name) {
    if (reservedVars .get(name) != null) return true;
    return scriptDefinedVars.get(name) != null;
  }

  public boolean get (String name) {
    Object value = null;
    if ((value = reservedVars .get(name)) != null) return value;

    return scriptDefinedVars.get(name);
  }

  public void set(String name, Object value) {
    scriptDefinedVars.set(name, value);
  }

  public Map<String, Object> getReservedVars () {
    return reservedVars;
  }
  public Map<String, Object> getScriptDefinedVars   () {
    return scriptDefinedVars ;
  }
}

In this way, you would have

  • a map of reserved var names to contain Objects you do not allow scripts to change their values. e.g.,
  • a separate map of vars that could be set from script.

And then add these methods.

public Object execute(File scriptFile) {
    JexlScript script = jexlEngine.createScript(scriptFile);
    return script.execute(this); // supply this as the context 
}
public Object execute (String scriptText) {
    JexlScript script = jexlEngine.createScript(scriptText);
    return script.execute(this); // supply this as the context 
}

You could modify the set method I wrote here, to inspect the variables in the maps, before allowing them to set.

However, this will not work for local script vars

var greeting = 'The rain in Maine falls plainly insane';

Because local var relies on a different mechanism, org.apache.commons.jexl3.internal.Scope, using the getLocalVariables() method, which has a bug.

Upvotes: 1

Related Questions