Displaying feeds using Digital Data Connector - Yahoo News Demo using DDC


This article demonstrates how to display news feed(yahoo) in portal without any coding required.

One of new feature introduced in the Websphere Portal and Content Management 8.5/8.0.0.1  is Digital Data Connector , where WCM authors can configure  RSS/ATOM feeds  and make style changes in the WCM itself and display them on portal.


1.       DDC is enabled by default in the WP8.5 but if you are on the 8.0.0.1 we need to enable it using the following command.
ConfigEngine.sh action-enable-plr -DwasPassword=password -DPortalAdminPwd=password

2.       By Default, out of the box  DDC plugin can read the both ATOM and RSS feeds ( But need to configure or update Resource environment provider properties). 
Note: You can write custom plugin for reading any custom source and generate beanlist.

3.       Using yahoo news feed to demonstrate DDC (Render the  yahoo news using the Web Content Viewer portlets on portal for this demo). Using yahoo news for the demo, but you can configure any rss feed in this method . (But if your feed is ATOM format you need use different set of resource environment provider properties to map the xml)


Sample Feed XML format
<rss xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
<channel>
<title>Yahoo News - Latest News & Headlines</title>
<link>http://news.yahoo.com/</link>
<description>The latest news and headlines from Yahoo! News. Get breaking news stories and in-depth coverage with videos and photos.</description>
<language>en-US</language>
<copyright>Copyright (c) 2014 Yahoo! Inc. All rights reserved</copyright>
<pubDate>Thur, 01 Jan 2015 12:46:00 -0500</pubDate>
<ttl>5</ttl>
<image>
<title>Yahoo News - Latest News & Headlines</title>
<link>http://news.yahoo.com/</link>
<url>http://l.yimg.com/rz/d/yahoo_news_en-US_s_f_p_168x21_news.png</url>
</image>                
<item>
<title>Wreckage, bodies reveal jet's fate days after it disappeared</title>
<description>
<p>
<a href="http://news.yahoo.com/wreckage-bodies-reveal-jets-fate-131429475.html">
<img
src="http://l.yimg.com/bt/api/res/1.2/b0JWI_jfaEgi.RqUmFb53Q--/YXBwaW_AP_FINANCIALTIMES/61a65017444cb333690f6a7067005a8d_original.jpg"
width="130" height="86"
alt="Wreckage, bodies reveal jet&#039;s fate days after it disappeared"
align="left"
title="Wreckage, bodies reveal jet&#039;s fate days after it disappeared"
border="0" />
</a>
Images of debris and a bloated body flash across Indonesian television screens.
</p>
<br clear="all" />
</description>
<link>http://news.yahoo.com/wreckage-bodies-reveal-jets-fate-131429475.html</link>
<pubDate>Thur, 01 Jan 2015 12:46:00 -0500</pubDate>
<source url="http://www.ap.org/">Associated Press</source>
<guid isPermaLink="false">wreckage-bodies-reveal-jets-fate-131429475</guid>
<media:content
url="http://l.yimg.com/bt/api/res/1.2/b0JWI_jfaEgi.RqLTIMES/61a65017444cb333690f6a7067005a8d_original.jpg"
type="" width="130" height="86" />
<media:text type="html">
<p>
<a href="http://news.yahoo.com/wreckage-bodies-reveal-jets-fate-131429475.html">
<img
src="http://l.yimg.com/bt/api/res/1.2/b0JWI_jfaEgiTTP_AP_FINANCIALTIMES/61a65017444cb333690f6a7067005a8d_original.jpg"
width="130" height="86"
alt="Wreckage, bodies reveal jet&#039;s fate days after it disappeared"
align="left"
title="Wreckage, bodies reveal jet&#039;s fate days after it disappeared"
border="0" />
</a>
Images of debris and a bloated body flash across Indonesian television screens.
</p>
<br clear="all" />
</media:text>
<media:credit role="publishing company" />
</item>
</channel>
</rss>


4.       Used default content library that is "Web Content" for demo
5.       Following are things need to create in WCM (not concentrating much on UI )
a.       Authoring Template with one text element


b.      Create PZN component with pluggable resources as content spot




