Custom Sling Model Injector using Annotations

Published
Sling Model Injectors allow developers to retrieve and inject data directly into Sling models. However, sometimes, standard injectors aren't enough when you need to pull custom or complex data, such as tag properties or content fragments. In such cases, custom Sling Model injectors can be developed to meet specific data requirements.
This post will walk you through the process of creating a custom Sling Model injector with a custom annotation. We'll focus on enabling Sling Models to inject Tag object directly from component property into your model.
To inject a custom property, we'll create a custom annotation @TagProperty, to designate fields in the Sling model that require specific data injection. There are three parts to consider in order to build a custom injector:

Custom Annotation

Create a new @TagProperty annotation, similar to standard annotations like @ValueMapValue or @RequestAttribute, to be used within the Sling Model.
models / injectors / annotations / TagProperty.java
@Target({ METHOD, FIELD, PARAMETER }) @Retention(RetentionPolicy.RUNTIME) @InjectAnnotation @Source(TagProperty.SOURCE) public @interface TagProperty { String name() default StringUtils.EMPTY; String SOURCE = "tag-property"; InjectionStrategy injectionStrategy() default InjectionStrategy.DEFAULT; }

Annotation Processor

An Annotation Processor defines how custom annotations process and inject values, interpreting parameters such as injection strategy and default values. By implementing the StaticInjectAnnotationProcessorFactory interface, it connects the custom annotation with the injector, mapping annotation attributes to the Sling framework.
models / injectors / annotations / impl / TagPropertyAnnotationProcessorFactory.java
@Component(service = StaticInjectAnnotationProcessorFactory.class) public class TagPropertyAnnotationProcessorFactory implements StaticInjectAnnotationProcessorFactory { @Override public InjectAnnotationProcessor2 createAnnotationProcessor(AnnotatedElement element) { return Optional.ofNullable( element.getAnnotation(TagProperty.class) ).map(TagPropertyAnnotationProcessorFactory.PagePropertyAnnotationProcessor::new) .orElse(null); } private static class PagePropertyAnnotationProcessor extends AbstractInjectAnnotationProcessor2 { private final TagProperty annotation; public PagePropertyAnnotationProcessor(TagProperty annotation) { this.annotation = annotation; } @Override public String getName() { return StringUtils.isBlank(annotation.name()) ? null : annotation.name(); } @Override public InjectionStrategy getInjectionStrategy() { return annotation.injectionStrategy(); } } }

Custom Injector

The Injector retrieves actual data from the AEM JCR repository and contains the core logic for handling the custom annotation.
models / injectors / TagPropertyInjector.java
@Component(service = Injector.class) public class TagPropertyInjector implements Injector { private static final Logger logger = LoggerFactory.getLogger(TagPropertyInjector.class); @NotNull @Override public String getName() { return TagProperty.SOURCE; } @Nullable @Override public Object getValue( Object adaptable, String name, Type declaredType, AnnotatedElement element, DisposalCallbackRegistry callbackRegistry ) { if (element.isAnnotationPresent(TagProperty.class)) { TagProperty annotation = element.getAnnotation(TagProperty.class); ResourceResolver resourceResolver = getResourceResolver(adaptable); if (resourceResolver == null) { logger.error("ResourceResolver is null, cannot inject tag property"); return null; } TagManager tagManager = resourceResolver.adaptTo(TagManager.class); if (tagManager == null) { logger.error("TagManager is null, cannot inject tag property"); return null; } String key = StringUtils.defaultIfEmpty(annotation.name(), name); final String[] tagKeys = getResource(adaptable).getValueMap().get(key, String[].class); if (tagKeys == null || tagKeys.length == 0){ return null; } final Stream<Tag> tagStream = Arrays.stream(tagKeys).map(tagManager::resolve); return tagStream.filter(Objects::nonNull).findFirst().orElse(null); } return null; } private static ResourceResolver getResourceResolver(Object adaptable) { if (adaptable instanceof SlingHttpServletRequest) { return ((SlingHttpServletRequest) adaptable).getResourceResolver(); } if (adaptable instanceof Resource) { return ((Resource) adaptable).getResourceResolver(); } return null; } private static Resource getResource(Object adaptable) { if (adaptable instanceof SlingHttpServletRequest) { return ((SlingHttpServletRequest) adaptable).getResource(); } if (adaptable instanceof Resource) { return (Resource) adaptable; } return null; } }
The custom injector is now ready for use and can be used to any Sling Model as shown below.
components / models / ArticleModel.java
@Model(adaptables = SlingHttpServletRequest.class) public class ArticleModel { @TagProperty private Tag topics; public Tag getTopics() { return topics; } }
Write your Comment