Sunday, April 24, 2011

Java ThreadLocal and It's use


Have you ever wondered about accessing you HTTPServletRequest object (or any other contextual/scoped object) in a non web class? What I mean by non web class is any class that does not goes through a web request/response initialization life cycle. HttpServlet is a web class because web container initializes and provides the HttpServletRequest and HttpServletResponse objects.

Let’s say you have a POJO and you want to initialize few properties/variables of it from request object/parameters. Since the POJO is plain class it by default does not have access to the request object, then how to get access if current request Object? We have two options:

1.       Override the public constructor of POJO class to accept HttpServletRequest as an argument
e.g. public MyClass(HttpServletRequest request) { }

When we are forcing a class’s constructor to accept an Object, we always need to make sure that we are passing it and the class is tightly coupled with the underlying implementation. Also, we can not instantiate it without passing required arguments or null.

2.       Use the thread local capabilities of Java: Thread Local can be considered as a scope of access, like a request scope or session scope. It’s a thread scope. You can set any object in Thread Local and this object will be global and local to the specific thread which is accessing this object. Let’s take a look at the class that uses the thread local concept and then I’ll explain the code:

import java.util.LinkedList;
import javax.servlet.http.HttpServletRequest;

public abstract class RequestUtil {
private static ThreadLocal> threadLocalRequest =
new ThreadLocal>();

                public static void setRequest(HttpServletRequest request) {
                                LinkedList list =  threadLocalRequest.get();
                                if (list == null) {
                                                list = new LinkedList();
                                                threadLocalRequest.set(list);
                                }
                                list.addLast(request);
                }

                public static HttpServletRequest getRequest() {
                                LinkedList list =  threadLocalRequest.get();
                                if (list != null && list.size() > 0) {
                                                return list.getLast();
                                };
                                return null;
                }

                public static void clearRequest() {
                                LinkedList list = threadLocalRequest.get();
                                if (list != null && list.size() > 0) {
                                                list.removeLast();
                                };
                }
}


So what we are doing here is, we just created a simple class that has three static methods setRequest(),getRequest() and clearRequest() for setting, fetching and clearing request object from thread local scope. At the entry point of request (e.g. service or doGet or doPost method) we just need to call

RequestUtil.setRequest(request);

This will set the request object in thread local context/scope, and to access it from anywhere (any class) where we don’t have access to request object we just need to call:
       
RequestUtil.getRequest();

This call will always make sure that it returns the current request object in current thread.

If you explicitly want to clear the current request object from thread local scope just to make sure that it not accessible to any other then just call:

RequestUtil.clearRequest();
       
I hope this will help you to implement some useful piece of functionality in your application. You can refer the java doc for more information http://download.oracle.com/javase/1.4.2/docs/api/java/lang/ThreadLocal.html

Sling Authentication

User authentication is core of any application; it can be a desktop application, a web application or a web service. CQ is a tool that is mainly used for building component based web sites and provides content management capabilities. The underlying frameworks Apache Felix and Sling together provide the foundation for CQ’s authentication capabilities.

Let’s explore the web authentication in brief and then I’ll try to explain the CQ’s authentication in detail. Authentication for web application mainly works on request/response headers, session and cookies.

Request/response based authentication: When a page is requested from a web server, the server looks for some specific information in header (to be specific in request “Authorization” header) and if information is available in header then that information is used by server to validate a user. So Authorization header can contain the credentials in Basic form or Digested form. The basic authentication header has following format:

Authorization: Basic username:password (encrypted in Base64 encoding)

There are few other Basic schemes that are used by some sites, some examples are:
a)       User/Password scheme
Authorization:  user  fred:mypassword
b)       Kerberos
Authorization:  kerberos  kerberosauthenticationsparameters

Once the authorization header information is received by server, it’s the responsibility of server to decode the credentials and validate it based on the Authorization scheme. If the request is authenticated on server then a 200 (OK) response is sent back to browser and subsequent request uses the same header information for validating user requests. If a request can not be authenticated on server because of invalid information in Authorization header, the server sends back a 401/Unauthorized response back and user is present a login form to enter username/password.

Path based restriction for header authorization: Let’s say we have a web application that is structured as /a/b/c/ and /a/d/c/. When a user is trying to access a resource that is under /a/b/c path, they need to enter valid user name and password so that server can validate the provided information and will cache it in request Authorization header. Now, user is authenticated to access any resource under /a/b/c but, when user will try to access anything under /a/d/c/ the authorization information in header (cached for /a/b/c/ path) will not be used by server and a 401/Unautorized response is sent back to browser (and a form will be presented to user where user can enter user name and password). So authorization headers are path specific.

