Expand my Community achievements bar.

Learn about Edge Delivery Services in upcoming GEM session

Delegate fails for Custom Container Component

Avatar

Level 2

Hello,

I am writing custom container component by extending AEM core container component. I am trying to use delegation pattern for updating a method. But delegation fails with Null Pointer Exception.

 

So far, I have AEM container component with sling:resourceSuperType="core/wcm/components/container/v1/container" set and no other logic set from frontend side. I created an interface ContainerModel.java extending Container model from 

com.adobe.cq.wcm.core.components.models to have a testMethod as addition method. I created 

ContainerModelImpl.class that implements ContainerModel.

 

My @Model looks like below.

 

 

 

@Model(
        adaptables = SlingHttpServletRequest.class,
        adapters = { Container.class, ComponentExporter.class},
        resourceType = ContainerModelImpl.RESOURCE_TYPE,
        defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)

 

 

 

 

 

 

The RESOURCE_TYPE is set as mysite/components/container pointing to container component. I am initializing delegate as below.

 

 

 

@Deleted Account
@Via(type = ResourceSuperType.class)
private Container delegate;

 

 

 

Now, all I am trying is to Override getId method that just returns delegate.getId() to test it out but it fails as well. Just a note that I am using Exporter as below.

 

 

 

@exporter(
        name = ExporterConstants.SLING_MODEL_EXPORTER_NAME,
        extensions = ExporterConstants.SLING_MODEL_EXTENSION
)

 

 

 

All other methods follow same logic and I am delegating to parent methods.

 

I am getting below warning and then null pointer exception pointing back to delegate.getId() statement.

 

 

 

*WARN*  GET /content/mysite/us/en/mysitepage.model.json HTTP/1.1] org.apache.sling.models.impl.via.ResourceSuperTypeViaProvider Could not determine forced resource type for ResourceTypeForcingResourceWrapper, type=wcm/foundation/components/responsivegrid, path=/content/mysite/us/en/mysitepage/jcr:content/root, resource=[ResourceTypeForcingResourceWrapper, type=core/wcm/components/container/v1/container, path=/content/mysite/us/en/mysitepage/jcr:content/root, resource=[JcrNodeResource, type=mysite/components/container, superType=null, path=/content/mysite/us/en/mysitepage/jcr:content/root]] using via value .

 

 

 

Am I missing some additional requirements for delegating Container component? The warning mentions about  wcm/foundation/components/responsivegrid. Do I need to configure something for that part? I am using delegation pattern for other components in same codebase with same approach but they are working fine and can initialize delegate as expected.

20 Replies

Avatar

Employee Advisor

hi @chintan1997 ,

This is one of the interesting issue and seems to have been fixed, refer :
https://github.com/adobe/aem-core-wcm-components/issues/1187

 

Can you please check the AEM version and AEM WCM Core components version being used on your instance ?

Thanks

Avatar

Level 2

Hi @milind_bachani ,

Thanks for pointing it out. I came across same link before posting but I am currently using near latest versions of both. According to that issue, a fix was added in core components version 2.14.0 and I am currently using 2.17.12. For AEM SDK, it's 2021.11.6013.20211105T162756Z-211000. But I am still having issues. Wondering if there is anyone else who could successfully implement Container component through delegation pattern.

Avatar

Community Advisor

Hi Chintan,

 

The core v1 container component at /apps/core/wcm/components/container/v1/container uses the Sling model "com.adobe.cq.wcm.core.components.models.LayoutContainer". You can check this at line 16  "/apps/core/wcm/components/container/v1/container/container.html". You can use this interface as adapter in your custom sling model. Please find below the working code which overrides getId() and also i have added a testMethod().

import com.adobe.cq.wcm.core.components.models.LayoutContainer;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.DefaultInjectionStrategy;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.Via;
import org.apache.sling.models.annotations.injectorspecific.Self;
import org.apache.sling.models.annotations.via.ResourceSuperType;

