Create custom sling injector – AEM 6.5

Custom Sling injector using annotations in AEM 6.5

Goal

Create a custom sling injector and use it inside a model. This can be very useful to avoid boilerplate and to centralise logic. In this case the annotation maps the internal link into the external one.

Procedure

First thing you need to know is that there are three parts to consider in order to build a custom injector:

1. The annotation, which is used to inject an object into a field.

In this specific case I called the annotation “ExternalizedLink”. This is the related code:

import org.apache.sling.models.annotations.Source;
import org.apache.sling.models.annotations.injectorspecific.InjectionStrategy;
import org.apache.sling.models.spi.injectorspecific.InjectAnnotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.FIELD, ElementType.PARAMETER})

@Retention(RetentionPolicy.RUNTIME)

// Declares an annotation as a custom inject annotation.
@InjectAnnotation

// This string is important. It is used in the Injector class itself
@Source("externalized-link-value")
public @interface ExternalizedLink {
    InjectionStrategy injectionStrategy() default InjectionStrategy.OPTIONAL;

    //additional parameter passed via annotation - you will see in the model at the end of the article
    String property() default "";
}

2. An annotation processor to map the attributes of your annotation to the Sling framework.

Here the code:

import org.apache.sling.models.annotations.injectorspecific.InjectionStrategy;
import org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessor2;

public class ExternalizedLinkValueProcessor implements InjectAnnotationProcessor2 {

    //Our annotation
    private ExternalizedLink annotation;

    public ExternalizedLinkValueProcessor(ExternalizedLink annotation) {
        this.annotation = annotation;
    }

    @Override
    public InjectionStrategy getInjectionStrategy() {
        return annotation.injectionStrategy();
    }


    
    @Override
    public String getName() {
        return null;
    }

    @Override
    public String getVia() {
        return null;
    }

    @Override
    public boolean hasDefault() {
        return false;
    }

    @Override
    public Object getDefault() {
        return null;
    }

    @Override
    public Boolean isOptional() {
        return null;
    }
}

3. And last, the injector itself, which has the code/ the logic of your annotation.

import com.adobe.training.core.annotations.ExternalizedLink;
import com.adobe.training.core.annotations.ExternalizedLinkValueProcessor;
import com.adobe.training.core.utils.GenericUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.models.spi.DisposalCallbackRegistry;
import org.apache.sling.models.spi.Injector;
import org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessor2;
import org.apache.sling.models.spi.injectorspecific.StaticInjectAnnotationProcessorFactory;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Type;

@Component(service = {Injector.class, StaticInjectAnnotationProcessorFactory.class})
public class ExternalizedLinkInjector implements Injector, StaticInjectAnnotationProcessorFactory {

    private static final Logger log = LoggerFactory.getLogger(ExternalizedLinkInjector.class);

   //Use the same value used in the annotation
    @Override
    public String getName() { 
        return "externalized-link-value";
    }

    @Override
    public Object getValue(final Object adaptable,
                           final String name,
                           final Type type,
                           final AnnotatedElement element,
                           final DisposalCallbackRegistry callbackRegistry) {

        // Injectors are cycled through, so it is important to get yours 
        final ExternalizedLink annotation = element.getAnnotation(ExternalizedLink.class);
        if (annotation == null) {
            return null;
        }

        Resource res = null;
        if (adaptable instanceof SlingHttpServletRequest) {
            final SlingHttpServletRequest request = (SlingHttpServletRequest) adaptable;
            res = request.getResource();
        }
        if (adaptable instanceof Resource) {
            res = (Resource) adaptable;
        }

        ResourceResolver resourceResolver = res.getResourceResolver();
//to get the parameter passed via annotation, in this case is the property name containing the link configured by the author
//The link is then passed in the utils which will map it via resourceResolver, anyway you can add here the logic you want to have
        String propertyValue = res.getValueMap().get(annotation.property(), String.class);
//        return GenericUtils.externalizeLink(resourceResolver, 
// propertyValue); this is an our Util class available in the project

    }

    @Override
    public InjectAnnotationProcessor2 createAnnotationProcessor(final AnnotatedElement element) {
        // Check if the element has the expected annotation
        final ExternalizedLink annotation = element.getAnnotation(ExternalizedLink.class);
        if (annotation != null) {
            return new ExternalizedLinkValueProcessor(annotation);
        }
        return null;
    }
}

Last thing is an example of how you can use it in a Sling Model

import com.adobe.training.core.annotations.ExternalizedLink;
import lombok.Getter;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.DefaultInjectionStrategy;
import org.apache.sling.models.annotations.Model;

@Model(adaptables = Resource.class, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class BaseModel {

    @ExternalizedLink(property = "yourLink")
    @Getter
    private String yourLink;
}

That’s all! This has been tested on AEM 6.5 but I’m pretty sure that can work on previous versions

Cheers! 🍻

Leave a comment