Thomas has posted 3 posts at DZone. View Full User Profile

Creating a Customized Solution With JBoss Portal

05.18.2009
| 21667 views |
  • submit to reddit

Out of the box, the JBoss Portal project delivers a simple portal, usually users will strip it down completely and start fresh or build on top of it. In this article we will see how to progressively transform the out of the box portal to a customized solution.

 

From:

To:

Changing the theme

The first thing we will do is to change the visual aspect of the portal. JBoss Portal separates the layout (how a page is shaped) and it's artistic aspect (usually a CSS file). In this example we will create both a new layout and a new theme (or skin). For this article we've found an existing theme designed for a blog under a public domain license which can be found here: http://www.opendesigns.org/design/?template=1090

The downloaded file was composed of a static HTML page, a CSS file and few images and then was easy to adapt, we just needed to remove the static parts to add the dynamic ones. The HTML page became our layout and the CSS file our skin. Also since we wanted to have three different layouts at the end we've splitted the documents with header and footer parts.

Here here a graphical description of the split, surrounded in red we can see the header (the footer doesn't fit on the page), on the top in the green rectangle we see the position where we want to include links, on the left a two column regions where we want to put windows and on the right another region for other windows.  On top of the right region we've added a place for Login/logout links.


Our three columns layout now looks like:

<%@ taglib uri="/WEB-INF/theme/portal-layout.tld" prefix="p" %>
<%@include file="/layouts/common/header.jsp"%>

<p:region regionName='FirstColumn' regionID='linkbar'/>
<p:region regionName='SecondColumn' regionID='left'/>

<div id="right">

<div class="content">

<!-- Utility controls -->
<p:region regionName='dashboardnav' regionID='dashboardnav'/>

<p:region regionName='MainColumn'/>
</div>
</div>
<%@include file="/layouts/common/footer.jsp"%>


As we can see, we used a JBoss Portal specific tag library to define places where the portal framework should include a region that we are naming FirstColumn, a region called dashboardnav (that will include Login/Logout links) and a region called SecondColumn. The linked CSS (which is standard CSS) will display things as we want. This layout doesn't specify how we want windows to be displayed though.
On the next image we can see how a page is decomposed, we've seen how to declare where in the markup we want to include the regions. We still have to define what markup we want to define the region, markup for each window, markup for the window decoration which usually consists of the window's title and links for portlet modes and for the markup around the content given by the portlet itself.


In our case we don't want any window decoration except the window title. We could have different kind of window decorations but what is important to us is to be able to reuse those across the portal..
We will define a class for all the four elements region, window, decoration and portlet implementing the Java interfaces org.jboss.portal.theme.render.renderer.RegionRenderer, WindowRenderer, DecorationRenderer and PortletRenderer. In our example we can find the classes in org.jboss.portal.myportal.theme.*
Let's just have a look at our WindowRenderer that simply adds a “div” tag around the window then delegates decoration rendering and portlet rendering.

package org.jboss.portal.myPortal.theme;

import [...]
import org.jboss.portal.theme.render.AbstractObjectRenderer;
import org.jboss.portal.theme.render.renderer.WindowRenderer;
import org.jboss.portal.theme.render.renderer.WindowRendererContext;

public class DivWindowRenderer extends AbstractObjectRenderer
implements WindowRenderer
{
public void render(RendererContext rendererContext, WindowRendererContext wrc) throws RenderException
{
Writer writer = rendererContext.getWriter();
try {
writer.write("<div>");
rendererContext.render(wrc.getDecoration());
rendererContext.render(wrc.getPortlet());
writer.write("</div>");
} catch (IOException e) {
e.printStackTrace();
}
}
}


The decoration renderer simply renders the title of the window:

package org.jboss.portal.myPortal.theme;

import [...]
import org.jboss.portal.theme.render.AbstractObjectRenderer;
import org.jboss.portal.theme.render.RenderException;
import org.jboss.portal.theme.render.RendererContext;
import org.jboss.portal.theme.render.renderer.DecorationRenderer;
import org.jboss.portal.theme.render.renderer.DecorationRendererContext;

public class DivDecorationRenderer extends AbstractObjectRenderer
implements DecorationRenderer
{

public void render(RendererContext rendererContext, DecorationRendererContext drc) throws RenderException
{
PrintWriter out = rendererContext.getWriter();

out.println("<span class=\"headline_three\">" + drc.getTitle() + "</span><br />");
}

}


We now have defined all markup and CSS required to display the content. We bundled those classes in a JAR and add the static resources within a WAR. The only thing left for the theming part is to declare our new layout and skin. To do that, we need to add few descriptors. First we need to declare a renderset which is a set of renderers. With those rendersets we can combine several existing renderers to pick from as we will build out portal.
In our myPortal.war/WEB-INF/layout/portal-renderSet.xml we'll give a name to our new renderset and defines all the renderers to use:

<portal-renderSet>
<renderSet name="myPortalRenderer">
<set content-type="text/html">
<ajax-enabled>false</ajax-enabled>
<region-renderer>org.jboss.portal.myPortal.theme.DivRegionRenderer</region-renderer>
<window-renderer>org.jboss.portal.myPortal.theme.DivWindowRenderer</window-renderer>
<portlet-renderer>org.jboss.portal.myPortal.theme.DivPortletRenderer</portlet-renderer>
<decoration-renderer>org.jboss.portal.myPortal.theme.DivDecorationRenderer</decoration-renderer>
</set>
</renderSet>
</portal-renderSet>
Now we need to define our layouts in myPortal.war/WEB-INF/portal-layouts.xml:
<layouts>
<layout>
<name>MyLayout</name>
<uri>/layouts/myLayout.jsp</uri>
<uri state="maximized">/layouts/myMaximizedLayout.jsp</uri>
<regions>
<region name="FirstColumn"/>
<region name="SecondColumn"/>
<region name="MainColumn"/>
</regions>
</layout>
<layout>
<name>TwoColumnsLayout</name>
<uri>/layouts/twoColumnsLayout.jsp</uri>
<uri state="maximized">/layouts/myMaximizedLayout.jsp</uri>
<regions>
<region name="FirstColumn"/>
<region name="SecondColumn"/>
</regions>
</layout>
</layouts>


Last but not least let's define our skin in myPortal.war/WEB-INF/portal-themes.xml and give it a name.

<themes>
<theme>
<name>mySkin</name>
<link href="/skins/mySkin.css" title="" rel="stylesheet" type="text/css" media="screen" />
</theme>
</themes>


With this war deployed in the out of the box portal, we would already be able to change the default theme by our own by using the admin portlet for example.


Applied to the front page, this would look like the next screenshot. As the region names have changed from the original page, existing windows aren't shown but links to the other pages are here with the Login/Logout links.
Same page, after adding some portlets/CMS content that fits well with the theme.



But we are not quite there yet, localization has not been handled neither how to add content, get proper declarative security and caching.

Adding content

For the purpose of this article we have built a simple RSS portlet that is able to either show a list of last n entry titles of a blog or the full content of an article (if provided in the feed). This can be found in the rssPortlet project. We won't go into the details of the portlet itself, it is a standard JSR-286 portlet that supports two parameters, 'rssFeed' which contains the URL of a RSS feed and 'limit' which is the maximum number of entries to show.
What matters here is how can we add windows using that portlet, and for this article we don't want to use the admin portlet but we want to be able to ship the portal to someone so that on first start up it will fill its database with correct values. We will then use XML to define our portal. Inside our portlet web archive, we include a file called WEB-INF/portlet-instances.xml that will be used to create customized instances of the portlet.
The file is pretty self-explanatory, we create three instances for different feeds with different values for the RSS Feed to display. Note that the last instance has a security constraint, it is a declarative way to show that instance only to users with the admin role.

<!DOCTYPE deployments PUBLIC
"-//JBoss Portal//DTD Portlet Instances 2.6//EN"
"http://www.jboss.org/portal/dtd/portlet-instances_2_6.dtd">

<deployments>
<deployment>
<instance>
<instance-id>JBossPortalRSSInstance</instance-id>
<portlet-ref>MyRSSPortlet</portlet-ref>
<preferences>
<preference>
<name>rssFeed</name>
<value>
http://feeds.feedburner.com/jbossportal
</value>
</preference>
<preference>
<name>limit</name>
<value>10</value>
</preference>
</preferences>
</instance>
</deployment>
<deployment>
<instance>
<instance-id>JBossRSSInstance</instance-id>
<portlet-ref>MyRSSPortlet</portlet-ref>
<preferences>
<preference>
<name>rssFeed</name>
<value>http://labs.jboss.org/feeds/all/atom</value>
</preference>
<preference>
<name>limit</name>
<value>10</value>
</preference>
</preferences>
</instance>
</deployment>
<deployment>
<instance>
<instance-id>AdminOnlyRSSInstance</instance-id>
<portlet-ref>MyRSSPortlet</portlet-ref>
<security-constraint>
<policy-permission>
<action-name>view</action-name>
<role-name>Admin</role-name>
</policy-permission>
</security-constraint>
<preferences>
<preference>
<name>rssFeed</name>
<value><![CDATA[http://pipes.yahoo.com/pipes/pipe.run?_id=fb3504450e04190b33a8ef9628599c76&_render=rss&forumurl=215]]></value>
</preference>
<preference>
<name>limit</name>
<value>10</value>
</preference>
</preferences>
</instance>
</deployment>
</deployments>

With those instances of portlet, we will be able to add them in windows that will compose our pages. We've decided to store the composition of our portal along with the new theme in myPortal.war. The file of interest here is located at myPortal.war/WEB-INF/default-object.xml. Since it's a bit too long to put entirely on this article, we will look at some parts.
The first deployment is the context itself, the root of all portals, it's where all default values can be defined as any child object will inherit from the context, we define here that we want to use our new layout, our new skin, our renderset and define the name of the portal to use as default (we'll name it 'default' here).

<deployments>
<deployment>
<context>
<context-name />
<properties>
<!--
| Set the layout for the default portal, see also portal-layouts.xml.
-->
<property>
<name>layout.id</name>
<value>MyLayout</value>
</property>
<!--
| Set the theme for the default portal, see also portal-themes.xml.
-->
<property>
<name>theme.id</name>
<value>mySkin</value>
</property>
<!--
| Set the default render set name (used by the render tag in layouts), see also portal-renderSet.xml
-->
<property>
<name>theme.renderSetId</name>
<value>myPortalRenderer</value>
</property>
<!--
| The default portal name, if the property is not explicited then the default portal name is "default"
-->
<property>
<name>portal.defaultObjectName</name>
<value>default</value>
</property>
[...]
</context>
</deployment>

The next deployment is our default portal, we define our pages and windows. See how we define a window with a reference to the portlet id ( Or CMS reference, Gadget reference...)., the name of the region where to put the window, and a number to define the ordering within the region.
We can also notice that the second page overrides the value for the layout to use to use a two column layout that we've defined instead of the default three column layout we've seen earlier.
All pages of this snippet are granted the right to be viewed by anybody.

    <deployment>
<parent-ref />
<if-exists>overwrite</if-exists>
<portal>
<portal-name>default</portal-name>
<supported-modes>
<mode>view</mode>
<mode>edit</mode>
<mode>help</mode>
</supported-modes>
<supported-window-states>
<window-state>normal</window-state>
<window-state>minimized</window-state>
<window-state>maximized</window-state>
</supported-window-states>
<security-constraint>
<policy-permission>
<action-name>view</action-name>
<unchecked />
</policy-permission>
</security-constraint>
[...]
<page>
<page-name>default</page-name>
<display-name xml:lang="en">Home</display-name>
<display-name xml:lang="fr">Page de Garde</display-name>
<security-constraint>
<policy-permission>
<action-name>view</action-name>
<unchecked />
</policy-permission>
</security-constraint>

<properties>
<property>
<name>order</name>
<value>0</value>
</property>
</properties>
<window>
<window-name>JBossPortalRSSWindow</window-name>
<instance-ref>JBossPortalRSSInstance</instance-ref>
<region>FirstColumn</region>
<height>0</height>
</window>
<window>
<window-name>JBossRSSWindow</window-name>
<instance-ref>JBossRSSInstance</instance-ref>
<region>SecondColumn</region>
<height>0</height>
</window>
<window>
<window-name>CMSWindow</window-name>
<content>
<content-type>cms</content-type>
<content-uri>/default/index.html</content-uri>
</content>
<region>MainColumn</region>
<height>1</height>
</window>
</page>
<page>
<page-name>otherpage</page-name>
<display-name xml:lang="en">Other Page</display-name>
<display-name xml:lang="fr">Autre Page</display-name>
<security-constraint>
<policy-permission>
<action-name>view</action-name>
<unchecked />
</policy-permission>
</security-constraint>
<properties>
<property>
<name>order</name>
<value>1</value>
</property>
<property>
<name>layout.id</name>
<value>TwoColumnsLayout</value>
</property>
<property>
<name>theme.id</name>
<value>mySkin</value>
</property>
</properties>
<window>
<window-name>SudokuWindow</window-name>
<content>
<content-type>widget/netvibes</content-type>
<content-uri>
http://sudokushark.com/netvibes_uwa.php
</content-uri>
</content>
<region>FirstColumn</region>
<height>0</height>
</window>
<window>
<window-name>JBossForumRSSWindow</window-name>
<instance-ref>AdminOnlyRSSInstance</instance-ref>
<region>SecondColumn</region>
<height>0</height>
</window>
</page>
[...]
</portal>
</deployment>

Before we deploy this in JBoss Portal, we should remove the default out of the box configuration, this can be done by deleting jboss-portal.sar/conf/data/default-object.xml. Also we should empty our database by deleting jboss-4.2.3/server/default/data to make sure our content will be in sync with what we have described here.
Also we want to customize some CMS content, again we could have create content through the CMS Admin portlet but here we simply want to add content on startup.
Declarative security and caching
We have already seen how we can restrict a portlet instance for a particular role, below is a partial definition of a page that is restricted to the role 'admin'. Any logged-in standard user will not be able to access the page.

            <page>
<page-name>admin</page-name>
<display-name xml:lang="en">Admin Only</display-name>
<display-name xml:lang="fr">Pour Admins</display-name>
<security-constraint>
<policy-permission>
<action-name>view</action-name>
<role-name>Admin</role-name>
</policy-permission>
</security-constraint>
[...]
</page>


Another given feature is the possibility to define page fragment caching. There are cases where calling  the render phase of a portlet doesn't make sense as the content would not change over time or not often. Our RSS portlet is a typical example and here we want to cache the content of the portlet for 10min to avoid unnecessary expensive calls to the feed.
Using the standard portlet.xml descriptor we add an expiration cache in seconds:
   

   <portlet>
<description>My RSS Portlet</description>
<portlet-name>MyRSSPortlet</portlet-name>
<display-name>My RSS Portlet</display-name>
<portlet-class>org.jboss.portal.rssPortlet.RSSPortlet</portlet-class>
<expiration-cache>600</expiration-cache>
[...]


At this stage, we have built a portal with organized content, security and caching.

Changing internals

The out of the box portal determines the user locale from the user's profile, if the user profile didn't set a preferred language, the one set by its web browser is used.
In our sample portal we want to let the user click on flags. This is our requirement.
To implement this requirement, we decide to store the chosen locale in the session of the portal itself. To do so we need to edit jboss-portal.sar/portal-server.war/WEB-INF/web.xml and add a Servlet definition and servlet mapping:

    <servlet>
<servlet-name>localeServlet</servlet-name>
<servlet-class>org.jboss.portal.myPortal.servlet.LocaleServlet</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>localeServlet</servlet-name>
<url-pattern>/locale</url-pattern>
</servlet-mapping>


The servlet simply takes the parameter obtained from GET parameter, store it in the session as "org.jboss.portal.myPortal.locale” and redirect the user to where he came from:

package org.jboss.portal.extension.servlet;

import [...]

public class LocaleServlet extends HttpServlet {

@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException
{
String country = request.getParameter("country");
String language = request.getParameter("language");
Locale locale = new Locale(language, country);
request.getSession(true).setAttribute("org.jboss.portal.myPortal.locale", locale);
response.sendRedirect(request.getParameter("from"));
}
}


In the layout, we've added two flag images with links to the Servlet:
<a href="<%= request.getAttribute("org.jboss.portal.PORTAL_CONTEXT_PATH") %>/locale?country=en&language=en&from=<%= request.getRequestURI() %>"><img src="<%= request.getContextPath() %>/images/flags/en.gif" alt="English"/></a>
&nbsp;
<a href="<%= request.getAttribute("org.jboss.portal.PORTAL_CONTEXT_PATH") %>/locale?country=ch&language=fr&from=<%= request.getRequestURI() %>"><img src="<%= request.getContextPath() %>/images/flags/fr.gif" alt="French"/></a>

Now to replace the default behavior to take the locale from the session instead of the web browser or user profile we need to replace the default interceptor defined in jboss-portal.sar/META-INF/jboss-service.xml
Instead of this MBean:

   <mbean code="org.jboss.portal.core.aspects.server.LocaleInterceptor"
name="portal:service=Interceptor,type=Server,name=Locale" xmbean-dd=""
xmbean-code="org.jboss.portal.jems.as.system.JBossServiceModelMBean">
<xmbean/>
</mbean>

We create our own org.jboss.portal.extension.aspect.LocaleInterceptor:

package org.jboss.portal.extension.aspect;

import java.util.Locale;

import javax.servlet.http.HttpServletRequest;

import org.jboss.portal.common.invocation.InvocationException;
import org.jboss.portal.server.ServerInterceptor;
import org.jboss.portal.server.ServerInvocation;
import org.jboss.portal.server.ServerRequest;

public class LocaleInterceptor extends ServerInterceptor {

protected void invoke(ServerInvocation invocation) throws Exception,
InvocationException {

HttpServletRequest request = invocation.getServerContext().getClientRequest();
Locale locale = (Locale)request.getSession().getAttribute("org.jboss.portal.myPortal.locale");

if (locale == null)
{
locale = Locale.ENGLISH;
}

ServerRequest req = invocation.getRequest();

// Set the locale for the request
Locale[] tmp = new Locale[]{locale};
req.setLocales(tmp);

// Invoke next interceptors
invocation.invokeNext();
}

}

We replace the MBean core value to our new class and don't forget to include the jar including this class in jboss-portal.sar/lib.

Putting everything together

All sources for this demo are available on SVN:
http://anonsvn.jboss.org/repos/portal/other/dzone_article
The readme.txt files explains how to setup and start the customized portal. If correctly setup you should be able to enjoy a visually customized portal using declarative security and caching, with a customized way to define the preferred language and a new web application creating content for a fragment of a page.

Published at DZone with permission of its author, Thomas Heute.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

George Kokkinos replied on Mon, 2009/05/18 - 5:10am

FINALLY!!!!!!!! An article on how to customize the portal!!!!!!!! Good work!

Michele Mauro replied on Mon, 2009/05/18 - 7:20am

Well, I would have needed it one month ago!

My customized solution is now in testing, and will go live in a few days. I followed exactly the steps described here, but had to discover them myself :-)

William Paul replied on Mon, 2009/06/01 - 9:39am

Without a doubt, the best article of it's kind.  Thanks for the great post!

Brian Holland replied on Tue, 2009/06/02 - 10:51pm

Do you know if there is anyway to create a theme that is based on jsf/facelets? And is it possible to add rich faces to that as well?

 

Thank you,

vaishali gokhale replied on Tue, 2009/08/11 - 7:42am

Very good article. Having a few doubts as am very new to portal development. What version of JBoss is used here? What is Java verions, Java 1.4 or 1.5 or 1.6? What all other components do i require for a portal development, does it need a Struts or Spring Framework? Would it need something know as a portlet bridge? Any other component? Thanks

marcel wolfenson replied on Sat, 2009/09/05 - 12:41am

Yes, this information is exactly what I was looking for:) Now I know how to customize the portal Thank you so much work from home scam reviews

emil gigi replied on Wed, 2009/09/16 - 12:46pm

Without a doubt, the best article of it's kind.  Thanks for the great post!

desene animate

marcel wolfenson replied on Thu, 2009/10/15 - 4:20am

I don't know how I could do all my portal development without this information. I keep using it. Thank you again.

kalle pallo replied on Tue, 2009/10/27 - 2:05pm

The JBoss Portal framework and architecture includes the portal container and supports a wide range of features including standard portlets, single sign-on, clustering and internationalization. geeli vai akryylikynnet?

Michael McCormack replied on Sun, 2009/11/22 - 8:23pm

A great example of the utilizatin of the portal is the following website that was created for Fantasy Party Hire which is located in Western Australia. The provide services for the party hire industry such as bucking bulls, bucking penis, fairy floss, bouncy castles, slushee machines and so forth. If you take a look at the website you will see how the customized portal has taken shape. The website http://www.fantasypartyhire.com.au takes shape with features such as standard portlets, single sign-on, and clustering

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.