For the purpose of illustration, I’m hard coding the tag identifier, although in a real case, you’d likely read this property from the current resource’s properties.
A different approach would be to further encapsulate this into an OSGi service and then invoke that service from your Use object with something like:
This is a lot better, but what I want to illustrate is a different technique which removes all references to Java classes from our code by injecting a custom object into the scope of the Use object. Specifically we’re going to inject a function into the scope so that this code instead becomes:
For this, we’re going to use a relatively under-used, but very cool1 Sling feature – the
BindingsValuesProvider. When a script is invoked by the Sling scripting engine as part of request processing2 an object is created to store the list of global variables – the request, the current resource, and so on. This object is called the script bindings and is an instance of
javax.script.Bindings, a class defined in the Java Scripting API. The Sling scripting engine itself only adds a handful of global variables (listed here); applications built on top of Sling, including AEM, are able to add additional global variables. This is how, for example, the
currentPage object is made available to scripts.
There are two ways to use this feature – an easy way and a less easy way. The easy way is that you can register an OSGi service using the
java.util.Map interface and having a service property named
javax.script.name. When Sling executes a script, it gets all of these services and adds their contents to the
Bindings object which will be passed to the script engine. This way is useful for when are adding an object which doesn’t require access to any of the existing bindings. For example, if we wanted to make the Java runtime version available as a global variable named
javaVersion we could do something like this:
anyfor the property
javax.script.nameindicates that this additional variable should be applied to any scripting language. If you only wanted to scope this to certain languages, the value would be the script engine name as we’ll see below.
Now in a Sightly template, we can simply write:
And see the value of that system property.
The less easy way is when we need access to objects from the current bindings. For this, you implement the interface
org.apache.sling.scripting.api.BindingsValuesProvider. This interface has a single method,
addBindings which is passed the current
Bindings object. This allows you to get access to the current request, response, resource, and so on. Sling calls these services in the order of their OSGi service ranking, so you can ensure access to variables created by other
BindingsValuesProviders as well.
A warning about performance - these BindingsValuesProviders get invoked on every script call, so you need to be very careful implementing them to ensure that they are performant. Use some kind of lazy loading or deferred invocation pattern wherever possible.
call (JavaDoc here) which gets invoked when the function is… called. This method gets invoked with four parameters:
context- this is the Rhino scripting context associated with the current thread. It has some utility methods for doing type conversions and working with the current thread.
thisObj- the value of
thiswhen the function is executed. In Sightly Use objects,
thisObjwill always be identical.
args- an argument array.
If your function’s arguments are booleans, numbers or strings, they will be passed as part of the
args array as their corresponding Java type (the wrapper type for primitives,
args array will contain instances of these classes:
unwrapmethod can be called to access the original Java object.
Putting this all together, we can write a function to get the tagged assets:
This code gets the tag identifier from the
args array and then interacts with the
TagManager and assets API to obtain the proper list of assets. It then wraps that list of assets into a
Strictly speaking, because Sightly is able to iterate over
java.util.Listobjects, we don’t necessarily need the wrapping of the list into a
BindingsValuesProvider implementation would be:
I hope you have found this post enlightening and adds a new tool to your AEM development arsenal.
The code in this post can be found on GitHub.