Quarkus: Support complex expressions in Qute

Created on 1 Jan 2020  路  20Comments  路  Source: quarkusio/quarkus

I'd like to use boolean operators like && and ||. Currently, when using an expression such as {update && todo.completed}, I'm getting a stacktrace like this (both update and todo are variables in the template context):

java.lang.IllegalArgumentException: Not a virtual method: &&(todo
    at io.quarkus.qute.Expressions.parseVirtualMethodParams(Expressions.java:32)
    at io.quarkus.qute.EvaluatorImpl$EvalContextImpl.<init>(EvaluatorImpl.java:118)
    at io.quarkus.qute.EvaluatorImpl.resolveReference(EvaluatorImpl.java:69)
    at io.quarkus.qute.EvaluatorImpl.lambda$resolveReference$1(EvaluatorImpl.java:72)
    at java.util.concurrent.CompletableFuture.uniComposeStage(CompletableFuture.java:981)
    at java.util.concurrent.CompletableFuture.thenCompose(CompletableFuture.java:2124)
    at java.util.concurrent.CompletableFuture.thenCompose(CompletableFuture.java:110)
    at io.quarkus.qute.EvaluatorImpl.resolveReference(EvaluatorImpl.java:70)
    at io.quarkus.qute.EvaluatorImpl.evaluate(EvaluatorImpl.java:48)
    at io.quarkus.qute.ResolutionContextImpl.evaluate(ResolutionContextImpl.java:31)
    at io.quarkus.qute.ExpressionNode.resolve(ExpressionNode.java:25)
    at io.quarkus.qute.SectionNode$SectionResolutionContextImpl.execute(SectionNode.java:112)
    at io.quarkus.qute.SectionHelper$SectionResolutionContext.execute(SectionHelper.java:31)
    at io.quarkus.qute.Parser$1$1.resolve(Parser.java:67)
    at io.quarkus.qute.SectionNode.resolve(SectionNode.java:33)
    at io.quarkus.qute.TemplateImpl.renderData(TemplateImpl.java:92)
    at io.quarkus.qute.TemplateImpl.access$200(TemplateImpl.java:14)
    at io.quarkus.qute.TemplateImpl$TemplateInstanceImpl.renderAsync(TemplateImpl.java:73)
    at io.quarkus.resteasy.qute.runtime.TemplateResponseFilter.filter(TemplateResponseFilter.java:58)
    at org.jboss.resteasy.core.interception.jaxrs.ContainerResponseContextImpl.filter(ContainerResponseContextImpl.java:361)
    at org.jboss.resteasy.core.ServerResponseWriter.executeFilters(ServerResponseWriter.java:232)
    at org.jboss.resteasy.core.ServerResponseWriter.writeNomapResponse(ServerResponseWriter.java:97)
    at org.jboss.resteasy.core.ServerResponseWriter.writeNomapResponse(ServerResponseWriter.java:70)
    at org.jboss.resteasy.core.SynchronousDispatcher.writeResponse(SynchronousDispatcher.java:578)
    at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:508)
    at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:252)
    at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:153)
    at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:363)
    at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:156)
    at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:238)
    at io.quarkus.resteasy.runtime.standalone.RequestDispatcher.service(RequestDispatcher.java:73)
    at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.dispatch(VertxRequestHandler.java:120)
    at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.access$000(VertxRequestHandler.java:36)
    at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler$1.run(VertxRequestHandler.java:85)
    at io.quarkus.runtime.CleanableExecutor$CleaningRunnable.run(CleanableExecutor.java:224)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
    at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:2011)
    at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1535)
    at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1426)
    at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
    at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
    at java.lang.Thread.run(Thread.java:748)
    at org.jboss.threads.JBossThread.run(JBossThread.java:479)

Interestingly, behavior is different when using an if:

