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! 🍻
