Expand my Community achievements bar.

Sling Model with custom selector not working in AEM 6.5

Avatar

Level 2

Hi All,

 

I am using a Sling model exporter to export json data in AEM 6.5 and using the selector "caas". When the selector is "model" , it works perfectly. However with custom selector , it shows the below error.

 

Invalid recursion selector value 'caas'

Cannot serve request to /content/abc/internal/exportsite/en/clientaccess/help/secondlevela/level3.caas.json in org.apache.sling.servlets.get.DefaultGetServlet

 

Already tried the below:

check if this helps - Re: Sling Exporter with custom selector

22 Replies

Avatar

Community Advisor

Hi @vikrams57194913 

 

Is /content/abc/internal/exportsite/en/clientaccess/help/secondlevela/level3 a page?

Did you try with /content/abc/internal/exportsite/en/clientaccess/help/secondlevela/level3/jcr:content.caas.json?

 

Thanks!

Avatar

Level 2

I am getting blank page with the above link also, only getting response from model.json

 

Thanks

Avatar

Level 2

@Model(adaptables = SlingHttpServletRequest.class, adapters = { PageExporter.class,
ComponentExporter.class }, resourceType = PageExporterImpl.RESOURCE_TYPE)
@exporter(name = ExporterConstants.SLING_MODEL_EXPORTER_NAME,selector = "caas", extensions = ExporterConstants.SLING_MODEL_EXTENSION)
public class PageExporterImpl extends com.adobe.cq.wcm.core.components.internal.models.v1.PageImpl implements PageExporter{

protected static final String RESOURCE_TYPE = "contentservices/components/structure/page";

/** The logger. */
private final Logger logger = LoggerFactory.getLogger(PageExporterImpl.class);



@ScriptVariable
protected com.day.cq.wcm.api.Page currentPage;

private Map<String, ComponentExporter> childModels = null;

protected String title;
protected String description;


@Override
public String getTitle() {
return "title";
}
@Override
public String getDescription() {
return description;
}

@Override
public String getLanguage() {
return currentPage == null ? Locale.getDefault().toLanguageTag()
: currentPage.getLanguage(false).toLanguageTag();
}

@PostConstruct
@Override
protected void initModel() {
title = currentPage.getTitle();
description = currentPage.getDescription();
}

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

@Override
public Map<String, ? extends ComponentExporter> getExportedItemsString() {
String response = "";
Map<String, ? extends ComponentExporter> componentMap = (Map<String, ? extends ComponentExporter>) super.getExportedItems();
Set<String> childKeyList = super.getExportedItems().keySet();
for (String childKey : childKeyList) {
ResponsiveGrid res= (ResponsiveGrid) componentMap.get(childKey);
Map<String, ? extends ComponentExporter> resMap = res.getExportedItems();
return resMap;
}
return null;

}

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

@Override
@JsonIgnore
public String getExportedType() {
// TODO Auto-generated method stub
return null;
}

}

Avatar

Community Advisor

Can you try with the below and confirm?

 

@Model(adaptables = Resource.class, resourceType="/apps/contentservices/components/structure/page", defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)

@exporter(name = "jackson", selector="caas", extensions = "json", options = { @ExporterOption(name = "SerializationFeature.WRITE_DATES_AS_TIMESTAMPS", value = "true") })

Avatar

Community Advisor

Hi @vikrams57194913,

Per the snippet you have shared, below highlighted in bold is the issue. This is OOTB Core Page Implementation which is internal and not allowed to use as is directly. (Bundle should have been in installed state)

If you are to use its method implementation or override, use Sling model delegation pattern. 

 

