Part 2: How to properly annotate your Sling models
In this Sling models PT2 blog post, I will show you how to properly annotate your Sling models.
There are two types of developers in this world: those who care about clean code and those who don’t. I like to believe that I’m part of that first group.
Throughout the projects that I’ve worked on, I’ve often seen some mad things in source code that I believe is the result of developers being either extremely lazy or not knowing any better due to a lack of knowledge/training. Here is a list of things I believe to be wrong when you create Sling models:
- Set the defaultInjectionStrategy to optional
- Use both SlingHttpServletRequest and Resource as adaptables
- Use @Inject everywhere
- No name added to the property using @Named
Let me illustrate with an example:
package com.mycompany.models;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.models.annotations.DefaultInjectionStrategy;
import org.apache.sling.models.annotations.Model;
import javax.inject.Inject;
@Model(adaptables = {Resource.class, SlingHttpServletRequest.class}, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class DontDoItLikeThisModel {
@Inject
private Resource resource;
@Inject
private String property;
@Inject
private ResourceResolverFactory resourceResolverFactory;
//all of the methods associated with this model
}
I challenge you to go through your current codebase and have a good look at the models. I bet there’s an 80% chance you’ll find instances of all the things I summed up earlier!
So, what would be my recommendations here?
- NEVER set defaultInjectionStrategy to optional, not even IF all of your properties can be optional, because this means that your Resource/SlingHttpServletRequest or any service that you want to inject in your model is also optional. More often than not, you will not be null-checking these, which results in you potentially getting NullPointerExceptions.
- Choose between Resource or SlingHttpServletRequest. You cannot adapt from both, because if you look at the example code I provided, if you would adapt from a SlingHttpServletRequest, then that resource property can never contain any value. I usually use the following rule: go with Resource, until you require something specifically from the request.
- Instead of using @Inject, use an appropriate annotation. Here’s a list of which annotation to use for each scenario:
- JCR property: @ValueMapValue
- Request attribute: @RequestAttribute
- The current resource/request = @Self
- Any Sling Binding: @ScriptVariable
- The bindings you can include can be found here https://cwiki.apache.org/confluence/display/SLING/Scripting+variables
- An OSGi service: @OSGiService
- Any child resource: @ChildResource
- Any other resource: @ResourcePath
- Commonly used sling objects: @SlingObject. The values you can inject depend on where you use the annotation.
More info can be found here: https://helpx.adobe.com/experience-manager/6-4/sites/developing/using/reference-materials/javadoc/org/apache/sling/models/annotations/injectorspecific/SlingObject.html
- If you still want to use @Inject to inject a property from a node, then combine that with @Named to ensure that you are always injecting the same property from that node. This approach makes your code backwards compatible if you ever decide to make a change to the property in your Sling model class. This is because by default it will look at the name of your java property to see check which property of the JCR node it has to inject. If you use @ValueMapValue, you can choose between using the name property of the annotation or using the @Named annotation
Here’s an example of a properly annotated Sling model (at least, in my humble opinion):
package com.mycompany.models;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.InjectionStrategy;
import org.apache.sling.models.annotations.injectorspecific.OSGiService;
import org.apache.sling.models.annotations.injectorspecific.Self;
import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;
@Model(adaptables = {Resource.class})
public class ProperlyAnnotatedModel {
private static final String PROPERTY_NAME = "propertyName";
@Self
private Resource self;
@ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL, name = PROPERTY_NAME)
private String property;
@OSGiService
private ResourceResolverFactory resourceResolverFactory;
//all of the methods associated with this model
}
Keep in mind that if you use this model for one specific component, you need to make sure to add a resourceType attribute to your @Model annotation. To see why, please read my previous blog post “Creating an interface for a model”. If you’re wondering why I didn’t create an interface here, that’s because this post is specifically about annotations. I would always advice to create an interface for any Sling model.
And there you have it: my personal view on how to properly annotate your Sling models. I hope that this has been informative to you and if you still have some questions, feel free to get in touch and we will gladly help you out.
Tip: Digitalum helps organizations to benchmark and advises on tooling architecture. Book time with an expert or contact us.