Tuesday Dec 08, 2009

Client-side Eventing Example

In the previous blog, Eventing in AJAX portlets, we saw an overview of client side eventing mechanism in OSPC and WebSpace, which works like a simple yet powerful extension of JSR-286 eventing.

Lets look at an example to see how this all works.

EventGeneratorAjaxPortlet in this example, takes a zip code through a text field and has a button "Get GeoCode".

<input name="getbtn" id="getbtn" value="Get Geocode" type="button" onclick="<portlet:namespace/>PortletObj.processAction('<portlet:resourceURL/>')"/>

The onClick of the button calls processAction method on a javacript object called PortletObj which is prefixed with portlet's namespace. So first define a processAction javascript function just like one would have done while overriding the GenericPortlet's processAction method in the Portlet's Java class on server side.

<portlet:namespace/>PortletObj = { portletReq : null, processAction : function(updateURL) { portletReq = new XMLPortletRequest("<portlet:namespace/>"); portletReq.onreadystatechange = function() { <portlet:namespace/>PortletObj.render(); }; //collect information and prepare data to POST to server ... ... portletReq.open("POST", updateURL, true); portletReq.send(data); }, render : function() { var result = JSON.parse(portletReq.responseText, null); // process the response and modify the DOM ... ... var qName = {uri: "http://www.example.com/clientevents", name : "MyEvent" }; var eventPayload = {}; eventPayload.x = result.x; eventPayload.y = result.y; portletReq.setEvent(qName, eventPayload); } };

The processAction method, uses XMLPortletRequest which is initialized using the portlet's namespace. Portlet container automatically makes this available by adding the javascript file to the portlet during deployment. It is only required to include the script in the jsp from under <context-path>/js/ like this,

<script type="text/javascript" src="<%=renderRequest.getContextPath()%>/js/XMLPortletRequest.js"> </script>

In the example above, function names processAction and render are only a matter of following the JSR-286 pattern. The names of these functions could have been anything. More important is to note how "setEvent" is called on the XMLPortletRequest and an arbitrary event payload can be passed as a JSON object.

It is possible to use qName without the uri. The new RFE makes it possible to call setEvent with a string as an event name instead of requiring QName.

Show/Hide JSP

The serveResource method of the portlet computes latitude and longitude for the zip code, using Yahoo geocode service and returns a JSON object from its serveResource method.

Show/Hide serveResource

EventConsumerAjaxPortlet in this example, consumes the event generated by EventGeneratorAjaxPortlet, by implementing a PortletObj.processEvent javascript function. This function is called in response to the XMLPortletRequest.setEvent call in the processAction of event generator.

The wiring is taken care by Portlet Container at runtime as we will see later, but is done in portlet.xml by the developer as shown below.

<portlet> <portlet-name>EventGeneratorAjaxPortlet</portlet-name> ... ... <supported-publishing-event xmlns:x='http://www.example.com/clientevents'> <qname>x:ZipEvent</qname> </supported-publishing-event> </portlet> <portlet> <portlet-name>EventConsumerAjaxPortlet</portlet-name> ... ... <supported-processing-event xmlns:x='http://www.example.com/clientevents'> <qname>x:ZipEvent</qname> </supported-processing-event> </portlet> <event-definition xmlns:x='http://www.example.com/clientevents'> <qname>x:ZipEvent</qname> <value-type>com.sun.portlet.ClientEvent</value-type> </event-definition>

The portlets could have been packaged separately and may have separate portlet.xml. Also, since this is same as server-side eventing, it is possible to use tools like NetBeans PortalPack, and to use the eventing storyboard to wire the portlets visually instead of handcoding the xml.

Typically, this is how a consumer is implemented.

<portlet:namespace/>PortletObj = { portletReq : null, processEvent : function(eventObj) { portletReq = new XMLPortletRequest("<portlet:namespace/>"); portletReq.onreadystatechange = function() { <portlet:namespace/>PortletObj.render(); }; //process the event payload and prepare data to POST to server ... portletReq.open("POST", '<portlet:resourceURL/>', true); portletReq.send(data); }, render : function() { var response = JSON.parse(portletReq.responseText, null); // process response and modify the DOM ... } };

The namespaced PortletObj.processEvent function is mandatory and is the only forced naming convention required to be followed. It is not required to implement render function. It was possible to include all render code, instead of a call to render, in the callback function. But again, as a matter of convention and pattern, its much cleaner to implement render.

Show/Hide JSP

The processEvent function gets zip, latitude and longitude in the event payload. It in turn calls serveResouce of the portlet to get all the pictures recently uploaded in 10 mile radius of the given latitude/longitude, using Flickr's REST service.

Show/Hide serveResource

How it works
Portlet Container reads portlet.xml during deployment and recognizes client-side events from the special marker class com.sun.portlet.ClientEvent. While rendering the portlet, it generates a namespaced javascript event queue, <portlet:namespace/>EventQueue, for the generator and populates it with a list of consumers on the page. When XMLPortletRequest.setEvent is called, it in turn calls setEvent on the generator's event queue. Generator's event queue loops through consumer list and calls <portlet:namespace/>PortletObj.processEvent on each. This is why it is mandatory to implement processEvent function to be able to consume events.

Tuesday Aug 18, 2009

JPA Myth - Busted!

Conclusion first for the busy
Like MythBusters of Discovery channel - If your application uses JPA compliant persistence, then you can plugin any JPA provider - Busted!
As yet, Aug 2009, which is more than three years after JSR 220 release, and five months after proposed final draft of JSR 317, it is still not possible to switch JPA providers. And remember, we are not talking about Hello World!
Moreover, EclipseLink turns out to be the winner when it comes to a real application.

Don't believe it, read on....

Liferay is one of the most popular open source portal and is consumed by WebSpace as its portal core. Currently, Liferay uses non-JPA Hibernate. I was working on implementing JPA persistence layer for Liferay.  This is a real world application with over 100 entity classes and as many tables. The experience below is the summary of what I found during this implementation.

The Spec
"xml-mapping-metadata-complete" tag only means ignore annotations. Even if you have specified it in the mapping xml, all providers (EclipseLink/Hibernate/OpenJPA), look into the entity classes (some for accessors while some for just getters) claiming spec adherence, when access is declared to be of type "PROPERTY".
I would interpret "xml-mapping-metadata-complete" as an indication to the provider that this is it. Whatever is there in the mapping xml is what you are supposed to deal with. Look no further. But that's not the case.
This results in 80% of the mapping file to contain transients defined for accessors which are not supposed to be interpreted as persistent properties.

It works!
Although I hit a small bug, the forum quickly provided a workaround.
Other than that, it was smooth.

Neither Hibernate nor OpenJPA work unless mapped-superclass is defined when entity class is a sub-class. See this forum post  for details. Since the spec requires mapped-super class declaration, it is okay for providers to expect it but EclipseLink works with or without it. I don't mind even if it violates the spec.

As mentioned earlier, the mapped-superclass declaration is needed for entity classes which are subclasses.  Otherwise Hibernate throws this exception,
Caused by: org.hibernate.AnnotationException: No identifier specified for entity ...
This is fine since spec defines it this way but it was difficult to figure out from the exception.

But here are the serious stopper issues.

  1. overriding accesors in entity need tranisent declaration
  2. getter interpreted as property
  3. presence of getX as well as isX thorws MappingException
OpenJPA has a serious issue that the overridden setters in entity class are never called. It only calls the setters of the mapped-superclass. This totally breaks the application.
The details are in this forum question.

I have not heard of a solution or a workaround from either Hibernate or OpenJPA forums, and hence the conclusion. If I get some help from them, I will be happy to re-visit and make this work. But as of now, only TopLink/EclipseLink works for me.

Wednesday May 07, 2008

Inter-Widget communication in cross-platform widgets

JavaONEProject WebSynergy is Sun's next generation web aggregation and presentation platform wherein,widgets developed using various programming languages and web technologies, like Java, Ruby and PHP, can co-exist and inter-communicate. For a demo, please visit our booth at JavaONE 2008, today or anytime this week. Alternatively, you may pick up a CD or a USB Memory Stick from the booth and try it out. The downloadable zip and instructions are also available.

RubySo how is inter-widget communication achieved? Well, "Whats the big deal?", someone may ask. Propreitory inter-widget communication can be acheived in many different ways. But these widgets use a standard defined by Java Portlet Specification 2.0 (JSR 286) known as public render parameters. And this makes all the difference.

PHPBut does this mean Ruby and PHP programmers have to first learn Java specification to be able to develop widgets? The answer is, absolutely not. That is why this becomes so important. Project WebSynergy uses Liferay's JRuby and PHP bridges to run the Ruby and PHP widgets. NetBeans 6.1 and Glassfish v3 already support Ruby/PHP. The native PHP support is in early access in NetBeans 6.1 but you can always write PHP code inside NetBeans. Project WebSynergy provides Portal Pack, which are NetBeans plugins for developing widgets. So the tools make all the difference and help Ruby/PHP programmer to write inter-communicating widgets without understanding the underlying details.

This is how a widget will be typically developed by Ruby/PHP programmer using NetBeans widget development plugins:

  • right-click and use a wizard to add a new Ruby or PHP widget
  • start writing native Ruby/PHP code
  • right-click and a few selections to deploy the widget
No Java code ever written.

This is how cross-technology widgets will be wired together by Ruby/PHP programmer using NetBeans widget developement plugins:
  • use widget story-board in NetBeans
  • drag-n-drop the widgets (Java/Ruby/PHP) on to the storyboard
  • right-click and add public render parameters (provide a name, thats all)
  • drag-n-connect to wire the widgets together
The only thing that Ruby/PHP developer needs to know is the name of the public render parameter while developing the widget. Everything else is taken care of by the tools.

Thursday Oct 11, 2007

OpenSSO and Liferay Integration Prototype

I would prefer to write a short blog and document this somewhere rather than put all of this in a blog, but was not sure where to put it. So this has become a blogument ;)
OpenSSO is an open source project for Single Sign-On. Liferay is an open source portal from Liferay, Inc.
Liferay portal already integrates with CAS single sign-on server. This blogument describes how Liferay portal can be integrated with OpenSSO for single sign-on.

OpenSSO server
  • Download the OpenSSO server
  • For this prototype, FAM 8.0 Build 1 Zip was used. (FAM stands for Federated Access Manager)
  • Turn off the security manager. On Glassfish v2, it is off by default. On AS9.1, access the admin console and turn it off.
  • If security manager needs to be on, then server.policy must be edited as described here. You may need a few more permissions than listed here.
  • Unzip the file and deploy the deployable-war/fam.war as /opensso
  • Access the server (http://opensso-host:port/opensso) to invoke the configurator. Once configured, it will take you to login page where you can login as "amadmin" user.
OpenSSO client
There are 2 ways for an application to leverage OpenSSO as a client
  1. Using client sdk from the downloaded zip (libraries/jars/famclientsdk.jar)
  2. Using web services or REST based services.
There are advantages/disadvantages in both. Client sdk comes with its own cache and a comprehensive set of Java APIs. So you can register SSO token event listners etc. Also you need to configure AMConfig.properties into your classpath.
If you use REST based identity services, then application is responsible for maintaining its own sessions and data cache. But using REST, the client does not have build and runtime dependency on OpenSSO jars. Thanks to Aravindan for providing the info on this latest and greatest feature.

The REST based services were used for this prototype. Currently, only authenticate/authorize/attributes/log are the REST operations available. So there is no way to validate a client session with server or to get a subjectid for an authenticated user. An issue 1079 has been opened with OpenSSO for this enhancement.
For the time being, the subjectid is extracted from the sso cookie and the following REST operation is used,

This returns user details in the following form:
userdetails.token.id=Et2RTHUb+C9TTNipgRqR0MECgg=@AAJTSQACMDE=# userdetails.attribute.name=sn userdetails.attribute.value=user1 userdetails.attribute.name=cn userdetails.attribute.value=user1 userdetails.attribute.name=objectclass userdetails.attribute.value=person userdetails.attribute.value=inetorgperson userdetails.attribute.value=top userdetails.attribute.value=organizationalperson userdetails.attribute.value=inetuser userdetails.attribute.name=employeenumber userdetails.attribute.value=5 userdetails.attribute.name=uid userdetails.attribute.value=user1 userdetails.attribute.name=userpassword userdetails.attribute.value=s9qne0wEqVUbh4HQMZH+CY8yXmc= userdetails.attribute.name=givenname userdetails.attribute.value=user1 userdetails.attribute.name=mail userdetails.attribute.value=user1@fam.com userdetails.attribute.name=inetuserstatus userdetails.attribute.value=Active

It is possible to get this information in the form of xml by using this url:

Authentication filter
First, write an auth filter which redirects a non-authenticated user to the OpenSSO server's login page. An authenticated user is the one who has the sso cookie.
The code is shown below:
package com.liferay.portal.servlet.filters.sso.fam; import java.io.IOException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import javax.servlet.http.Cookie; import javax.servlet.Filter; import javax.servlet.FilterConfig; import javax.servlet.FilterChain; public class FAMFilter implements Filter { String loginUrl = null; String logoutUrl = null; String idServicesUrl = null; String ssoCookieName = null; public void init(FilterConfig filterConfig) throws ServletException { loginUrl = filterConfig.getInitParameter("loginUrl"); logoutUrl = filterConfig.getInitParameter("logoutUrl"); ssoCookieName = filterConfig.getInitParameter("ssoCookieName"); } public void destroy() {} public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // If any of the filter params are null or empty // then skip doing anything because it is misconfig if(loginUrl == null || loginUrl.length() == 0 || logoutUrl == null || logoutUrl.length() == 0 || ssoCookieName == null || ssoCookieName.length() == 0) { chain.doFilter(request, response); } HttpServletRequest httpReq = (HttpServletRequest)request; HttpServletResponse httpRes = (HttpServletResponse)response; String pathInfo = httpReq.getPathInfo(); if (pathInfo != null && pathInfo.indexOf("/portal/logout") != -1) { HttpSession httpSes = httpReq.getSession(); httpSes.invalidate(); httpRes.sendRedirect(logoutUrl); } else { if(isAuthenticated(httpReq)) { chain.doFilter(request, response); } else { httpRes.sendRedirect(loginUrl); } } } private boolean isAuthenticated(HttpServletRequest request) { boolean authenticated = false; Cookie cookie = null; Cookie[] cookies = request.getCookies(); int nCookies = cookies == null ? 0 : cookies.length; for(int i = 0; i < nCookies; i++) { if(ssoCookieName.equalsIgnoreCase(cookies[i].getName())) { cookie = cookies[i]; break; } } if(cookie != null) { authenticated = true; request.getSession().setAttribute("subjectid", cookie.getValue()); } return authenticated; } }

Next add the filter to web.xml:

<filter-name>FAM Filter</filter-name>

<filter-name>FAM Filter</filter-name>

Now write a FAMAutoLogin class which implements the AutoLogin interface provided by Liferay. This class implements the "login" method of the interface.
In the implementation, it gets the subjectid from the session (which has been stored off by the auth filter).
Then makes a REST call to get the user attribtues.
Liferay needs firstName, lastName, screenName, email for creating a user profile dynamically in its database, if one does not exist. If the authenticated user (from OpenSSO) is not found, then UserLocalServiceUtil is used to add the user to Liferay database.

The source is shown below:
package com.liferay.portal.security.auth; import java.util.Map; import java.util.HashMap; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.liferay.portal.PortalException; import com.liferay.portal.NoSuchUserException; import com.liferay.portal.model.User; import com.liferay.portal.SystemException; import com.liferay.portal.service.UserLocalServiceUtil; import com.liferay.portal.util.PortalUtil; import com.liferay.portal.util.PrefsPropsUtil; import com.liferay.util.PwdGenerator; import com.liferay.util.ldap.LDAPUtil; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import java.net.URLEncoder; import java.net.URLConnection; import java.io.BufferedReader; import java.net.URL; import java.io.InputStreamReader; import java.io.InputStream; public class FAMAutoLogin implements AutoLogin { public FAMAutoLogin() {} public String[] login(HttpServletRequest req, HttpServletResponse res) throws AutoLoginException { String[] credentials = null; long companyId = PortalUtil.getCompanyId(req); String idServicesUrl = null; try { idServicesUrl = PrefsPropsUtil.getString(companyId, "fam.idservices.url"); } catch(PortalException pe) { throw new AutoLoginException(pe); } catch(SystemException se) { throw new AutoLoginException(se); } String subjectid = (String)req.getSession().getAttribute("subjectid"); if(subjectid == null) { //this should not happen since filter will have already blocked return credentials; } String errorMsg = null; Map nameValues = new HashMap(); String line = null; try { subjectid = URLEncoder.encode(subjectid, "UTF-8"); String url = idServicesUrl + "/attributes?subjectid=" + subjectid; URL iurl = new URL(url); URLConnection connection = iurl.openConnection(); BufferedReader reader = new BufferedReader( new InputStreamReader((InputStream)connection.getContent())); while((line = reader.readLine()) != null) { //each line is returned as x=y String[] parts = line.split("="); //should not happen but we never know if(parts == null || parts.length != 2) { //skip this line continue; } String attrName = null; String attrValue = null; if(parts[0].endsWith("name")) { attrName = parts[1]; //next line must be value line = reader.readLine(); if(line == null) { //something wrong - name must be followed by value throw new AutoLoginException( "Error reading user attributes"); } //each line is returned as x=y parts = line.split("="); //should not happen but we never know if(parts == null || parts.length != 2 || !parts[0].endsWith("value")) { attrValue = null; } else { attrValue = parts[1]; } nameValues.put(attrName, attrValue); } } } catch(java.io.UnsupportedEncodingException use) { throw new AutoLoginException(use); } catch(java.net.MalformedURLException me) { throw new AutoLoginException(me); } catch(java.io.IOException ioe) { throw new AutoLoginException(ioe); } //Liferay user must have these attrs String firstName = (String)nameValues.get("cn"); String lastName = (String)nameValues.get("sn"); String screenName = (String)nameValues.get("givenname"); String email = (String)nameValues.get("mail"); if(email == null || email.length() == 0) { throw new AutoLoginException("No email set for user"); } User user = null; try { user = UserLocalServiceUtil.getUserByEmailAddress(companyId, email); } catch(NoSuchUserException nsue) { try { user = addUser(companyId, screenName, firstName, lastName, email); } catch(Exception e) { throw new AutoLoginException(e); } } catch(Exception e) { throw new AutoLoginException(e); } credentials = new String[3]; credentials[0] = String.valueOf(user.getUserId()); credentials[1] = user.getPassword(); credentials[2] = Boolean.TRUE.toString(); return credentials; } protected User addUser(long companyId, String screenName, String firstName, String lastName, String email) throws PortalException, SystemException { long creatorUserId = UserLocalServiceUtil.getDefaultUserId(companyId); User user = null; try { user = UserLocalServiceUtil.addUser(creatorUserId, companyId, true, "", "", false, screenName, email, java.util.Locale.ENGLISH, firstName, "", lastName, 0, 0, true, 1, 1, 1970, "", 0, 0, false); } catch (Exception e){ _log.error( "Problem adding user with screen name " + screenName + " and email address " + email, e); } return user; } private static Log _log = LogFactory.getLog(FAMAutoLogin.class); }

Liferay Hooks
Liferay portal provides hooks to plugin auto login classes.
Edit (or create if one does not exist in the deployment) portal-ext.properties and add the following:
auto.login.hooks=com.liferay.portal.security.auth.FAMAutoLogin,com.liferay.portal.security.auth.CASAutoLogin,com.liferay.portal.security.auth.NtlmAutoLogin,com.liferay.portal.security.auth.OpenIdAutoLogin,com.liferay.portal.security.auth.RememberMeAutoLogin fam.idservices.url=http://opessso-host:port/opensso/identity

  1. Access http://opensso-host:port/opensso/
  2. Login as amadmin
  3. Click on the root realm and goto Subjects tab
  4. Add a new user
  5. Now click on the new user's name to edit user profile
  6. Enter an email for the new user (this is important since Liferay needs email)
  7. Logout from OpenSSO and try login as the new user to verify
  8. Access Liferay portal to be redirected to OpenSSO login page
  9. Login as the new user to be redirected back to Liferay portal
  10. Accept the terms and conditions (first time only) to see the Liferay portal pages
The prototype demonstrates how Liferay can be integrated to leverage OpenSSO. For production, use of client sdk may be considered. Liferay can also integrate with LDAP and import membership information. OpenSSO can integrate with various user repositories, so similar implementation can be provided to import membership from OpenSSO user repositories. Even more desirable scenario would be if Liferay can fetch membership at runtime instead of importing it to a local datastore and then struggling to keep it in sync with corporate user repository. The auth filter can be further enhanced to allow anonymous/guest access and then writing an OpenSSO login portlet.

  1. Setting_up the Extension Environment
  2. Integrating Liferay With CAS
  3. Developing a Custom Authentication System
  4. Liferay LDAP integration
  5. OpenSSO project page

Tuesday Jul 24, 2007

Debugging Portal - Dynamic Service Attributes

Portal Server (PS) uses Access Manager (AM) features which in turn uses Directory Server (SunDS) features. One of such features of SunDS is Class Of Service or COS which is used by AM in Service Management System (SMS).
Portal defines services and registers these services into AM.
Some services are shared by multiple portals while some services are portal specific.
Shared services are:
    SSO Adapter
    All Sercure Remote Access (SRA) related services

Portal specific services are shown below and have their names mangled with portal-id
    Desktop - SunPortalmyPortalDesktopService
    Subscriptions - SunPortal

Most of these services use "Dynamic" type of attributes which are stored by AM using the COS feature. It means that these are virtual attributes and are not set at the node, but set into the COS template.
Many times we come across a situation while debugging portal, when we want to see the the value of such a COS attribute at a specific node. Looking at the node does not tell us if the value is set at the node or the value is being inherited.

For example, running the following query on a user shows this:

/opt/SUNWam/bin> ./ldapsearch -b 'o=EnterpriseSample,dc=red,dc=iplanet,dc=com'
-D "cn=Directory Manager" -w 11111111 "cn=u1"

version: 1
dn: uid=u1,ou=People,o=EnterpriseSample,dc=red,dc=iplanet,dc=com
sn: u1
cn: u1
uid: u1
sunPortalmyPortalDesktopCommunityCreateContainerName: CommunityCreatePortlet
sunPortalmyPortalDesktopCommunityHomeContainerName: CommunityHomeContainer
sunPortalmyPortalDesktopDefaultChannelName: JSPTabContainer
sunPortalmyPortalDesktopEditProviderContainerName: JSPEditContainer
sunPortalmyPortalDesktopType: enterprise_sample
sunPortalmyPortalMaxCategorySubscriptions: 5
sunPortalmyPortalMaxDiscussionSubscriptions: 5
sunPortalmyPortalMaxSavedSearch: 5

The COS feature of SunDS gives the attribute value as sunPortalmyPortalDesktopType=enterprise_sample which in this case is inherited from the parent organization.
If the user belongs to different roles, then it becomes even more complex to track down where the value is being inherited because it will be resolved using COS priority and if the priority is same then the value is indeterministic.

One easy way is to execute an ldapsearch like this and then parse through the dump to locate the template attribtues. This gets a bit cumbersome with a huge dump.

/opt/SUNWam/bin> ./ldapsearch -b 'o=EnterpriseSample,dc=red,dc=iplanet,dc=com'
-D "cn=Directory Manager" -w 11111111 "(objectclass=\*)"

Another easier way is to run a search on individual node templates.

To see the attribtues set for the organization template for desktop service, execute the search shown below. Notice that the base dn always has cn=ContainerDefaultTemplateRole for an organization and the name of the service is mangled with portal-id.

/opt/SUNWam/bin>./ldapsearch -b
-D "cn=Directory Manager" -w 11111111 "(objectclass=\*)"

version: 1
dn: cn="cn=ContainerDefaultTemplateRole,o=EnterpriseSample,..."
cn: cn=ContainerDefaultTemplateRole,o=EnterpriseSample,dc=red,dc=iplanet,dc=com
objectClass: costemplate
objectClass: top
objectClass: extensibleObject
sunPortalmyPortalDesktopEditProviderContainerName: JSPEditContainer
sunportaldesktopdpcanview: true
cosPriority: 0
sunportaldesktopdpdocument:: PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgi

sunportaldesktopdplastmodified: 1184692202155
sunPortalmyPortalDesktopCommunityHomeContainerName: CommunityHomeContainer
sunPortalmyPortalDesktopDefaultChannelName: JSPTabContainer
sunPortalmyPortalDesktopCommunityCreateContainerName: CommunityCreatePortlet
sunPortalmyPortalDesktopType: enterprise_sample

To look at the role nodes, following search can be used. Notice here that the base dn starts with cn="cn=RoleName... and also has portal qualified desktop service name.

/opt/SUNWam/bin> ./ldapsearch -b 'cn="cn=AustraliaExpansionRole,o=EnterpriseSample,dc=red,dc=iplanet,dc=com",

-D "cn=Directory Manager"
-w 11111111 "(objectclass=\*)"

version: 1
cn: "cn=AustraliaExpansionRole,o=EnterpriseSample,dc=red,dc=iplanet,dc=com"
cn: cn=AustraliaExpansionRole,o=EnterpriseSample,dc=red,dc=iplanet,dc=com
objectClass: costemplate
objectClass: top
objectClass: extensibleObject
sunPortalmyPortalDesktopEditProviderContainerName: JSPEditContainer
sunportaldesktopdpcanview: true
cosPriority: 0
sunPortalmyPortalDesktopDefaultChannelName: JSPTabContainer
sunPortalmyPortalDesktopType: enterprise_sample
sunportaldesktopdpdocument:: PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgi
sunportaldesktopdplastmodified: 1184692031309

And the easiest way is of course using an ldapbrowser.
First select the organization node in the Explore tab on the left and then set the quick search bar to
and click "Quick Search"

Then select the ContainerDefaultTemplateRole under a service in Results tab, to see the dynamic service attributes set for the organization, on the right.


Change the name from ContainerDefaultTemplateRole to any other role name to see dynamic attributes at the role node.

Happy Debugging...


Prashant Dighe


« February 2017