Common Myths Around Sling Model


Fortunately I got a chance to talk to several people from AEM fraternity to understand their take about sling model and below are the summery of those discussions:

  1. Sling model is only back-end business logic provider to AEM components.
  2. Adaptable are just a way to get respective sling object reference. For example if you need slingRequest object in sling model then make your sling model adaptable to HttpSlingServletRequest.
  3. You can not get a value from sling model in a servlet or in any other class.
  4. When you need a reference of an OSGI service, you have only one way, that is @ OSGiService annotation.
  5. You can export only JSON data thru sling model exporter.
  6. You can not extend one sling model into another sling model.
  7. Sling model can to be created outside of the designated package in AEM.
  8. One Interface can not be a Sling Model, whenever you need to create a Sling Model you will have to write a pure POJO concrete implemented java class.
  9. You can only read properties of a node thru sling model, you can not change a node property thru sling model.
  10. Constructor injection..! Nothing like that in sling model.
After seeing those outcomes I thought why not invest some time to dispel the common myths around this beautiful sling API.
So here we go.

    1. Yes, sling model is back-end business logic provider to AEM Components  but it is not limited to it only. When a content author, author a component on a page, a node in the repository gets created under the root node of jcr:content for the component. Now when a developer use a sling model with an AEM component, it used to pick the resource path of the component and inject the properties, if any modification required into it, it used to do or simply pass it HTL to be displayed on the page or for other stuffs.  
So in summery we can say that it is used to read property value from the node in the repository. Since it can be used to read property value so definitely there will have so many use cases, where we can use sling model at least to get the property value(s) from the node. Like it is done in point #3 to read a property from a resource at 
content/we-retail/resources/states/us/al

    2. This is partially true only, adaptables are not a way to get respective object in sling model because in either adaptables we can get either objects for example if you need to read a property of a component you can get it with both of the adaptables like
with Resource.class as adaptables:

@Model(adaptables=Resource.class)

public class HelloWorldModel {

    


    @Inject

    @Default(values="dummy text")


    private String text;


public String getText() {


return text;

}

}

with SlingHttpServletRequest.class as adaptables:

@Model(adaptables=SlingHttpServletRequest.class)

public class HelloWorldModel {

    

    @Inject

    @Via("resource")

    @Default(values="dummy text")

    private String text;


public String getText() {


return text;

}

}

 It means adaptables are something else.

To understand it lets deep dive in the working mechanism of HTL  Java Use API (data-sly-use) :

when you write 

<div data-sly-use.model="org.example.Model">

      ${model.shine}

</div>

first HTL will locates the respective java class and then try to adapt the current Resource object with the Model class then bind the newly adapted or created object to the name model OR else try to adapt the current SlingHttpServletRequest object with the Model class then bind the newly adapted or created object to the name model. That is why It's important to note that this use provider will only load models that are adaptable from SlingHttpServletRequest or Resource .

Now you have model object to play around.

I will explain it more while answering point #3.

3. This understanding is totally wrong in any way, you can easily get a property value from sling model to servlet or any request processing class by adapting sling model either from request or resource. Since sling model let you map Java objects to Sling resources so there can be lots of scenarios where we can use model's mapping behaviour to map entire node with java object instead of creating a separate POJO for the same.

Examples are:

lets say you have a node and its property needed in a sing servlet to process its value so how we gona do that

if you need to read the properties of /content/we-retail/resources/states/us/al node your sling model will be like


@Model(adaptables=Resource.class)

public class HelloWorldModel {

    

    @Inject

    private String text;

    

    @Inject

    private String value;


public String getText() {

return text;

}

    

public String getValue() {

return value;

}

    

}

and servlet will be 

@Component(name = "Sling model consumer servlet", immediate = true, service = Servlet.class, property = {

"sling.servlet.paths=/bin/sling/slingmodelservlet", "sling.servlet.methods=GET" })

public class SimpleServlet extends SlingSafeMethodsServlet {


    @Override

    protected void doGet(final SlingHttpServletRequest req,

            final SlingHttpServletResponse resp) throws ServletException, IOException {

    

    Resource resource = req.getResourceResolver().getResource("/content/we-                         retail/resources/states/us/al");

    

    HelloWorldModel helloWorldModel = resource.adaptTo(HelloWorldModel.class);

    

    String text = helloWorldModel.getText();

    String value = helloWorldModel.getValue();

        

        resp.setContentType("text/plain");

        resp.getWriter().write("Text = "+text+" Value = "+value);

    }

} 

Same implementation when your Sling model is adaptables to request:

Sling Model

@Model(adaptables=SlingHttpServletRequest.class)

public class HelloWorldModel {

    

    @Inject

    @Via("resource")

    @Default(values="dummy text")

    private String text;

    

    @Inject

    @Via("resource")

    @Default(values="dummy value")

    private String value;


public String getText() {

return text;

}

    

public String getValue() {

return value;

}

    

}


Servlet

@Component(name = "Sling model consumer servlet", immediate = true, service = Servlet.class, property = {

"sling.servlet.paths=/bin/sling/slingmodelservlet", "sling.servlet.methods=GET" })

public class SimpleServlet extends SlingSafeMethodsServlet {