Header markup
<div role="main">
<div id="header">
<h1>
[Plugin:ListRenderingContext action="getListProperty" key="title"]</h1>
<div id="channelImg">
<a href="
[Plugin:ListRenderingContext action="getListProperty" key="link"]">
<img name="
[Plugin:ListRenderingContext action="getListProperty" key="imageTitle"]" title="[Plugin:ListRenderingContext action="getListProperty" key="imageTitle"]" alt="[Plugin:ListRenderingContext action="getListProperty" key="imageDescription"]" src="[Plugin:ListRenderingContext action="getListProperty" key="imageUrl"]" height="[Plugin:ListRenderingContext action="getListProperty" key="imageHeight"]" width="[Plugin:ListRenderingContext action="getListProperty" key="imageWidth"]" border="0" align="top" >
</a>
</div>
<div id="channelDesc">
<p>
[Plugin:ListRenderingContext action="getListProperty" key="description"]</p>
</div>
</div> <!-- End header-->
<div id="feedsBody">
<table class="lotusTable" border="0" cellspacing="0" cellpadding="0" role="presentation">
<tbody>


Result design
<tr>
<td class="lotusLastCell">
<h4>
<a href="
[AttributeResource attributeName="link" separator=","]" target="_blank">[AttributeResource attributeName="title" separator=","]</a>
</h4>
<div class="lotusMeta">
[AttributeResource attributeName="pubDate" format="DATE_MEDIUM" separator=","]
</div>
</td>
</tr>
<tr class="lotusDetails">
<td class="lotusLastCell">
<p>
[AttributeResource attributeName="description" separator=","]</p>
</td>
</tr>

Footer markup
</tbody>
</table>
</div> <!-- End body-->
<div id="footer">
[Plugin:ListRenderingContext action="getListProperty" key="copyright"]
</div> <!-- End footer-->
</div> <!-- End main-->


c.       Presentation Template
Once presentation template is finished, set this as default in the above authoring template
Markup

[Plugin:ListRenderingContext action="set" profile="ibm.portal.rss" extension-id="ibm.portal.ddc.xml" attribute="source=[Element context='current' type='content' key='feedUrl']"]


d.      Create  SiteArea and Content using above authoring template and give the yahoo news feed url

e.       

6.       Create a page and add Web Content Viewer portlet to it and configure web content viewer portlet with above content.

7.       Add following resource environment provider custom properties need to create in WP ListRenderingProfileService
NOTE:: These Entries are actual mappings to the xml elements in feed

name="rss.Name"                                 value="ibm.portal.rss"
name="rss.NamespaceMapping.media"                 value="http://search.yahoo.com/mrss/"
name="rss.ListItemSelection"                         value="//item"
name="rss.ItemAttribute.id"                         value="./title"
name="rss.ItemAttribute.title"                         value="./title"
name="rss.ItemAttribute.description"                 value="./description"
name="rss.ItemAttribute.link"                         value="./link"
name="rss.ItemAttribute.author"                 value="./author"
name="rss.ItemAttribute.category"                 value="./category"
name="rss.ItemAttribute.comments"                 value="./comments"
name="rss.ItemAttribute.guid"                         value="./guid"
name="rss.ItemAttribute.pubDate"                 value="./pubDate"
name="rss.ItemAttribute.pubDate.Type"         value="Date"
name="rss.ItemAttribute.pubDate.Format"         value="EEE, d MMM yyyy HH:mm:ss z"
name="rss.ItemAttribute.source"                 value="./source"
name="rss.ItemAttribute.encoded"                 value="./content:encoded"
name="rss.ItemAttribute.enclosureType"         value="./enclosure/@type"
name="rss.ItemAttribute.enclosureUrl"                 value="./enclosure/@url"
name="rss.ItemAttribute.itunesDuration"         value="./itunes:duration"
name="rss.ItemAttribute.mediaContentURL"         value="./media:content/@url"
name="rss.ItemAttribute.mediaEncoded"         value="./media:encoded"
name="rss.ListProperty.title"                         value="/rss/channel/title"
name="rss.ListProperty.link"                         value="/rss/channel/link"
name="rss.ListProperty.description"                 value="/rss/channel/description"
name="rss.ListProperty.language"                 value="/rss/channel/language"
name="rss.ListProperty.copyright"                 value="/rss/channel/copyright"
name="rss.ListProperty.pubDate"                 value="/rss/channel/pubDate"
name="rss.ListProperty.pubDate.Type"                 value="Date"
name="rss.ListProperty.pubDate.Format"         value="EEE, d MMM yyyy HH:mm:ss z"
name="rss.ListProperty.ttl"                         value="/rss/channel/ttl"
name="rss.ListProperty.category"                 value="/rss/channel/category"
name="rss.ListProperty.cloud"                         value="/rss/channel/cloud"
name="rss.ListProperty.docs"                         value="/rss/channel/docs"
name="rss.ListProperty.generator"                 value="/rss/channel/generator"
name="rss.ListProperty.imageTitle"                 value="/rss/channel/image/title"
name="rss.ListProperty.imageLink"                 value="/rss/channel/image/link"
name="rss.ListProperty.imageUrl"                 value="/rss/channel/image/url"
name="rss.ListProperty.imageWidth"                 value="/rss/channel/image/width"
name="rss.ListProperty.imageHeight"                 value="/rss/channel/image/height"
name="rss.ListProperty.imageDescription"         value="/rss/channel/image/description"