{#if update && todo.completed}
...
{/if}

In this case no exception is raised but it seems the second operand is silently ignored.

arequte kinenhancement

Most helpful comment

Qute is not an EL, it's a template language which also supports expressions. The choice of expression language should be a technical decision, not a political one: it seems possible that Jakarta EL is not going to be the right fit.

All 20 comments

Neither of these is supported ATM (and it's documented ;-). I personally tend to avoid complex expressions in templates (as it usually leads to more error prone code) and follow the idea of templates with minimal logic. However, the {#if update && todo.completed} use case makes sense and we'll need to support it sooner or later.

FWIW, we also need some expression language support in the Spring Security extension. /cc @geoand

It might be worth it to use the same implementation.

For the record, Hibernate Validator uses Jakarta EL.

Yeah I had to do a pretty lame implementation of a small subset of SpEl for the security stuff.
It would be nice if we had some kind of actual EL support we could use everywhere.

So I don't want to repeat myself but I really don't think it's a good idea to build template expressions on top of a complex EL by default, of course it could be optional... ;-)

@mkouba I don't think we want to push you to do what you don't feel right, just wanted to say that there is a common need for this sort of things.

Complexity surely is in the eye of the beholder, but boolean expressions with && or || are fairly common in template logic to conditionally display contents. I also was missing method calls, in particular equals(), as otherwise string comparisons are impossible.

Yeah, my intuition is that using an existing EL implementation will be easier than implementing our own EL implementation/parser rules but I'll let @mkouba and @geoand decide.

I agree that keeping the templates simple is a good thing but I also foresee we will at least need some advanced features.

I'm not sure an EL makes sense when you've already got a template language: then you have two languages, possibly with overlap, certainly with combinatorial effects... Wouldn't it be better to define one language with a complete grammar and predictable behaviors?

Well, if included in the template engine, at least let's make the syntax consistent with what we will use for Spring Security where we definitely need an EL.

From my PoV, if we define the minimum common set of expressions we could reuse everywhere, it would be great.
I don't think anyone here wants to have multiple mini ELs all over the place

It would be a good idea to provide Jakarta EL as the primary choice EL language in Quarkus. I understand that Qute is optimized for build time but why not provide a quarkus extension on top of Jakarta EL which is also optimized for build time? With this, there is no need for Qute EL and can be removed from Quarkus codebase.

Qute is not an EL, it's a template language which also supports expressions. The choice of expression language should be a technical decision, not a political one: it seems possible that Jakarta EL is not going to be the right fit.

Ok, so Qute is something like Velocity or Freemarker but optimized for quarkus runtime. The Jakarta EL is primarly used in JSF but can also be used standalone.

I dont know why Jakarta EL is not the right fit for Qute but there will be definitly a need for more advanced expressions in the future because we see it in the use cases of velocity or freemarker or jsf. There is a possibility that Jakarta EL will be used in future for other things in quarkus (for example, if someone provides a jsf-extension).

Would something like quarkus-el-extension (backed by jakarta el) used by quarkus-qute-extension (where the _qute template engine_ uses the decoupled EL as expression language) make it possible to be a better fit? I mean it is the same what JSF/Facelets, Hibernate, etc does: uses a decoupled expression language (Jakarta EL) for its templating

I also was missing method calls, in particular equals(),

"Method calls" are supported but not available out of the box. You can use @TemplateData or Template Extension Methods.

as otherwise string comparisons are impossible

{#if foo.name is 'bar'}, {#if foo.name eq bar.name} and {#if foo.name == 'bar'} - all these should work.

@gunnarmorling FYI ;-)

{#if foo.name is 'bar'}, {#if foo.name eq bar.name} and {#if foo.name == 'bar'} - all these should work.

That's very similar to what I have in @PreAuthorize for Spring Security :)

"Method calls" are supported but not available out of the box

Didn't we want to auto-register methods for classes we know are used in the template thanks to typing info?

I definitely vote for supporting boolean, math and comparison expressions.

IMO a strict subset of Unified EL would be good, if we can or don't want to support the full thing. This would reduce friction when coming from or combining Qute and Unified EL in a project, e.g. when using HV.

Didn't we want to auto-register methods for classes we know are used in the template thanks to typing info?

That's the plan. But so far we only support "properties" because it's much simpler to implement.

A related issue was documented in a comment here:

The bean I use in my template contains a java.util.Map and I need to test, if a certain value is contained in this map.

FYI we already support a bit more complex expressions in 1.2.0, for example:

  • {#if !item.active || (item.sold && item.price == 100)}
  • {#if item.age > 10 && item.price > 500}
  • {#if (item.age > 10 || item.price > 500) && user.loggedIn}
Was this page helpful?
0 / 5 - 0 ratings

Related issues

GregJohnStewart picture GregJohnStewart  路  51Comments

galderz picture galderz  路  80Comments

sberyozkin picture sberyozkin  路  51Comments

cescoffier picture cescoffier  路  682Comments

cescoffier picture cescoffier  路  43Comments