Request header based authorization does not deals with cookies or session, the authorization information is cached by browsers and is available till the browser is not closed or a user does not forcibly clears the header information (active logins)

NOTE: Authorization header is a request header (and NOT a response header). We can (and should) not explicitly set this header.


Session based authentication: When a resource is requested from server a unique id (called as session ID) is generated by server and is maintained on the server (something like JSESSIONID=053d59e1-3398-cd4a-8a12-61d371f9f120). For any subsequent requests server checks that whether it already contains a session ID for the received request or not? If a session ID is already there then the same session ID will be used. We can store arbitrary data/objects in session. When user provides a valid user name and password then application validates it and store some information in session to indicate that the user has already provided credentials and is authorized to access resources on server. If a user has not requested any resource for more than a timeout setting value from server then that user is treated as inactive and the his/her session (and data in it) is destroyed. Almost all servers have a setting for timeout value that can be configured based on the requirement to invalidate sessions. The main problem that we have with session is that sometimes it become difficult to replicate user session (on different servers) in clustered environment and authentication does not work properly but, its a deployment issue and there are various ways to fix it (like sticky session at load balancer level etc.)


Cookie based authentication: Cookies are small piece of data that is stored on user’s machine. Again the format of cookies is very simple, they are just key value pair (the value can be plain or encrypted, it depends on how do we sent it to browser). When a user requests some resource on server, browser sends cookies in request header and on server side application extract the required information from cookies. Some sites use cookies for authentication and store an encrypted value in cookies that is used by application/server to identify a user. For security reason many users disable cookies on their browser so, an application that is completely dependent on cookie based authentication will not work (if cookies are disabled). Also, it is worth to note that the size of cookie is restricted to max of 4K size.


Before actually implementing any of the available authentication scheme/mechanism it is important to consider following things:
1)       Who are our end users? Do they need to provide credentials for accessing resources on server?
2)       What kind of application we are developing? What APIs and framework we are using?
3)       Is it a secure site and the credentials should be encrypted before transmitting over wire?
4)       What kind of deployment environment we have in production? Is it a clustered environment with a load balancer? Do we have session replication mechanism in place if we are planning to deploy same application on multiple servers?

Based on type of content security, available resource and deployment environment we need to judge which combination fits well to our need.


CQ’s/Sling’s Authentication

We just explored the various ways of authenticating a user on server now let’s explore the CQ’s authentication implementation and how to develop our own site using CQ’s underlying frameworks (Sling and Felix). As I explained in my earlier post Understanding Day's CQ & Underlying frameworks - Part 1, Sling is a REST based web framework and Felix is and OSGI specification implementation and the authentication in CQ works little differently as compared to traditional applications. In traditional application we normally have a controller (at entry point of our application) that validates user but, in CQ a request goes through chain of authenticators exposed as OSGI services (CQ’s own authenticator and any custom authenticator that we have) for validating user.

Also, as I have mentioned in my earlier posts that CQ uses JCR (CRX) for storing data (files, images etc.) and user’s information. If an user want to access something on server (basically from JCR) then either they need to provide credentials (if the requested resource is secured using ACL) or they can access it directly if the resource does not have any ACL (access control list restrictions). So there are basically two types of users we can have in JCR:

a)       Anonymous: an anonymous user can only access unsecured/public resources and does not require any credentials.
b)       Registered: If a user wants to access secured/private resources on server then they must be registered on JCR and need to provide their credentials. JCR validates the provided credentials against the user account data/information stored in JCR repository and creates a JCR session (note that it is not HTTP session) and a resource resolver. I’ll cover the session and resource resolver later in this post.

Following are few main interfaces/classes that we need to explore for understanding Sling’s authentication framework:
1)       Authenticator (Interface)
This is an interfaces that defines basic login() and logout() methods that is implemented by an actual Authenticator class (e.g. SlingAuthenticator). What and how to implement login and logout functionality is complete responsibility of class that implements this interface.

2)       AuthenticationSupport (Interface): This interface defines handleSecurity() method which must be implemented class that is exposed as an Authenticator. Sling framework internally calls the handleSecurity() method for extracting the user credentials and returns a Boolean flag depending up on whether user validation is successful or failed.