8.       Add following  custom property in "WP ConfigService" (adding url  to ajax proxy for allowing the reverse proxy)
name="wp.proxy.config.urlreplacement.digital_data_connector_policy.yahoo" value="http://news.yahoo.com/*"

9.       Restart the server , once portal restarted I yahoo news feed as below on the portal .


Reference

PUMA API Scenarios (Portal User Management Architecture)

This article talks more about not so frequently discussed PUMA API related scenarios with samples


1.        Accessing the PUMA from webapp that is deployed on the portal (not part of themes or portlets)
        a.       If user doesn't have the portal security context in request 
        b.      Want to execute the PUMA as system user (where you want to access to all users)

2.        Searching for multiple users by passing the list of uid's without using the wildchars ( search using wildchar may returns thousands of results ) 
       a.       Searching the users based on the multiple attributes 
       b.      Retrieving multiple user objects using 'OR' condition like uid='user1' or uid='user2'
       c.       Retrieving users without using the wildcard chards

3.        Using Paged Search in PUMA
       a.       To handle large result sets returning from the LDAP.

Accessing the PUMA from webapp deployed on the Portal or Making the PUMA api calls without logging into portal.

     /*
      * <p>Execute as authenticated admin user(retrieve users without login to portal)</p>
      * <p> You can return the list of users instead of printing them by just uncommenting couple of statement in this method </p>
      */
     //public List<User> getUsersByLastName(final String lastName) throws Exception{
     public String printUsersByLastName(final String lastName) throws Exception{
                 
          final PumaLocator pumaLocator = pumaHome.getLocator();
          final PumaProfile pumaProfile = pumaHome.getProfile();
         
          final List<String> attribList = new ArrayList<String>();
          attribList.add("uid");
          attribList.add("cn");
          attribList.add("sn");
        
          //final List<User> usersList = new ArrayList<User>();
          final StringBuffer sb = new StringBuffer();
   PrivilegedExceptionAction<Void> privilegedAction = new PrivilegedExceptionAction<Void>() {
  
        @Override
        public Void run() {
          try{
            //search users who have the same last name i.e users with last name as "vaka"
            List<User> users = pumaLocator.findUsersByAttribute("sn", lastName);
            //usersList.addAll(users);
            for(User user : users){
            Map<String, Object> attributesMap = pumaProfile.getAttributes(user, attribList);
            for (Map.Entry<String, Object> entry : attributesMap.entrySet()) {
               sb.append(entry.getKey() + " : " + entry.getValue() +" , \t");
            }
            sb.append("\n").append("</br>");
          }
          }catch(Exception ex){
               System.err.println("Error while gettting the users from the PUMA");
          }
              return null;
          }
         };

         try {
              PumaEnvironment pumaEnv = pumaHome.getEnvironment();
              pumaEnv.runUnrestricted(privilegedAction);
         } catch (PrivilegedActionException ex) {
              System.err.println("error while executing the previleged action");
              ex.printStackTrace();
         }
        
         //return usersList;
         return sb.toString();

     }

Searching for multiple users by passing list of Uid’s (or any attribute) without using the wild chars

