Sling model: Basics


Sling Models in AEM serve as a bridge between AEM’s content-centric architecture and Java-based development. These Java classes with specialized annotations, facilitate the mapping of AEM content to Java objects, providing an array of essential features and architectural advantages:

  • Decoupling Logic: Sling Models separate business logic from presentation, enhancing code modularity.
  • Simplified Data Access: Developers effortlessly access and manipulate AEM content as Java objects.
  • Type Safety: Sling Models ensure reliability by providing type-safe content property binding.
  • Code Reuse: Encouraging reusable Java classes, promoting maintainable code.
  • HTL Integration: Seamlessly integrating with AEM’s templating language (HTL).
  • Automatic Data Binding: AEM automates content adaptation to Sling Models.

It streamlines AEM development, improves code quality, and empowers developers to efficiently work with AEM components and content in an object-oriented manner.

If you are interested in learning abou best practices, please refer to Sling model annotations: Best Practices

Common Annotations

@Model

Apply this annotation at the class level to define a Sling Model. You can specify optional properties like:

  • adaptables – resource types to which the model can be adapted. A Models can be adapted both Resource / SlingHttpServletRequest. This depends upon the fields required to be injected.
    • If only resource properties are required, prefer using Resource
    • If Sling Objects (eg. currentStyle, currentPage, xssApi etc) are also required, prefer adapting to SlingHttpServletRequest.
    • Avoid using both together to avoid injection differences. Explanation at the end in “Best practices” section
  • adapters – adapters to which the model can be adapted
  • resourceType – the resource type that triggers the model
  • defaultInjectionStrategy – In sling models, by default all the fields are required. If a majority of injected fields/methods are optional
    • Use ‘defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL’ to mark all injected fields as optional
    • Use ‘defaultInjectionStrategy = DefaultInjectionStrategy.REQUIRED’ to mark all injected fields as required. Its also the default configuration, if ‘defaultInjectionStrategy’ is not specified.

Using Resource adaptable