3)       SlingAuthenticator (class):  This class implements Authenticator and AuthenticationSupport interfaces (see label 1 in below diagram) and is registered as an OSGI service with Felix. This class is foundation for Authentication in Sling, apart from implementing the methods from Authenticator and AuthenticationSupport class this class holds a mapping between request paths and corresponding authentication handlers (a class that implements AuthenticationHandler interface) that will be used for authenticating a request. When a user request for a resource from server, sling authenticator extracts the request path from request and it’ll try to find whether there is any authentication handler that is mapped for the path (see label 2 & 4 in below diagram), if an authentication handler is mapped for the requested path then the authentication control is delegated to authentication handler class.

4)       AuthenticationHandler (Interface): This interface defines extractCredentials(), requestCredentials() and dropCredentials() methods (see label 5 in below diagram) that must be implement by an Authentication Handler implementation class that we need to register/map as authentication handler with SlingAuthenticator service. Once Sling authentication has selected an authentication handler based on request path and deligated authentication job to a particular authentication handler implementation class then, following methods are called:

a.       extractCredentials(): Based on the authentication scheme that we want to implement (Authorization header based authentication, session based authentication or cookie based authentication) the implementation class should implement this method to extract credentials from (header, session or cookies) and must one of the following:
                                                               i.      AuthenticationInfo: If authentication is successful then this method must return an AuthenticationInfo object (that holds the authentication scheme used, user name and password) to SlingAuthenticator. If valid AuthenticationInfo object is returned by this method then sling authenticator will NOT execute any other Authentication Handler that is in the list after the current authentication handler.
                                                              ii.      Null: If authentication failed then this method must return a null. When a null value is returned from this method, sling authenticator will try the next authentication handler that is mapped for this path (if any).
                                                            iii.      AuthenticationInfo.DOING_AUTH: if authentication is failed or because of some reason we want to redirect request to some other page (that can be a login page or any other resource) then this method must return DOING_AUTH to sling authenticator. When sling authenticator receives a DOING_AUTH as return value then it stops there and request is suspended.
b.       requestCredentials(): An authentication handler that implements AuthenticationHandler interface must implement this method for rendering a login form. If validation fails and sling framework wants to ask for user name and password in that case sling authenticator will call this method and users will be presented a login form to enter user name and password.
c.        dropCredentials(): An authentication handler that implements AuthenticationHandler interface should implement this method to removed user credentials (from either authorization header, session or cookies). When a user logs out, sling authenticator calls this method to remove/clean user credentials.

Once all authentication handler have been executed and no one responded back with a valid credentials (AuthenticationInfo), then sling authenticator will try to see if anonymous user can access the requested resource (see label 3 in below diagram).

5)       AbstractHTTPAuthHandler (abstract class): This is an abstract class that has basic authentication handler implementation. This class implements Basic Authorization scheme with base 64 encoding of username and password. This class has basic implementation of extractCredentials(), requestCredentials() and dropCredentials() methods (see label 6 & 7 in below in diagram) and provides an abstract method getLoginPage() that can be implemented by child classes to return a custom login page.

6)       HTTPAuthHandler (class):  This class extends AbstractHTTPAuthHandler and implements Basic Authorization (see label 9 & 10 in below diagram) and CUG support) and is mapped for root path (/) i.e. by default this is the authentication handler that will be called by Sling Authenticator.



UML representation of Sling/CQ Authentication Implementation



The AuthenticationInfo class

For more information on sling authentication you can refer http://sling.apache.org/site/authentication-framework.html

Friday, April 22, 2011

CQ - Component Design/Development best practices

One of the main aims that CQ focuses on is, component based development. In my last post I have tried to give an overview of components and in this post I am going to explain some best practices that we can follow for designing/developing CQ components.

Following are the main points that we should consider before developing a component:
1)       What is the main function of a component?
2)       Is the component going to be only UI component or is there some business logic associated with it?
3)       If a component has some business logic associated with it then is it going to be same for all websites?
4)       Is this component is abstract i.e. component itself does not provide any functionality but other components will be inheriting from it and will be adding their own logic (UI or business)?

