Monday, December 23, 2024

Mastering Sling Models and Resource Resolvers in Adobe AEM: A Complete Guide

 In this article, we will learn about the Complete Flow of Resource Resolvers that we use in the Sling Model.



What is ResourceResolver in AEM Sling Model?


In AEM, ResourceResolver is an interface that provides access to resources in the JCR (Java Content Repository). It is part of the Sling API and serves as a bridge between your code and the JCR repository, allowing you to read, update, or manipulate resources (e.g., pages, components, nodes).



Resource Resolver Flow



Here's a rephrased and more understandable version:
  • The AEM instance starts running.
  • Once the AEM instance is running, the Resource Resolver Factory executes and provides all possible Resource Resolvers. There can be one or multiple Resource Resolvers.
  • In our Sling Model, the Resource used in the @Model adaptable annotation comes from the Resource Resolver provided by the Resource Resolver Factory.
  • Using this Resource, we can access the getResourceResolver() function.
  • Through this function, we obtain the QueryBuilder and Session objects.






  • Predicates are the parameter that we get form Query Builder AEM instance to pass in QueryBuilder in Code.





Sling Model and its Annotations Explained

In AEM, Sling Models are a framework that allows developers to map Java objects to resources in the JCR (Java Content Repository). They simplify data access and manipulation in AEM components.

Key Annotations in Sling Models:

@Model:
This annotation defines a class as a Sling Model.
It specifies the adaptables
(the source object types the model can work with, such as SlingHttpServletRequest or Resource)

.Adaptables:
The adaptables attribute in the @Model annotation specifies what the Sling Model can adapt to:

SlingHttpServletRequest.class: Used when you want to adapt HTTP requests, often to access page-related objects like currentPage.
example
@Model(adaptables = SlingHttpServletRequest.class) public class MyModel { // Model logic here }
Resource.class: Used when you want to work with JCR nodes directly.
@Model(adaptables = Resource.class) public class MyModel { // Model logic here }


full code example



import com.adobe.cq.sightly.WCMUsePojo;
import com.day.cq.wcm.api.Page;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.ScriptVariable;
import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;

@Model(adaptables = SlingHttpServletRequest.class)
public class ExampleModel {

// Injecting the current page object , for this we need to use Resource in adbaptables
@ScriptVariable
private Page currentPage;

// ValueMapValue Injecting a non-page property (e.g., fname) for this we need to use SlingHttpServletRequest
@ValueMapValue
private String fname;

// Injecting a property directly using @ValueMapValue
@ValueMapValue
private String sname;

// Injecting ResourceResolver to access JCR
@SlingOnject
private ResourceResolver resourceResolver;

// Fetching a resource using ResourceResolver
public String getCustomProperty() {
String customProperty = "Not found";

// Using resourceResolver to fetch a resource
Resource resource = resourceResolver.getResource("/content/mywebsite/home");
if (resource != null) {
// Fetching the property from the resource
customProperty = resource.getValueMap().get("customProperty", String.class);
}
return customProperty;
}

// Getter methods for the properties
public String getCurrentPageTitle() {
return currentPage.getTitle();
}

public String getFname() {
return fname;
}

public String getSname() {
return sname;
}
}






Some other UseFull annotation of Sling Modal

1. What is @RequestAttribute

The @RequestAttribute annotation is used in Sling Models to inject a variable or value that is passed from Sightly (HTL) into the Sling Model. It provides a simple way to pass data dynamically from the front end to the back end.


Sightly Code
<section
class="gv4-container w-full"
data-sly-use.obj="eand.com.money.core.models.GiftPlayZone @ variableName='TestAttribute'"
>
<!-- Example: Output the value -->
<p>${obj.reqVariable}</p>
</section>

Here, variableName='TestAttribute' passes the value "TestAttribute" as an attribute to the Sling Model.

Sling modal code

@RequestAttribute(name = "variableName")
private String reqVariable;

public String getReqVariable() {
return reqVariable;
}
  • @RequestAttribute injects the variableName value from Sightly into the reqVariable field in the Sling Model.




2. @PostConstruct

The @PostConstruct annotation marks a method to be executed after all injections (like @Inject, @ValueMapValue, @ScriptVariable) are completed. It is primarily used for initialization logic in the Sling Model.

Use Case:

  • When you need to process or prepare data after dependencies have been injected into the model.
@PostConstruct
protected void init() {
// Logic to initialize or process after all injections are done
if (reqVariable != null) {
reqVariable = reqVariable.toUpperCase();
}
}


Key Notes:

  • This method is executed automatically after the object has been fully initialized.
  • Use it for data manipulation, validation, or additional setup.

3. @OSGiService

The @OSGiService annotation is used to inject OSGi services (either default or custom) into the Sling Model. These services are managed by the OSGi container and provide reusable business logic or utilities.

Use Case:

  • When you need to call a default or custom OSGi service in your model for additional logic or processing.
@OSGiService
private MyCustomService myService;

public String getServiceResult() {
return myService.processData(); // Call a method from the OSGi service
}









Sling Model for multifield-like faqs with GraphQL and OSGI Service in AEM


