Did you know you can create an RSS feed in AEM (Adobe Experience Manager) for external applications like Eloqua? While AEM provides out-of-the-box functionality for RSS feeds, customizing them may require additional steps. Below you’ll find several options for creating RSS feeds in AEM along with steps for creating one using HTL. Â
3 Options to Create an RSS Feed in AEMÂ Â
Override Default JSP Functionality (JSP Approach)Â
Customize the JSP code to tailor the RSS feed according to your requirementsÂ
This approach requires writing backend logic in Java and JSP
Create a Servlet for RSS Feed
Implement the logic within the servlet to fetch and format the necessary data into RSS feed XML
Configure the servlet to respond to specific requests for the RSS feed endpoint
This approach allows more control and flexibility over the RSS feed generation process
Use HTL with Sling Model (HTL Approach)
Write HTL templates combined with a Sling Model to generate the RSS feed
Leverage Sling Models to retrieve data from AEM and format it within the HTL template
This approach utilizes AEM’s modern templating language and component models
HTL is preferred for templating tasks due to its simplicity and readability
Expected RSS FeedÂ
Below is the feed response for an external source to integrate and send emails accordingly. Here the feed results can be filtered by category tag names (category) using query parameters in the feed URL.Â
https://www.demoproject.com/products/aem.rssÂ
https://www.demoproject.com/products/aem.rss?category=web
<?xml version=”1.0″ encoding=”UTF-8″?>
<rss xmlns:atom=”http://www.w3.org/2005/Atom” version=”2.0″>
   <channel>
       <atom:link rel=”self” href=”https://www.demoproject.com/products/aem” />
       <link>https://www.demoproject.com/products/aem</link>
       <title>AEM</title>
       <description />
       <pubDate>Fri, 29 Sep 2023 02:08:26 +0000</pubDate>
       <item>
           <guid>https://www.demoproject.com/products/aem/one.rss.xml</guid>
           <atom:link rel=”self” href=”https://www.demoproject.com/products/aem/sites” />
           <link>https://www.demoproject.com/products/aem/sites</link>
           <title>Aem Sites</title>
           <description><![CDATA[AEM Sites is the content management system within Adobe Experience Manager that gives you one place to create, manage and deliver remarkable digital experiences across websites, mobile sites and on-site screens.]]></description>
           <pubDate>Tue, 31 Oct 2023 02:23:04 +0000</pubDate>
       </item>
       <item>
           <guid>https://www.demoproject.com/products/aem/two.rss.xml</guid>
           <atom:link rel=”self” href=”https://www.demoproject.com/products/aem/assets” />
           <link>https://www.demoproject.com/products/aem/assets</link>
           <title>Aem Assets</title>
           <description><![CDATA[Adobe Experience Manager (AEM) Assets is a digital asset management system (DAM) that is built into AEM. It stores and delivers a variety of assets (including images, videos, and documents) with their connected metadata in one secure location.]]></description>
           <pubDate>Thu, 26 Oct 2023 02:21:19 +0000</pubDate>
           <category>pdf,doc,image,web</category>
       </item>
   </channel>
</rss>
Steps for Creating RSS Feed Using HTLÂ
Create a HTML file under the page componentÂ
Create a PageFeed Sling Model that returns data for the RSS feedÂ
Add a rewrite rule in the dispatcher rewrite rules fileÂ
Update the ignoreUrlParams for the required paramsÂ
Page Component – RSS html Â
Create an HTML file with the name “rss.xml.html†under page component. Both ‘rss.html’ or ‘rss.xml.html’ work fine for this. Here, ‘rss.xml.html’ naming convention indicates that it is generating XML data. PageFeedModel provides the page JSON data for the expected feed. Â
Category tag is rendered only when page properties are authored with tag values
CDATA (character data) is a section of element content to render as only character data instead of markup
<?xml version=”1.0″ encoding=”UTF-8″?>
<sly data-sly-use.model=”com.demoproject.aem.core.models.PageFeedModel” />
<rss version=”2.0″ xmlns:atom=”http://www.w3.org/2005/Atom”>
   <channel>
       <atom:link rel=”self” href=”${model.link}”/>
       ${model.linkTag @ context=’unsafe’}
       <title>${model.title}</title>
       <description>${model.subTitle}</description>
       <pubDate>${model.publishedDate}</pubDate>
       <sly data-sly-list.childPage=”${model.entries}”>
           <item>
               <guid>${childPage.feedUrl}</guid>
               <atom:link rel=”self” href=”${childPage.link}”/>
               ${childPage.linkTag @ context=’unsafe’}
               <title>${childPage.title}</title>
               <description><![CDATA[${childPage.description}]]></description>
               <pubDate>${childPage.publishedDate}</pubDate>
               <sly data-sly-test=”${childPage.tags}”>
                   <category>${childPage.tags}</category>
               </sly>
           </item>
       </sly>
   </channel>
