From WCMUsePojo to Sling Models in AEM - Custom Injectors

How To Switch From WCMUsePojo to Sling Models in AEM Part Three – Custom Injectors

When you are writing Sling Models code, you are constantly invoking injectors for the objects you use in your Sling Models class. There are eight standard injectors Sling provides out of the box currently (based on version 1.3.9.r1784960 of org.apache.sling.models.impl installed in AEM 6.3). But sometimes you may find the eight injectors don’t meet a specific project requirement, or when you switch from WCMUsePojo to Sling Models, you find certain pieces of logic can be encapsulated into Sling Models injector. Here in part three (part one and part two for reference), I am going to take a specific example I have in my project and discuss the reasons, the steps, the gotchas and the benefits to writing custom injectors for your Sling Models classes and hopefully help the transition from WCMUsePojo to Sling Models.

Note: this was tested on Adobe Experience Manager (AEM) 6.3.

In many cases, you may want to get an inherited property value from parent page. For example, you have a page structure set up with a root level page, and there are child/grandchild pages created programmatically under that. These can be products, person or facilities pages that are created automatically from a template.

You want content authors to do the authoring in the root page, and your child/grandchild pages’ components can inherit the value. And of course, content authors can choose to overwrite the individual child page components. There are many ways to achieve this. In a component Java class that extends WCMUsePojo, you can write your own implementation of  the readPropertyValue(propertyName, currentResource) method, and you can also use com.day.cq.commons.inherit.InheritanceValueMap to get the inherited property value. Below is a sample code snippet:

Resource resource = getResource();
InheritanceValueMap iProperties = new HierarchyNodeInheritanceValueMap(resource);
iProperties.getInherited(PROPERTY_NAME, String.class);

Note: That was using HierarchyNodeInheritanceValueMap implementation.  ComponentInheritanceValueMap implementation is provided in the same package.

In Sling Models, you can still use InheritanceValueMap in the @PostConstruct init() method and it will work. But if your inheritance logic deviates from the normal parent flow, or you are using this method in many Sling Models classes, you can create a custom injector and put your inheritance logic there.

There are several reasons/benefits I find for creating a custom Sling Models injector:

  1. Ability to inject the object/value directly into the model class, write less code;
  2. Custom logic for the injector is encapsulated into the injector implementation class;
  3. Ability to be used in multiple model classes easily;
  4. Ability to leverage the Sling mechanism, annotation driven.

I am using the helloworld component as an example to show you how to create a custom injector. You can refer to all the source codes in my GitHub project.

Here are the steps to create a custom injector:

1. Create the structure for the injector.

I am using the models directory came with Adobe archetype, and created the path /core/src/main/java/org/myorg/blog/core/models/injectors.

2. Create injector class.

2.1 Register the injector class with OSGI (org.osgi.service.component.annotations) or Felix annotations.

2.2 Add property for service ranking.

Service ranking is important because Sling Models injectors are invoked in order of their service ranking, from lowest to highest. The first injector that returns a non-null value will be used and the value will be injected to the class.

You can see all the installed Sling Models injectors here: http://localhost:4502/system/console/status-slingmodels

If all installed Sling Models injectors return a null value, and your field/method is not optional, you may get a org.apache.sling.scripting.sightly.SightlyException, Caused by: org.apache.sling.models.factory.MissingElementsException: Could not inject all required fields into class and further Caused by: org.apache.sling.models.factory.ModelClassException: No injector returned a non-null value!

2.3 Implement org.apache.sling.models.spi.Injector interface.

3. Implement your custom injector class.

3.1 Implement getName() method.

This will return the name of your custom Sling Models injector. This name can be specified in the @Source when you invoking the injector, and if there may be more than one injector that will respond to the injection, Sling will pick the injector from @Source annotation.

3.2 Implement getValue() method.

There are several parameters in this method. The adaptable is the object being adapted, i.e. the helloworld resource. Name is the name of field/method passed to the injector (either name in @Named annotation or variable name), i.e. text. DeclaredType is the object type you want injector to return, i.e. String. Element is the field/method. CallbackRegistry is the call back in the injector when adapted model is garbage collected.

Inside this method, based on your adaptable object, you can implement your custom logic accordingly.

In my sample InheritedPropertyInjector.java, I am just using the InheritanceValueMap interface directly, but if you need a custom inheritance implementation, you can take a look at the com.day.cq.commons.inherit.HierarchyNodeInheritanceValueMap or com.day.cq.commons.inherit.ComponentInheritanceValueMap class.

4. Use your custom injector class.

Invoke custom injector with @Inject annotation, and optionally use @Source annotation to explicitly call the custom injector. Test if custom injector class works as expected and exceptions are caught properly.

So what are the use cases for custom injectors? I think there are several. It can act as central place to provide all available AEM/Sling objects, instead of using different injectors or annotations. It can inject objects that the standard injectors don’t provide as of today. It can also be a tool to meet specific requirements of your project.

Let me know what you are interested to know more about around WCMUsePojo and Sling Models. Happy coding.

Subscribe to the Perficient Digital Weekly Digest

* indicates required

2 responses to “How To Switch From WCMUsePojo to Sling Models in AEM Part Three – Custom Injectors”

  1. Shannon Sumner says:

    Hi Eddie,

    I’m looking to create a custom injector like your “InheritedPropertyInjector”. How can I use this in my model?

    Thanks,

    Shannon

  2. Eddie Yao says:

    You can use the @Inject annotation (optionally use @Source annotation), or create a custom annotation for you injector. Details see: https://sling.apache.org/documentation/bundles/models.html

Leave a Reply