Once we have decided purpose of component we need to jump on developing it (by writing JSP, CSS, JS and Java code). So, while developing components we should try to follow some best practices and conventions that helps our peers (other developers and teams) to understand the code and it also makes the maintenance task easy:
1)       We should avoid writing business logic at UI layer (i.e JSP). It’s always a good practice to extract the business logic in to a separate class so that it can be used in other parts of application, this is very important because our business logic should NOT be scattered around multiple areas. Also, when we have our business logic at one place it makes maintenance task easy, we just need to change it at one place and it’ll be reflected for all. For those case where we have a requirement of different business logic for different component in that case we can extend existing class(es) and add our own functionality.
2)       User JSP beans (jsp:usebean) for accessing and class/logic.
3)       Application level initialization should be done at one single place. Commonly used variables like user account information, session etc. should not be initialized by each component/page, we should have a common place that is hooked up at entry level of application (when a request comes in for a page) and is initialized only once per request.
4)       Use the JSP Expression Language (EL) as much as possible that keeps out code clean/readable.
5)       Avoid defining CSS styling in the components, this keeps our component loosely coupled from styling perspective and we can modify it as and when required. We can have some kind of convention for naming the HTML elements so that it can be modified via and external CSS file.
6)       It’s fair enough to keep JavaScript code in JSP file but, if there is some code is common for all components then it should be moved to a common JS file.

Ok, let’s try to build a component. I am going to build a component that will read the RSS feeds and will display it on web page, we can place this component on any page.

Purpose “RSS feed” component:
1)       It should read the RSS feeds supplied by various web sites and should display it on web page.
2)       A user can choose to display RSS feeds from a specific site so component should adapt itself to display the RSS feeds from URL (configured in user’s preference) when he login.
3)       User should be able to place the component on as many pages as pages they want.

Development consideration:
1)       The URL from where RSS feeds can be pull should be configurable (we’ll CQ’s dialog feature here).
2)       I should have control over the number of feeds that is going to be displayed at one time (again we’ll user CQ’s dialog feature).
3)       The logic which parses the RSS feeds might change in future and it should not be coded inside component, my components main responsibility is just displaying the RSS content.
4)       Styling of a component should be configurable (i.e. it should come from CSS).

Component Setup:
I’ll create a CQ component that consist a
a)       JSP file: that contains the rendering logic.
b)       JS (JQuery) code (embedded in JSP itself): that handles the click events.
c)       A generic helper class (RequestAwareComponent) that initializes the properties (jcr:content properties configured via dialogs) of a component and current request object (thread local request).
i.   RequestAwareComponent: there are few things that is common for all components, all components alwaysneed to access certain properties (jcr:content nodes authored by site authors) and data from request so I have abstracted this piece in RequestAwareComponent which performs property initialization and data extraction tasks from request.
d)       Supporting classes: component backing bean (RSSFeedComponent) that contains the logic of reading/parsing the RSS feed component, an entity object (RSSFeed) that represents a rss feed entry
i.   RSSFeedComponent: This class contains rss feed parsing logic and constant for each author able property. We should define (and use) the constants for component properties because if property name(s) changes in future then the changes that we need to do will be easier (just changing the constant value). Also, since we have the parsing logic in a class this same class can be used by other components (or some other piece of application).
ii.  RSSFeed : this class represents a RSS feed entry with getters and setters for accessing feed data (title, link, publish date, author and description). We should always (wherever possible) represents our domain data in the form of objects so that it can be transferred to the other piece of application and that piece can access it without knowing how (and who) has created it.



UML representation of classes that I have used for creating RSS feed component


This how the final component looks like, I have mapped the RSSFeed Object to the actual RSS feed entry that is displayed in below diagram:



Final RSS Feed component: Mapping between RSSFeed class and Actual RSS feed entry


As you can see that we have map the RSSFeed class with actual component and it makes easy to use same RSSFeed class (object) as data for other components. Now let’s see the actual code, first I’ll try to explain the classes and then I’ll move on to the components and its styling.

The RequestAwareComponent class: This class is a base class that holds the Current Request object (HttpServletRequest), component properties (valueMap) and a flag (validValueMap) that indicates whether the value map is valid or not ? Each component that has some business logic should extend this class and implement intializeComponentProperties() method to initialize component specific properties (I’ll show an example of this shortly).

import javax.servlet.http.HttpServletRequest;
import org.apache.sling.api.resource.ValueMap;

public abstract class RequestAwareComponent {
                public static final String COMPONENT_PROPERTIES = "properties";
                private HttpServletRequest request;
                private ValueMap valueMap;
                private boolean validValueMap = false;
               
                public RequestAwareComponent() {
                                intialize();
                }
               