/*
 * <p> Execute the FindUsersByQuery with or condition(retrieve users without login to portal)</p>
 * <p> You can return the list of users instead of printing them by just uncommenting couple of statement in this method </p>
 */
 //public List<User> getListOfUsers(List<String> userIds) throws Exception{
 public String printListOfUsers(List<String> userIds) throws Exception{
        
          final PumaLocator pumaLocator = pumaHome.getLocator();
          final PumaProfile pumaProfile = pumaHome.getProfile();
         
          final List<String> attribList = new ArrayList<String>();
          attribList.add("uid");
          attribList.add("cn");
          attribList.add("sn");
        
          //final List<User> usersList = new ArrayList<User>();
          final StringBuffer usersDetailsStr = new StringBuffer();
         
          if(null == userIds || 0 == userIds.size()) return "Empty userIds List passed";
          final StringBuilder userIdsListQuery = new StringBuilder();
          int i = 0;
          if( 1 == userIds.size() ){
                userIdsListQuery.append("(uid='"+userIds.get(0)+"*')");
          } else {
               for (String uid : userIds) {
                    if(i == 0) {
                    userIdsListQuery.append("((uid='"+uid+"*') or ");
                    }else if(i == userIds.size()-1)
                    userIdsListQuery.append("(uid='"+uid+"*'))");
                    else
                    userIdsListQuery.append("(uid='"+uid+"*') or ");
                    ++i;
                }
          }
                
   PrivilegedExceptionAction<Void> privilegedAction = new PrivilegedExceptionAction<Void>() {
     @Override
     public Void run() {
       try{

        List<User> users = pumaLocator.findUsersByQuery(userIdsListQuery.toString());
        //usersList.addAll(users);
        for(User user : users){
           Map<String, Object> attributesMap = pumaProfile.getAttributes(user, attribList);
           for (Map.Entry<String, Object> entry : attributesMap.entrySet()) {
              usersDetailsStr.append(entry.getKey() + " : " + entry.getValue() +" , \t");
           }
           usersDetailsStr.append("\n").append("</br>");
        }
                
      }catch(Exception ex){
         System.err.println("Error while gettting the users from the PUMA");
      }
         return null;
     }
    };

         try {
              PumaEnvironment pumaEnv = pumaHome.getEnvironment();
              pumaEnv.runUnrestricted(privilegedAction);
         } catch (PrivilegedActionException ex) {
              System.err.println("error while executing the previleged action");
              ex.printStackTrace();
         }
        
         //return usersList;
         return usersDetailsStr.toString();
     }

Paged Search in PUMA

    /*
      * Print All users (using the paged search)
      * <p> You can return the list of users instead of printing them by just uncommenting couple of statement in this method </p>
      */
     //public List<User> getAllUsers() throws Exception{
     public String printAllUsers() throws Exception{
                
          final PumaLocator pumaLocator = pumaHome.getLocator();
          final PumaProfile pumaProfile = pumaHome.getProfile();
         
          //Page size is 5
          final Map<String, Object> configMap = new HashMap<String, Object>();
          configMap.put(PumaLocator.RESULTS_PER_PAGE, new Integer(5));
         
          final List<String> attribList = new ArrayList<String>();
          attribList.add("uid");
          attribList.add("cn");
          attribList.add("sn");
        
          //final List<User> usersList = new ArrayList<User>();
          final StringBuffer sb = new StringBuffer();
              
          PrivilegedExceptionAction<Void> privilegedAction = new PrivilegedExceptionAction<Void>() {

  @Override
  public Void run() {
  try{
   //you can use findUsersByQuery or findUsersByAttribute
   //PagingIterator<User> users=pumaLocator.findUsersByQuery("uid='*'", configMap);
   PagingIterator<User> users=pumaLocator.findUsersByAttribute("uid""*", configMap);
   List<User> temp = new ArrayList<User>();
   while(users.hasNextPage()){
      temp = users.getNextPage(temp);
      //usersList.addAll(temp);
      if(null != temp && 0 < temp.size()){
         for (User user : temp) {                                          
 Map<String, Object> attributesMap = pumaProfile.getAttributes(user, attribList);
      for (Map.Entry<String, Object> entry : attributesMap.entrySet()) {
        sb.append(entry.getKey() + " : " + entry.getValue() +" , \t");
      }
      sb.append("\n").append("</br>");
     }
    }
   }                      
                  
  }catch(Exception ex){
     System.err.println("Error while gettting the users from the PUMA");
  }
  return null;
 }
 };

         try {
              PumaEnvironment pumaEnv = pumaHome.getEnvironment();
              pumaEnv.runUnrestricted(privilegedAction);
         } catch (PrivilegedActionException ex) {
              System.err.println("error while executing the previleged action");
              ex.printStackTrace();
         }
        
         //return usersList;
         return sb.toString();