</rss>Â Â
Page Feed Model
This is a component model that takes the currentPage as the root and retrieves a list of its child pages. Subsequently, it dynamically constructs properties such as publish date and categories based on the page’s tag field. These properties enable filtering of results based on query parameters. Once implemented, you can seamlessly integrate this model into your component to render the RSS feed.
Using currentPage get the current page properties as a value mapÂ
Retrieve title, description, pubDate, link for current pageÂ
Retrieve title, description, pubDate, link, tags (categories) for child pagesÂ
Filter the child pages list based on the query param value (category)
//PageFeedModel sample code
package com.demoproject.aem.core.models;
import com.adobe.cq.export.json.ExporterConstants;
import com.day.cq.commons.Externalizer;
import com.day.cq.commons.jcr.JcrConstants;
import com.day.cq.wcm.api.Page;
import com.day.cq.wcm.api.PageFilter;
import com.demoproject.aem.core.utility.RssFeedUtils;
import lombok.Getter;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.SlingException;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
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.injectorspecific.SlingObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
@Model(adaptables = {
Resource.class,
SlingHttpServletRequest.class
}, resourceType = PageFeedModel.RESOURCE_TYPE, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
@Exporter(name = ExporterConstants.SLING_MODEL_EXPORTER_NAME, extensions = ExporterConstants.SLING_MODEL_EXTENSION)
public class PageFeedModel {
protected static final String RESOURCE_TYPE = “demoproject/components/page”;
private static final Logger logger = LoggerFactory.getLogger(PageFeedModel.class);
@SlingObject
ResourceResolver resourceResolver;
@SlingObject
SlingHttpServletRequest request;
@Inject
private Page currentPage;
@Getter
private String title;
@Getter
private String link;
@Getter
private String linkTag;
@Getter
private String description;
@Getter
private List < ChildPageModel > entries;
@Inject
private Externalizer externalizer;
@Getter
private String feedUrl;
@Getter
private String publishedDate;
@PostConstruct
protected void init() {
try {
ValueMap properties = currentPage.getContentResource().adaptTo(ValueMap.class);
title = StringEscapeUtils.escapeXml(null != currentPage.getTitle() ? currentPage.getTitle() : properties.get(JcrConstants.JCR_TITLE, String.class));
description = StringEscapeUtils.escapeXml(properties.get(JcrConstants.JCR_DESCRIPTION, String.class));
link = RssFeedUtils.getExternaliseUrl(currentPage.getPath(), externalizer, resourceResolver);
feedUrl = link + “.rss.xml”;
linkTag = RssFeedUtils.setLinkElements(link);
String category = request.getParameter(“category”) != null ? request.getParameter(“category”).toLowerCase().replaceAll(“\s”, “”) : StringUtils.EMPTY;
entries = new ArrayList < > ();
Iterator < Page > childPages = currentPage.listChildren(new PageFilter(false, false));
while (childPages.hasNext()) {
Page childPage = childPages.next();
ChildPageModel childPageModel = resourceResolver.getResource(childPage.getPath()).adaptTo(ChildPageModel.class);
if (null != childPageModel) {
if (StringUtils.isBlank(category)) entries.add(childPageModel);
else {
String tags = childPageModel.getTags();
if (StringUtils.isNotBlank(tags)) {
tags = tags.toLowerCase().replaceAll(“\s”, “”);
List tagsList = Arrays.asList(tags.split(“,”));
String[] categoryList = category.split(“,”);
boolean flag = true;
for (String categoryStr: categoryList) {
if (tagsList.contains(StringEscapeUtils.escapeXml(categoryStr)) && flag) {
entries.add(childPageModel);
flag = false;
}
}
}
}
}
}
publishedDate = RssFeedUtils.getPublishedDate(properties);
} catch (SlingException e) {
logger.error(“Repository Exception {}”, e);
}
}
}
//ChildPageModel
package com.demoproject.aem.core.models;
import com.adobe.cq.export.json.ExporterConstants;
import com.day.cq.commons.Externalizer;
import com.day.cq.commons.jcr.JcrConstants;
import com.demoproject.aem.core.utility.RssFeedUtils;
import lombok.Getter;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.sling.api.SlingException;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
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.injectorspecific.SlingObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
@Model(adaptables = {
Resource.class
}, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
@Exporter(name = ExporterConstants.SLING_MODEL_EXPORTER_NAME, extensions = ExporterConstants.SLING_MODEL_EXTENSION)
public class ChildPageModel {
private static final Logger logger = LoggerFactory.getLogger(ChildPageModel.class);
@SlingObject
Resource resource;
@Getter
private String title;
@Getter
private String link;
@Getter
private String linkTag;
@Getter
private String feedUrl;
@Getter
private String description;
@Getter
private String publishedDate;
@Getter
private String tags;
@Inject
private Externalizer externalizer;
@PostConstruct
protected void init() {
try {
if (null != resource) {
String url = resource.getPath();
ResourceResolver resourceResolver = resource.getResourceResolver();
link = RssFeedUtils.getExternaliseUrl(url, externalizer, resourceResolver);
feedUrl = link + “.rss.xml”;
linkTag = RssFeedUtils.setLinkElements(link);
ValueMap properties = resource.getChild(JcrConstants.JCR_CONTENT).adaptTo(ValueMap.class);
title = StringEscapeUtils.escapeXml(properties.get(JcrConstants.JCR_TITLE, String.class));
description = StringEscapeUtils.escapeXml(properties.get(JcrConstants.JCR_DESCRIPTION, String.class));
publishedDate = RssFeedUtils.getPublishedDate(properties);
tags = StringEscapeUtils.escapeXml(RssFeedUtils.getPageTags(properties, resourceResolver));
}
} catch (SlingException e) {
logger.error(“Error: ” + e.getMessage());
}
}
}
//RSS Feed Utils
package com.demoproject.aem.core.utility;
import com.day.cq.commons.Externalizer;
import com.day.cq.commons.jcr.JcrConstants;
import com.day.cq.tagging.Tag;
import com.day.cq.tagging.TagManager;
import com.day.cq.wcm.api.NameConstants;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ValueMap;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* @desc RSS Feed Utils
*/
@Slf4j
public class RssFeedUtils {
public static final String FORMAT_DATE = “E, dd MMM yyyy hh:mm:ss Z”;
public static final String CONTENT_PATH = “/content/demoproject/us/en”;
public static String getPublishedDate(ValueMap pageProperties) {
String publishedDate = StringUtils.EMPTY;
SimpleDateFormat dateFormat = new SimpleDateFormat(FORMAT_DATE);
Date updatedDateVal = pageProperties.get(JcrConstants.JCR_LASTMODIFIED, pageProperties.get(JcrConstants.JCR_CREATED, Date.class));
if (null != updatedDateVal) {
Date replicatedDate = pageProperties.get(NameConstants.PN_PAGE_LAST_REPLICATED, updatedDateVal);
publishedDate = dateFormat.format(replicatedDate);
}
return publishedDate;
}
public static String getExternaliseUrl(String pagePath, Externalizer externalizer, ResourceResolver resourceResolver) {
String url = StringUtils.EMPTY;
if (StringUtils.isNotBlank(pagePath) && null != externalizer && null != resourceResolver)
url = externalizer.publishLink(resourceResolver, resourceResolver.map(pagePath)).replace(CONTENT_PATH, “”);
return url;
}
public static String setLinkElements(String link) {
String url = StringUtils.EMPTY;
if (StringUtils.isNotBlank(link)) {
url = “<link>” + link + “</link>”;
}
return url;
}
public static String getPageTags(ValueMap properties, ResourceResolver resourceResolver) {
String tags = StringUtils.EMPTY;
String[] pageTags = properties.get(NameConstants.PN_TAGS, String[].class);
if (pageTags != null) {
List < String > tagList = new ArrayList < > ();
TagManager tagManager = resourceResolver.adaptTo(TagManager.class);
for (String tagStr: pageTags) {
Tag tag = tagManager.resolve(tagStr);
if (tag != null) {
tagList.add(tag.getName());
}
}
if (!tagList.isEmpty()) tags = String.join(“,”, tagList);
}
return tags;
}
}
Dispatcher Changes Â
demoproject_rewrites.rulesÂ
In the client project rewrites.rules (/src/conf.d/rewrites) file add a rewrite rule for .rss extension. This rewrite rule takes a URL ending with .rss and rewrites it to point to a corresponding rss.xml file in the page component, effectively changing the file extension from .rss to .rss.xml
#feed rewrite rule
RewriteRule ^/(.*).rss$ /content/demoproject/us/en/$1.rss.xml [PT,L]
100_demoproject_dispatcher_farm.any Â
Set the URL parameters that should not be cached for the rss feed. It is recommended that you configure the ignoreUrlParams setting in an allowlist manner. As such, all query parameters are ignored and only known or expected query parameters are exempt (denied) from being ignored.
When a parameter is ignored for a page, the page is cached upon its initial request. As a result, the system subsequently serves requests for the page using the cached version, irrespective of the parameter’s value in the request. Here, we add URL parameters below to serve the content live as required by an external application.
/ignoreUrlParams {
   /0001 { /glob “*” /type “allow” }
   /0002 { /glob “category” /type “deny” }
   /0003 { /glob “pubdate_gt” /type “deny” }
   /0004 { /glob “pubdate_lt” /type “deny” }
}
Â
Why is HTL Better? Â
We can utilize this approach to produce any XML feed, extending beyond RSS feeds. We have the flexibility to add custom properties to tailor the feed to our specific needs. Plus, we can easily apply filters using query parameters.
Â
Big thanks to my director, Grace Siwicki, for her invaluable assistance in brainstorming the implementation and completing this blog work.
Source: Read MoreÂ