    private static final long serialVersionUid = 1L;


    @Override

    protected void doGet(final SlingHttpServletRequest req,

            final SlingHttpServletResponse resp) throws ServletException, IOException {

    

    HelloWorldModel helloWorldModel = req.adaptTo(HelloWorldModel.class);

    

    String text = helloWorldModel.getText();

    String value = helloWorldModel.getValue();

        

        resp.setContentType("text/plain");

        resp.getWriter().write("Text = "+text+" Value = "+value);

    }

} 


so code in red is the actual use of adaptables in model.


4. Yes, that is correct when you need to have an OSGI service reference you can use @OSGIService annotation but you can use @Inject annotation too for the same purpose and it will work fine like:

@Model(adaptables=Resource.class)

public class HelloWorldModel {

    

    @Inject

    SomeService someService;

    @Inject

    @Via("resource")

    @Default(values="dummy text")

    private String text;

    

    @Inject

    @Via("resource")

    @Default(values="dummy value")

    private String value;


    public String getText() {

return someService.getName();

    }

    public String getValue() {

 return value;

    }

    

}


5. Since you can create custom model exporter for different format like XML, YML etc. by extending ModelExporter interface in your custom model exporter class, you can easily export data in above formats so saying that we can export only JSON data thru sling model exporter is not correct.

a good example can be found on below link

http://www.sgaemsolutions.com/2018/01/custom-sling-model-exporter-in-aem-63.html

6. Since inheritance is one of the mostly used feature of Java and so sling model is not an exception of it. You can create another java class, a sling model or an interface to provide the abstraction to sling model easily like any other java classes.

These are the most common cases when we try to extend core components in our custom application.

like:

import com.adobe.cq.wcm.core.components.models.Image;

@ProviderType
public interface BannerModel extends Image {

    public String getBannerText();

}

Example can be found on below URL:

https://experienceleague.adobe.com/docs/experience-manager-learn/getting-started-with-aem-headless/spa-editor/react/extend-component.html?lang=en

7. Since the introduction of bnd scanner plugins we can create sling model any package of the core module of the application.

More details can be found on below link

https://sling.apache.org/documentation/bundles/models.html#registration-of-sling-models-classes-via-bnd-plugin

8. As Sling model supports both Class and interface so obviously an interface can be a sling model. If you have nested composite multifields in your dialog specially, it is recommended to use only interface based sling model if there is no modification required on the properties.

For Example:

this 

@Model(adaptables=Resource.class)

public class HelloWorldModel {

    @Inject

    @Default(values="dummy text")

    private String text;

    

public String getText() {

return text;

}    

}

class based sling model can be written in interface as

@Model(adaptables=Resource.class)

public interface NestedMultifield {


@Inject

public String getText();

} 

Please see the beautiful perficient blog on the use of sling model for nested multifields

https://blogs.perficient.com/2018/08/24/using-sling-models-with-nested-composite-mulitifields-in-aem-6-3/  

9. Sling model has a rich support of CRUD operation so off-course we not only read properties but also can write, delete and update properties with sling model as it is also the part of sling API.

Below  code is updating a node property to accomplish one requirement with authored value from a component in sling model

@Model(adaptables=Resource.class)

public class HelloWorldModel {

    @SlingObject

    private ResourceResolver resourceResolver;

    @Inject

    @Default(values="dummy text")

    private String text;

    

    @PostConstruct

    private void init()

    {

    Resource targetNodeResource = resourceResolver.getResource("/content/we-retail/resources/states/us/al");

    ModifiableValueMap modfiableValueMap = targetNodeResource.adaptTo(ModifiableValueMap.class);

    modfiableValueMap.put("text",text);

    try {

resourceResolver.commit();

} catch (PersistenceException e) {

e.printStackTrace();

}

    }


public String getText() {

return text;

}    

}


NOTE: While doing anything like above please think once about the read and write permission of the user otherwise in some scenario(s) it can fails apart.

Sling API CRUD operation details can be found on below link

https://sling.apache.org/documentation/the-sling-engine/sling-api-crud-support.html


10.Normally we use field based injection in sling model but since sling model version 1.1.0 it supports constructor based injection as well.


Field based injection:


@Model(adaptables=Resource.class)

public class HelloWorldModel {

    @Inject

    @Default(values="dummy text")

    private String text;

    

public String getText() {

return text;

   

}

 

Same implementation with constructor based injection:


@Model(adaptables=Resource.class)

public class HelloWorldModel {

private String text;

    @Inject

    public HelloWorldModel(@Named("text") String text)

    {

    this.text = text;

    }

    

public String getText() {

return text;

}    

}


I strived my best to prepare this article but still there are chances of improvement is there so expecting your feedback or suggestion to make it more useful for you and others.


Thank You.



Comments

  1. Nice Article. Thanks for sharing the details in brief.

    ReplyDelete
  2. Great Article.
    Found it really helpful

    ReplyDelete

Post a Comment

Popular posts from this blog

Common cause for NullPionterException or Null value in Junit test case for sling model in aem

@Inject : A discouraged sling-model annotation

"java.lang.NoSuchFieldError: FACTORY" while running Junit test in AEM codebase