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.
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.
- Go to Felix Console.
- Click on Sling Adapters from the Sling tab.
- Sling Model class must be visible over here.
Sightly & Sling Models:
- Sling models can be used with slightly to access business logic as we used to do with WCMUsePojo earlier.
- 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
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();
}
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>
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
Hi Saurabh, I want to create AEM page dynamically leveraging sling model import capability. Do you have any reference ?
ReplyDelete