@Model(adaptables = {Resource.class, SlingHttpServletRequest.class},
adapters = LayoutContainer.class, //Use LayoutContainer.class instead of Container.class
resourceType = "mysite/components/content/container",
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class CustomContainerModel implements LayoutContainer {
@Self
@Via(type = ResourceSuperType.class)
private LayoutContainer container;

@Override
public String getBackgroundStyle() {
return "black";
}

// Override getId method
@Override
public String getId() {
return "myId";
}

// adding a custom method
public String testMethod() {
return "From test method";
}
}

Below is the code inside my proxy container component.

<sly data-sly-use.container="com.adobe.cq.wcm.core.components.models.LayoutContainer"/>
<p>Container id: ${container.id}</p>
<p>Container test method: ${container.testMethod}</p>

Let me know if you have further questions. 

Thanks

Avatar

Level 2

Hi @JeevanRaj ,

Thanks for detailed solution and this solution seems to be a little relief. However, I was expecting it to return children data as well. For example, if we have text component under container component, delegating through LayoutContainer doesn't help to get that component data in model.json. To reproduce the behaviour, add text component under container and check model.json with CustomContainerModel and without any custom sling model implementation. The one without custom sling model returns children component data as well but if we implement Container through CustomContainerModel, it just returns container data and no children underneath it. 

Avatar

Community Advisor

Hi @chintan1997 

 

The solution to your issue is to add LayoutContainer and ComponentExporter as adapters to your CustomContainer model. You will get the child resource data as well as acess to any custom method you write in your CustomContainer model. Below is the code snippet.

 

@Model(
adaptables = { SlingHttpServletRequest.class },
adapters = { LayoutContainer.class, ComponentExporter.class },
resourceType = "mysite/components/content/container",
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
@Exporter(name = ExporterConstants.SLING_MODEL_EXPORTER_NAME,
selector = ExporterConstants.SLING_MODEL_SELECTOR,
extensions = ExporterConstants.SLING_MODEL_EXTENSION,
options = {
@ExporterOption(name = "MapperFeature.SORT_PROPERTIES_ALPHABETICALLY", value = "true"),
@ExporterOption(name = "SerializationFeature.WRITE_DATES_AS_TIMESTAMPS", value="false")
}
)
@JsonSerialize(as = CustomContainerModel.class)

public class CustomContainerModel implements ComponentExporter, LayoutContainer {

@Self
@Via(type = ResourceSuperType.class)
private LayoutContainer container;


@Override
public Map<String, ? extends ComponentExporter> getExportedItems() {
return container.getExportedItems();
}

public String getTestMethod() {
return " From SampleComponentExporter Model ";
}
}

 Below is a snapshot of the model.json

dev_aem_1-1642772467876.png

'text' and 'textimage' are the components added in the container. 'testMethod' is the method i have addedion the CustomContainer model. Let me know if this satisfies your need.

Thanks

Avatar

Level 2

Hi @JeevanRaj ,

Thanks so much for your help and efforts, much appreciated! The solution looks great but my hierarchy is little different and even after having ComponentExporter added, it doesn't return same data as it does without having CustomContainer. Just for example, my component hierarchy looks something like below.

--- Conatainer
   --- title
   --- image
   --- Container2 (same container component, just naming it Container2 for simplicity here)
        --- text
   --- Container3
        --- text2
   --- Container4

 

model.json returns all data for title and image as expected (including Container having testMethod data), but for Container2, Container3 and for Container4, it returns empty items list which holds important data. For example, one of those containers data literally look like below

 

"id": "container-c195d4ar52",
"items": [],
"testMethod": "test",
":items": {},
":itemsOrder": []

Avatar

Community Advisor

Hi @chintan1997 

I did try with the same hierarchy you have mentioned above and i can see all the data of the child containers and resources inside those containers. Please see the below model.json

tempsnip.png

 This is how the hierarchy looks like.

dev_aem_1-1642862790260.png

I'm adding the full code for your reference.

@Model(
adaptables = { SlingHttpServletRequest.class },
adapters = { LayoutContainer.class, ComponentExporter.class },
resourceType = "mysite/components/content/container",
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
@Exporter(name = ExporterConstants.SLING_MODEL_EXPORTER_NAME,
selector = ExporterConstants.SLING_MODEL_SELECTOR,
extensions = ExporterConstants.SLING_MODEL_EXTENSION,
options = {
@ExporterOption(name = "MapperFeature.SORT_PROPERTIES_ALPHABETICALLY", value = "true"),
@ExporterOption(name = "SerializationFeature.WRITE_DATES_AS_TIMESTAMPS", value="false")
}
)
@JsonSerialize(as = SampleComponentExporter.class)

public class SampleComponentExporter implements ComponentExporter, LayoutContainer {

@Self
@Via(type = ResourceSuperType.class)
private LayoutContainer container;


@Override
public Map<String, ? extends ComponentExporter> getExportedItems() {
return container.getExportedItems();
}

@Override
public String getRoleAttribute() {
return container.getRoleAttribute();
}

@Override
public String[] getExportedItemsOrder() {
return container.getExportedItemsOrder();
}

@Override
public List<ListItem> getItems() {
return container.getItems();
}

public String getTestMethod() {
return " From SampleComponentExporter Model ";
}
}

 Thanks

Avatar

Level 2

Hi @JeevanRaj ,

Thanks so much again for detailed answer. I created same class with same methods but the container is still empty. Each of the child container components have testMethod as well but other data is empty as mentioned in above response. However now, I created another component that holds the requirements and that would be child of container component and hold the data that I wanted on top of container component. 

As this could be something on my end, I will accept the answer considering you have posted working solution. Thanks again!

Avatar

Community Advisor

Hi @chintan1997 

  • We can make use of lombok's Delegate to delegate all public methods of desired Type (in this case LayoutContainer) along with its Super Types 
    • Here is the hierarchy of interface which LayoutContainer extends 
      • LayoutContainer -> Container -> Component(in turn extends ComponentExporter) & ContainerExporter
    • and with excludes attribute you can define the methods we wish to override. 
@Delegate(types = LayoutContainer.class, excludes = ContainerExcludes.class)
@Self
@Via(type = ResourceSuperType.class)
private LayoutContainer layoutContainer;

private interface ContainerExcludes {
String getId();
String getExportedType();
}
  • Other than the methods we wish to override,
    • we need to explicitly override getExportedType() of ComponentExporter (that returns our proxy container resourceType) +
    • add ComponentExporter to adapters  to export this Custom Sling Model (for it to appear in getExportedItems() / ":items" property of the JSON response)

Look for all the public getter methods in the hierarchy above, if you wish to not export any of those, override the same and annotate it with @JsonIgnore.

 

Lombok in Sling Model Delegation:

https://www.initialyze.com/blog/2020/11/simplify-extending-sling-models-with-lombok/

Sample snippet (that override getId() method) added to my GitHub - CustomContainerModel.java

Avatar

Level 2

Hi @Vijayalakshmi_S ,

Thanks for detailed answer and providing sample code snippet. As I mentioned in @JeevanRaj 's answer thread, the child container data populates ":items" as empty once CustomContainer class is introduced. I tried having CustomContainerModel.class from the sample project and still having same issue.

Thanks

Avatar

Community Advisor

@chintan1997 

Are you providing custom implementation for getExportedItems() method ?

Is it possible for you to share the complete Java class file for further debugging

Avatar

Community Advisor

@chintan1997 

Could you please let know the Core components version you are using in your project in addition to above 

Avatar

Level 2

Hi @Vijayalakshmi_S ,

I haven't added any custom implementation for getExportedItems(). I pretty much used same CustomContainerModel.class from the GitHub link code. I am using 2.17.12 core components version in my project.

Thanks

Avatar

Community Advisor

@chintan1997 

Can you let know the components that are added within the child container.

  • Is it proxy of core components or completely custom components.
  • If it is the latter, Does that component has Sling Model with exporter?

Avatar

Level 2

Hi @Vijayalakshmi_S ,

They are proxy of core components. To be precise, they are text components. A text component is literally just having .content.xml with pointing core text v2 component through resourceSuperType. You can refer below for sample hierarchy.

--- Conatainer
   --- title
   --- image
   --- Container2 (same container component, just naming it Container2 for simplicity here)
        --- text
   --- Container3
        --- text2
   --- Container4

title and image returns data as expected but Container2 and Container3 are having ":items" as empty (as mentioned in @JeevanRaj 's answer thread).

 

Thanks

Avatar

Community Advisor

@chintan1997 

Can you let know if you have written Custom Sling Model for any of this three - Title, Image and Text component. 

Also, can you try to remove the text component from child containers and author Title or Image (working ones in the parent container) just to narrow down the issue. 

 

Avatar

Level 2

Hi @Vijayalakshmi_S ,

Sorry for a late response, was caught up in other things. I tried removing text component from the container and added image component (which works in parent container) and still seeing the same issue. I used CustomContainerModel.java as available in the GitHub code with only minor changes (removed selector = "customcontainer", updated RESOURCE_TYPE to my component and updated package name obviously) and kept the entire class as-is. The parent container component shows data related to image component. Kept only image component in child container with same image resource, but seeing same issue as mentioned above! The items and some other data is still empty, and I do see custom method change i.e. id as 

CustomId in child container.

Regards,

Chintan Patel.

Avatar

Community Advisor

No worries @chintan1997 

Can you please add the custom selector to the code and access the JSON using the same upon code deploy. 

Avatar

Level 2

Hi @Vijayalakshmi_S ,

I tried adding customcontainer selector in the code and I found another pointer. For example, consider there is a page called PAGENAME.html. If I hit PAGENAME.model.json, I don't get "items" data as empty in child container. PAGENAME.customcontainer.json doesn't work here because there is no customcontainer selector at page level. If I drill down to PAGENAME/jcr:content/root/container.customcontainer.json, it returns all data as expected including data from child container. If I hit PAGENAME/jcr:content/root/container.model.json, I get all data from child container as well (similar to customcontainer.json). The issue remains same where it doesn't return child container data at page level.

Thanks,

Avatar

Community Advisor

If container resource export path (PAGENAME/jcr:content/root/container.<<default/custom>>.json) works fine per your confirmation with both default selector(model) as well as custom selector, then it should be the same response at page level unless you have customization for Page model or you are checking a different property.