public class PageExporterImpl extends com.adobe.cq.wcm.core.components.internal.models.v1.PageImpl implements PageExporter{

Avatar

Level 2

No, that's not the issue, I have already tried that also. But getting the same error.

 

@Model(adaptables = SlingHttpServletRequest.class, adapters = { PageExporter.class,
ComponentExporter.class }, resourceType = PageExporterImpl.RESOURCE_TYPE)
@exporter(name = ExporterConstants.SLING_MODEL_EXPORTER_NAME,selector="mobile",extensions = ExporterConstants.SLING_MODEL_EXTENSION)
//@Model(adaptables = Resource.class, resourceType="/apps/contentservices/components/structure/page", defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
//
//@Exporter(name = "jackson", selector="caas", extensions = "json", options = { @ExporterOption(name = "SerializationFeature.WRITE_DATES_AS_TIMESTAMPS", value = "true") })
public class PageExporterImpl implements PageExporter{

protected static final String RESOURCE_TYPE = "contentservices/components/structure/page";

/** The logger. */
private final Logger logger = LoggerFactory.getLogger(PageExporterImpl.class);

/** The sling http servlet request. */
@Deleted Account(injectionStrategy = InjectionStrategy.REQUIRED)
private SlingHttpServletRequest slingHttpServletRequest;

@ScriptVariable
protected com.day.cq.wcm.api.Page currentPage;

private Map<String, ComponentExporter> childModels = null;

protected String title;
protected String description;



@Override
public String getTitle() {
return "title";
}
@Override
public String getDescription() {
return description;
}

@Override
public String getLanguage() {
return currentPage == null ? Locale.getDefault().toLanguageTag()
: currentPage.getLanguage(false).toLanguageTag();
}

@PostConstruct
protected void initModel() {
title = currentPage.getTitle();
description = currentPage.getDescription();

}

@Override
@JsonIgnore
public String getExportedType() {
return null;
}

}

Avatar

Community Advisor

@vikrams57194913 

Is it possible for you to share the details about your POC. (Use case that you are trying to achieve)

And about this resource - contentservices/components/structure/page. Is this in turn inheriting from any other Page resource. If yes, share the complete resource hierarchy

Avatar

Level 2

Actually, we are planning to use the exporter at two level, one is page level and second is component level, but we want to run these exporter on the basis of selector.

 

 

contentservices/components/structure/page - It's resource super type is core/wcm/components/page/v2/page 

 

Please let me know if you need any further details.

 

Thanks

 

Avatar

Community Advisor

Thanks for the inputs @vikrams57194913 

Can you share the complete Java package name of PageExporter that you are implementing from. 

 

Avatar

Level 2

 

Sure, please find the interface PageExporter code below:

 

 

@ConsumerType
public interface PageExporter extends ComponentExporter{

default String getLanguage() {
return null;
}

default String getTitle() {
return null;
}

default String getDescription() {
return null;
}

}

Avatar

Community Advisor

Thanks for sharing @vikrams57194913 .

Have a backup of your model class separately and use the below (have used the PageExporter interface similar to yours), see if it works. 

getMessage is just added for testing if it is from this impl (Its not part of the interface)

 

package com.aem.demoproject.core.models;

import com.adobe.cq.export.json.ComponentExporter;
import com.adobe.cq.export.json.ExporterConstants;
import com.day.cq.wcm.api.Page;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.models.annotations.Exporter;
import org.apache.sling.models.annotations.Model;

import javax.annotation.Nonnull;
import javax.inject.Inject;

@Model(adaptables = SlingHttpServletRequest.class, adapters = {PageExporter.class,
        ComponentExporter.class}, resourceType = PageExporterImpl.RESOURCE_TYPE)
@Exporter(name = ExporterConstants.SLING_MODEL_EXPORTER_NAME, selector = "mobile", extensions = ExporterConstants.SLING_MODEL_EXTENSION)
public class PageExporterImpl implements PageExporter {

    protected static final String RESOURCE_TYPE = "demoproject/components/structure/page";

    @Inject
    private Page currentPage;

    @Nonnull
    @Override
    public String getExportedType() {
        return RESOURCE_TYPE;
    }

    @Override
    public String getLanguage() {
        return "en";
    }

    @Override
    public String getTitle() {
        return currentPage != null ? currentPage.getTitle() : "Default Title";
    }

    @Override
    public String getDescription() {
        return currentPage != null ? currentPage.getDescription() : "Default Desc";
    }