                protected void intialize() {
                                request = ComponentUtil.getRequest();
                               
                                if(request != null) {
                                                valueMap = (ValueMap)request.getAttribute(
                                                                                RequestAwareComponent.COMPONENT_PROPERTIES);
                                                if(valueMap != null) {
                                                                intializeComponentProperties();
                                                                validValueMap = true;
                                                }
                                }
                }
               
                /**
                 * This class must be implemented by extended component
                 * classes to do component specific initializations
                 */
                public abstract void intializeComponentProperties();

                public HttpServletRequest getRequest() {
                                return request;
                }

                public void setRequest(HttpServletRequest request) {
                                this.request = request;
                }

                public ValueMap getValueMap() {
                                return valueMap;
                }

                public void setValueMap(ValueMap valueMap) {
                                this.valueMap = valueMap;
                }

                public boolean isValidValueMap() {
                                return validValueMap;
                }

                public void setValidValueMap(boolean validValueMap) {
                                this.validValueMap = validValueMap;
                }
}


The RSSFeedComponent class: This class is component backing class and it extends from RequestAwareComponent.This class will implement the intializeComponentProperties() method to initialize the component specific properties (jcr:content) and will have another component specific method getRssFeeds() that will return the feed entries (List) parsed from provided RSS feed URL.

import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.sling.api.resource.ValueMap;
import org.w3c.dom.CharacterData;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class RSSFeedComponent extends RequestAwareComponent {
                public static final String COMPONENT_PROPERTIES = "rssFeedComponentProperties";
                //Component Constants
                public static final String RSS_ITEM_ATTRIBUE = "item";
                public static final String RSS_TITLE_ATTRIBUE = "title";
                public static final String RSS_LINK_ATTRIBUE = "link";
                public static final String RSS_PUBDATE_ATTRIBUE = "pubDate";
                public static final String RSS_AUTHOR_ATTRIBUE = "author";
                public static final String RSS_DESCRIPTION_ATTRIBUE = "description";

                //Component Value/Property constants
                public static final String RSS_FEED_ULR = "rssFeedUrl";
                public static final String RSS_MAX_FEED_COUNT = "maxFeedCount";

                //Components (Configurable) Attributes
                private String rssFeedUrl;
                private String maxFeedCount;

                public RSSFeedComponent() {

                }

                public void intializeComponentProperties() {
                                if(getValueMap() != null && getRequest() != null) {
                                                setValueMap((ValueMap)getRequest().getAttribute(
                                                                                RSSFeedComponent.COMPONENT_PROPERTIES));
                                }
                                this.rssFeedUrl = getValueMap().get(RSS_FEED_ULR,
                                                                "http://suryakand-shinde.blogspot.com/feeds/posts/default?alt=rss");
                                this.maxFeedCount = getValueMap().get(RSS_MAX_FEED_COUNT, "2");
                }

                public List getRssFeeds() {
                                List feeds = null;

                                try {
                                                DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
                                                URL u = new URL(this.rssFeedUrl);
                                                Document doc = builder.parse(u.openStream());
                                                NodeList nodes = doc.getElementsByTagName(RSS_ITEM_ATTRIBUE);

                                                if(nodes != null) {
                                                                feeds = new ArrayList();
                                                                for(int feedIndex = 0; feedIndex
                                                                                if(feedIndex < Integer.parseInt(this.maxFeedCount)) {
                                                                                                Element element = (Element)nodes.item(feedIndex);
                                                                                                feeds.add(new RSSFeed(getElementValue(element, RSS_TITLE_ATTRIBUE),
                                                                                                                getElementValue(element, RSS_LINK_ATTRIBUE),
                                                                                                                getElementValue(element, RSS_PUBDATE_ATTRIBUE),
                                                                                                                getElementValue(element, RSS_AUTHOR_ATTRIBUE),
                                                                                                                getElementValue(element, RSS_DESCRIPTION_ATTRIBUE)));
                                                                                } else {
                                                                                                break;
                                                                                }
                                                                }
                                                }

                                } catch(Exception ex) {
                                                ex.printStackTrace();
                                }
                                return feeds;
                }


                public String getElementValue(Element parent,String label) {
                                return getCharacterDataFromElement(
                                                                (Element)parent.getElementsByTagName(label).item(0));
                }

                public String getCharacterDataFromElement(Element e) {
                                if(e != null) {
                                                try {
                                                                Node child = e.getFirstChild();
                                                                if(child instanceof CharacterData) {
                                                                                CharacterData cd = (CharacterData) child;
                                                                                return cd.getData();
                                                                }
                                                } catch(Exception ex) {
                                                                ex.printStackTrace();
                                                }
                                }
                               
                                return " ";
                }

                public String getRssFeedUrl() {
                                return rssFeedUrl;
                }

                public void setRssFeedUrl(String rssFeedUrl) {
                                this.rssFeedUrl = rssFeedUrl;
                }

                public String getMaxFeedCount() {
                                return maxFeedCount;
                }

                public void setMaxFeedCount(String maxFeedCount) {
                                this.maxFeedCount = maxFeedCount;
                }

}


