http://www.developer.com/

Back to article

Reusable Syndicated Media Portlets: An Example of Simplified Content Presentation


July 22, 2008

In the early days of the World Wide Web, the battle cry of successful sites was "Content is king!" Web pages needed to be content heavy to gain rankings on the search engines and draw visitors, and the content needed to be updated regularly to bring those visitors back. As the technology evolved and business models changed, functionality and interactivity gained importance in capturing clicks. Portals provide the most manageable framework for organizing and presenting both functionality and content. But, "most manageable" isn't the same is "easily manageable" and content is still a key part of a successful portal. For portal developers and administrators, this means that the content for a portal will grow, both in source and type.

Managing Content Growth

All portal frameworks either include a content management system (CMS) or provide an interface to popular CMS applications (or both). CMS is an important topic of its own, and in some case the CMS approach will be all that you need. Or, you may have an external content provider, many of which provide presentation capabilities as well. RSS feeds, blogging, and wiki frameworks are also sources of content. Each of these content sources generally provides some method of presentation that can be used out-of-the-box (OOTB).

Sometimes, the presentation that comes OOTB doesn't fit your requirements. In that case, you need to build custom portlets to render the content. The usual approach is to build a complete portlet for each presentation. For the sake of semantics, let me define a complete portlet as minimally consisting of a DAO, a controller, a JSP, and a portlet configuration file (frequently includes a delegate class as well). For one content portlet, this isn't so much work and is fairly easy to manage. But, for three portlets—that is, 12 to 16 files and a completely new deployment for each additional portlet—it doesn't take long for this to become unwieldy.

For some reason, reuse isn't often applied to these types of situations. Because the content is different for each of these presentations, it is frequently considered a given that the solution needs to be different. Of course, "frequently" doesn't mean "always" and there are probably many other solutions than what will be demonstrated in this article (yes, that is four non-specific qualifiers in the same sentence).

One Solution to Many Content Portlets

Let me start with some specific qualifiers to clarify the intent and approach of this solution. The code snippets, IDE references, and framework naming conventions used in this article are based on the WebLogic Portal. The approach will work on any JSR-168 or JSR 286 compliant portal framework, only some of the base classes and file extensions may need to be modified. However, because the examples are built on WLP, some WLP-specific tips will be included and noted as such.

Note: The examples are not fully production-level in that they do not include robust error handling and do use string literals where static final values would provide far better performance. Please be sure to address this if you adapt this example to your own solution.

The data model used for the examples is XML, and the DAO object is a static dummy bean because this article is based on a real-world example where that part of the code is proprietary and the data source is irrelevant to the solution. That dummy object always doubles as a delegate.

Using a delegate class in this approach will allow you the highest level of reuse. Your delegate can expose an interface for your controller that is consistent for content in general. Then, no matter what your content source or format is, the delegate can abstract away all of the differences, returning it to your controller with a single method signature.

Take an iterative approach to building your content portlet, making the first step to simply fetch and display some content. Because your delegate is abstracting the nature of the content to a single method call, youcan start with a simple portlet controller:

