Resource Discovery Network - Home
SPP Open Source: Portlet HOWTO
documents

How to...


Write an SPP portlet

from jan's email to spp-dev...

File locations

First, where to put your code? The easiest method is to follow the standard SPP source code layout:

Backend functionality

Write all your functionality and make it available via service facades using the stock startup mechanism. This is important: the job of a portlet should simply be to choreograph the actions of back-end services (which are then testable separately from the UI, with any luck).

Your Portlet class

Now, put the SPP Portlet boiler-plate code in place:

package uk.ac.portal.spp.actions.@@YOUR-PACKAGE@@;
import org.apache.velocity.context.Context;

import uk.ac.portal.spp.common.Log;
import uk.ac.portal.spp.common.portlet.SPPPortletState;
import uk.ac.portal.spp.common.portlet.SPPRuntimeData;
import uk.ac.portal.spp.common.portlet.SPPPortletConfig;

import uk.ac.portal.spp.glue.portlet.BaseErrorHandlingPortlet;

@@YOUR PACKAGE-SPECIFIC IMPORTS GO HERE@@

public class @@YOUR-PORTLET@@ extends BaseErrorHandlingPortlet
{

}

BaseErrorHandlingPortlet is an implementation of some error-handling methods on top of an implementation of uk.ac.portal.spp.common.portlet.SPPPortlet. Such a base class provides facilities for you to use, as well as having some conventions for you to follow.

Views

From a UI point of view, your portlet might have a number of "views". (These are not _necessarily_ in one-to-one correspondence with velocity templates, but may be.) They represent the current "page" your portlet should display. The current "view" is called the "state switch".

State

In addition, your portlet has a "session" associated with it, called its "state". This is where you can store and fetch values that will persist between HTTP requests. You can set your portlet up initially by implementing the following method:

	public void initialiseState(
		SPPPortletConfig conf,
		SPPRuntimeData in,
		SPPPortletState state)
	{

		// To get configuration information
		String xregConfigParameter = (String)
			conf.getAttribute("@@YOUR XREG PARAM NAME@@");

		// We may also wish to get details about the user,
		// the user profile, etc.
		// the "in" parameter supplies these.

		state.setAttribute("attname", xregConfigParameter);
		setStateSwitch(state, "MainView");
	}

Producing a view

To produce a particular view, you have to do three things: write the code to populate a velocity context, add a call to choose the correct template, and write the velocity template itself.

