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.

Comments

Linda said…
Regarding this line:

request = ComponentUtil.getRequest();

Where is ComponentUtil defined?

I'm not able to get the slingRequest/HttpServletRequest instance from inside my classes.
rwxrwx said…
ComponentUtil:
Isn't it the same as RequestUtil he describes here:
http://suryakand-shinde.blogspot.com/2011/04/java-threadlocal-and-its-use.html
Suryakant said…
Hi,

Yes rwxrwx is right. ComponentUtil class is nothing but a class with few static method that sets the current request object in thread local scope. Here is the code of ComponentUtil class:


import java.util.LinkedList;

import javax.servlet.http.HttpServletRequest;

public abstract class ComponentUtil {
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();
};
}
}


In order to use this class, you need to call ComponentUtil.setRequest(request) at the entry level of request, I would suggest to call this method to set the request at CQ's template level.

Let me know if you have futher questions.

Thanks,
-- Surya
gaurav kapoor said…
Hi All,
Can you please let me the options to integrate existing J2EE application with Day CQ CMS ?
I mean how can i display the CQ content on the JSP pages in J2EE application ?

Thanks in advance !!
Great one..!Thank you...!!
Anonymous said…
Hi Surya,

we have added the above listed files. But after the deployment in publish and author, Bundle is changing to installed state and with the exception component cannot be resolved error. Package which we added is not getting recognized.

Kindly let us know do we need to add anything apart from the above listed files.
Thanks in Advance.

Popular posts from this blog

Sling Authentication

CQ Development - OSGi bundles and Components

Create an AEM (CQ) project using Maven

Multiple log files using log4j appender