Close Menu
    DevStackTipsDevStackTips
    • Home
    • News & Updates
      1. Tech & Work
      2. View All

      Sunshine And March Vibes (2025 Wallpapers Edition)

      May 16, 2025

      The Case For Minimal WordPress Setups: A Contrarian View On Theme Frameworks

      May 16, 2025

      How To Fix Largest Contentful Paint Issues With Subpart Analysis

      May 16, 2025

      How To Prevent WordPress SQL Injection Attacks

      May 16, 2025

      Microsoft has closed its “Experience Center” store in Sydney, Australia — as it ramps up a continued digital growth campaign

      May 16, 2025

      Bing Search APIs to be “decommissioned completely” as Microsoft urges developers to use its Azure agentic AI alternative

      May 16, 2025

      Microsoft might kill the Surface Laptop Studio as production is quietly halted

      May 16, 2025

      Minecraft licensing robbed us of this controversial NFL schedule release video

      May 16, 2025
    • Development
      1. Algorithms & Data Structures
      2. Artificial Intelligence
      3. Back-End Development
      4. Databases
      5. Front-End Development
      6. Libraries & Frameworks
      7. Machine Learning
      8. Security
      9. Software Engineering
      10. Tools & IDEs
      11. Web Design
      12. Web Development
      13. Web Security
      14. Programming Languages
        • PHP
        • JavaScript
      Featured

      The power of generators

      May 16, 2025
      Recent

      The power of generators

      May 16, 2025

      Simplify Factory Associations with Laravel’s UseFactory Attribute

      May 16, 2025

      This Week in Laravel: React Native, PhpStorm Junie, and more

      May 16, 2025
    • Operating Systems
      1. Windows
      2. Linux
      3. macOS
      Featured

      Microsoft has closed its “Experience Center” store in Sydney, Australia — as it ramps up a continued digital growth campaign

      May 16, 2025
      Recent

      Microsoft has closed its “Experience Center” store in Sydney, Australia — as it ramps up a continued digital growth campaign

      May 16, 2025

      Bing Search APIs to be “decommissioned completely” as Microsoft urges developers to use its Azure agentic AI alternative

      May 16, 2025

      Microsoft might kill the Surface Laptop Studio as production is quietly halted

      May 16, 2025
    • Learning Resources
      • Books
      • Cheatsheets
      • Tutorials & Guides
    Home»Development»Create an RSS Feed using HTL

    Create an RSS Feed using HTL

    May 3, 2024

    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 

    Facebook Twitter Reddit Email Copy Link
    Previous ArticleIntroducing configurable maximum throughput for Amazon DynamoDB on-demand
    Next Article Exploring frontiers of mechanical engineering

    Related Posts

    Security

    Nmap 7.96 Launches with Lightning-Fast DNS and 612 Scripts

    May 16, 2025
    Common Vulnerabilities and Exposures (CVEs)

    CVE-2025-2305 – Apache Linux Path Traversal Vulnerability

    May 16, 2025
    Leave A Reply Cancel Reply

    Continue Reading

    Distribution Release: Tails 6.4

    Development

    Critical mySCADA myPRO Flaws Could Let Attackers Take Over Industrial Control Systems

    Development

    Apple expanding iPhone’s NFC to third-party apps – all the ways that benefits you

    Development

    Enhancing Referral Management with Salesforce Health Cloud

    Development

    Highlights

    Google Drive is now available for Arm64 Windows 11 PCs

    March 26, 2025

    Google has finally launched its Drive app for Windows on Arm, months after the beta…

    EitBiz – Mobile App Development Company

    March 18, 2025

    Will OpenAI’s new AI detection tool put an end to student cheating?

    August 5, 2024

    Watch out Copilot, Google could add Gemini Live to Windows 11 taskbar via Chrome

    January 1, 2025
    © DevStackTips 2025. All rights reserved.
    • Contact
    • Privacy Policy

    Type above and press Enter to search. Press Esc to cancel.