@Model(adaptables = Resource.class, adapters=ContactUsModel, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class ContactUsModelImpl implements ContactUsModel{
@ValueMapValue
private String mailAddress;

@PostConstruct
protected void init() {
// Business logic to transform injected values and assign to bean variables
// Example: Resolve mailAddress/phoneNumber applicable for the user's region, based on officeCode
}

public String getMailAddress(){
return mailAddress;
}
}
  • In the above example, ‘adaptables = Resource.class’ maps the sling model against a Sling Resource.
  • The @ValueMapValue would inject ‘mailAddress’ property from the resource into ‘mailAddress’ class variable. The property value injection occurs after adapting the Resource into Value Map.

Using SlingHttpServletRequest adaptable

@Model(adaptables = SlingHttpServletRequest.class, adapters=ContactUsModel, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class ContactUsModelImpl implements ContactUsModel{
@ValueMapValue
private String mailAddress;

@PostConstruct
protected void init() {
// Business logic to transform injected values and assign to bean variables
// Example: Resolve mailAddress/phoneNumber applicable for the user's region, based on officeCode
}

public String getMailAddress(){
return mailAddress;
}
}
Injection strategy on Field level:
  • @Required / @Optional annotations can be used to selectively mark fields as required/optional.
@Model(adaptables = SlingHttpServletRequest.class, adapters=ContactUsModel, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class ContactUsModelImpl implements ContactUsModel{
        @ValueMapValue
	private String mailAddress;
        @ValueMapValue
        @Required
	private String phoneNumber;
...

 @Default annotation

@Default allows to specify default value for fields (including arrays)

@ValueMapValue
@Default(values="Contact Us")
private String title;

@ValueMapValue @Default(intValues={1,2,3,4})
private int[] integers;

@ValueMapValue Annotation

The annotation retrieves a property from a ValueMap by name.

  • If @Via is not set, it automatically takes the current resource (even if the adaptable is a SlingHttpServletRequest)
  • If the name is not set, the name is derived from the method/field name.
   import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;
   @Model(adaptables = SlingHttpServletRequest.class, 
adapters=ContactUsModel, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class ContactUsModelImpl implements ContactUsModel{
        @ValueMapValue(name = "jcr:title")
        private String titleText;
        @ValueMapValue
	    private String mailAddress;
...

In the example, @ValueMapValue is used to fetch properties “jcr:title” and “mailAddress” from the ValueMap of the adaptable SlingHttpServletRequest.

@PostConstruct Annotation

When a Sling Model is instantiated, Sling automatically injects values into specified fields, eliminating the need for manual assignment and facilitating Dependency Injection.

After Sling injects the necessary dependencies into these fields, it proceeds to invoke the method marked with the @PostConstruct annotation. Typically, developers utilize this method to perform additional setup tasks for the Sling Model.

The entire sequence unfolds as follows:

  1. Upon receiving a request, the Sling Framework selects a Sling Model corresponding to the resource.
  2. A new instance of the chosen model, such as new MyModel(), is generated by Sling through the adaptation of the current resource.
  3. Sling then fulfills all declared dependencies utilizing annotations like @Inject, @ValueMapValue, @OSGiService, among others.
  4. Subsequently, Sling executes the method annotated with @PostConstruct.

The @PostConstruct annotation essentially serves as an alternative to a constructor. If you were to employ a constructor in your model, you would observe that, upon its execution, the fields annotated with @Inject are still unassigned. Attempting further initialization with these fields at this point would result in a NullPointerException.

@Model(adaptables = SlingHttpServletRequest.class, adapters=ContactUsModel, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class ContactUsModelImpl implements ContactUsModel{
@ValueMapValue
private String mailAddress;

@PostConstruct
protected void init() {
// Business logic to transform injected values and assign to bean variables
// Example: Resolve mailAddress/phoneNumber applicable for the user's region, based on officeCode
}

public String getMailAddress(){
return mailAddress;
}
}

@Named annotation

When the @Named annotation is unspecified, name is automatically inferred from the variable name. If the field name differs from the property name, consider using the @Named annotation. This annotation is effective when paired with the @Inject annotation, although it is recommended to favor @ValueMapValue instead.

@Inject @Named("jcr:title")
private String title;

@Via annotation

The @Via annotation proves useful in scenarios where:

  • two injectors—one from the request and another from the resource—need to be employed.
  • When a different object is required as the adaptable instead of the original one, this can be achieved using the via parameter.

In such situations, it is crucial to explicitly specify to the annotation that the injection is taking place via the resource.

For example, when obtaining a property from a child resource:

@Model(adaptables=Resource.class)

public interface MyModel {

// will return eesource.getChild("jcr:content").getValueMap().get("propertyName", String.class)
@Inject @Via(value = "jcr:content", type = ChildResource.class)
String getPropertyName();

Similarly, if the adaptable is sourced from Resource, then we can get Tagmanager like:

@Model(adaptables = Resource.class, adapters=ContactUsModel, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class ContactUsModelImpl implements ContactUsModel {
@Self
@Via("resourceResolver")
final TagManager tagManager,
}

@Self annotation

Injects the current resource into the object

@Model(adaptables = SlingHttpServletRequest.class, adapters=ContactUsModel, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class ContactUsModelImpl implements ContactUsModel {
    //Accessing the resource that is adapted to ContactusModel
    @Self
    protected Resource resource;
}

@OSGiService annotation

We can inject OSGi services as dependencies using the @OSGiService annotation. This annotation allows you to look up services based on their class name, and it returns the service with the highest service ranking. Example:

@Model(adaptables = SlingHttpServletRequest.class, adapters=ContactUsModel, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class ContactUsModelImpl implements ContactUsModel {
    // Inject an OSGi service of type SomeService
    @OSGiService
    private SomeService someService;
}

@ScriptVariable annotation

The @ScriptVariable annotation is utilized to inject objects via script variables that are defined in Sling Bindings. It enables developers to easily look up and inject objects present in the script bindings object by their specified names.

@Model(adaptables = SlingHttpServletRequest.class, adapters=ContactUsModel, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class ContactUsModelImpl implements ContactUsModel{
    @ScriptVariable
    private Component component;
    @ScriptVariable
    private ComponentContext componentContext;
    @ScriptVariable
    private Design currentDesign;
    @ScriptVariable
    private Node currentNode;
    @ScriptVariable
    private Page currentPage;
    @ScriptVariable
    private HttpSession currentSession;
    @ScriptVariable
    private Style currentStyle;
    @ScriptVariable
    private Designer designer;
    @ScriptVariable
    private EditContext editContext;
    @ScriptVariable
    private PageManager pageManager;
    @ScriptVariable
    private ValueMap pageProperties;
    @ScriptVariable
    private SlingHttpServletRequest request;
    @ScriptVariable
    private ResourceResolver resolver;
    @ScriptVariable
    private Resource resource;
    @ScriptVariable
    private Design resourceDesign;
    @ScriptVariable
    private Page resourcePage;
    @ScriptVariable
    private SlingHttpServletResponse response;
    @ScriptVariable
    private SlingScriptHelper sling;
    @ScriptVariable
    private WCMScriptHelper slyWcmHelper;
    @ScriptVariable
    private SightlyWCMMode wcmmode;
    @ScriptVariable
    private XSSAPI xssAPI;
}

The @ScriptVariable annotation can take attributes like name and injectionStrategy:

  • name: Specifies the name of the script variable to be injected. If the name attribute is not set, the name is derived from the method or field name.
  • injectionStrategy: This attribute determines the injection strategy, which can be one of “Optional,” “Required,” or “Default.” It defines whether the injection is mandatory or optional for the Sling Model.

@ResourcePath annotation

The @ResourcePath annotation is used to inject one or multiple resources.

@Model(adaptables = Resource.class, adapters=ContactUsModel, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class ContactUsModelImpl implements ContactUsModel{
       @ResourcePath(path = "/content/sourcedcode/en/home")
       Resource techrevelHomeRes;
}

@ChildResource annotation

The @ChildResource annotation is used to fetch a child resource by its name.

   import org.apache.sling.models.annotations.injectorspecific.ChildResource;
@Model(adaptables = Resource.class, adapters=ContactUsModel, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class ContactUsModelImpl implements ContactUsModel{
       @ChildResource (name="mailAddress")
       private Resource child_node_of_current_resource;  // Fetches a child resource named "mailAddress"
       @ChildResource
       private List<Resource> emailAddress;  // Fetches multiple child resources under "emailAddress" child Node

@RequestAttribute annotation

Used with SlingHttpServletRequest adaptable, the @RequestAttribute annotation is used to inject a request attribute by its name. If the name attribute is not set, the name is derived from the method or field name, making it convenient to access request attributes in a Sling Model.

   import org.apache.sling.models.annotations.injectorspecific.RequestAttribute;
@Model(adaptables = SlingHttpServletRequest.class, adapters=ContactUsModel, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class ContactUsModelImpl implements ContactUsModel{
       @RequestAttribute(name = "area")
       private String areaParam;  // Injects a request attribute named "area"
...

@SlingObject annotation

The @SlingObject annotation is used to inject commonly used Sling objects based on the field’s type. It automatically injects the appropriate Sling object if there is a match with the field’s class. The supported Sling objects include SlingHttpServletRequest, SlingHttpServletResponse, Resource, ResourceResolver, and SlingScriptHelper.

   import org.apache.sling.models.annotations.injectorspecific.SlingObject;
@Model(adaptables = Resource.class, adapters=ContactUsModel, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class ContactUsModelImpl implements ContactUsModel{
       @SlingObject
       private SlingHttpServletRequest slingHttpServletRequest;  // Injects the request
       @SlingObject
       private SlingHttpServletResponse slingHttpServletResponse;  // Injects the response
       @SlingObject
       private Resource currentResource;  // Injects the current resource
       @SlingObject
       private ResourceResolver resourceResolver;  // Injects the resource resolver
   }

In this example, @SlingObject is used to inject commonly used Sling objects into the Sling Model. The annotation automatically injects the appropriate object based on the field’s type, making it convenient to work with Sling-related functionality. Note that the availability of these objects depends on the adaptable, and all of them are available via SlingHttpServletRequest, while ResourceResolver can only resolve the ResourceResolver object.

Additional Notes

Design Object

The Design object can be accessed on adapting model to Resource / SlingHttpServletRequest

Injecting currentStyle object
Used when the Sling Model is adapted from SlingHttpServletRequest. Example:

@Model(adaptables = SlingHttpServletRequest.class, adapters=ContactUsModel, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class ContactUsModelImpl implements ContactUsModel {
     @ScriptVariable
     private Style currentStyle;
}

Fetch currentStyle object from Resource
Used when the Sling Model is adapted from Resource. Example:

@Model(adaptables = Resource.class, adapters=ContactUsModel, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class ContactUsModelImpl implements ContactUsModel {
   private Style currentStyle;
   private Designer designer;
   @PostConstruct
   protected void init() {
       // Getting the currentStyle from the Designer.class
       designer = this.resourceResolver.adaptTo(Designer.class);
       if (designer != null) {
           currentStyle = designer.getStyle(resource);
       }
    }
}

To learn best practices around Sling Models, refer to Sling model annotations: Best Practices

One thought on “Sling model: Basics

Leave a comment