The RSSFeed class: This class is represents the RSS Feeds as a data structure. The component backing bean class parses the RSS feeds XML and constructs a list (List) of RSSFeed objects.

public class RSSFeed {
                private String title;
                private String link;
                private String publishDate;
                private String author;
                private String description;
               
                public RSSFeed (String title, String link, String publishDate, String author, String description) {
                                this.title = title;
                                this.link = link;
                                this.publishDate = publishDate;
                                this.author = author;
                                this.description = description;
                }
               
                public String getTitle() {
                                return title;
                }
                public void setTitle(String title) {
                                this.title = title;
                }
                public String getLink() {
                                return link;
                }
                public void setLink(String link) {
                                this.link = link;
                }
                public String getPublishDate() {
                                return publishDate;
                }
                public void setPublishDate(String publishDate) {
                                this.publishDate = publishDate;
                }
                public String getAuthor() {
                                return author;
                }
                public void setAuthor(String author) {
                                this.author = author;
                }
                public String getDescription() {
                                return description;
                }
                public void setDescription(String description) {
                                this.description = description;
                }
}

The RSS Feed component JSP: The JSP code is very simple, it will simple use the RSSFeedComponent class a jsp bean (rssFeeder) and will pass the component properties to it. The “rssFeeder” bean will return List that is iterated on JSP to render the feed data/entries. Please not that we are not writing the parsing logic and java code in JSP, rather its part of a reusable class (RSSFeedComponent), this keeps our rendering logic loosely couple with data and its parsing logic. Here is the code of JSP:


DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<%@include file="/libs/foundation/global.jsp" %>
<%@page import="com.newcorp.ccp.web.components.rssfeed.RSSFeedComponent"%>
<%--
    Development Notes
    =================
    Each component has its own properties and in order to pass those properties
    to component backing bean (in this case RSSFeedComponent class) we need to
    set this as an request (thread local) attribute.
   
    NOTE: while setting the attribute please use the component specific constant
    (COMPONENT_PROPERTIES) so that attributes key should not clash with each other.
--%>

<% request.setAttribute(RSSFeedComponent.COMPONENT_PROPERTIES, properties); %>
<jsp:useBean id="rssFeeder" class="com.newcorp.ccp.web.components.rssfeed.RSSFeedComponent" scope="page" />

<script>
    $(function() {
        $("#rssFeedBox" ).accordion({
           fillSpace: true,
           collapsible: true
        });
    });
script>

<c:choose>
                <c:when test="${not empty rssFeeder.rssFeeds}">
                                <div id="rssFeedBox">
                                                <c:forEach var="feedItem" items="${rssFeeder.rssFeeds}" varStatus="feedCounter" >
                                                                <h3><a href="#">Title: ${feedItem.title}a>h3>
                                                                <div>
                                                                                <span id="rssAuthor">Author: ${feedItem.author}<br>span>
                                                                                <span id="rssLink">Link: <a href="${feedItem.link}">Visit Bloga><br>span>
                                                                                <span id="rssPubDate">Description: ${feedItem.description}<br>span>
                                                                                <span id="rssPubDate">Date: ${feedItem.publishDate}<br>span>
                                                                div>                                                        
                                                c:forEach>
                                div>
                c:when>
                <c:otherwise>
                   <span id="rssPubDate">RSS Feeds (from: <c:out value="${rssFeeder.rssFeedUrl}" />) is unavailable at this time!<br>span>
                c:otherwise>
c:choose>


There are many other ways to lay down a foundation/convention for components but, it all depends on what kind of application we are building and what level of extensibility we want to achieve. Please feel free to comment and provide your suggestions.

Strapi CMS Plugin - Generate Content using Gen AI

An AI Strapi Plugin (@genai/gemini-content-generator) to generate content using Gen AI in Strapi Introduction The @genai/gemini-content-ge...