Gowtham Kesa
Gowtham Kesa

Reputation: 99

Jexl expression string with stream, map and collect is throwing org.apache.commons.jexl3.JexlException$Parsing Exception

I have a method evaluateExpression present in a different library and that library is using Jexl3 for evaluating expressionString passed to evaluateExpression method.
inputMap contains the object that is used for evaluating the expressionString on. This is the second parameter to that function.

expressionString is record.getDriversMap().values().stream().filter(myDriver -> 'Fav_Driver'.equals(vtd.getAttributeName())).collect(Collectors.toList())

record is a class which contains driversMap whose values are of type MyDriver class and it has an attributeName field. I have added a filter condition and tried to collect the list. Doing this I was getting an exception org.apache.commons.jexl3.JexlException$Parsing with message ( which is unclear. Does anyone know how to resolve this issue.

It would be great if someone provides examples of jexl expression with stream, map and collect ? Below is the method present in the library.

Object evaluateExpression(String expressionString, Map<String, Object> inputMap) {
        MapContext mapContext = new MapContext(inputMap);
        JexlExpression jexlExpression = this.jexlEngine.createExpression(expressionString);
        return jexlExpression.evaluate(mapContext);
    }

Upvotes: 0

Views: 1313

Answers (1)

henrib
henrib

Reputation: 364

JEXL offers many ways to achieve a tight integration but it requires some code to do so.

Not sure if JEXLs integration in your library allows this but JEXL misses some information to achieve what you seek. For JEXL to filter streams, it needs the definition of how to to do that.

The example below (running on JEXL 3.3 SNAPSHOT) does illustrate one way of doing so by extending the JexlContext (could also be done by extending the JexlArithmetic).

/**
 * Mock driver.
 */
public static class Driver0930 {
    private String name;
    Driver0930(String n) {
        name = n;
    }
    public String getAttributeName() {
        return name;
    }
}

public static class Context0930 extends MapContext {
    /**
     * This allows using a JEXL lambda as a filter.
     * @param stream the stream
     * @param filter the lambda to use as filter
     * @return the filtered stream
     */
    public Stream<?> filter(Stream<?> stream, JexlScript filter) {
        return stream.filter(x -> Boolean.TRUE.equals(filter.execute(this, x)));
    }
}

@Test
public void testStackOvflw20220930() {
    // fill some drivers in a list
    List<Driver0930> values = new ArrayList<>();
    for(int i = 0; i < 8; ++i) {
        values.add(new Driver0930("drvr" + Integer.toOctalString(i)));
    }
    for(int i = 0; i < 4; ++i) {
        values.add(new Driver0930("favorite" + Integer.toOctalString(i)));
    }
    // Use a context that can filter and that exposes Collectors
    JexlEngine jexl = new JexlBuilder().safe(false).create();
    JexlContext context = new Context0930();
    context.set("values", values);
    context.set("Collectors", Collectors.class);
    // The script with a JEXL 3.2 (lambda function) and 3.3 syntax (lambda expression)
    String src32 = "values.stream().filter((driver) ->{ driver.attributeName =^ 'favorite' }).collect(Collectors.toList())";
    String src33 = "values.stream().filter(driver -> driver.attributeName =^ 'favorite').collect(Collectors.toList())";
    for(String src : Arrays.asList(src32, src33)) {
        JexlExpression s = jexl.createExpression(src);
        Assert.assertNotNull(s);

        Object r = s.evaluate(context);
        Assert.assertNotNull(r);
        // got a filtered list of 4 drivers whose attribute name starts with 'favorite'
        List<Driver0930> l = (List<Driver0930>) r;
        Assert.assertEquals(4, l.size());
        for (Driver0930 d : l) {
            Assert.assertTrue(d.getAttributeName().startsWith("favorite"));
        }
    }
}

Upvotes: 1

Related Questions