Custom inline expression parser in dialect

classic Classic list List threaded Threaded
4 messages Options
Reply | Threaded
Open this post in threaded view
|

Custom inline expression parser in dialect

xtian
In my custom dialect I want to process inline expressions in order to change urls by adding a prefix.

For example

[[@{/res/some.json}]]

should create an url as if the following were typed:

[[@{/myprefix/res/some.json}]]

For the time being I only need it in a javascript section, so I think I have to provide a custom th:inline="javascript" that intercepts the expression somehow.

I know how to create an extension of AbstractStandardTextInlinerAttrProcessor but I don't know how to intercept the expression parsing. I see that somewhere a IStandardExpressionParser is fetched by calling StandardExpressions.getExpressionParser() so I might only need to insert my parser in there, but I don't know how. Or maybe that's the wrong way to do it.

Any help would be appreciated.

Thank you
Reply | Threaded
Open this post in threaded view
|

Re: Custom inline expression parser in dialect

danielfernandez
Administrator
I'm afraid your problem is quite complex.

First, the easiest workaround: create an utility object that does whatever you want to do with your URLs, then make it available to your expressions by either 1. Adding it always to your model, or 2. Creating a custom dialect that adds it as expression object by implementing the IExpressionEnhancingDialect interface.

Then you can use it like this if you add it to your model:

[[@{myurlprocessor.process('/res/some.json')}]]

Or like this if you add it as a Dialect expression object:

[[@{#myurlprocessor.process('/res/some.json')}]]


Next option is to implement your own inliner, with its own expression syntax (i.e. NOT using the Thymeleaf Standard Expressions). You'd need to create an attribute processor similar to "th:include", but which would set into the execution environment your own Inliner implementation (perhaps extending AbstractStandardTextInlinerAttrProcessor). This inliner would be in charge of understanding whatever you inline in your Text nodes, be it between [[...]] or in whichever other form you decide. For example:

<div th:mycustominline="text">
   The URL is [[/res/some.json]]
</div>

(Note I didn't write a "@{...}" above because you would be performing your own expression parsing and executing, so I'm considering the possibility you don't want to use the @{...} expression wrapper)


Then... why all this and not modify the behaviour of the @{...} expressions? because they are part of the Thymeleaf Standard Expression language (which also includes #{...} expressions, ${...} expressions which are delegated to OGNL/SpringEL, etc.), and this expression engine does not allow link behaviour extension. So there is no real way for you to modify how a @{...} expression works from a Dialect (and if you could, it would be quite messy because you'd be changing it for absolutely every @{...} in your template, in whichever context).

And why do I say "from a Dialect"? Because the usual way to fine-tune how URLs are produced is by means of setting URL Rewriting filters like http://tuckey.org/urlrewrite/ . These filters are configured at the web application level (not thymeleaf level), and they wrap the HttpServletResponse objects used during execution, implementing their response.encodeURL(...) methods, which are called by thymeleaf for every produced URL. This is by far the most elegant solution.

BUT: of course a rewrite filter, being an application-scope configuration artifact, is not as modular as a Dialect, which seems to be what you are looking for.

Regards,
Daniel.
Reply | Threaded
Open this post in threaded view
|

Re: Custom inline expression parser in dialect

xtian
Thank you for your comprehensive answer.

My idea was to use a dialect because it seemed the most elegant approach: no need to configure anything on the web server and no need to change the normal syntax, just use my:inline="javascript" and all would work.

As for the implementation, I really just need to "intercept" the parsing of the url, so I was looking for a way to reuse the standard implementation of the th:inline attribute and add my bit somewhere.

In any case, maybe it's better for me to explain what I'm trying to do, so that you could help me understand the best approach.

In a few words, I'm trying to implement the paragraph "Configuring Serving of Resources" of the Spring manual: http://docs.spring.io/spring/docs/4.0.4.RELEASE/spring-framework-reference/htmlsingle/#mvc-config-static-resources (second half, from "When serving resources that may change when a new version of the application is deployed...").
In that paragraph they show how resources could be cached by setting a very long "cache period", but in order to make new versions available, they use the application version as a prefix in the url:

<spring:eval expression="@applicationProps[application.version]" var="applicationVersion"/>

<spring:url value="/resources-{applicationVersion}" var="resourceUrl">
    <spring:param name="applicationVersion" value="${applicationVersion}"/>
</spring:url>

<script src="${resourceUrl}/dojo/dojo.js" type="text/javascript"> </script>
I was hoping to reduce the above JSP code in Thymeleaf by implementing a dialect, and I actually managed to do so:

<script myDialect:src="/res/myscript.js">

but I got stuck with the th:inline implementation.

As this is something suggested in the Spring documentation, I hope that someone will come up with an elegant solution that could be of help to many.

Thank you

Xtian



 

Reply | Threaded
Open this post in threaded view
|

Re: Custom inline expression parser in dialect

Zemi
Administrator
In my opinion, given the choices we have, the first option that Daniel suggested is the best for your case.

You could use
  #Resources.versionedURL('/dojo/dojo.js')
both in th:src and th:inline, so there will be no need for your myDialect:src processor.

The use is coherent: every time is want to access a versioned resource, you use #Resources.versionedURL, no matter where you are.

Regards,
  Zemi