JBoss Portal provides an Application Programming Interface (API) which allows to write code that interacts with the portal. The life time and validity of the API is tied to the major version which means that no changes should be required when code is written against the API provided by the JBoss Portal 2.x versions and used in a later version of JBoss Portal 2.x.
The Portal API package prefix is org.jboss.portal.api and all the classes part of the API are prefixed with that package except for two of them which are the org.jboss.portal.Mode and org.jboss.portal.WindowState classes, the main reason being that twose two classes were defined very early before the official Portal API framework was created.
The Portlet API defines two classes that represents a portion of the visual state of a Portlet which are javax.portlet.PortletMode and javax.portlet.WindowState. Likewise the Portal API defines similar classes named org.jboss.portal.Mode and org.jboss.portal.WindowState which offer comparable characteristics, the main differences are:
The Portal API defines the org.jboss.portal.api.PortalURL interface to represent URL managed by the portal.
It is possible to have access to a portion of the portal session to store objects. The org.jboss.portal.api.session.PortalSession interface defines its API and is similar to the javax.servlet.http.HttpSession except that it does not offer methods to invalidate the session as the session is managed by the portal.
The org.jboss.portal.api.PortalRuntimeContext gives access to state or operations associated at runtime with the current user of the portal. It allows to retrieve the user id when the method String getUserId() returns a non null string. It also gives access to the PortalSession instance associated with the current user. Finally it gives access to the NavigationalStateContext associated with the current user.
The portal structure is a tree formed by nodes. It is possible to programmatically access the portal tree in order to
As usual with tree structures, the main interface to study is the org.jboss.portal.api.node.PortalNode. That interface is intentionally intended for obtaining useful information from the tree. It is not possible to use it to modify the tree shape because it is not intended to be a management interface.
public interface PortalNode { int getType(); String getName(); String getDisplayName(Locale locale); Map getProperties(); PortalNodeURL createURL(PortalRuntimeContext portalRuntimeContext); ... }
The interface offers methods to retrieve informations for a given node such as the node type, the node name or the properties of the node. The noticeable node types are:
The org.jboss.portal.api.node.PortalNodeURL is an extension of the PortalURL interface which adds additional methods useful for setting parameters on the URL. There are no guarantees that the portal node will use the parameters. So far portal node URL parameters are only useful for nodes of type PortalNode.TYPE_WINDOW and they should be treated as portlet render parameters in the case of the portlet is a local portlet and is not a remote portlet. The method that creates portal node URL requires as parameter an instance of PortalRuntimeContext.
The interface also offers methods to navigate the node hierarchy:
public interface PortalNode { ... PortalNode getChild(String name); Collection getChildren(); PortalNode getRoot(); PortalNode getParent(); ... }
The navigational state is a state managed by the portal that associates to each user the state triggered by its navigation. A well known part of the navigational state are the render parameters provided at runtime during the call of the method void render(RenderRequest req, RenderResponse resp). The portal API offers an interface to query and update the navigational state of the portal. For now the API only exposes mode and window states of portal nodes of type window.
Portal events are a powerful mechanism to be aware of what is happening in the portal at runtime. The base package for event is org.jboss.portal.api.event and it contains the common event classes and interfaces.
The org.jboss.portal.api.event.PortalEvent abstract class is the base class for all kind of portal events.
The org.jboss.portal.api.event.PortalEventContext interface defines the context in which an event is created and propagated. It allows to retrieve the PortalRuntimeContext in order to obtain the portal context. Note that this method may return null if no context is available.
The org.jboss.portal.api.event.PortalEventListener interface defines the contract that class can implement in order to receive portal event notifications. It contains the method void onEvent(PortalEvent event) called by the portal framework.
Listeners declaration requires a service to be deployed in JBoss that will instantiate the service implementation and register it with the service registry. We will see how to achieve that in the example section of this chapter.
Portal node events extends the abstract portal event framework in order to provide notifications about user interface events happening at runtime. For instance when the portal renders a page or a window, a corresponding event will be fired.
The org.jboss.portal.api.node.event.PortalNodeEvent class extends the org.jboss.portal.api.node.PortalEvent class and is the base class for all events of portal nodes. It defines a single method PortalNode getNode() which can be used to retrieve the node targetted by the event.
The org.jboss.portal.api.node.event.WindowEvent is an extension for portal nodes of type window. It provides access to the mode and window state of the window. It has 3 subclasses which represent different kind of event that can target windows.
The org.jboss.portal.api.node.event.WindowNavigationEvent is fired when the window navigational state changes. For a portlet it means that the window is targetted by an URL of type render.
The org.jboss.portal.api.node.event.WindowActionEvent is fired when the window is targetted by an action. For a portlet it means that the window is targetted by an URL of type action.
The org.jboss.portal.api.node.event.WindowRenderEvent is fired when the window is going to be rendered by the portal.
The org.jboss.portal.api.node.event.PageEvent is an extension for portal nodes of type page.
The org.jboss.portal.api.node.event.PageRenderEvent is fired when the page is going to be rendered by the portal.
A portal node event is fired when an event of interest happens to a portal node of the portal tree. The notification model is comparable to the bubbling propagation model defined by the DOM specification. When an event is fired, the event is propagated in the hierarchy from the most inner node where the event happens to the root node of the tree.
The org.jboss.portal.api.node.event.PortalNodeEventListener interface should be used instead of the too generic org.jboss.portal.api.event.PortalEventListener when it comes down of listening portal node events. Actually it does not replace it, the PortalEventListener interface semantic allows only traditional event delivering. The PortalNodeEventListener interface is designed to match the bubbling effect during an event delivery.
The PortalNodeEvent onEvent(PortalNodeEventContext context, PortalNodeEvent event) declare a PortalNodeEvent as return type. In normal circumstances it will return the null value, however if the method call returns an event then this event should be considered by the portal as behavior replacing the current one.
The org.jboss.portal.api.node.event.PortalNodeEventContext interface extends the PortalEventContext interface and plays an important role in the event delivery model explained in the previous section. That interface gives full control over the delivery of the event to ascendant nodes in the hierarchy, even more it gives the possiblity to replace the current event being delivered by a new event that will be transformed into the corresponding portal behavior. However there are no guarantees that the portal will turn the returned event into a portal behavior, here the portal provides a best effort policy, indeed sometime it is not possible to achieve the substitution of one event by another.
Here the simplest implementation of a listener that does nothing except than correctly passing the control to a parent event listener if there is one.
public PortalNodeEvent onEvent(PortalNodeEventContext context, PortalNodeEvent event) { return context.dispatch(); }
The method PortalNode getNode() returns the current node being selected during the event bubbler dispatching mechanism.
The life cycle of the session of the portal associated with the user can also raise events. This kind of event is not bound to a portal node since it is triggered whenever a portal session is created or destroyed
There are two different types of events:
The life cycle of the portal user can also raise events such as its authentication. A subclass of the wider scope UserEvent class is provided and triggers events whenever a user signs in or out. The UserEvent object gives access to the user name of the logged-in user through the method String getId().
The UserAuthenticationEvent triggers two events that can be catched:
Based on the UserEvent class other custom user related events could be added like one that would trigger when a new user is being registered
The events mechanism is quite powerful, in this section of the chapter we will see few simple examples to explain how it works.
In this example, we will create a simple counter of the number of logged-in registered users. In ordder to do that we just need to keep track of Sign-in and Sign-out events.
First, let's write our listener. It just a class that will implement org.jboss.portal.api.event.PortalEventListener and its unique method void onEvent(PortalEventContext eventContext, PortalEvent event). Here is such an example:
package org.jboss.portal.core.portlet.test.event; import[...] public class UserCounterListener implements PortalEventListener { /** Thread-safe long */ private final SynchronizedLong counter = new SynchronizedLong(0); /** Thread-safe long */ private final SynchronizedLong counterEver = new SynchronizedLong(0); public void onEvent(PortalEventContext eventContext, PortalEvent event) { if (event instanceof UserAuthenticationEvent) { UserAuthenticationEvent userEvent = (UserAuthenticationEvent)event; if (userEvent.getType() == UserAuthenticationEvent.SIGN_IN) { counter.increment(); counterEver.increment(); } else if (userEvent.getType() == UserAuthenticationEvent.SIGN_OUT) { counter.decrement(); } System.out.println("Counter : " + counter.get()); System.out.println("Counter ever: " + counterEver.get()); } } }
On this method we simply filter down to UserAuthenticationEvent then depending on the type of authentication event we update the counters. counter keeps track of the registered and logged-in users, while counterEver only counts the number of times people logged-in the portal.
Now that the Java class has been written we need to register it so that it can be actionned when the events are triggered. To do so we need to register it as an mbean. It can be done by editing the sar descriptor file: YourService.sar/META-INF/jboss-service.xml so that it looks like the following:
<?xml version="1.0" encoding="UTF-8"?> <server> <mbean code="org.jboss.portal.core.event.PortalEventListenerServiceImpl" name="portal:service=ListenerService,type=counter_listener" xmbean-dd="" xmbean-code="org.jboss.portal.jems.as.system.JBossServiceModelMBean"> <xmbean/> <depends optional-attribute-name="Registry" proxy-type="attribute">portal:service=ListenerRegistry</depends> <attribute name="RegistryId">counter_listener</attribute> <attribute name="ListenerClassName"> org.jboss.portal.core.portlet.test.event.UserCounterListener </attribute> </mbean> </server>
This snippet can be kept as it is, providing you change the values:
That's it we now have a user counter that will display it states each time a user logs-in our logs-out.
The first version of the Portlet Specification (JSR 168), regretfully, did not cover interaction between portlets. The side-effect of diverting the issue to the subsequent release of the specification, has forced portal vendors to each craft their own proprietary API to achieve interportlet communication. Here we will see how we can use the event mechanism to pass parameters from one portlet to the other.
The overall scenario will be that Portlet B will need to be updated based on some parameter set on Portlet A. To achieve that we will use a portal node event.
Portlet A is a simple Generic portlet that has a form that sends a color name:
public class PortletA extends GenericPortlet { protected void doView(RenderRequest request, RenderResponse response) throws PortletException, PortletSecurityException, IOException { response.setContentType("text/html"); PrintWriter writer = response.getWriter(); writer.println("<form action=\"" + response.createActionURL() + "\" method=\"post\">"); writer.println("<select name=\"color\">"); writer.println("<option>blue</option>"); writer.println("<option>red</option>"); writer.println("<option>black</option>"); writer.println("</select>"); writer.println("<input type=\"submit\"/>"); writer.println("</form>"); writer.close(); } }
The other portlet (Portlet B) that will receive parameters from Portlet A is also a simple Generic portlet:
public class PortletB extends GenericPortlet { public void processAction(ActionRequest request, ActionResponse response) throws PortletException, PortletSecurityException, IOException { String color = request.getParameter("color"); if (color != null) { response.setRenderParameter("color", color); } } protected void doView(RenderRequest request, RenderResponse response) throws PortletException, PortletSecurityException, IOException { String color = request.getParameter("color"); response.setContentType("text/html"); PrintWriter writer = response.getWriter(); writer.println("<div" + (color == null ? "" : " style=\"color:" + color + ";\"") + ">some text in color</div>"); writer.close(); } // Inner listener explained after }
With those two portlets in hands, we just want to pass parameters from Portlet A to Portlet B (the color in as a request parameter in our case). In order to achieve this goal, we will write an inner Listener in Portlet B that will be triggered on any WindowActionEvent of Portlet A. This listener will create a new WindowActionEvent on the window of Portlet B.
public static class Listener implements PortalNodeEventListener { public PortalNodeEvent onEvent(PortalNodeEventContext context, PortalNodeEvent event) { PortalNode node = event.getNode(); // Get node name String nodeName = node.getName(); // See if we need to create a new event or not WindowActionEvent newEvent = null; if (nodeName.equals("PortletAWindow") && event instanceof WindowActionEvent) { // Find window B WindowActionEvent wae = (WindowActionEvent)event; PortalNode windowB = node.resolve("../PortletBWindow"); if (windowB != null) { // We can redirect newEvent = new WindowActionEvent(windowB); newEvent.setParameters(wae.getParameters()); // Redirect to the new event return newEvent; } } // Otherwise bubble up return context.dispatch(); } }
It is important to note here some of the important items in this listener class. Logic used to determine if the requesting node was Portlet A.:
nodeName.equals("PortletAWindow")
Get the current window object so we can dispatch the event to it:
PortalNode windowB = node.resolve("../PortletBWindow");
Set the original parameter from Portlet A, so Portlet B can access them in its processAction():
newEvent.setParameters(wae.getParameters());
Linking to some other pages or portals is also out of the scope of the portlet specification. As seen previously JBoss Portal offers an API in order to create links to other portal nodes. The JBoss request gives access to the current window node from which we can navigate from.
// Get the ParentNode. Since we are inside a Window, the Parent is the Page PortalNode thisNode = req.getPortalNode().getParent(); // Get the Node in the Portal hierarchy tree known as "../default" PortalNode linkToNode = thisNode.resolve("../default"); // Create a RenderURL to the "../default" Page Node PortalNodeURL pageURL = resp.createRenderURL(linkToNode); // Output the Node's name and URL for users html.append("Page: " + linkToNode.getName() + " -> "); html.append("<a href=\"" + pageURL.toString() + "\">" + linkToNode.getName() + "</a>");
From this, it is easy to create a menu or sitemap, the List getChildren() method will return all the child nodes on which the user has the view right access.
Those examples are available in the core-samples package in the sources of JBoss Portal. There are more examples of events usage in the samples delivered with JBoss Portal. One of them shows the usage of a portal node event to only have one window in normal mode at a time in a region. Anytime another window is being put in normal mode, all the other windows of the same regions are automatically minimized.