    public String getMessage(){
        return "Custom Page Exporter Impl";
    }
}

Result :

 

Vijayalakshmi_S_0-1637267493508.png

 

Avatar

Level 2

Thanks now its work. But now when I am using Sling model delegation pattern for retrieving page get exported items I am getting Null pointer exception.

 

@Model(adaptables = SlingHttpServletRequest.class, adapters = { PageExporter.class,
ComponentExporter.class, Page.class }, resourceType = PageExporterImpl.RESOURCE_TYPE, defaultInjectionStrategy=DefaultInjectionStrategy.OPTIONAL)
@exporter(name = ExporterConstants.SLING_MODEL_EXPORTER_NAME,selector = "mobile",extensions = ExporterConstants.SLING_MODEL_EXTENSION)
//@Model(adaptables = Resource.class, resourceType="/apps/contentservices/components/structure/page", defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
//
//@Exporter(name = "jackson", selector="caas", extensions = "json", options = { @ExporterOption(name = "SerializationFeature.WRITE_DATES_AS_TIMESTAMPS", value = "true") })
public class PageExporterImpl implements PageExporter, Page{

protected static final String RESOURCE_TYPE = "contentservices/components/structure/page";

/** The logger. */
private final Logger logger = LoggerFactory.getLogger(PageExporterImpl.class);

/** The sling http servlet request. */
@Deleted Account(injectionStrategy = InjectionStrategy.REQUIRED)
private SlingHttpServletRequest slingHttpServletRequest;

@ScriptVariable
protected com.day.cq.wcm.api.Page currentPage;

private Map<String, ComponentExporter> childModels = null;

protected String title;
protected String description;
@Deleted Account @Via(type = ResourceSuperType.class)
private Page page;


@Override
public String getTitle() {
return "title";
}
@Override
public String getDescription() {
return description;
}

@Override
public String getLanguage() {
return currentPage == null ? Locale.getDefault().toLanguageTag()
: currentPage.getLanguage(false).toLanguageTag();
}

@PostConstruct
protected void initModel() {
title = currentPage.getTitle();
description = currentPage.getDescription();

}


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

@Override
@JsonIgnore
public String getExportedType() {
// TODO Auto-generated method stub
return null;
}

}

Avatar

Community Advisor

We need to provide implementation for all methods and delegate. We can make use of lombok @Delegate annotation for this - https://www.initialyze.com/blog/2020/11/simplify-extending-sling-models-with-lombok/

 

Also, note that your custom interface(PageExporter) has methods in the same name as OOTB core Page interface. Cross check the same and rename/remove based on your need. 

 

You can use the below and see if it works. 

package com.aem.demoproject.core.models;

import com.adobe.cq.export.json.ComponentExporter;
import com.adobe.cq.export.json.ExporterConstants;
import com.day.cq.wcm.api.Page;
import lombok.experimental.Delegate;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.models.annotations.Exporter;
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;

import javax.inject.Inject;
import java.util.Map;

@Model(adaptables = SlingHttpServletRequest.class, adapters = {com.adobe.cq.wcm.core.components.models.Page.class}, resourceType = PageExporterImpl.RESOURCE_TYPE)
@Exporter(name = ExporterConstants.SLING_MODEL_EXPORTER_NAME, selector = "mobile", extensions = ExporterConstants.SLING_MODEL_EXTENSION)
public class PageExporterImpl implements com.adobe.cq.wcm.core.components.models.Page {

    protected static final String RESOURCE_TYPE = "demoproject/components/structure/page";

    @Inject
    private Page currentPage;

    @Delegate(types = com.adobe.cq.wcm.core.components.models.Page.class, excludes = Handled.class)
    @Self
    @Via(type = ResourceSuperType.class)
    private com.adobe.cq.wcm.core.components.models.Page pageDelegate;

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

    public String getMessage() {
        return "Custom Page Exporter Impl";
    }