You put this code into a method whose name is derived from the state switch: the following method would be called if the state switch had been set to "MainView" -

	public void buildNormalMainView(SPPRuntimeData in,
		Context cx,
		SPPPortletState state)
	{

		cx.put("velocity_variable_name",
			state.getAttribute("attname");

		setTemplate(in, cx, "mypackage/mytemplate");

	}

When the portal wants to draw your portlet, it examines the stateswitch, calls the right buildNormalXXX method, which constructs a velocity context (making variables available to the velocity template) and finally chooses which template to draw. Your velocity file itself can display the values of its supplied parameters (see example templates under the alerting portlet).

User interactions

A web application is also about user interaction. How do user operations get back to the portlet? Here's a sample velocity snippet:

<form action="$spplink.event("RenameWidget")" method="POST">
<p> The widget is called
	<input type="hidden" name="id" value="$widget.id" />
	<input type="text" name="name" value="$widget.name" />
	<a href="$spplink.event("DeleteWidget").add("id",$widget.id)">
		[delete this]
	</a>
	<input type="submit" value="Rename" />
</p>
</form>

which looks like this:

The widget is called [delete this]

OK, the snippet assumes our buildNormalXXX method has put an object called "widget" into the context, with methods getId() and getName(). The template can trigger two "events", which are the last piece of the lifecycle of a portlet. As you can see, events are generated using the "spplink" object. If the user submits the form, the following method in your code is called:

	public void eventRenameWidget(SPPRuntimeData in,
		SPPPortletState state)
	{
		String id = in.get("id");
		String name = in.get("name");

		/* Rename the widget */
		...

		setStateSwitch("DisplayWidget");
	}

If you click on the delete link, instead the eventDeleteWidget method will be called.

Convenience methods

The SPPPortlet supplies a number of convenience methods:

This last call can be used to get a facade instance. For example, my missing widget renaming code above might look like this:

		/* rename the widget */
		WidgetFacade wf = (WidgetFacade) getService(in, "spp.wf");
		wf.renameWidget(id, name);

Error handling

Finally, the error-handling. This may need a little sprucing up, but currently, the BaseErrorHandlingPortlet provides additional methods for getting responses to a user. These methods are as follows:

	void addNotice("some notice", SPPPortletState state);

with analogous addNoticeInfo, addNoticeWarn, and addNoticeError.

If you want to utilise these methods, you can cause a list of current pending notices to appear in your velocity template by including the line

#parse("portlets/html/spp/error-handling.vm")

which does the magic.

Example

There is a working example portlet that demonstrates all this: src/portlets/sppexample.

Summary

Here, in summary, are the steps you need to take to write an SPP portlet:

  1. Write back-end functionality
  2. Identify main views and allocate a stateswitch to each
  3. Identify the events that a user can cause to be sent
  4. Write buildNormalXXX for each stateswitch
  5. Write corresponding templates.
  6. Write eventYYY methods for each event.

The robustness diagrams can be a big help in identifying the views and events.

PS. From the MVC point of view:
The "model" is held in your SPPPortletState, and maybe also in various facades.
The "view" is selected by the stateswitch, and supplied by buildNormalXXX methods (and velocity templates selected via setTemplate)
The "controller" is implemented by supplying eventYYY methods. These can manipulate the supplied parameters from the user, the state, services, the user profile, etc, and possibly change the value of the stateswitch.
top

Add a portlet to the portal

The basic steps are as follows:

Portlets in Jetspeed

In jetspeed portlets are defined within .xreg files. Any file with the extension .xreg in the WEB-INF/conf/ directory will be parsed by jetspeed at startup. Portlets are defined within the <portlet-entry> construction. Here's an example:

    <portlet-entry name="AggregatePortlet" hidden="false"
        type="abstract" application="false">
        <security-ref parent="default"/>
        <classname>org.apache.jetspeed.portal.portlets.AggregatePortlet</classname>
        <url cachedOnURL="true"/>
    </portlet-entry>

Portlets have a unique name, and a classname, which must refer to a java class that implements the org.apache.jetspeed.portal.Portlet interface.

Each portlet definition can be one of three types:

abstract
cannot be created directly. It acts as a portlet template, useful for defining common properties for a group of related portlets.
instance
should provide all the necessary information to create the portlet, at least the classname.
ref
defines a new portlet based on an existing portlet entry which can be of any type, including other ref entries. The engine will follow all refs until it finds either an abstract or instance entry, override all parameters found in the these definitions by those in the ref definitions, and then create the portlet using the combined definitions.

(definitions lifted from the jetspeed site)

SPP conventions

At present we use the Velocity type abstract portlets. These link a name, and default parameters, to a classname, which means our ref portlet definitions don't need to include the classname parameter. We define SPP portlets that ref the Velocity portlets, and use the action parameter to point to the SPPPortlet class in question. See below for an example.

Jetspeed has several .xreg files installed by default:

portlets.xreg
abstract portlets. Overwritten by spp deploy to remove any non-abstract ones. Here's where the template RSS and Velocity portlets are defined.
admin.xreg
admin-specific portlets. Not available to non-admin users.
demo-portlets.xreg
example portlets. Overwritten, removing most of the examples.

In addition SPP portlets can be defined in spp/src/portlets/[portlet-name]/configuration/*.xreg files. These files should just contain the <portlet-entry> XML fragments. On deployment they are combined into a spp.xreg file and placed in the WEB-INF/conf directory. Here's an example, an RSS feed. Note how this is a ref type portlet, with a parent Velocity. The Velocity portlet (abstract) is defined in the portlets.xreg file.

<portlet-entry name="RDN_News" hidden="false" type="ref" parent="Velocity" application="false">
	<meta-info>
		<title>RDN News</title>
		<description>The latest news at the Resource Discovery Network</description>
	</meta-info>
	<parameter name="url" value="http://www.rdn.ac.uk/news/channels/rdnrss.xml"/>
	<parameter name="template" value="newsfeed/newsfeed.vm" hidden="false"/>
	<parameter name="action" value="newsfeed.NewsFeedPortlet" hidden="false"/>
	<media-type ref="html"/>
	<category>news</category>
</portlet-entry>

Notes:

top