The Sling Model @Via Annotation
I love writing code. What I love more is writing less. That is why I like Sling models. I have written about leveraging its injectors. And how to create custom ones. Now I will cover the @Via annotation, the OOTB providers, and how to create custom ones.
What @Via Does
Sling relies heavily on the adapter pattern. This is the pattern used by the Sling model framework. The "adaptable" is the thing that will adapt to an instance of that model. This more often than not is a Resource or SlingHttpServletRequest. But it is not limited to these.
What @Via does is swap out the original adaptable for something else. The ModelAdapterFactory will use the new adaptable from that point on.
So what are some potential use-cases?
⚠️ In the following examples I am using Groovy to implement the models and OSGi components. I am also using Spock to write unit tests. If you are using Java, these examples are easily translated into Java. If you are interested in Groovy & Spock, read my other articles.
Extending Core Components with Sling Delegation
Let's take for example the Button component. There are 2 versions of this. V2 ButtonImpl extends V1. Those are internal implementation classes. I can not extend them in my own project because I do not have visibility into them. I have to use @Via and specify the ResourceSuperTypeViaProvider. First, in a TDD fashion, let's write some unit tests.
The whole purpose of Spock and GWT is to convey the intention of the code. So this test should be pretty self-explanatory. Now we can write the model
Using the @Via(type = ResourceSuperType) annotation will invoke the ResourceSuperTypeViaProvider. That will get the resource supertype and create a resource wrapper. This will be the new adaptable. Because the field type is Button, the model factory will adapt the new adaptable to a Button.
Getting The Adaptable Via A Bean Property
We are going to extend our model further. The following unit test should convey the new functionality. That is, when there is no topic property set, the priority will be the button's topic tag.
Here is the updated model
To simplify things we are going to use @Via(value = 'resourceResolver'). By default, it will use the BeanPropertyViaProvider. The original adaptable is a SlingHttpServletRequest. The provider will get the new adaptable from its "resourceResolver" property. The model factory will adapt the new adaptable to a TagManager because that is the target field type.
Writing a Custom @Via Provider
The last thing is to fetch the topic from the page tag when it is not found in the component tags or property. This is usually done with something like this
To simplify things and make reusable code, let's write a custom via type provider to get a page property.
There are 4 via providers in the framework and we already covered the first 2
As you can see, they are not hard to write. As the documentation states, you need two things.
A marker class that will be used by the @Via annotation
And an OSGi service
Make usre to update the unit test setup function by registering the new OSGi service with the context
Here is the updated model
Using the @Via(type = PageProperty) annotation will invoke the custom provider. It will return the page property as a resource. This will be the new adaptable. Because the field type is String, the model factory will adapt the new adaptable to String.
By and large, the primary reason to use @Via would be with the ResourceSuperType provider. This is the correct way to extend core component models.
Having a better understanding of how it works will help you cut down on the glue code. As you can see from the example, the framework is the glue. This allows the model to focus on picking one prefix for the button's text. Take a look at the other 2 providers I didn't cover. ChildResourceViaProvider and ForcedResourceTypeViaProvider .
Groovy & Spock make it more fun. You may have spotted a bug not covered by the unit tests. What happens when the page or component has tags, but none are topic tags? Write a unit test. See the failure. And then take a look a Groovy's safe navigation operator.