public Forward begin()
{
   Forward forward = new Forward("default");
   List<String> contentDocList = new ArrayList<String>();

   contentDocList.add(ContentStub.
      getSyndicatedContent("techNews");
   forward.addActionOutput("contentDocList", contentDocList);

   return forward;
}

The content is set into a list to provide for the possibility of multiple calls to getSyndicatedContent for different content types of the same format, such as general news type that has multiple categories.

The choice of using a PageFlow controller above is based on it being the fastest way to build a portlet in Workshop. This leads to your first WebLogic-specific note.

WebLogic-Specific Note: PageFlow controls from at least version 9.2 are stored in the session; this makes this solution a major resource saver as well. The use of action output is also for the sake of speed. It could just easily be request attribute or a bean field.

Skipping the basic JSP elements and field declarations, the content then can be rendered with the following:

saxBuilder = new SAXBuilder();
formatPath = (String)pageContext.getAttribute("contentType");
docListIt  = contentDocList.iterator();
while(docListIt.hasNext())
{
   stringReader  = new StringReader(docListIt.next());
   document      = saxBuilder.build(stringReader);
   contentList   = document.getRootElement().getChildren();
   contentListIt = contentList.iterator();
<%//assume if only one category there we don't need the heading.
   if(contentDocList.size()>1){%>
   <span style ="margin-left:10px" id =
      "<%=contentList.get(0).getChildText("category").
       replace(" ", "") %>_heading">
       <%=contentList.get(0).getChildText("category") %></span>
       <%} %>
   <ul style = "margin-top:0; margin-right:10px"
       id = "<%=contentList.get(0).getChildText("category") %>
       _headlines">
<% while(contentListIt.hasNext())
   {
      contentElement = contentListIt.next();%>
      <li>
         <a href = "<%=contentElement.getChildText("url") %>"
         target = "_blank">
         <%=contentElement.getChildText("headline") %></a>
      </li>
<%     } %></ul>

And finally, a basic portlet config file (again, using the WLP syntax for the sake of speed):

<netuix:portlet definitionLabel="simpleExample"
                title="Simple Example">
   <netuix:titlebar/>
   <netuix:content>
      <netuix:pageflowContent
              contentUri="/portlets/syndicatedContent/
                          SyndicatedContentController.jpf"/>
   </netuix:content>
</netuix:portlet>

So far, you haven't done anything special because you have only one type of content. But, what if your audience becomes worldlier and wants to know about what is going on in the world outside of technology? Often, that would result in writing all of the above all over again, except the delegate (and only because it takes a content source parameter; otherwise, you'd need another delegate, too). Instead, tweak what you already have by adding some very minor changes to your original components.

First, you will add a preference value to your first portlet definition to indicate the content source:

<netuix:portlet definitionLabel="techNews" title="Tech News">
   <netuix:titlebar/>
   <netuix:content>
      <netuix:pageflowContent
              contentUri="/portlets/syndicatedContent/
                          SyndicatedContentController.jpf"/>
   </netuix:content>
   <netuix:preference description="Content Source Key"
                      modifiable="true"
                      multiValued="true"
                      name="contentSources"
                      value="techContent"/>
</netuix:portlet>

Note that you have set mutliValued to true so that you can aggregate content sources with the same presentation if you want to at some later time.

Next, create a second definition file for the new content source:

<netuix:portlet definitionLabel="worldNews"
                title="World News">
   <netuix:titlebar/>
   <netuix:content>
      <netuix:pageflowContent contentUri="/portlets/
         syndicatedContent/SyndicatedContentController.jpf"/>
   </netuix:content>
   <netuix:preference description="Content Source Key"
                      modifiable="true"
                      multiValued="true"
                      name="contentSources"
                      value="worldNews"/>

Some portal frameworks (including WLP) allow for the run-time creation of portlet definition through an administration console, a viable alternative if available and (once you are done) will allow the addition of new content without redeploying the portal application, only updating the source application (assuming you have segregated your deployments between model and view). This is why you have modifiable set to true.

Next, you will modify your controller to read the contentSource preference and pass it as your parameter to the delegate:

backingContext =
   PortletBackingContext.getPortletBackingContext(getRequest());
preferences = backingContext.getPortletPreferences(getRequest());
contentPrefs = preferences.getValues("contentSources", null);

for(int i=0; i < contentPrefs.length; i++)
{
   strContent =
      ContentStub.getSyndicatedContent(contentPrefs[i].trim());
   //the trim operation is necessary due to some quirks
   //in how multiple preferences are stored in WLP
   if(strContent!=null){contentDocList.add(strContent);}
}

Now, rather than creating a whole new set of classes to provide World News in addition to Tech News, you have achieved the same with six lines of code and one configuration file.



Click here for a larger image.

Figure 1: Two Content Portlets from One Set of Code

Multiple Views with the Same Solution

You know that our portal content won't stop at news headlines. If your portal does well, you may want to track the value of stocks you bought with your bonuses for adding more content with less code. Of course, stock prices have a different layout than headlines, so you need a new set of code to add that, right? That's either obviously rhetorical or you skipped to this section to see whether the article was worth finishing. You can add stock prices to your content and then you can decide.

Because there will be multiple presentations of content, you need a way to define what type of content you will show. Go back to your first two portlet definition files to add one more preference:

<netuix:preference description="Presentation Style"
                   modifiable="true"
                   multiValued="false"
                   name="contentType"
                   value="news"/>

This time, you have set multiValued to false for the sake of simplicity. It is entirely possible to have multiple content types using this approach, though if all of the possibilities were covered you will never finish reading this article in time to use it.

Now,make some minor additions to your controller to allow for multiple presentation options. In the earlier examples, you skipped the forward (or view, to be more generic) definition. Here, you will include the forward declaration showing an additional parameter added for contentType:

@Jpf.Action(forwards = { @Jpf.Forward(name = "default",
   path = "index.jsp", actionOutputs =
   { @Jpf.ActionOutput(name = "contentDocList",
     type = java.util.List.class, required = false),
     @Jpf.ActionOutput(name = "contentType",
     type = java.lang.String.class, required = true) }) })

Then, you will add one line to pass this value through from the portlet configuration file to the JSP:

forward.addActionOutput("contentType",
   preferences.getValue("contentType", null));

Because you have a good chunk of code in creating the JSP, declaring the variables, and looping through the content, you can use an include for just the part that is different, rather than writing a whole new JSP that would be mostly a copy/paste from your first one. Before you go into the while loop, you can use the content type to determine your format path:

formatPath = (String)pageContext.getAttribute("contentType");

Then, pick the include that has your format-specific handling:

<% if(formatPath.equals("news")){%>
   <%@ include file="news.jspf" %>
<% }if(formatPath.equals("weather")){ %>
   <%@ include file="weather.jspf" %>
<% }if(formatPath.equals("stocks")){ %>
   <%@ include file="stocks.jspf" %><%} %>

Yes, weather is there and you haven't coded for it yet. But, you don't need to code for it. You just need to create a portlet definition for it and so long as your delegate can fetch weather, you can show it (or any other content format) based on the display templates in the includes. Although a new presentation layout will require adding a new include, and a new portlet, that is one JSP fragment and one configuration file to go from four content portlets to five, rather than the usual addition of four (or more) complete objects. If the weather is nice, you'll have more time to enjoy it.

WebLogic-Specific Note: The more recent versions of Workshop/WorkSpace studio that use AppXRay for validation require a patch for validating .jspf files as used in this article. If you have a non-commercial developer version, you will need to turn off JSP validation or use .jsp includes that take request parameters.

Conclusion

The example detailed in this article dealt with multiple presentations of multiple content types that can be managed more by configuration than new development. This approach is extensible to other content presentation solutions. A more complex solution could include multiple forward methods to provide content search or filtering, for example. There are also simpler uses for this approach, such as storing parameters for content management integration as preferences that would require no additional includes. If your portal framework supports creating new configurations at run time, you don't even need additional portlet definition files.

Where the reuse of patterns can reduce the time and risk of designing an application, finding opportunities to reuse code not only leads to faster development time, it reduces maintenance while improving reliability.

About the Author

Scott Nelson is a Senior Principal Consultant with 12 years of experience designing, developing, and maintaining web-based applications for manufacturing, pharmaceutical, financial services, non-profit organizations, and real estate agencies for use by employees, customers, vendors, franchisees, executive management, and others who use a browser. He also blogs all of the funny emails forwarded to him at Frequently Unasked Questions.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date