    protected static interface Handled {
        public Map<String, ? extends ComponentExporter> getExportedItems();
    }
}

Result :

Vijayalakshmi_S_0-1637282572492.png

 

Avatar

Level 2
Getting below error with the above code:
<h1>Error during include of component
'/apps/contentservices/components/structure/page'</h1><h3>Error Message:</h3> <pre>org.apache.sling.models.factory.MissingElementsException:
Could not inject all required fields into class
ca.demo.web.contentservices.core.impl.PageExporterImpl</pre><h3>Processing Info:</h3> <table style='font-family: monospace'> <tr><td>Page</td><td>=</td><td>
/content/demo/internal/exportsite/en/clientaccess/help/secondlevela/level3<td></tr><tr>
<td>Resource Path</td><td>=</td><td>/content/demo/internal/exportsite/en/clientaccess/help
/secondlevela/level3/jcr:content<td></tr><tr><td>Cell</td><td>=</td><td>page<td></tr><tr>
<td>Cell Search Path</td><td>=</td><td>page|basicpage<td></tr><tr><td>Component Path</td>
<td>=</td><td>/apps/contentservices/components/structure/page<td></tr></table> <h3>Sling Request Progress:</h3>

I also applied defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
in the model, after that getting Null pointer exception on language etc.

Thanks

Avatar

Community Advisor

Please share your model class - PageExporterImpl (that introduced this exception)

Avatar

Level 2

package ca.sunlife.web.contentservices.core.impl;

import com.adobe.cq.export.json.ComponentExporter;
import com.adobe.cq.export.json.ExporterConstants;
import com.day.cq.wcm.api.NameConstants;
import com.day.cq.wcm.api.Page;
import lombok.experimental.Delegate;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.models.annotations.DefaultInjectionStrategy;
import org.apache.sling.models.annotations.Exporter;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.Via;
import org.apache.sling.models.annotations.injectorspecific.ScriptVariable;
import org.apache.sling.models.annotations.injectorspecific.Self;
import org.apache.sling.models.annotations.via.ResourceSuperType;

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

import java.util.Calendar;
import java.util.Locale;
import java.util.Map;

@Model(adaptables = SlingHttpServletRequest.class,defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL, adapters = {com.adobe.cq.wcm.core.components.models.Page.class}, resourceType = PageExporterImpl.RESOURCE_TYPE)
@exporter(name = ExporterConstants.SLING_MODEL_EXPORTER_NAME, selector = "mobile", extensions = ExporterConstants.SLING_MODEL_EXTENSION)
public class PageExporterImpl implements com.adobe.cq.wcm.core.components.models.Page {

protected static final String RESOURCE_TYPE = "contentservices/components/structure/page";

 

@Delegate(types = com.adobe.cq.wcm.core.components.models.Page.class, excludes = Handled.class)
@Deleted Account
@Via(type = ResourceSuperType.class)
private com.adobe.cq.wcm.core.components.models.Page pageDelegate;

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

public String getMessage() {
return "Custom Page Exporter Impl";
}

protected static interface Handled {
public Map<String, ? extends ComponentExporter> getExportedItems();
}

}

Avatar

Level 2

But below code works for me

 

@Model(adaptables = SlingHttpServletRequest.class,defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL, adapters = {PageExporter.class}, resourceType = PageExporterImpl.RESOURCE_TYPE)
@exporter(name = ExporterConstants.SLING_MODEL_EXPORTER_NAME, selector = "mobile", extensions = ExporterConstants.SLING_MODEL_EXTENSION)
public class PageExporterImpl extends PageImpl implements PageExporter {

protected static final String RESOURCE_TYPE = "contentservices/components/structure/page";

@Override
public String getExportedType() {
return null;
}

@Override
public Map<String, ? extends ComponentExporter> getExportedItems() {
Map<String, ? extends ComponentExporter> componentMap = super.getExportedItems();
Set<String> childKeyList = super.getExportedItems().keySet();
for (String childKey : childKeyList) {
ResponsiveGrid res= (ResponsiveGrid) componentMap.get(childKey);
Map<String, ? extends ComponentExporter> componentMap1 = res.getExportedItems();
return componentMap1;
}
return componentMap;
}

@Override
public String getLanguage() {
return currentPage == null ? Locale.getDefault().toLanguageTag()
: currentPage.getLanguage(false).toLanguageTag();
}
}

 

 

Actually we need to manipulate the getExportedItems method, as its provide the full JSON structure like below but we only want items and itemsOrder from inside the root.

 

vikrams57194913_0-1637333646584.png

 

Avatar

Community Advisor

getExportedItems method of Core Page (PageImpl.java), just returns the Map of child nodes with its respective model class. 

In the screenshot above, root is the child node name with resourceType set as wcm/foundation/components/responsivegrid (:type). 

 

So in the screenshot you have shared is the getters(columnCount till :type) exposed by ResponsiveGrid Model (com.day.cq.wcm.foundation.model.responsivegrid.ResponsiveGrid)

 

Given this, the requirement you stated now is like altering the OOTB ResponsiveGrid model or since you are doing POC, perhaps you can give a try of custom responsivegrid component that inherits from OOTB grid and hence provide custom implementation.