Sling Models

Introduction: Sling models are released as part of aem 6.0.Sling Models are simple POJO classes which are mapped automatically with Sling Objects (resource, request objects.) and allow us to access jcr node property values directly into java classes.

Features of Sling Models: -
  • Pure POJO classes.
  •  Entirely annotation driven (Need to write less code).
  •  Can adapt multiple objects –  – minimal required Resource and SlingHttpServletRequest
  •  OOTB, support resource properties (via ValueMap), SlingBindings, OSGi services, request attributes
  • Support both classes and interfaces.
  • Work with existing Sling infrastructure (i.e. not require changes to other bundles).
  • Using Sling Models, you can do more with less code so you can reduce your coding efforts.
  • Your code is more maintainable using Sling Modes.


There are lot of ways to accomplish the component backend logic like JSP,Server Side Javascript,WCMuse,WCMUsePOJO. However, with the release of AEM 6.3 and AEM Core WCM Components, we see that using Sling Models have been advocated by Adobe as the best practice. 

How to Write a Sling Model:-

A Sling Model is implemented as an OSGi bundle. A Java class located in the OSGi bundle is annotated with @Model and the adaptable class (for example, @Model(adaptables = Resource.class). The data members (Fields) use @Inject annotations. These data members map to node properties.

package org.test.poc.sling.models;
import javax.inject.Inject;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.Model;

@Model(adaptables = Resource.class)
public class StudentInfo {
        @Inject
        private String firstName;
        @Inject
        private String lastName;
        @Inject
        private String contactNo;
   
        public String getFirstName() {
            return firstName;
        }
        public String getLastName() {
            return lastName;
       }
       public String getContactNo() {
            return contactNo;
       }
}   

How to use Sling Models with AEM: -
  • Add maven dependency to the pom.xml. If you are working with AEM 6.3, Make sure you are using the recent sling model version (1.3.2)

<dependency>
        <groupId>org.apache.sling</groupId>
        <artifactId>org.apache.sling.models.api</artifactId>
        <version>1.3.2</version>
        <scope>provided</scope>
</dependency>
  • Create a package under which you will be writing all sling models and make an entry of that package (here com.sling.models.core.models) in bundle(core) ‘s pom.xml in Sling-Model-Packages tag within maven-bundle-plugin.

<plugin>
<groupId>org.apache.felix</groupId>              
<artifactId>maven-bundle-plugin</artifactId>
         <extensions>true</extensions>
         <configuration>
                   <instructions>
                       <Sling-Model-Packages>
                           com.test.sling.models.core.models
                       </Sling-Model-Packages>
                    </instructions>
          </configuration>
</plugin>

Sling Models – Important Annotations: -

@Inject:- This annotation is used to inject a property, resource, request anything. This is a generic annotation, which traverses all the sling model injectors based on service ranking.

@Named:- Use  it if jcr node property name is different than class variable. If there is a need to change the getter of any attribute like (sling:resourceType, jcr:primaryType) @Named annotation helps to achieve this.

@Default:- use it if you want to assign default values, If empty then assign default value as ‘NA’.

@Optional and @Required: - In the sling models, by default all the fields supposed to be required.Sometimes there is a need to mark them as optional and required specifically. So injector fields can be annotated with @Optional and @Required.
If a majority of @Injected fields/methods are optional, it is possible to change the default injection strategy by using adding defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL to the @Model annotation:

@PostConstructor:- @PostConstruct annotation can be used to add methods which are invoked upon completion of all injections: This method automatically gets called when a sling model instance is created.
Note:The name of the method doesn’t matter, only it is matters that on which method the PostConstruct annotation exist.

@Via : Sometimes there is a need of using two injectors one from request and one from resource, then we need to tell annotation explicitly that you are coming via resource.

Injecting properties in POJO class: -

// Retrieving vales from jcr node through inject in Sling Model 

@Model(adaptables = Resource.class)
public class slingModelExample {
            @Inject
            /* email property is always looked from Resource( after adapting to ValueMap),
            *if this property value is not present then it returns null. If this property is in itself not available then it throws exception. */
             private String email;
            @Inject @Optional // It is not mandatory to have firstname property in Resource.
            private String firstName;
            // Use @Named if jcr node property name is different than class variable.
            //use @Default if you want to assign default values, If empty then assign default value as 'NA’
            @Inject @Named("surname") @Default(values="NA")
             private String lastName;
            @Inject // OSGi Service
            private Externalizer externalizer;  
            //@PostConstructor annotation defines that this method will run after injecting all field
            @PostConstruct
            protected void postMethod() {
  // gets executed after the class is created and all variables are injected
            }
}

Sling Models – How to Validate 
To validate that your class is actually working as a sling model.
Sightly & Sling Models:
  1. Sling models can be used with slightly to access business logic as we used to do      with WCMUsePojo earlier.
  2.  Sling Models-based components (i.e. POJOs with the  @Model annotation) can be instantiated in Sightly templates with a data-sly-use block statement.

          From a Sightly template:
            <div data-sly-use.model=‘‘com.foo.core.MyModel’’>${model.result}</div>
          From Java code:
           TestModel model =  resource.adaptTo(TestModel .class)

           Sling Models are instantiated every time they are used with data-sly-use or adaptTo.
The Sightly template for the preceding Navigation component would be implemented as follows:

<sly data-sly-use.navigation="com.projectname.components.content.Navigation">
    <h1>${navigation.title}</h1>
    <ul data-sly-list.page="${navigation.pages}">
        <li><a href="${page.href}">${page.title}</a></li>
    </ul>
</sly>

Two major differences with WCM Use class and Sling Model:-
·         WCM Use classes expect you to overwrite the activate() method, But Sling Models provides @PostConstruct annotation, where the init method will be called.
·         WCMUse Use classes use Felix annotation @Reference to reference the OSGI service, whereas in Sling Models, you will use @Inject or @OSGiService


Sling Model Example :-

Create an interface 
package com.aem.fsd.assignments.core.models;

public interface SampleSlingModel { 

public String getMessage();
    public String getFirstName(); 
    public String getLastName();
    public String getEmail();
    public String getLogoImage();
public String getLogoAltText();
}


and its implementation class.

package com.aem.fsd.assignments.core.models;

import javax.annotation.PostConstruct;
import javax.inject.Inject;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.Source;
import org.apache.sling.models.annotations.Via;
import org.apache.sling.models.annotations.injectorspecific.SlingObject;
import org.apache.sling.models.annotations.DefaultInjectionStrategy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.day.cq.wcm.api.designer.Style;

@Model(adaptables = {SlingHttpServletRequest.class, Resource.class}, 
adapters = {SampleSlingModel.class},
resourceType="FSDAssignmentsSampleProject/components/content/sample",
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class SampleSlingModelImpl implements SampleSlingModel{ 

    Logger logger = LoggerFactory.getLogger(this.getClass());
    private String message;
    private String logoImage;
    private String logoAltText;
    
    @SlingObject
    private SlingHttpServletRequest request;
    
    @Inject @Source("script-bindings")
private Style currentStyle;
    
    @Inject @Via("resource")
    private String firstName;
   
    @Inject  @Via("resource")
    private String lastName;
    @Inject  @Via("resource")
    private String email;
    
   
    @PostConstruct
    protected void postConstruct() {
    logger.info("inside post construct");
        message ="This is sample project created for demonstrate sling model assignments...";
        if (request != null) {
            this.message += "Request Path: "+request.getRequestPathInfo().getResourcePath()+"\n";
        }
        message += "First Name: "+ firstName +" \n";
        message += "Last Name: "+ lastName + "\n";
        message += "Email Id: "+ email + "\n";
        logoImage = currentStyle.get("logoImage",String.class); 
        logoAltText = currentStyle.get("logoAltText",String.class); 
    }

    @Override
    public String getMessage() {
        return message;
    }
    @Override
    public String getFirstName() {
        return firstName;
    }
    @Override
    public String getEmail() {
        return firstName;
    }
    @Override
    public String getLastName() {
        return lastName;
    }

    @Override 
public String getLogoImage() {
return logoImage;
}
    
    @Override
public String getLogoAltText() {
return logoAltText;
}
}

Call the SlingModel from HTL component using data-sly-use

Create a sample component and write below code in html file of it.

<!--/*
@author:saurabh.kumar
*/-->
<sly data-sly-use.sample="com.aem.fsd.assignments.core.models.SampleSlingModel">
<img src= "${sample.logoImage}" alt = "${sample.logoAltText}">
<h2>Data coming from Sling model....</h2>
<h4>${sample.message}</h4>
</sly>

Create a dialog :

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
    jcr:primaryType="nt:unstructured"
    jcr:title="Properties"
    sling:resourceType="cq/gui/components/authoring/dialog">
    <content
        jcr:primaryType="nt:unstructured"
        sling:resourceType="granite/ui/components/coral/foundation/fixedcolumns">
        <items jcr:primaryType="nt:unstructured">
            <column
                jcr:primaryType="nt:unstructured"
                sling:resourceType="granite/ui/components/coral/foundation/container">
                <items jcr:primaryType="nt:unstructured">
                    <firstName
                        jcr:primaryType="nt:unstructured"
                        sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
                        fieldLabel="First Name"
                        name="./firstName"/>
<lastName
                        jcr:primaryType="nt:unstructured"
                        sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
                        fieldLabel="Last Name"
                        name="./lastName"/>
<email
                        jcr:primaryType="nt:unstructured"
                        sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
                        fieldLabel="Email Id"
                        name="./email"/>
                </items>
            </column>
        </items>
    </content>
</jcr:root>

Create a design dialog to get style properties.

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
    jcr:primaryType="nt:unstructured"
    jcr:title="Sample Component"
    sling:resourceType="cq/gui/components/authoring/dialog"
    helpPath="https://www.adobe.com/go/aem_cmp_breadcrumb_v1">
    <content
        jcr:primaryType="nt:unstructured"
        sling:resourceType="granite/ui/components/coral/foundation/container">
        <items jcr:primaryType="nt:unstructured">
            <fixedcolums
                jcr:primaryType="nt:unstructured"
                sling:resourceType="granite/ui/components/coral/foundation/fixedcolumns">
                <items jcr:primaryType="nt:unstructured">
                    <properties
                        jcr:primaryType="nt:unstructured"
                        sling:resourceType="granite/ui/components/coral/foundation/container">
                        <items jcr:primaryType="nt:unstructured">
                            <logo
                                jcr:primaryType="nt:unstructured"
                                sling:resourceType="granite/ui/components/coral/foundation/form/pathbrowser"
                                fieldDescription="Select logo path for sample component)"
                                fieldLabel="Logo Image"
                                name="./logoImage"/>
<logoAltText
                                jcr:primaryType="nt:unstructured"
                                sling:resourceType="granite/ui/components/coral/foundation/form/pathbrowser"
                                fieldDescription="Select logo Alt Text for sample component)"
                                fieldLabel="logoAltText"
                                name="./logoAltText"/>
                            
                        </items>
                    </properties>
                </items>
            </fixedcolums>
        </items>
    </content>
</jcr:root>

Create a page and drop this sample component:-





















Write a Sling Model Exporter class using @Exporter annotation.

package com.aem.fsd.assignments.core.models;

import javax.annotation.PostConstruct;
import javax.inject.Inject;

import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.models.annotations.Exporter;
import org.apache.sling.models.annotations.ExporterOption;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.Source;
import org.apache.sling.models.annotations.Via;
import org.apache.sling.models.annotations.injectorspecific.SlingObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.day.cq.wcm.api.designer.Style;

@Model(adaptables = SlingHttpServletRequest.class, resourceType="FSDAssignmentsSampleProject/components/content/sample")
@Exporter(name = "jackson", extensions = "json", options = { @ExporterOption(name = "SerializationFeature.WRITE_DATES_AS_TIMESTAMPS", value = "true") })
public class SampleSlingModelExporter implements SampleSlingModel{

    Logger logger = LoggerFactory.getLogger(this.getClass());
    private String message;
    private String logoImage;
    private String logoAltText;
    
    @SlingObject
    private SlingHttpServletRequest request;
    
    @Inject @Source("script-bindings")
private Style currentStyle;
    
    @Inject @Via("resource")
    private String firstName;
   
    @Inject  @Via("resource")
    private String lastName;
    @Inject  @Via("resource")
    private String email;
    
   
    @PostConstruct
    protected void postConstruct() {
    logger.info("inside post construct");
        message ="This is sample project created for demonstrate sling model assignments...";
        if (request != null) {
            this.message += "Request Path: "+request.getRequestPathInfo().getResourcePath()+"\n";
        }
        message += "First Name: "+ firstName +" \n";
        message += "Last Name: "+ lastName + "\n";
        message += "Email Id: "+ email + "\n";
        logoImage = currentStyle.get("logoImage",String.class); 
        logoAltText = currentStyle.get("logoAltText",String.class); 
    }
    @Override
    public String getMessage() {
        return message;
    }
    @Override
    public String getFirstName() {
        return firstName;
    }
    @Override
    public String getEmail() {
        return firstName;
    }
    @Override
    public String getLastName() {
        return lastName;
    }

    @Override 
public String getLogoImage() {
return logoImage;
}
    
    @Override
public String getLogoAltText() {
return logoAltText;
}
}

Access using http://localhost:4503/content/FSDAssignmentsSampleProject/en/slingmodelassignment/jcr:content/par/sample.model.json




Comments

  1. Hi Saurabh, I want to create AEM page dynamically leveraging sling model import capability. Do you have any reference ?

    ReplyDelete

Post a Comment

Popular posts from this blog

AEM Scheduler

AEM Sling Servlet

Event Handling in AEM