  • modal class

package eand.com.money.core.models;

import eand.com.money.core.services.BaseUrlOsgiConfig;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.OSGiService;
import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;
import org.apache.sling.models.annotations.DefaultInjectionStrategy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.annotation.Resource;
import org.apache.http.HttpResponse;
import org.apache.http.util.EntityUtils;
import eand.com.money.core.utilities.ApisConstant;
import eand.com.money.core.utilities.GQueries;
import eand.com.money.core.Beans.FaqsBean;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

@Model(adaptables = { SlingHttpServletRequest.class, Resource.class },
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class Faqs {

@OSGiService
private BaseUrlOsgiConfig config;

public String getBaseURL() {
assert config != null;
return config.getBaseUrl();
}
@ValueMapValue
private boolean rtl; // Remove 'static' so AEM can inject the value

public String fetchFaqsCF() {
try {
CloseableHttpClient httpClient = HttpClients.createDefault();
// String fullPath = "http://localhost:4502/" + ApisConstant.EMONEY_GRAPHQL;
String fullPath = getBaseURL() + ApisConstant.EMONEY_GRAPHQL;
HttpPost httpPost = new HttpPost(fullPath);

// Set request headers
httpPost.setHeader("Authorization", "Basic YWRtaW46YWRtaW4=");
httpPost.setHeader("Content-Type", "application/json");
httpPost.setHeader("Cookie", "cq-authoring-mode=TOUCH");

// Set request body
String requestBody;
if (rtl) {
requestBody = GQueries.FaqsArabic; // Arabic query if RTL is true
} else {
requestBody = GQueries.FaqsEnglish; // English query if RTL is false
}

httpPost.setEntity(new StringEntity(requestBody));
HttpResponse response = httpClient.execute(httpPost);

if (response.getStatusLine().getStatusCode() == 200) {
String responseData = EntityUtils.toString(response.getEntity());
return responseData;
} else {
String errorMessage = "Error: " + response.getStatusLine().getStatusCode() + " "
+ response.getStatusLine().getReasonPhrase();
return errorMessage;
}
} catch (Exception e) {
return "";
}
}

public List<FaqsBean> getFaqs() {
List<FaqsBean> FaqsList = new ArrayList<>();
String jsonString = fetchFaqsCF();

// Parsing the JSON string
JsonParser parser = new JsonParser();
JsonObject jsonObject = parser.parse(jsonString).getAsJsonObject();

// Accessing data
JsonObject data = jsonObject.getAsJsonObject("data");
JsonObject emoneyFaqsList = data.getAsJsonObject("faqsList");
JsonArray items = emoneyFaqsList.getAsJsonArray("items");

if (items != null) {
for (int i = 0; i < items.size(); i++) {
JsonObject item = items.get(i).getAsJsonObject();
FaqsBean faqsList = new FaqsBean();
faqsList.setQuestion(item.get("question").getAsString());
faqsList.setAnswer(item.get("answer").getAsJsonObject().get("html").getAsString());
faqsList.setTutorial(item.get("tutorial").getAsString());
FaqsList.add(faqsList);
}
}

Collections.reverse(FaqsList);
return FaqsList;
}

// public static void main(String[] args) {
// // Create a dummy Faqs object to simulate injection
// Faqs faqs = new Faqs();
// faqs.rtl = true; // Simulate AEM setting for testing

// for (FaqsBean faqsList : faqs.getFaqs()) {
// System.out.println("Tutorial: " + faqsList.getTutorial());
// }
// }
}



  • OSGI Configuration
package eand.com.money.core.services.Impl;
import org.osgi.service.metatype.annotations.*;
import eand.com.money.core.services.BaseUrlOsgiConfig;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;

@Component(service = BaseUrlOsgiConfig.class,immediate = true)
@Designate(ocd = BaseUrlOsgiConfigImpl.ServiceConfig.class )
public class BaseUrlOsgiConfigImpl implements BaseUrlOsgiConfig{

@ObjectClassDefinition(name="Emoney Base URL Configuration",
description = "Emoney Base URL Configuration")
public @interface ServiceConfig {

@AttributeDefinition(
name = "Base URL",
description = "Enter files Path Ex: 'http://localhost:4502/'",
type = AttributeType.STRING)
public String baseURL() default "";
}

private String baseURL;

@Activate
protected void activate(ServiceConfig serviceConfig){
baseURL=serviceConfig.baseURL();
}

@Override
public String getBaseUrl() {
return baseURL;
}
}

  • Gquery.java

package eand.com.money.core.utilities;

public class GQueries {
public static final String AppsLink = "{\"query\":\"{\\n" + "appsLinkList(\\n" + "variation: \\\"en\\\"\\n"
+ " )\\n" + " {\\n" + "items {\\n" + "icon\\n" + "link\\n" + "}\\n" + "}\\n" + "}\\n"
+ "\",\"variables\":{}}";
public static final String FaqsEnglish = "{\"query\":\"{\\n" +"faqsList(\\n" + "variation: \\\"en\\\"\\n"+")\\n"+"{\\n"+"items{\\n"+"question\\n"+"answer{\\n"+"html\\n"+"}\\n"+"tutorial\\n"+"}\\n"+"}\\n"+"}\\n"+"\",\"variables\":{}}";

public static final String FaqsArabic = "{\"query\":\"{\\n" + //
" faqsList(\\n" + //
" variation: \\\"ar\\\"\\n" + //
" )\\n" + //
" {\\n" + //
" items {\\n" + //
" question\\n" + //
" answer {\\n" + //
" html\\n" + //
" }\\n" + //
" tutorial\\n" + //
" }\\n" + //
" }\\n" + //
"}\\n" + //
" \",\"variables\":{}}";
}



  • ApiConstan.java
package eand.com.money.core.utilities;

public class ApisConstant {

public static final String EMONEY_GRAPHQL = "/content/cq:graphql/emoney/endpoint.json";
}



Key Points for Managing Multifields in AEM Sling Models:

  1. Using a Map: Best suited when all fields in the multifield are of the same type, such as String or Boolean.
  2. Using a Bean: Ideal when fields in the multifield have different types, allowing for better structure and type safety.





Previous Post
Next Post

post written by:

0 comments: