Deep diving into Sling models PT2

19 juni 2020by Dylan Reniers

Part 2: 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:
  • 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.

Until next time!

CONTACTHoofdkantoor
Klaverbladstraat 7a b5,
3560 Lummen - BE
JONG EN HIP?Gebruik onze social links
U kan ons ook contacteren via
onze verschillende social media kanalen.
JOBSOok Joker worden?
Bezoek onze carrière pagina voor
openstaande opportuniteiten.
ContactHoofdkantoor
Klaverbladstraat 7a b5, 3560 Lummen - BE
JONG & HIP?Gebruik onze Social links
U kan ons ook contacteren via onze verschillende social media kanalen.