Portlet Development Guide

advertisement
Portlet Development Guide
Introduction to the Portlet API
Edition 1.1
January 30, 2002
Authors:
Stephan Hesmer
Peter Fischer
Ted Buckner
Pervasive Computing Development
1.
Abstract .............................................................................................................................. 4
1.1.
2.
3.
Changes for this edition ........................................................................................... 4
Overview ............................................................................................................................. 5
2.1.
Portlets and Portlet Applications ............................................................................ 6
2.2.
Portlet modes ............................................................................................................ 6
2.3.
Portlet states .............................................................................................................. 7
2.4.
Portlet deployment descriptor ................................................................................. 7
2.5.
Examples in this document - the Bookmark Portlet ............................................. 8
Portlet API - Basic elements ........................................................................................... 10
3.1. Portlet ...................................................................................................................... 10
3.1.1.
getLastModified()............................................................................................. 11
3.2.
The scope of the portlet objects ............................................................................. 12
3.3. PortletRequest ........................................................................................................ 12
3.3.1.
Client ................................................................................................................ 13
3.4. PortletResponse ...................................................................................................... 15
3.4.1.
PortletURI ........................................................................................................ 16
3.5.
PortletConfig........................................................................................................... 17
3.6. PortletContext ......................................................................................................... 19
3.6.1.
PortletLog ......................................................................................................... 20
4.
3.7.
PortletSession .......................................................................................................... 21
3.8.
PortletData .............................................................................................................. 22
3.9.
PortletWindow ........................................................................................................ 24
3.10.
PortletTitle .......................................................................................................... 25
3.11.
the complete example ......................................................................................... 26
Java Server Pages ............................................................................................................ 30
4.1. Standard JSP Tags ................................................................................................. 30
4.1.1.
<jsp:useBean> .................................................................................................. 30
4.1.2.
<jsp:setProperty> ............................................................................................. 30
4.1.3.
<jsp:getProperty> ............................................................................................. 31
4.1.4.
<jsp:include> .................................................................................................... 31
4.1.5.
<jsp:forward> ................................................................................................... 31
4.1.6.
<jsp:param> ...................................................................................................... 31
4.1.7.
<jsp:plugin> ..................................................................................................... 31
4.1.8.
<jsp:params> .................................................................................................... 31
4.1.9.
<jsp:fallback> ................................................................................................... 31
4.2. Additional Portlet Tags .......................................................................................... 31
4.2.1.
<portletjsp:encodeURI> ................................................................................... 31
4.2.2.
<portletjsp:text> ............................................................................................... 32
4.3.
2
Rendering the sample using a JSP ........................................................................ 32
5.
4.4.
Model-View-Controller Portlets based on Actions ............................................. 34
4.5.
Implicit Objects ...................................................................................................... 35
4.6.
Reserved Parameter Names .................................................................................. 36
Portlet API - Advanced elements .................................................................................... 37
5.1. Portlet events .......................................................................................................... 37
5.1.1.
Action events .................................................................................................... 38
5.1.2.
Window events ................................................................................................. 42
5.1.3.
Message events ................................................................................................. 43
5.2.
Portlet Caching ....................................................................................................... 47
5.3. Portlet Services ....................................................................................................... 50
5.3.1.
ContentAccessService ...................................................................................... 50
6.
Portlet development issues .............................................................................................. 52
6.1.
No instance and class variables ............................................................................. 52
6.2.
Storing data ............................................................................................................. 52
6.3.
Namespaces/Javascript .......................................................................................... 52
6.4.
Multi-markup support ........................................................................................... 52
6.5.
Multi-language support ......................................................................................... 53
6.6. Portlet creation guidelines ..................................................................................... 53
6.6.1.
Performance issues ........................................................................................... 53
6.6.2.
Guidelines for markup ...................................................................................... 54
6.6.2.1.
Accessibility ............................................................................................. 54
7.
Using Single Sign-On ...................................................................................................... 55
7.1.
Basic authentication sample .................................................................................. 56
7.2.
LTPA example ........................................................................................................ 59
3
1. Abstract
The purpose of this document is to show how to develop a portlet using the Portlet
API, introducing concepts of the Portlet API along the way. Elements of the Portlet
API are described with the help of an example that progresses from a simple portlet
with no output to a complex portlet application with more advanced features. Portlet
development for the WebSphere Portal Server environment is also described.
1.1.
Changes for this edition
The following changes were made for edition 1.1:



4
Added a note about Java resource bundles for the example in
BookmarkPortlet_05.
Updated portlet deployment descriptor examples to show how to support edit
mode.
Updated the description of PortletData under the PortletRequest and
PortletData sections.
2. Overview
Portals are Web sites that serve as a starting point to information and applications on
the Internet or from an intranet. Early Internet portals, such as Yahoo, Excite, and
Lycos, categorized Web content and provided search facilities. Over the years,
portals have evolved to provide aggregation of content from diverse sources, such as
rich text, video, or XML, and personalized services, such as user customization of
layout and content.
To accommodate the aggregation and display of such diverse content, a portal server
must provide a framework that breaks the different portal components into portlets.
Each portlet is responsible for accessing content from its source (for example, a Web
site, database, or email server) and transforming the content so that it can be
rendered to the client. In addition, a portlet might be responsible for providing
application logic or storing information associated with a particular user. The portal
server provides a framework of services to make the task of writing and managing
portlets easier.
From a user's perspective, a portlet is a window in the portal that provides a specific
service or information, for example, a calendar or news feed. From an application
development perspective, portlets are pluggable modules that are designed to run
inside a portlet container of a portal server, similar to a servlet running on an
application server. User related data has to be stored in a session object that persists
across several requests and creates, along with the portlet instance, a virtual portlet
instance.
Figure: Portlets in a Portlet Container
There is always one instance of a portlet class per portlet configuration running inside
the portlet container. This means that all class variables of a portlet are shared
between multiple threads. Therefore the portlet developer has to be cautious when
using them. User related data has to be stored in a session object that persists
across several requests and creates together with the portlet instance a virtual portlet
instance.
Each portlet has the following life cycle:



The portlet is constructed and initialized with the init() method.
Any calls from the portlet container to the service() method are handled.
The portlet is taken out of service, then destroyed with the destroy()
method.
5
The service method takes a PortletRequest and a PortletResponse object as
parameters. The PortletRequest allows accessing different parameters of the
request as well as the PortletSession, the PortletConfig or the
PortletContext. The latter is used to obtain information about the portlet
container or access to resources. The PortletResponse has a method to obtain
an output stream as well as methods to create URIs and to encode them as markup.
A PortletURI represents a URI to a specific portlet function. The developer can
add parameters and PortletActions to a PortletURI. PortletActions are
portlet specific activities that need to be performed before the service method of
the portlet is called.
2.1.
Portlets and Portlet Applications
One or more portlets compose a portlet application. Portlet applications provide no
code on their own but form a logical group of portlets. Beside this more logical gain,
portlets of the same portlet application can also exchange messages. In the example
used in this document, when a URL link of the BookmarkPortlet is clicked,
BookmarkBrowserPortlet displays the contents of the URL. Both of these portlets are
packaged as part of the same portlet application. BookmarkPortlet sends the URL of
the link that was clicked to BookmarkBrowserPortlet. For more information about
messaging, see Message events.
2.2.
Portlet modes
Portlet modes allow a portlet to display a different user interface, depending on the
task required of the portlet. The following modes are provided by the Portlet API:

VIEW
When a portlet is initially constructed on the portal page for a
user, it is displayed in its view mode. This is the portlet’s
normal mode of operation.

HELP
If this mode is supported by a portlet, the portlet provides a
help page for users to obtain more information about the
portlet.

EDIT
If this mode is supported by a portlet, the portlet provides a
page for users to customize the portlet for their own needs.
For example, a portlet can provide a page for users to specify
their location for obtaining local weather and events.

CONFIGURE If this mode is supported by a portlet, the portlet provides a
page for portal administrators to configure a portlet for a user
or group of users. This mode is not support by WebSphere
Portal Server Version 1.2 .
The Portlet API provides methods for the portlet to determine the current mode.
6
2.3.
Portlet states
Portlet states allow users to change how the portlet window is displayed within the
portal. In a browser, users invoke these states with icons in the title bar in the same
way that Windows applications are manipulated. Portlet states are maintained in the
PortletWindow.State object with a boolean value.

Normal
When a portlet is initially constructed on the portal page, it is
displayed in its normal state – arranged on the page along with
other portlets.

Maximized
When a portlet is maximized, it is displayed in the entire body
of the portal, replacing the view of other portlets.

Minimized
When a portlet is minimized, only the portlet title bar is
displayed on the portal page.

Closed
When a portlet is closed, it is not displayed on the portal page.
This state is not support by WebSphere Portal Server Version
1.2 .

Detached
When a portlet is detached, it is rendered in a secondary
window. This state is not support by WebSphere Portal Server
Version 1.2 .

Moving
When a portlet is moving, the user is changing the portlet’s
position on the page. This state is not support by WebSphere
Portal Server Version 1.2 .

Resizing
When a portlet is resized, the user is changing the portlet’s
width and height. This state is not support by WebSphere
Portal Server Version 1.2 .
2.4.
Portlet deployment descriptor
The portlet deployment descriptor is an XML document that provides configuration
information about the portlet to the portal server. This information includes
configuration parameters specific to a particular portlet or portlet application as well
as general information that all portlets provide, such as the type of markup that the
portlet supports. The portal server uses this information to provide services for the
portlet. For example, if a portlet registers its support for help and edit mode in the
portlet deployment descriptor, the portal server will render icons to allow the user to
invoke the portlet’s help and edit pages.
The Portlet API provides methods for the portlet to read its configuration information
using the PortletConfig object. The portlet also has write access to this information
when it is in configure mode.
Simple Deployment Descriptor Sample
<?xml version="1.0"?>
<!DOCTYPE portlet-app PUBLIC "-//IBM//DTD Portlet Application 1.0//EN" "portlet.dtd">
<portlet-app>
<portlet-app-name>Bookmark Application</portlet-app-name>
<portlet>
<portlet-name>Bookmark Portlet</portlet-name>
7
<portlet-class>com.mycompany.portlets.bookmark.BookmarkPortlet</portlet-class>
<portlet-type>instance</portlet-type>
<allows>
<maximized/>
<minimized/>
</allows>
<language locale="en">
<title>My Bookmarks</title>
<title-short>Bookmarks</title-short>
<description>Portlet showing your personalized bookmarks</description>
<keywords>bookmarks</keywords>
</language>
<supports>
<markup name="html">
<view output="fragment"/>
<edit output="fragment"/>
</markup>
</supports>
</portlet>
</portlet-app>
2.5.
Examples in this document - the Bookmark Portlet
This guide shows all functions of the Portlet API by using only one example portlet
which is constantly extended during the sections, starting with a very simple empty
portlet. At last, a fully functional portlet is created that demonstrates each aspect of
the Portlet API and allows users to manage bookmarks.
BookmarkPortlet_01 demonstrates a portlet that implements the Portlet interface and
all of its methods. It does not provide any output for the portlet’s interface.
BookmarkPortlet_02 calls different methods depending on the current display mode.
It still does not provide any output
BookmarkPortlet_02a is the same as BookmarkPortlet_02 except that it extends
PortletAdapter rather than implementing the Portlet interface.
BookmarkPortlet_03 uses a Java PrintWriter to provide HTML markup to the
PortletResponse for view mode.
BookmarkPortlet_04 retrieves configuration parameters for the portlet.
BookmarkPortlet_05 uses the PortletContext object to retrieve the localized text from
a resource bundle and to write to the portlet log.
BookmarkPortlet_06 sets and gets session attributes for the virtual instance of the
portlet.
BookmarkPortlet_07 gets persistence data from the PortletData object, which is data
that is stored for a particular user for each use of the portlet.
BookmarkPortlet_08 tests the PortletWindow object to determine if the portlet is
maximized.
BookmarkPortletTitle_09 implements the PortletTitle class to allow the user to
change the title as it appears in the portlet title bar.
BookmarkPortlet_10(portlet.xml) shows the portlet deployment descriptor for
BookmarkPortlet_08 and BookmarkPortletTitle_09.
8
BookmarkPortlet_11 uses PortletContext to include a JSP for view mode
(bookmarkView.jsp) and instantiates ViewBean_11 for adding user-defined
bookmarks.
BookmarkPortlet_12 includes a JSP for edit mode (bookmarkEdit.jsp) and associates
a PortletAction to the JSP form’s POST method.
BookmarkPortletActionListener_12 listens for “add” and “removeAll” actions in
BookmarkPortlet12.
BookmarkPortlet_13 adds a browse action which launches its
BookmarkPortletActionListener_13 to send the URL of the link to
BookmarkBrowserPortlet_13, which uses its
BookmarkPortletMessageListener_13 to receive the URL.
9
3. Portlet API - Basic elements
This section describes the basic interfaces and methods of the Portlet API. The
following figure shows a map of many of the common objects in the Portlet API.
3.1.
Portlet
The Portlet interface is the central abstraction of the Portlet API. All portlets
implement this interface, most often by extending a class that implements the
interface, such as PortletAdapter. The portlet container calls the following methods
of the Portlet interface during the portlet’s life cycle:
10

init()
The portlet is constructed, after portal initialization, and then
initialized with the init() method. The portal always instantiates
only a single instance of the portlet, and this instance is shared
among all users, much the same way a servlet is shared
among all users of an application server.

login()
After a user logs in to a portal, each portlet creates a session
for the user. The combination of the physical portlet instance
and the user session creates the portlet virtual instance. The
start of a virtual instance is signaled by the portal calling the
login() method on the portlet. This method allows the portlet to
initialize the user's session instance of the portlet, for example,
to store attributes in the session.

service()
The portal calls the service() method when the portlet is
required to render it's content. During the life cycle of the
portlet, the service() method is typically called many times.

logout()
When the user ends the session with the portal, the portal
server calls the logout() method to inform the portlet that the
user's session instance is terminating. The portlet should then
clean up any resources for the virtual instance.

destroy()
When the portal is terminating, portlets are taken out of
service, then destroyed with the destroy() method. Finally the
portlet is garbage collected and finalized.
3.1.1. getLastModified()
The portlet container provides a built-in caching mechanism to gain better
performance. The Portlet interface provides the getLastModified() method, which is
called by every request to enable the portlet to inform the container when the cache
entry for the portlet should be invalidated and therefore the portlet’s content should
be refreshed. The getLastModified() method returns the last time the content of the
portlet changed, in milliseconds, between the current time and midnight, January 1,
1970 UTC (java.lang.System.currentTimeMillis()) or a negative value if it is unknown.
For an example of how this method is used, see Portlet Caching .
The following example shows a basic portlet that implements all functions of the
Portlet interface and stores the PortletConfig in an instance variable to make
it available in all methods. At this stage of development, the portlet does not provide
any output. As a result, an empty portlet window is displayed in the portal.
BookmarkPortlet_01.java
package com.mycompany.portlets.bookmark;
import org.apache.jetspeed.portlet.*;
import java.io.IOException;
public class BookmarkPortlet_01 implements Portlet
{
private PortletConfig config = null;
public void init(PortletConfig config)
throws UnavailableException
{
// init is called when the portlet is taken in service
this.config = config;
}
// an additional helper method to get the stored config
// -> not part of the portlet interface
public PortletConfig getConfig ()
{
return config;
}
11
public void login(PortletRequest request)
throws PortletException
{
// login is called when the user is logged in and the portlet is shown on a page
}
public void service(PortletRequest request, PortletResponse response)
throws PortletException,IOException
{
// service is called to render the portlet
// in this case the portlet does not render anything
// and an empty portlet is being shown
}
public void logout(PortletSession session)
throws PortletException
{
// logout is called when the session times out or the user logs out
}
public void destroy()
{
// when the portlet is taken out of service the destroy method is called
}
public long getLastModified(PortletRequest request)
{
// we don't know the last modification time of this portlet, use default.
return -1;
}
}
3.2.
The scope of the portlet objects
The following table shows the scope of portlet objects as they correspond to other
elements of the Portlet API.
Scope
Objects
PortletRequest
PortletResponse
PortletData
PortletSession
PortletConfig
PortletContext
Request
per
per
All
All
All
All
Portlet
portlet instance
virtual instance
per
all
User
per
all
all
Portlet Application
per
Legend:
 per, means that the object is directly related to the scope. You can read it like
this and replace the cursive words with the appropriate object/scope: It exists
one object per scope.
 -, means that the object has no relation to the scope.
 virtual instance, means that there is one PortletSession for each virtual
instance of the portlet.
 portlet instance, means that there is one PortletData for each portlet
occurrence on a page.
3.3.
12
PortletRequest
The PortletRequest object is passed to the portlet through the login() and service()
methods, providing the portlet with request-specific data and the opportunity to
access further important information as listed below.

Attributes
Attributes are name/value pairs associated with a request. Attributes are
available only for the scope of the request. The portlet can get, set, and
remove attributes during one request.

Parameters
Parameters are name/value pairs sent by the client in the URI query string as
part of a request. Often the parameters are posted from a form. Parameters
are available for the scope of a specific request. The portlet can get but not set
parameters from a request.

Client
The Client object encapsulates all information about the user agent of a
specific request. Information from the Client object includes the manufacturer
of the user agent or the type of markup that the client supports.

User data
The PortletData object represents data specific to the portlet instance that is
saved to persistent store. For example, a user can set a portlet e-mail
application to check for new mail every 30 minutes. This preference is stored
for the instance in the PortletData object.

Session
The PortletSession object contains a user’s data for more than one request.
In contrast with the request, which does not retain data after the request is
completely processed, session attributes can be remembered/saved over
more than one request.

Mode
Portlet.Mode provides the current or previous mode of the portlet.

PortletWindow
The PortletWindow object represents the state of the current portlet window.
The portlet can access this object to determine if the portlet is currently
maximized, minimized, or rendered in its normal view.

ModeModifier
This object can be used in a PortletAction to set the portlet mode to its
previous or requested mode before the portlet is rendered.
3.3.1. Client
The Client object encapsulates request-dependent information about the user agent
of a specific request. The Client is extracted from the PortletRequest using the
getClient() method. The following information can be obtained from the Client:

User agent
13
The portlet can get the String sent by the user agent to identify itself to the
portal.

Markup name
The portlet can get the String that indicates the markup language that the
client supports, for example, “wml”.

MIME type
The portlet can get the String that indicates the MIME types supported by the
client (for example: “text/vnd.wap.wml”). If the portlet supports multiple types
of devices, it should get the markup name rather than the MIME type. The
following table shows MIME types and their corresponding markup types.
MIME types
text/html
text/vnd.wap.wml
text/html

Markup types
html
wml
chtml
Capabilities
The Capability object contains more detailed information than the markup type
about what the client can support, such as the level of HTML, Javascript, or
WML tables.
Information about the portlet’s mode retrieved from the request can be used to
implement helper methods for each mode to achieve more structured code as shown
in the following example.
BookmarkPortlet_02.java
…
public void service(PortletRequest request, PortletResponse response)
throws PortletException,IOException
{
// get the mode we are currently running in
Portlet.Mode mode = request.getMode();
if (mode == Portlet.Mode.VIEW)
doView (request, response);
else if (mode == Portlet.Mode.EDIT)
doEdit (request, response);
else if (mode == Portlet.Mode.HELP)
doHelp (request, response);
else if (mode == Portlet.Mode.CONFIGURE)
doConfigure (request, response);
}
public void doView (PortletRequest request,
PortletResponse response) throws PortletException,
IOException
{
// insert your view code here
// get an attribute out of the request
String someAttribute = (String)request.getAttribute("SomeAttribute");
// get the client of the current request
Client client = request.getClient();
}
public void doEdit (PortletRequest request,
PortletResponse response) throws PortletException,
14
IOException
{
// insert your edit code here
}
public void doHelp (PortletRequest request,
PortletResponse response) throws PortletException,
IOException
{
// insert your help code here
}
public void doConfigure (PortletRequest request,
PortletResponse response) throws PortletException,
IOException
{
// insert your configure code here
}
…
The sample code here (plus the code of the first sample shown above) is almost
equivalent to the implementation of the PortletAdapter class (for more details see
JavaDoc). You should not implement the Portlet interface directly in your portlets, but
rather extend PortletAdapter or any other helper class that in turn implements the
Portlet interface. Extending one of the helper classes helps protect your portlet from
changes in the Portlet interface. Moreover it saves you the work of implementing all
of the methods of the Portlet interface, even if your portlet does not need to use them
all. Using the PortletAdapter class, you only have to overwrite the methods you really
need.
The example in BookmarkPortlet_02a provides the same functionality as
BookmarkPortlet_02, but using the PortletAdapter class.
BookmarkPortlet_02a.java
package com.mycompany.portlets.bookmark;
import org.apache.jetspeed.portlet.*;
import java.io.IOException;
public class BookmarkPortlet_02a extends PortletAdapter {
public void doView (PortletRequest request,
PortletResponse response) throws PortletException,
IOException
{
// get an attribute out of the request
String someAttribute = (String)request.getAttribute("SomeAttribute");
// get the client of the current request
Client client = request.getClient();
}
}
3.4.
PortletResponse
The response object encapsulates information to be returned from the server to the
client. PortletResponse is passed via the service method and is used by the portlet to
return portlet content using a Java PrintWriter. The response also includes methods
for creating the PortletURI object or qualifying portlet markup with the portlet’s
namespace.
15
Use one of the following methods to create the PortletURI:



createURI() - Creates a PortletURI object pointing to the calling portlet with the
current mode
createURI(PortletWindow.State state) - Creates a PortletURI object pointing to
the calling portlet with the current mode and given portlet window state.
createReturnURI() - Creates a portlet URI pointing at the caller of the portlet.
For example, createReturnURI() can used to create a back button in an edit
mode.
Each portlet runs in its own unique namespace. encodeNamespace() is used by
portlets to bring attributes in the portlet’s namespace to avoid name clashes with
other portlets. Attributes can include parameter names, global variables, or javascript
function names.
3.4.1. PortletURI
The PortletURI object contains a URI pointing to the Portlet instance and can be
further extended by adding portlet-specific parameters and by attaching actions.
Actions are portlet-specific activities that need to be performed as result of the
incoming request, but before the service() method of the portlet is called. For
example, when a user is entering data in the portlet’s edit mode and clicks a “Save”
button, the portlet needs to process the posted data before the next markup is
generated. This can be achieved by adding a "Save" action to the URI that
represents the "Save" button.
The complete URI can be converted to a string which is ready for embedding into
markup.
BookmarkPortlet_03.java
package com.mycompany.portlets.bookmark;
import org.apache.jetspeed.portlet.*;
import java.io.IOException;
import java.io.PrintWriter;
public class BookmarkPortlet_03 extends PortletAdapter
{
public void doView(PortletRequest request, PortletResponse response)
throws PortletException,IOException
{
// get the writer to output text
PrintWriter writer = response.getWriter();
writer.println("<h2>This is the bookmark portlet.</h2>");
// show an image
writer.println("<br>Example graphic: <img
src='"+response.encodeURI("/images/java.gif")+"'>");
// output an URL pointing to this portlet with the state maximized
PortletURI url = response.createURI(PortletWindow.State.MAXIMIZED);
writer.println("<br><a href=\""+url.toString()+"\">Click here to maximize this
portlet</a><br>");
}
}
16
In BookmarkPortlet_03, the portlet writes HTML markup to the PortletResponse for
view mode. Included in the markup is a link that points to the portlet’s maximized
state.
The portlet looks like this:
3.5.
PortletConfig
The PortletConfig provides the portlet with its configuration. The configuration holds
information about the portlet that is valid for the portlet instance (that is, the same
configuration information applies for every virtual instance of the portlet).
A portlet’s configuration is initially read from the portlet deployment descriptor. This
information is normally maintained by the portlet administrator. When the portlet is in
view, edit, or help mode, it has only read access to its configuration. When the portlet
is in configure mode, it has read and write access to its configuration.
Often, the PortletConfig object is used to access portlet-specific configuration
parameters using getAttribute() or the PortletContext object.
Attributes are
name/value pairs available for the complete life cycle of a portlet. A portlet’s
attributes are defined by the <config-param> tag in the portlet deployment descriptor.
The following shows the deployment descriptor for BookMarkPortlet_04. In this
example, the descriptor defines five configuration parameters that the portlet can
read using the getAttribute() method.
portlet.xml (Deployment Descriptor)
<?xml version="1.0"?>
<!DOCTYPE portlet-app PUBLIC "-//IBM//DTD Portlet Application 1.0//EN" "portlet.dtd">
<portlet-app>
<portlet-app-name>Bookmark Application</portlet-app-name>
<context-param>
<param-name>Webmaster</param-name>
<param-value>userid@de.ibm.com</param-value>
</context-param>
<portlet>
<portlet-name>Bookmark Portlet</portlet-name>
<portlet-class>com.mycompany.portlets.bookmark.BookmarkPortlet_08</portlet-class>
<portlet-type>instance</portlet-type>
<allows>
<maximized/>
17
<minimized/>
</allows>
<language locale="en">
<title>My Bookmarks</title>
<title-short>Bookmarks</title-short>
<description>Portlet showing your personalized bookmarks</description>
<keywords>bookmarks</keywords>
</language>
<supports>
<markup name="html">
<view output="fragment"/>
<edit output="fragment"/>
</markup>
</supports>
<config-param>
<param-name>BookmarkCount</param-name>
<param-value>2</param-value>
</config-param>
<config-param>
<param-name>Bookmark.Name.1</param-name>
<param-value>Jetspeed</param-value>
</config-param>
<config-param>
<param-name>Bookmark.URL.1</param-name>
<param-value>http://jakarta.apache.org/jetspeed/site/index.html</param-value>
</config-param>
<config-param>
<param-name>Bookmark.Name.2</param-name>
<param-value>Google</param-value>
</config-param>
<config-param>
<param-name>Bookmark.URL.2</param-name>
<param-value>http://www.google.com</param-value>
</config-param>
</portlet>
</portlet-app>
In BookmarkPortlet_04, the doView() method retrieves the number of bookmarks, the
name for each bookmark, and its URL in the portlet configuration. All of these
parameters are set in the portlet deployment descriptor; although, they can be
changed by the portal administrator after deployment using Portlet Administration.
BookmarkPortlet_04.java
package com.mycompany.portlets.bookmark;
import org.apache.jetspeed.portlet.*;
import java.io.IOException;
import java.io.PrintWriter;
public class BookmarkPortlet_04 extends PortletAdapter
{
public void doView(PortletRequest request, PortletResponse response)
throws PortletException,IOException
{
PrintWriter writer = response.getWriter();
writer.println("<b>Preconfigured bookmarks:</b><br>");
String preConfCount = (String)getConfig().getAttribute("BookmarkCount");
if (preConfCount!=null)
{
int max = Integer.parseInt(preConfCount);
for (int i=1; i<=max; i++)
{
String name = (String)getConfig().getAttribute("Bookmark.Name."+i);
String url = (String)getConfig().getAttribute("Bookmark.URL."+i);
writer.println("<br><a href=\""+url+"\">"+name+"</a>");
}
}
}
}
18
The portlet looks like this:
3.6.
PortletContext
The PortletContext interface defines a portlet's view of the portlet container within
each portlet that is running. The PortletContext also allows a portlet to access
resources available to it. For example, using the context, a portlet can access the
portlet log, access context parameters common to all portlets within the portlet
application, obtain URL references to resources, or get portlet services
The most important information related to the PortletContext is described in detail
below:

Attributes
Attributes are name/value pairs available to all portlets within the portlet
application. These are defined in the portlet deployment descriptor under the
<context-parameter> element. For example, if a group of portlets share a
context parameter called “Webmaster” that contains the portal site’s
administrator email, each portlet could get that value and provide a “mailto”
link in their help. Attributes of the context are stored on a single machine and
are not distributed in a cluster.

Log
The PortletLog provides the portlet with the ability to log informational,
warning, or error messages.

Localized text
The getText() method is used by the portlet to access resource bundles within
a given locale.

Resources
It is through the PortletContext that a portlet can load or include resources
located in the portlet’s application scope. Available methods are include() and
getResourceAsStream(). The include() method is typically used to invoke
JSPs for output.

Messaging
Through messaging, it is possible to communicate between portlets and share
data or send notifications. A message is sent by using the send() method.

PortletServices
19
PortletServices allow portlets to use plugable services via dynamic discovery.
See Portlet Services for more information.
3.6.1. PortletLog
The PortletLog provides the portlet with the ability to log informational, warning or
error messages. The log is maintained by the portlet container. Whether logging is
enabled or not is at the discretion of the portlet container.
The following line of code shows how you would obtain the PortletLog from the
PortletConfig:
PortletLog log = portletConfig.getContext().getLog();
As the implementation of the PortletAdapter class does this for you, just use the
adapter’s method getPortletLog().
In BookmarkPortlet_05, the getText() method is used to retrieve the localized value of
the PreDefinedBookmarks parameter, which is set in a properties file,
nls_bookmark_05.properties . This value is added to the PrintWriter to be
rendered in the portlet as a heading. Finally, a loop generates bookmark names and
links and adds each of them to the PrintWriter.
BookmarkPortlet_05.java
package com.mycompany.portlets.bookmark;
import org.apache.jetspeed.portlet.*;
import java.io.IOException;
import java.io.PrintWriter;
public class BookmarkPortlet_05 extends PortletAdapter
{
public void service(PortletRequest request, PortletResponse response)
throws PortletException,IOException
{
getPortletLog().debug("service doView called");
PrintWriter writer = response.getWriter();
// get PortletContext
PortletContext context = getConfig().getContext();
// get localized Text
String localizedText =
context.getText("nls.bookmark_05","PreDefinedBookmarks",request.getLocale());
writer.println("<b>"+localizedText+"</b><br>");
String preConfCount = (String)getConfig().getAttribute("BookmarkCount");
if (preConfCount!=null)
{
int max = Integer.parseInt(preConfCount);
for (int i=1; i<=max; i++)
{
String name = (String)getConfig().getAttribute("Bookmark.Name."+i);
String url = (String)getConfig().getAttribute("Bookmark.URL."+i);
// get localized Text
localizedText = context.getText("nls.bookmark_05",name,request.getLocale());
writer.println("<br><a href=\""+url+"\">"+localizedText+"</a>");
}
}
}
}
20
Note: The samples in this document following standard Java conventions for resource
bundles.
In
this
example,
the
properties
file
should
be
renamed
bookmark_05.properties and packaged in the /PORTLET-INF/classes/nls
directory of the PAR file. For more information, see Accessing Resources in a LocationIndependent Manner in the JDK documentation.
nls_bookmark_05.properties
# localized text of the bookmark portlet
PreDefinedBookmarks = Predefined bookmarks:
Jetspeed = Jetspeed Portal Project
Google = Google Search
The portlet looks like this:
3.7.
PortletSession
The PortletSession holds user-specific data for the virtual instance of the portlet.
Virtual instances differ from each other only by the data stored in their respective
PortletSession or objects. The PortletSession contains transient data for the portlet
virtual instance. Any persistent data must be stored using PortletData. Information
stored in a portlet’s class variables is shared between all virtual instances – with read
and write access. Make sure you do not use class attributes for user-specific data.
On the other hand, you have to be cautious about what the portlet adds to the
session, especially if the portlet ever runs in a cluster environment where the session
is being serialized to a shared database. Everything being stored in the session must
be serializable, too.
Like the HttpSession, a PortletSession is not available on an anonymous page.
During login, a PortletSession is automatically created for each portlet on a page. To
get a PortletSession, the getSession() method (available from the PortletRequest)
has to be used. The method returns the current session or, if there is no current
session and the given parameter “create” is true, it creates one and returns it.
In BookmarkPortlet_06, the session is retrieved in the login() method. Then the
BookmarkCount parameter is retrieved and associated with the session.
The BookmarkCount value is retrieved from the session (if the user is logged in) or
from the PortletConfig (if the user is not logged in and no session exists).
BookmarkPortlet_06.java
21
package com.mycompany.portlets.bookmark;
import org.apache.jetspeed.portlet.*;
import java.io.IOException;
import java.io.PrintWriter;
public class BookmarkPortlet_06 extends PortletAdapter
{
public void login(PortletRequest request)
throws PortletException
{
// login is called when the user is logged in and the portlet is shown on a page
getPortletLog().debug("login called");
// get PortletSession
PortletSession session = request.getSession();
String preConfCount = (String)getConfig().getAttribute("BookmarkCount");
session.setAttribute("BookmarkCount", preConfCount);
}
public void doView(PortletRequest request, PortletResponse response)
throws PortletException,IOException
{
getPortletLog().debug("service doView called");
PrintWriter writer = response.getWriter();
// get PortletContext
PortletContext context = getConfig().getContext();
// get localized Text
String localizedText =
context.getText("nls.bookmark_05","PreDefinedBookmarks",request.getLocale());
writer.println("<b>"+localizedText+"</b><br>");
int max = 0;
String preConfCount = null;
// get PortletSession, only when logged in
PortletSession session = request.getSession(false);
// when valid session is returned, get the counter from the session
if (session!=null)
preConfCount = (String)session.getAttribute("BookmarkCount");
// when counter could not be gotten from the session load it from the
// configuration (only on default page)
if (preConfCount==null)
preConfCount = (String)getConfig().getAttribute("BookmarkCount");
// convert counter into int
if (preConfCount!=null)
max = Integer.parseInt(preConfCount);
for (int i=1; i<=max; i++)
{
String name = (String)getConfig().getAttribute("Bookmark.Name."+i);
String url = (String)getConfig().getAttribute("Bookmark.URL."+i);
// get localized Text
localizedText = context.getText("nls.bookmark_05",name,request.getLocale());
writer.println("<br><a href=\""+url+"\">"+localizedText+"</a>");
}
}
}
nls_bookmark_05.properties
# localized text of the bookmark portlet
PreDefinedBookmarks = Predefined bookmarks:
Jetspeed = Jetspeed Portal Project
Google = Google Search
3.8.
22
PortletData
The PortletData holds portlet instance specific data for the virtual instance of the
portlet. For each occurrence on a page there is a portlet instance. This means, when
a portlet is on a group page, PortletData holds group specific data, and when a
portlet is on a user page, PortletData holds user specific data. The PortletData
contains persistent information about the virtual instance of a portlet while the
PortletSession contains only the transient data of the virtual instance.
The PortletData stores attributes as name/value pairs. The portlet can get, set, and
remove attributes during one request. To commit the changes, the store() method
has to be called.
Portions of the doView() method from BookmarkPortlet_07 are shown in the following
example. This portlet is like BookmarkPortlet_06 with the addition of a new section
that shows a list of user-defined bookmarks. The bookmarks are retrieved using the
getAttribute() method from the PortletData object. The bookmarks are stored in the
portlet’s edit mode, which is shown further in this document in bookmarkEdit.jsp and
using an ActionListener.
BookmarkPortlet_07.java
…
public void doView(PortletRequest request, PortletResponse response)
throws PortletException,IOException
{
…
for (int i=1; i<=max; i++)
{
String name = (String)getConfig().getAttribute("Bookmark.Name."+i);
String url = (String)getConfig().getAttribute("Bookmark.URL."+i);
// get localized Text
localizedText = context.getText("nls.bookmark_05",name,request.getLocale());
writer.println("<br><a href=\""+url+"\">"+localizedText+"</a>");
}
// show a list with user defined bookmarks
// get PortletData holding user related data
PortletData data = request.getData();
// get localized Text
localizedText =
context.getText("nls.bookmark_07","UserDefinedBookmarks",request.getLocale()
);
writer.println("<br><br><b>"+localizedText+"</b><br>");
max = 0;
String userConfCount = (String)data.getAttribute("BookmarkCount");
// convert counter into int
max = Integer.parseInt(userConfCount);
for (int i=1; i<=max; i++)
{
String name = (String)data.getAttribute("Bookmark.Name."+i);
String url = (String)data.getAttribute("Bookmark.URL."+i);
writer.println("<br><a href=\""+url+"\">"+name+"</a>");
}
…
23
The portlet looks like this:
3.9.
PortletWindow
The PortletWindow represents the window that encloses a portlet. The portlet window
can send events on manipulation of its various window controls, like when the user
clicks minimize or close. The portlet, in turn, can interrogate the window about its
current visibility state. For example, a portlet may render its content differently
depending on whether its window is maximized or not. The PortletWindow is
available from the PortletRequest object.
In BookmarkPorlet_08, the PortletWindow is used to determine if the window is
maximized. Further on in the loop that generates the bookmarks for output, the URL
is also displayed if the window is maximized. If not, only the bookmark name is
displayed. The same code is used that displays the user-defined bookmarks.
BookmarkPortlet_08.java
…
public void doView(PortletRequest request, PortletResponse response)
throws PortletException,IOException
{
getPortletLog().debug("service doView called");
// check the current state of the portlet
// if it is maximized we will also output the URL of the bookmark
boolean isMaximized = request.getWindow().isMaximized();
PrintWriter writer = response.getWriter();
// get PortletContext
PortletContext context = getConfig().getContext();
// get localized Text
String localizedText =
context.getText("nls.bookmark_07","PreDefinedBookmarks",request.getLocale());
writer.println("<b>"+localizedText+"</b><br>");
int max = 0;
String preConfCount = null;
// get PortletSession, only when logged in
PortletSession session = request.getSession(false);
// when valid session is returned, get the counter from the session
if (session!=null)
preConfCount = (String)session.getAttribute("BookmarkCount");
// when counter could not be get from the session load it from the configuration (only
on default page)
if (preConfCount==null)
preConfCount = (String)getConfig().getAttribute("BookmarkCount");
// convert counter into int
if (preConfCount!=null)
max = Integer.parseInt(preConfCount);
24
for (int i=1; i<=max; i++)
{
String name = (String)getConfig().getAttribute("Bookmark.Name."+i);
String url = (String)getConfig().getAttribute("Bookmark.URL."+i);
// get localized Text
localizedText = context.getText("nls.bookmark_07",name,request.getLocale());
if (isMaximized)
writer.println("<br><a href=\""+url+"\">"+localizedText+" - "+url+"</a>");
else
writer.println("<br><a href=\""+url+"\">"+localizedText+"</a>");
}
// show a list with user defined bookmarks
// get PortletData holding user related data
PortletData data = request.getData();
// get localized Text
localizedText =
context.getText("nls.bookmark_07","UserDefinedBookmarks",request.getLocale());
writer.println("<br><br><b>"+localizedText+"</b><br>");
max = 0;
String userConfCount = (String)data.getAttribute("BookmarkCount");
// convert counter into int
max = Integer.parseInt(userConfCount);
for (int i=1; i<=max; i++)
{
String name = (String)data.getAttribute("Bookmark.Name."+i);
String url = (String)data.getAttribute("Bookmark.URL."+i);
if (isMaximized)
writer.println("<br><a href=\""+url+"\">"+name+" - "+url+"</a>");
else
writer.println("<br><a href=\""+url+"\">"+name+"</a>");
}
}
…
The maximized portlet looks like this:
3.10.
PortletTitle
The PortletTitle interface is used to allow the portlet title, as it is displayed in the title
bar, to be changed based on a condition (for example, the type of device used to
access the portal) or user input (for example, a preference that the user sets on the
edit page). The class implementing PortletTitle is specified in the <title-class>
element of the portlet deployment descriptor. If <title-class> is not specified, the
portlet will display the title specified on the <title> element (under <language>).
The PortletTitle has the same life cycle as the portlet itself.
BookmarkPortletTitle_09.java
25
package com.mycompany.portlets.bookmark;
import org.apache.jetspeed.portlet.*;
import java.io.IOException;
import java.io.PrintWriter;
public class BookmarkPortletTitle_09 implements PortletTitle
{
private PortletConfig config = null;
public void init(PortletConfig config)
{
// init is called when the portlet is taken in service
this.config = config;
}
public void service(PortletRequest request, PortletResponse response)
throws PortletException,IOException
{
// service is called to render the portlet title
PrintWriter writer = response.getWriter();
// get session. when user is not logged in we get "null"
PortletSession session = request.getSession(false);
if (session!=null) {
String userConfCount = (String)session.getAttribute("UserDefBookmarkCount");
// when counter could not be get from the session load it from the configuration
once
if (userConfCount==null) {
PortletData data = request.getData();
userConfCount = (String)data.getAttribute("BookmarkCount");
}
writer.print("Bookmark Portlet ("+userConfCount+" bookmark(s))");
}
else {
// return static title
writer.print(config.getTitle(request.getLocale(),request.getClient()));
}
}
public void destroy()
{
// when the portlet is taken out of service the destroy method is called
}
}
The portlet’s title bar looks like this:
3.11.
the complete example
BookmarkPortlet_08.java
package com.mycompany.portlets.bookmark;
import org.apache.jetspeed.portlet.*;
import java.io.IOException;
import java.io.PrintWriter;
public class BookmarkPortlet_08 extends PortletAdapter
{
public void login(PortletRequest request)
throws PortletException
{
// login is called when the user is logged in and the portlet is shown on a page
getPortletLog().debug("login called");
// get PortletSession
PortletSession session = request.getSession(false);
26
// when valid session is returned, get the counter from the session
if (session!=null) {
String preConfCount = (String)getConfig().getAttribute("BookmarkCount");
session.setAttribute("BookmarkCount", preConfCount);
}
}
public void doView(PortletRequest request, PortletResponse response)
throws PortletException,IOException
{
getPortletLog().debug("service doView called");
// check the current state of the portlet
// if it is maximized we will also output the URL of the bookmark
boolean isMaximized = request.getWindow().isMaximized();
PrintWriter writer = response.getWriter();
// get PortletContext
PortletContext context = getConfig().getContext();
// get localized Text
String localizedText =
context.getText("nls.bookmark_07","PreDefinedBookmarks",request.getLocale());
writer.println("<b>"+localizedText+"</b><br>");
int max = 0;
String preConfCount = null;
// get PortletSession, only when logged in
PortletSession session = request.getSession(false);
// when valid session is returned, get the counter from the session
if (session!=null)
preConfCount = (String)session.getAttribute("BookmarkCount");
// when counter could not be get from the session load it from the configuration (only
on default page)
if (preConfCount==null)
preConfCount = (String)getConfig().getAttribute("BookmarkCount");
// convert counter into int
if (preConfCount!=null)
max = Integer.parseInt(preConfCount);
for (int i=1; i<=max; i++)
{
String name = (String)getConfig().getAttribute("Bookmark.Name."+i);
String url = (String)getConfig().getAttribute("Bookmark.URL."+i);
// get localized Text
localizedText = context.getText("nls.bookmark_07",name,request.getLocale());
if (isMaximized)
writer.println("<br><a href=\""+url+"\">"+localizedText+" - "+url+"</a>");
else
writer.println("<br><a href=\""+url+"\">"+localizedText+"</a>");
}
// *** new section
// show a list with user defined bookmarks
// get PortletData holding user related data
PortletData data = request.getData();
// get localized Text
localizedText =
context.getText("nls.bookmark_07","UserDefinedBookmarks",request.getLocale());
writer.println("<br><br><b>"+localizedText+"</b><br>");
max = 0;
String userConfCount = (String)data.getAttribute("BookmarkCount");
// convert counter into int
max = Integer.parseInt(userConfCount);
for (int i=1; i<=max; i++)
{
String name = (String)data.getAttribute("Bookmark.Name."+i);
String url = (String)data.getAttribute("Bookmark.URL."+i);
if (isMaximized)
writer.println("<br><a href=\""+url+"\">"+name+" - "+url+"</a>");
else
writer.println("<br><a href=\""+url+"\">"+name+"</a>");
}
}
27
}
BookmarkPortletTitle_09.java
package com.mycompany.portlets.bookmark;
import org.apache.jetspeed.portlet.*;
import java.io.IOException;
import java.io.PrintWriter;
public class BookmarkPortletTitle_09 implements PortletTitle
{
private PortletConfig config = null;
public void init(PortletConfig config)
{
// init is called when the portlet is taken in service
this.config = config;
}
public void service(PortletRequest request, PortletResponse response)
throws PortletException,IOException
{
// service is called to render the portlet title
PrintWriter writer = response.getWriter();
PortletData data = request.getData();
String userConfCount = (String)data.getAttribute("BookmarkCount");
if (userConfCount!=null)
writer.print("My Bookmarks ("+userConfCount+" bookmark(s))");
else
// return static title
writer.print(config.getTitle(request.getLocale(),request.getClient()));
}
public void destroy()
{
// when the portlet is taken out of service the destroy method is called
}
}
nls_bookmark_07.properties
# localized text of the bookmark portlet
PreDefinedBookmarks = Predefined bookmarks:
Yahoo = My Yahoo Germany
UserDefinedBookmarks = User defined bookmarks:
BookmarkPortlet_10.portlet.xml
portlet.xml (Deployment Descriptor)
<?xml version="1.0"?>
<!DOCTYPE portlet-app PUBLIC "-//IBM//DTD Portlet Application 1.0//EN" "portlet.dtd">
<portlet-app>
<portlet-app-name>Bookmark Application</portlet-app-name>
<context-param>
<param-name>Webmaster</param-name>
<param-value>userid@de.ibm.com</param-value>
</context-param>
<portlet>
<portlet-name>Bookmark Portlet</portlet-name>
<portlet-class>com.mycompany.portlets.bookmark.BookmarkPortlet_08</portlet-class>
<portlet-type>instance</portlet-type>
<title-class>com.mycompany.portlets.bookmark.BookmarkPortletTitle_09</title-class>
<allows>
<maximized/>
<minimized/>
</allows>
28
<language locale="en">
<title>My Bookmarks</title>
<title-short>Bookmarks</title-short>
<description>Portlet showing your personalized bookmarks</description>
<keywords>bookmarks</keywords>
</language>
<supports>
<markup name="html">
<view output="fragment"/>
<edit output="fragment"/>
</markup>
</supports>
<config-param>
<param-name>BookmarkCount</param-name>
<param-value>2</param-value>
</config-param>
<config-param>
<param-name>Bookmark.Name.1</param-name>
<param-value>Jetspeed</param-value>
</config-param>
<config-param>
<param-name>Bookmark.URL.1</param-name>
<param-value>http://jakarta.apache.org/jetspeed/site/index.html</param-value>
</config-param>
<config-param>
<param-name>Bookmark.Name.2</param-name>
<param-value>Google</param-value>
</config-param>
<config-param>
<param-name>Bookmark.URL.2</param-name>
<param-value>http://www.google.com</param-value>
</config-param>
</portlet>
</portlet-app>
Now that we are able to show user defined bookmarks we need to enable the user to
add his favorite bookmarks. This is done by implementing the portlet’s edit mode
within which the portlet instance can be customized. To render the edit mode, we will
use a portlet JSP which will be explained in detail in the following chapter.
29
4. Java Server Pages
4.1.
Standard JSP Tags
The portlet container redefines the semantics of some of the standard JSP tags in
JSPs that are invoked through the PortletContext.include method. The following
sections explain the semantics of each JSP tag in comparison to its behavior when
the JSP would be invoked through the ServletContext.include method.
4.1.1. <jsp:useBean>
When a JSP is executed in the portlet container, the semantics of the scope attribute
of the <jsp:useBean> tag are different from JSPs running in a servlet container:




Page scope: For both the servlet container and the portlet container, the page
scope means that the bean is only visible in the scope of the JSP itself.
Request scope: In a servlet container, the request scope means that the
bean is stored in the HttpServletRequest while in a portlet container it means
that the bean is visible in the PortletRequest.
Session scope: In a servlet container, the session scope means that the bean
is stored in the HttpSession that is accessible to all servlets in the web
application while in a portlet container, the session scope means that the bean
is stored in the PortletSession that is only accessible for portlets within the
same portlet application.
Application scope: In a servlet container, the application scope means that
the bean is stored in the application context, (that is, it is visible to all servlets
in a web application). In a portlet container, the application scope means that
the bean is stored in the PortletContext, (that is, only visible to portlets in a
portlet application).
Scope
Page
Request
Session
Application
Portlet API semantics
Visible in the JSP itself
PortletRequest
PortletSession
Portlet Application
Servlet API semantics
Visible in the JSP itself
(Http)ServletRequest
HttpSession
Web Application
4.1.2. <jsp:setProperty>
When used in a JSP included by a portlet, the <jsp:setProperty> tag works in the
context of the portlet container. The following tag
<jsp:setProperty name=”javaBean” property=”*” />
results in the bean properties being set from the PortletRequest, not from the
ServletRequest that would be used in the servlet container. Other forms of the
<jsp:setProperty> tag work exactly as described in the JSP 1.1 specification, which
means they set values for the properties of beans with a name as specified in a prior
<jsp:useBean> tag.
30
4.1.3. <jsp:getProperty>
Works exactly as defined in the JSP 1.1 specification - it gets a value from a bean
with a name specified in a prior <jsp:useBean> tag.
4.1.4. <jsp:include>
In the portlet container, the <jsp:include> tag includes a JSP that also belongs to the
portlet application, providing it with a PortletRequest/PortletResponse pair instead of
a ServletRequest/ServletResponse pair. To include servlets or JSPs that run in the
same web application as the portal, but outside the portlet application, a service has
to be used.
4.1.5. <jsp:forward>
The <jsp:forward> action is not allowed in portlets, as portlets are being displayed as
parts of larger pages and have no control over the output stream. Using this tag in a
JSP that is invoked from the portlet container throws an IllegalStateException.
4.1.6. <jsp:param>
The <jsp:param> tag allows to specify parameters to be added to the portlet request
when including a JSP.
4.1.7. <jsp:plugin>
Works as described in the JSP 1.1 specification.
4.1.8. <jsp:params>
Works as described in the JSP 1.1 specification.
4.1.9. <jsp:fallback>
Works as described in the JSP 1.1 specification.
4.2.
Additional Portlet Tags
In addition to the standard JSP tags that can be used in JSPs displayed by portlets,
the portlet container provides some additional tags for use in portlet JSPs. To make
these additional tags available in a JSP, the following directive is required at the
beginning of the JSP:
<%@ taglib uri="/WEB-INF/tld/portlet.tld" prefix="portletjsp" %>
4.2.1. <portletjsp:encodeURI>
31
The <portletjsp:encodeURI> tag encodes a given URI and writes the result to the
output stream. The URI is encoded so that it can be used in links within portlets and
always points to the portlet that invoked the JSP.
<portletjsp:encodeURI path=URI />
4.2.2. <portletjsp:text>
The <portletjsp:text> tag allows convenientl access to resource bundles. It works
as follows:
<portletjsp:text key=keyname bundle=resourcebundle />
4.3.
Rendering the sample using a JSP
BookmarkPortlet_11.java
package com.mycompany.portlets.bookmark;
…
public void doView(PortletRequest request, PortletResponse response)
throws PortletException,IOException
{
getPortletLog().debug("service doView called");
// check the current state of the portlet
// if it is maximized we will also output the URL of the bookmark
boolean isMaximized = request.getWindow().isMaximized();
ViewBean_11 bean = new ViewBean_11();
int max = 0;
String preConfCount = (String)getConfig().getAttribute("BookmarkCount");
// convert counter into int
if (preConfCount!=null)
max = Integer.parseInt(preConfCount);
for (int i=1; i<=max; i++) {
String name = (String)getConfig().getAttribute("Bookmark.Name."+i);
String url = (String)getConfig().getAttribute("Bookmark.URL."+i);
// get localized Text
String localizedText =
getConfig().getContext().getText("nls.bookmark_07",name,request.getLocale());
if (isMaximized)
bean.addPreDefinedBookmark(localizedText+" - "+url,url);
else
bean.addPreDefinedBookmark(localizedText,url);
}
// get PortletData holding user related data
PortletData data = request.getData();
max = 0;
String userConfCount = (String)data.getAttribute("BookmarkCount");
// convert counter into int
if (userConfCount!=null)
max = Integer.parseInt(userConfCount);
for (int i=1; i<=max; i++) {
String name = (String)data.getAttribute("Bookmark.Name."+i);
String url = (String)data.getAttribute("Bookmark.URL."+i);
if (isMaximized)
bean.addUserDefinedBookmark(name+" - "+url,url);
else
bean.addUserDefinedBookmark(name,url);
}
32
request.setAttribute("viewBean",bean);
getConfig().getContext().include("/PORTLET-NF/jsp/bookmarkView.jsp",request,response);
}
}
ViewBean_11.java
package com.mycompany.portlets.bookmark;
import java.util.Hashtable;
import java.util.Enumeration;
public class ViewBean_11
{
private Hashtable preDefinedBookmarks = new Hashtable();
private Hashtable userDefinedBookmarks = new Hashtable();
public ViewBean_11()
{
}
public void addPreDefinedBookmark(String name, String url)
{
preDefinedBookmarks.put(name,url);
}
public void addUserDefinedBookmark(String name, String url)
{
userDefinedBookmarks.put(name,url);
}
public Enumeration getPreDefiniedNames()
{
return preDefinedBookmarks.keys();
}
public String getPreDefiniedURL(String name)
{
return (String)preDefinedBookmarks.get(name);
}
public Enumeration getUserDefiniedNames()
{
return userDefinedBookmarks.keys();
}
public String getUserDefiniedURL(String name)
{
return (String)userDefinedBookmarks.get(name);
}
}
bookmarkView.jsp
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<%@ page session="false" %>
<%@ taglib uri="/WEB-INF/tld/portlet.tld" prefix="portletAPI" %>
<jsp:useBean id="viewBean" class="com.mycompany.portlets.bookmark.ViewBean_11"
scope="request"/>
<META http-equiv="Content-Type" content="text/html">
<META http-equiv="Content-Style-Type" content="text/css">
<TABLE class="Portlet" width="100%" border="0" cellspacing="0" cellpadding="0">
<TR>
<TD>
<b><portletAPI:text key="PreDefinedBookmarks" bundle="nls.bookmark_07" /></b><br>
<%
java.util.Enumeration bookmarks = viewBean.getPreDefiniedNames();
while (bookmarks.hasMoreElements()) {
String name = (String)bookmarks.nextElement();
%>
33
<br><a href="<%=viewBean.getPreDefiniedURL(name)%>"><%=name%></a>
<% } %>
</TD>
</TR>
<!-- Empty row -->
<TR>
<TD> </TD>
</TR>
<TR>
<TD>
<%
java.util.Enumeration userBookmarks = viewBean.getUserDefiniedNames();
if (userBookmarks.hasMoreElements()) {
%>
<b><portletAPI:text key="UserDefinedBookmarks" bundle="nls.bookmark_07" /></b><br>
<%
}
while (userBookmarks.hasMoreElements()) {
String name = (String)userBookmarks.nextElement();
%>
<br><a href="<%=viewBean.getUserDefiniedURL(name)%>"><%=name%></a>
<% } %>
</TD>
</TR>
</TABLE>
The portlet looks like this:
4.4.
Model-View-Controller Portlets based on Actions
When portlets are written according to the Model-View-Controller pattern, the portlet
can use the PortletResponse.createURI method to create a PortletURI object and
use the PortletURI.addAction method to associate actions and parameters with the
URI. In order to make such a PortletURI object available to a JSP, the portlet can
pass the URI object in the PortletRequest. For the following example, the
bookmarkEdit.jsp gets the URI from EditBean_12 and uses it as the action target in a
form (see BookmarkPortlet_12 example further in this document).
BookmarkPortlet_12.java
package com.mycompany.portlets.bookmark;
…
public void doEdit(PortletRequest request, PortletResponse response)
throws PortletException,IOException
{
EditBean_12 bean = new EditBean_12();
PortletURI addURI = response.createURI();
DefaultPortletAction addAction = new DefaultPortletAction("add");
addURI.addAction(addAction);
bean.setAddURI(addURI.toString());
…
request.setAttribute("editBean",bean);
34
getConfig().getContext().include("/PORTLET-INF/jsp/bookmarkEdit.jsp",
request,response);
}
}
bookmarkEdit.jsp
…
<jsp:useBean id="editBean" class="com.mycompany.portlets.bookmark.EditBean_12"
scope="request"/>
…
Please add a new bookmark here:<br>
<FORM method='POST' action='<%=editBean.getAddURI()%>'>
Name: <INPUT name='bookmark.name'><br>
URL: <INPUT name='bookmark.url'><br>
<INPUT type='submit' name='addButton' value='Add'><br>
…
Potentially, there may be different portlets using the same bean names to put beans
into the portlet session. As portlet applications are isolated from each other by usage
of separate portlet sessions and classloaders, WebSphere Portal Server will handle
this case without problems.
The portlet looks like this:
4.5.
Implicit Objects
Similarly to JSPs invoked by servlets, JSPs invoked by portlets can access implicitly
defined objects. The following table shows the implicit objects that are available for
JSPs invoked by portlets and the type of each of these objects. For comparison, the
last column of the table shows the types that these objects would have in the servlet
container.
Implicit Object
Type in Portlet Container
Type in Servlet Container
Config
PortletConfig
ServletConfig
request
PortletRequest
(Http)ServletRequest
Response
PortletResponse
(Http)ServletResponse
Session
PortletSession
HttpSession
Application
PortletContext
ServletContext
pageContext
PageContext
PageContext
Out
JspWriter
JspWriter
Page
Object
Object
35
In error pages, the following implicit object is available additionally:
Implicit Object
Portlet Container
Exception
Throwable
4.6.
Servlet Container
Equivalent
Throwable
Reserved Parameter Names
The following parameter names may not be used in portlet JSPs without namespace
encoding as they are reserved by the WPS framework:





36
template
screen
action
names starting with a point ‘.’
names starting with an underscore ‘_’
5. Portlet API - Advanced elements
This chapter describes features of the Portlet API that are implemented by more
advanced portlets. You should already be familiar with the basic elements before
you use the features described in this chapter. Advanced features include capturing
and processing portlet events, caching, and portlet services.
In this chapter, the bookmark portlet example is expanded to take advantage of the
advanced features. We introduce an edit mode to let the user define user-specific
bookmarks. Then BookmarkPortlet is modified to listen for action events (when the
user clicks a bookmark link) and sends the bookmark URL to the
BookmarkBrowserPortlet. This new portlet listens for message events and displays
the appropriate URL content using the ContentAccessService. BookmarkPortlet
caches its output and uses this output until either the cache expires or the portlet’s
window state changes.
5.1.
Portlet events
Portlet events contain information about an event to which a portlet might need to
respond. For example, when a user clicks a link or button, this generates an action
event. To receive notification of the event, the portlet must have an event listener
registered in the portlet deployment descriptor. In the Portlet API, there are three
types of events:



ActionEvents: generated when an HTTP request is received by the portlet
container that is associated with an action, such as when the user clicks a link.
MessageEvents: generated when another portlet within the portlet application
sends a message.
WindowEvents: generated when the user changes the state of the portlet
window.
The portlet container delivers all events to the respective event listeners (and thereby
the portlets) before generating the new content that the event requires. Should a
listener, while processing the event, find that another event needs to be generated,
that event will be queued by the portlet container and delivered at a point of time that
is at the discretion of the portlet container. It is only guaranteed that it will be
delivered and that it will happen before the content generation phase.
No further events will be delivered once the content generation phase has started.
For example, messages cannot be sent from within the service() methods. The
resulting message event will not be delivered and essentially discarded.
The event listener is specified in the portlet’s deployment descriptor. The listener can
access its Portlet object and corresponding PortletRequest from the event and
respond using the PortletSession object.
37
5.1.1. Action events
An ActionEvent is sent to the related portlet when an HTTP request is received that is
associated with a PortletAction. PortletActions can be linked to URLs by using the
PortletAPI. Normally, PortletActions are linked with http references or buttons in html
forms enabling the portlet programmer to implement different processing paths for
different user selections. The ActionEvent of the clicked link, then carries the
associated PortletAction back to the portlet which in turn is able to process different
paths on behalf of the PortletAction.
A portlet action can carry any information. It should, however, not store a request,
response, or session object. This information is part of the action event that will be
sent to the registered action listener(s).
PortletAction
An object with the type PortletAction has to be implemented which will be linked to
the URL and will be passed via the ActionEvent. For example, BookmarkPortlet_12
instantiates the DefaultPortletAction class and links the action to the URL by the
action’s name. The following shows how to create an DefaultPortletAction:
DefaultPortletAction addAction = new DefaultPortletAction("add");
Creating a URI including a PortletAction
A URI that references a PortletAction is created by using the class PortletURI and its
addAction() method. The following snippet shows how to include the PortletAction to
an URI:
PortletURI addURI = response.createURI();
DefaultPortletAction addAction = new DefaultPortletAction("add");
addURI.addAction(addAction);
ActionEvent
An ActionEvent is sent by the portlet container when an HTTP request is received
that is associated with one more actions and can be used to get the associated
PortletAction, the portlet which sent the action, and the PortletRequest.
The following shows the JSP and how it uses the URL, including the PortletAction.
When the user clicks the “add” button, a new HTTP request is sent to the action URL
of the FORM.
<FORM method='POST' action='<%=editBean.getAddURI()%>'>
Name: <INPUT name='bookmark.name'><br>
URL: <INPUT name='bookmark.url'><br>
<INPUT type='submit' name='addButton' value='Add'><br>
</FORM>
38
ActionListener
The ActionListener interface must be implemented if a portlet wishes to receive
action events. The registration is done in the portlet descriptor. The following shows
how the ActionListener differentiates between actions.
public void actionPerformed (ActionEvent event)
{
PortletAction _action = event.getAction();
if (_action instanceof DefaultPortletAction) {
…
DefaultPortletAction action = (DefaultPortletAction)_action;
if (action.getName().equals("add")) {
The complete example:
BookmarkPortlet_12.java
package com.mycompany.portlets.bookmark;
…
public void doEdit(PortletRequest request, PortletResponse response)
throws PortletException,IOException
{
EditBean_12 bean = new EditBean_12();
PortletURI addURI = response.createURI();
DefaultPortletAction addAction = new DefaultPortletAction("add");
addURI.addAction(addAction);
bean.setAddURI(addURI.toString());
PortletURI removeAllURI = response.createURI();
DefaultPortletAction removeAllAction = new DefaultPortletAction("removeAll");
removeAllURI.addAction(removeAllAction);
bean.setRemoveAllURI(removeAllURI.toString());
PortletURI returnURI = response.createReturnURI();
bean.setReturnURI(returnURI.toString());
PortletSession session = request.getSession();
// check for existing message
if ((session!=null) && (session.getAttribute("message")!=null)) {
bean.setMessage((String)session.getAttribute("message"));
// clear message
session.setAttribute("message","");
}
request.setAttribute("editBean",bean);
getConfig().getContext().include("/PORTLETINF/jsp/bookmarkEdit.jsp",request,response);
}
}
EditBean.java
package com.mycompany.portlets.bookmark;
import java.util.Hashtable;
import java.util.Enumeration;
public class EditBean_12
{
private String removeAllURI = "";
private String returnURI
= "";
39
private String addURI
private String message
= "";
= "";
public EditBean_12()
{
}
public void setRemoveAllURI(String uri) {
removeAllURI = uri;
}
public String getRemoveAllURI() {
return removeAllURI;
}
public void setReturnURI(String uri) {
returnURI = uri;
}
public String getReturnURI() {
return returnURI;
}
public void setAddURI(String uri) {
addURI = uri;
}
public String getAddURI() {
return addURI;
}
public void setMessage(String msg) {
message = msg;
}
public String getMessage() {
return message;
}
}
bookmarkEdit.jsp
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<%@ page session="false" %>
<%@ taglib uri="/WEB-INF/tld/portlet.tld" prefix="portletAPI" %>
<jsp:useBean id="editBean" class="com.mycompany.portlets.bookmark.EditBean_12"
scope="request"/>
<META http-equiv="Content-Type" content="text/html">
<META http-equiv="Content-Style-Type" content="text/css">
<TABLE class="Portlet" width="100%" border="0" cellspacing="0" cellpadding="0">
<TR>
<TD>
<%=editBean.getMessage()%>
</TD>
</TR>
<TR>
<TD>
Please add a new bookmark here:<br>
<FORM method='POST' action='<%=editBean.getAddURI()%>'>
Name: <INPUT name='bookmark.name'><br>
URL: <INPUT name='bookmark.url'><br>
<INPUT type='submit' name='addButton' value='Add'><br>
</FORM>
<FORM method='POST' action='<%=editBean.getRemoveAllURI()%>'>
<INPUT type='submit' name='removeAllButton' value='Remove All'><br>
</FORM>
<FORM method='POST' action='<%=editBean.getReturnURI()%>'>
<INPUT type='submit' name='returnButton' value='Finish'><br>
</FORM>
</TD>
</TR>
</TABLE>
40
BookmarkPortletActionListener_12.java
package com.mycompany.portlets.bookmark;
import
import
import
import
org.apache.jetspeed.portlet.*;
org.apache.jetspeed.portlet.event.*;
org.apache.jetspeed.portlets.*;
java.io.IOException;
public class BookmarkPortletActionListener_12 implements ActionListener
{
public BookmarkPortletActionListener_12()
{
}
public void actionPerformed (ActionEvent event)
{
PortletAction _action = event.getAction();
if (_action instanceof DefaultPortletAction) {
PortletRequest request = event.getRequest();
PortletAdapter portlet = (PortletAdapter)event.getPortlet();
PortletLog log = portlet.getConfig().getContext().getLog();
log.debug("BookmarkPortletActionListener_12 - Action called");
DefaultPortletAction action = (DefaultPortletAction)_action;
if (action.getName().equals("add")) {
log.debug("BookmarkPortletActionListener_12 - adding bookmark");
PortletData data = request.getData();
String userConfCount = (String)data.getAttribute("BookmarkCount");
if (userConfCount==null) userConfCount = "0";
int max = Integer.parseInt(userConfCount);
max++;
String name = request.getParameter("bookmark.name");
data.setAttribute("Bookmark.Name."+max,name);
data.setAttribute("Bookmark.URL."+max,request.getParameter("bookmark.url"));
data.setAttribute("BookmarkCount",String.valueOf(max));
try {
data.store();
request.getSession().setAttribute("message","<b>Added successfully
bookmark "+name+"</b>");
}
catch (IOException e) {
log.error("failed to save data!",e);
request.getSession().setAttribute("message","<font COLOR=#ff0000><b>Failed
to add bookmark!</b></font>");
try {
request.setModeModifier(Portlet.ModeModifier.CURRENT);
}
catch (AccessDeniedException ade) {
}
}
}
else if (action.getName().equals("removeAll")) {
log.debug("BookmarkPortletActionListener_12 - removing all bookmarks");
PortletData data = request.getData();
data.removeAllAttributes();
try {
data.store();
request.getSession().setAttribute("message","<b>Removed successfully all
bookmarks.</b>");
}
catch (IOException e) {
log.error("failed to save data!",e);
request.getSession().setAttribute("message","<font COLOR=#ff0000><b>Failed
to remove bookmarks!</b></font>");
try {
request.setModeModifier(Portlet.ModeModifier.CURRENT);
}
catch (AccessDeniedException ade) {
41
}
}
}
}
}
}
portlet.xml
<?xml version="1.0"?>
<!DOCTYPE portlet-app PUBLIC "-//IBM//DTD Portlet Application 1.0//EN" "portlet.dtd">
<portlet-app>
<portlet-app-name>Bookmark Application</portlet-app-name>
<context-param>
<param-name>Webmaster</param-name>
<param-value>userid@de.ibm.com</param-value>
</context-param>
<portlet>
<portlet-name>Bookmark Portlet</portlet-name>
<portlet-class>com.mycompany.portlets.bookmark.BookmarkPortlet_12</portlet-class>
<portlet-type>instance</portlet-type>
<title-class>com.mycompany.portlets.bookmark.BookmarkPortletTitle_09</title-class>
<listener>
<listener-class type="action">
com.mycompany.portlets.bookmark.BookmarkPortletActionListener_12
</listener-class>
</listener>
<allows>
<maximized/>
<minimized/>
</allows>
<supports>
<markup name="html">
<view output="fragment"/>
<edit output="fragment"/>
</markup>
</supports>
…
The portlet looks like this:
5.1.2. Window events
A WindowEvent is sent by the portlet container whenever a user clicks on one of the
control buttons that change the window’s state, such as maximize, minimize or
restore. For example, WindowEvents can be used to show different information for
different window states as shown in BookmarkPortletWindowListener_14.
42
WindowEvent
A WindowEvent is sent by the portlet container whenever the user or the portal
interacts with portlet window controls.
WindowListener
The WindowListener interface must be registered in the portlet deployment descriptor
if the portlet needs to receive window events. The registration is done in the portlet
descriptor.
WindowAdapter
The WindowAdapter is a window listener in which all callback methods are empty
implementations. The window adapter makes it more convenient for an object to
listen for window events. In particular, by using the window adapter, it is possible to
implement only those callback methods needed by your portlet. Without the window
adapter, you must implement all callback methods, even if the method is empty.
BookmarkPortletWindowListener_14.java
package com.mycompany.portlets.bookmark;
import org.apache.jetspeed.portlets.*;
import org.apache.jetspeed.portlet.*;
import org.apache.jetspeed.portlet.event.*;
//Java stuff
import java.io.IOException;
public class BookmarkPortletWindowListener_14 extends WindowAdapter {
public void windowMaximized (WindowEvent event) throws PortletException
{
setLastModified(event);
}
public void windowRestored (WindowEvent event) throws PortletException
{
setLastModified(event);
}
private void setLastModified(WindowEvent event) {
PortletSession session = event.getRequest().getSession(false);
if (session != null) {
session.setAttribute(BookmarkPortlet_14.LAST_MODIFIED, new
Long(System.currentTimeMillis()));
}
}
public void windowDetached (WindowEvent event) throws PortletException
{
}
}
5.1.3. Message events
MessageEvents can be sent from one portlet to others if the recipient portlets are
members of the same portlet application and are placed on the same page as the
sending portlet. MessageEvents can only be sent actively to other portlets when the
43
portlet is in the event processing cycle of the portlet container, otherwise an
exception is thrown.
There are two different types of messages:
 Single addressed messages: Messages sent to a specific portlet by specifying
the portlet name on the send() method.
 Broadcast messages: Messages sent to all portlets of the same portlet
application remaining on the same page.
MessageEvents are useful when changes in one portlet should be reflected in
another one. For example, clicking a link in our bookmark portlet could show the web
page in a bookmark browser portlet. In BookmarkPortlet_13, only the link is shown in
the portlet. How to show the actual site is shown further down this guide.
To achieve this, the links in the bookmark browser must not point to the web site
anymore but trigger a “browse” action containing the link. So the ActionListener gets
the “browse” action and sends a message to the browse portlet.
PortletMessage
An object with the type PortletMessage has to be implemented which will be passed
via the MessageEvent.
MessageEvent
A MessageEvent is sent by the portlet container if one portlet sends a message to
another.
MessageListener
The MessageListener interface must be implemented if a portlet wishes to receive
message events. The registration is done in the portlet descriptor.
BookmarkPortlet_13.java
package com.mycompany.portlets.bookmark;
…
public void doView(PortletRequest request, PortletResponse response)
throws PortletException,IOException
{
…
for (int i=1; i<=max; i++) {
String name = (String)getConfig().getAttribute("Bookmark.Name."+i);
String url = (String)getConfig().getAttribute("Bookmark.URL."+i);
DefaultPortletAction browseAction = new
org.apache.jetspeed.portlets.DefaultPortletAction("browse");
browseAction.addParameter("url", url);
PortletURI portletURI = response.createReturnURI();
portletURI.addAction(browseAction);
String actionURI = portletURI.toString();
// get localized Text
localizedText = context.getText("nls.bookmark_07",name,request.getLocale());
if (isMaximized)
bean.addPreDefinedBookmark(localizedText+" - "+url,actionURI);
else
bean.addPreDefinedBookmark(localizedText,actionURI);
}
44
// *** new section
// show a list with user defined bookmarks
// get PortletData holding user related data
PortletData data = request.getData();
max = 0;
String userConfCount = (String)data.getAttribute("BookmarkCount");
// convert counter into int
if (userConfCount!=null) {
max = Integer.parseInt(userConfCount);
// get localized Text
localizedText =
context.getText("nls.bookmark_07","UserDefinedBookmarks",request.getLocale());
for (int i=1; i<=max; i++) {
String name = (String)data.getAttribute("Bookmark.Name."+i);
String url = (String)data.getAttribute("Bookmark.URL."+i);
DefaultPortletAction browseAction = new
org.apache.jetspeed.portlets.DefaultPortletAction("browse");
browseAction.addParameter("url", url);
PortletURI portletURI = response.createReturnURI();
portletURI.addAction(browseAction);
String actionURI = portletURI.toString();
if (isMaximized)
bean.addUserDefinedBookmark(name+" - "+url,actionURI);
else
bean.addUserDefinedBookmark(name,actionURI);
}
}
…
BookmarkPortletActionListener_13.java
package com.mycompany.portlets.bookmark;
package com.mycompany.portlets.bookmark;
…
public class BookmarkPortletActionListener_13 extends ActionAdapter {
public void actionPerformed (ActionEvent event) {
…
} else if (action.getName().equals("browse")) {
log.debug("BookmarkActionListener_13 - browse action");
String url = (String) action.getParameters().get("url");
log.debug("BookmarkPortletActionListener_13 - opening link: " + url);
try {
portlet.getConfig().getContext().send(null, new
DefaultPortletMessage(url), request);
} catch (AccessDeniedException ade) {
log.error("BookmarkPortletActionListener_13 - unable to send message
\"url=" + url + "\" - AccessDenied");
}
}
}
}
}
BookmarkPortletMessageListener_13.java
package com.mycompany.portlets.bookmark;
import org.apache.jetspeed.portlets.*;
45
import org.apache.jetspeed.portlet.*;
import org.apache.jetspeed.portlet.event.*;
//Java stuff
import java.io.IOException;
public class BookmarkPortletMessageListener_14 extends MessageAdapter {
public void messageReceived (MessageEvent event)
throws PortletException
{
PortletMessage msg = event.getMessage();
if (msg instanceof DefaultPortletMessage) {
String url = ((DefaultPortletMessage)msg).getText();
PortletAdapter portlet = (PortletAdapter)event.getPortlet();
portlet.getPortletLog().debug("BookmarkPortletMessageListener_13
messageReceived");
PortletRequest request = event.getRequest();
PortletSession session = request.getSession();
session.setAttribute("url",url);
}
}
}
BookmarkBrowserPortlet_13.java
package com.mycompany.portlets.bookmark;
import
import
import
import
org.apache.jetspeed.portlet.service.ContentAccessService;
org.apache.jetspeed.portlets.*;
org.apache.jetspeed.portlet.*;
org.apache.jetspeed.portlet.event.*;
//Java stuff
import java.io.IOException;
import java.io.PrintWriter;
public class BookmarkBrowserPortlet_13 extends PortletAdapter {
public void doView(PortletRequest request, PortletResponse response)
throws PortletException,IOException
{
getPortletLog().debug("doView called");
try {
PortletContext context = getConfig().getContext();
PrintWriter writer = response.getWriter();
String url = (String)request.getSession().getAttribute("url");
writer.println("<br><a href=\""+url+"\">” + url + “</a><br><br>");
} catch (Exception exc) {
getPortletLog().error("ServletInvokerPortlet: An error occurred");
}
}
}
portlet.xml
<?xml version="1.0"?>
<!DOCTYPE portlet-app PUBLIC "-//IBM//DTD Portlet Application 1.0//EN" "portlet.dtd">
<portlet-app>
<portlet-app-name>Bookmark Application</portlet-app-name>
<context-param>
<param-name>Webmaster</param-name>
<param-value>userid@de.ibm.com</param-value>
</context-param>
<portlet>
<portlet-name>Bookmark Portlet 13</portlet-name>
<portlet-class>com.mycompany.portlets.bookmark.BookmarkPortlet_13</portlet-class>
46
<portlet-type>instance</portlet-type>
<title-class>com.mycompany.portlets.bookmark.BookmarkPortletTitle_09</title-class>
<listener>
<listener-class type="action">
com.mycompany.portlets.bookmark.BookmarkPortletActionListener_13
</listener-class>
</listener>
…
</portlet>
<portlet>
<portlet-name>BookmarkBrowser</portlet-name>
<portlet-class>com.mycompany.portlets.bookmark.BookmarkBrowserPortlet_13</portletclass>
<portlet-type>instance</portlet-type>
<listener>
<listener-class type="message">
com.mycompany.portlets.bookmark.BookmarkPortletMessageListener_13
</listener-class>
</listener>
<allows>
<maximized/>
<minimized/>
</allows>
<language locale="en">
<title>BookmarkBrowser</title>
<title-short>Browser</title-short>
<description>Portlet showing your personalized bookmarks</description>
<keywords>bookmarks,browser</keywords>
</language>
<supports>
<markup name="html">
<view output="fragment"/>
<edit output="fragment"/>
</markup>
</supports>
</portlet>
</portlet-app>
5.2.
Portlet Caching
A portlet container can provide a per portlet cache which stores the content of a
portlet. To configure the caching policy of a portlet, there are two properties in the
portlet’s deployment descriptor:
expires
indicates for how long the content of a portlet should be cached until it expires.
 <0
The portlet provides static content and therefore does never expire
 =0
The portlet expires at once and is not cached at all
 >0
The time in seconds
shared
indicates if the portlet is user specific
 YES
This portlet provides the same content for all users -> one cache entry for all
users
 NO
This portlet is user specific -> separate cache entries for each user
47
Example:
Now the bookmark portlet shall be cached but needs to change its content
immediately if the window state changes. It is possible for a portlet to inform the
container about such a thing by implementing the getLastModified() method. The
getLastModified method returns the time of the last modification to the portlet. In our
case, this would be the time when the window state changed as we add more
information for the maximized state. Or in case of the bookmark browser portlet, the
content only changes if it receives a new URL or, in other words, if the content of the
portlet’s session changes.
portlet.xml
…
<listener>
<listener-class type="action">
com.mycompany.portlets.bookmark.BookmarkPortletActionListener_13
</listener-class>
</listener>
<listener>
<listener-class type="window">
com.mycompany.portlets.bookmark.BookmarkPortletWindowListener_14
</listener-class>
</listener>
<cache>
<expires>-1</expires>
<shared>NO</shared>
</cache>
<allows>
<maximized/>
<minimized/>
</allows>
<supports>
<markup name="html">
<view output="fragment"/>
<edit output="fragment"/>
</markup>
</supports>
…
BookmarkPortletWindowListener_14.java
package com.mycompany.portlets.bookmark;
import org.apache.jetspeed.portlets.*;
import org.apache.jetspeed.portlet.*;
import org.apache.jetspeed.portlet.event.*;
//Java stuff
import java.io.IOException;
public class BookmarkPortletWindowListener_14 extends WindowAdapter {
public void windowMaximized (WindowEvent event) throws PortletException
{
setLastModified(event);
}
public void windowRestored (WindowEvent event) throws PortletException
{
setLastModified(event);
}
private void setLastModified(WindowEvent event) {
48
PortletSession session = event.getRequest().getSession(false);
if (session != null) {
session.setAttribute(BookmarkPortlet_14.LAST_MODIFIED, new
Long(System.currentTimeMillis()));
}
}
public void windowDetached (WindowEvent event) throws PortletException
{
}
}
BookmarkPortlet_14.java
package com.mycompany.portlets.bookmark;
…
public class BookmarkPortlet_14 extends PortletAdapter
{
public final static String LAST_MODIFIED = "com.mycompany.portlets.bookmark.lastModified";
…
public long getLastModified(PortletRequest request) {
PortletSession session = request.getSession(false);
if (session != null) {
Long lastModified = (Long) session.getAttribute(LAST_MODIFIED);
if (lastModified != null) {
return lastModified.longValue();
}
}
return -1;
}
}
BookmarkBrowserPortlet_14.java
package com.mycompany.portlets.bookmark;
…
public class BookmarkBrowserPortlet_14 extends PortletAdapter {
…
public long getLastModified(PortletRequest request) {
PortletSession session = request.getSession(false);
if (session != null) {
String url
String oldUrl
Long lastModified
= (String) session.getAttribute("url");
= (String) session.getAttribute("oldUrl");
= (Long)
session.getAttribute("lastModified");
if (url != null) {
if ((oldUrl == null) || (!url.equalsIgnoreCase(oldUrl))) {
session.setAttribute("oldUrl", url);
long time = System.currentTimeMillis();
session.setAttribute("lastModified", new Long(time));
return time;
}
return lastModified.longValue();
}
}
return -1;
}
}
49
5.3.
Portlet Services
To enable portlets to use plugable services via dynamic discovery, the Portlet API
provides the PortletService interface. A PortletService is accessed from the
PortletContext.getService() method that looks up the appropriate factory for the
service, creates the service, and returns it to the portlet. Take for example a
Credential Vault that enables portlets to access credentials for authentication. The
CredentialVault.class would be available on the portal server but does not belong to
the portlet application (that is, it is not packaged with the portlet’s PAR file). That
service can be retrieved as follows:
Example: Portlet using a Portlet Service
import org.apache.jetspeed.portlet.service.PortletService;
…
PortletService portletService =
(PortletService) portletContext.getService(CredentialVault.class);
Various services may be implemented by different vendors, for example, a
SearchService, LocationService, ContentAccessService, or a MailService. The
Portlet API provides a ContentAccessService.
5.3.1. ContentAccessService
To retrieve content from sources outside the intranet, usually a proxy must be used.
To enable portlets to transparently make use of a proxy to access remote content,
WPS provides a ContentAccessService. This service allows portlets to access content
either from remote web sites or from JSPs and servlets of the whole portal web
application. (In contrast, the PortletContext.include method only includes JSPs local
to the portlet application.)
Hint: The disadvantage when accessing internet resources directly is that the
content is simply taken and written to the portlet’s output. This means that all relative
links to other pages or images will be broken. This can be solved by parsing the
content or use some enhanced browser portlet.
Example: Portlet using ContentAccessService
import org.apache.jetspeed.portlet.service.ContentAccessService;
…
ContentAccessService service =
(ContentAccessService)portletContext.getService(ContentAccessService.class);
//get an URL
URL url = service.getURL(“http://www.ibm.com”, request, response);
//include the content of a web site into the portlet
service.include(“http://www.ibm.com”, request, response);
//include a JSP or servlet belonging to the portal web application
service.include(“/templates/weather.jsp”, request, response);
50
51
6. Portlet development issues
6.1.
No instance and class variables
Limit the use of instance or class variables in a portlet. Within WebSphere Portal
Server, only a single instance of the Portlet class is instantiated. Even if different
users have the same portlet on their pages, it is the same portlet object instance that
generates the markup. Therefore, each portlet instance has to be treated like a
singleton. The implication is that you should not use instance or class variables to
store any state or other information between calls to the portlet. Being a singleton,
each instance variable of a portlet effectively becomes a class variable with similar
scope and the potential for problems when the variable is accessed many times in
each user's context of the portlet. It is better to avoid these problems by limiting the
use of instance or class variables to read only values. Otherwise, reentrance or
thread safety may be compromised.
There is an exception to this rule in that you can use instance variables. Variables
whose values do not change can be stored safely in instance variables. For example,
on initialization of the portlet, its configuration is set within the portlet. Because the
configuration does not change for different users or between calls, you can store it in
an instance variable.
6.2.
Storing data
The following lists where data is stored by a portlet, depending on how it is used.





6.3.
Be careful with use of class variables, these will be shared by all users
who access the portlet.
Administrator's config data is stored in the portlet.xml
User preferences are stored in persistence using PortletData.
Portlet beans for a particular view or mode should be added to a
PortletRequest
Portlet beans containing global portlet information should be stored in a
PortletSession
Namespaces/Javascript
Resource URIs should be namespace-encoded, allowing the portal to supply a direct
path to the resource with respect to protected, unprotected, and NLS resource
placement. You should also encode named elements (form fields, Javascript
variables, etc) to avoid clashes with other elements on the portal page or in other
portlets on the page. See PortletURI for more information about encoding .
6.4.
52
Multi-markup support
Whenever possible, provide support for more than one type of markup (HTML, WML,
and cHTML). This means that you will need to use the getMarkupName() method
from the Client object to test for the markup. You must also indicate the markup
types supported by your portlet in the portlet deployment descriptor.
6.5.
Multi-language support
To prepare your portlet for translation, be sure all strings are retrieved from a
properties file using getText() (see BookmarkPortlet_05 for an example) rather than
hard-coded in the portlet. Allow for string expansion in the portlet user interface for
certain NLS languages.
6.6.
Portlet creation guidelines
6.6.1. Performance issues
The WebSphere Portal Server uses the same portlet instance to generate the
markup for different pages for different users. There are a limited number of threads
that perform these tasks. It is imperative to implement the portlet to do its job as
quickly as possible so that response time for a whole page is optimized.
Optimizations you should consider are:
1. Limit the use of the synchronized methods or synchronized code blocks in
the processing of a portlet method.
2. Limit the use of complex string operations. This includes


Minimize transient XSL style sheets as they cause a lot of temporary
Java object instantiations
Consider using a StringBuffer to replace temporary String objects.
These are more efficient in processing strings in general and also
don't generally instantiate other temporary String objects.
3. Limit the use of long running loops.
4. Do not create large pools of threads.
5. Minimize calls to external data sources. If possible, set your portlet to
cache its output.
6. Use JSPs instead of XSL. JSP with view beans are much faster and
instantiate less intermediate Java objects. The XSL engine creates a lot of
temporary strings.
7. Portlet JSPs can be optimized for performance by reducing white space
and using the non-transferrable JSP comment format (<%-- --%>) as
opposed to the HTML comment format (<!-- -->).
8. Minimize Java object instantiations.
53
6.6.2. Guidelines for markup
One of the goals in writing portlet markup is to provide a consistent, clean, and
complete user interface. The portal server page is displayed using skins and themes
defined by the portal administrator. Themes represent the look and feel of the portal
overall, including colors and fonts. Skins represent the border rendering around an
individual portlet. WebSphere Portal Server is installed with some predefined skins
and themes.
For portlets to appear integrated with an organization's portal or user's customized
portal, they should generate markup that invokes the generic style classes for
portlets, rather than using tags or attributes to specify colors, fonts, or other visual
elements. See the WebSphere Portal Server InfoCenter for more information about
use of style sheets, themes, and skins.
Portlets are allowed to render only markup fragments, which are then assembled by
the portlet framework for a complete page. Therefore, be sure to exclude all pagelevel tags, such as <html>, <head>, <body>, <wml>, and so on.
Because HTML, WML, and cHTML code fragments are embedded into other code
fragments, make sure that they are complete, contain no open tags, and only contain
valid HTML, WML, or cHTML. This helps to prevent the portlet's HTML code, for
example, from corrupting the portal's aggregated HTML code. It is recommended to
use a validation tool, such as the W3C HTML Validation Service or a tool from a
markup editor, such as WebSphere Studio.
For specific guidelines for HTML, WML, and cHTML, see the WebSphere Portal
Server InfoCenter .
6.6.2.1.
Accessibility
When writing the markup for a portlet, consider how the portlet will be accessed by
people with disabilities. For example, people with sight limitations might not be able
to distinguish certain colors or require the assistance of a reader to access the
content of your portlet. Make the markup accessible:


For all field prompts (labels) be sure to use the <LABEL> tag around
the label for screen readers.
Test the portlet with each browsers range of large and small fonts.
You can find out more about accessibility guidelines the Center for Applied Special
Technology (CAST) or W3C Web Accessibility Initiative . IBM provides guidelines for
Web accessibility at http://www.ibm.com/able/accessweb.html .
54
7. Using Single Sign-On
Single sign-on support in WebSphere Portal Server allows users to communicate
through the portal server, via portlets, with many different back end systems without
being prompted multiple times for a username and password by each individual
portlet's back end server. WebSphere Portal Server internally makes use of the Java
Authentication and Authorization Service (JAAS) API to provide a framework for
integrating single sign-on into portlets and via these portlets to back end applications.
After a user is authenticated, WebSphere Application Server generates a security
context for the user. Websphere Portal Server uses this security context to set up
(internally to the portal) a JAAS Subject containing several Principals and Private
Credentials. When the portal first handles the HTTP request, it will set up the JAAS
Subject and make it available as an attribute of the User, which the portlet accesses
from the PortletSession object. From the Subject, a portlet can use one of the
following methods to extract the Principal and Credentials to pass to its back end
application.
getPrincipals(java.lang.Class)
Class is optional. If no argument is specified, this method returns a set
containing all Principals. Use the Class.forName(name) method to generate a
class argument for getPrincipals. See the following example.
// Retrieve the Subject from the Session
PortletSession session = request.getSession();
UserImpl user = (UserImpl) session.getUser();
Subject subject = user.getSubject();
// Get the User DN
Set set = subject.getPrincipals(Class.forName("com.ibm.wps.sso.UserDNPrincipal"));
UserDNPrincipal userDn = null;
if (set.hasNext()) {
userDn = (UserDNPrincipal) set.next();
// Got it, print out what it contains.
System.out.println("The User DN is " + userDn.getName();
}
The org.apache.jetspeed.portletcontainer.UserImpl class implements the
org.apache.jetspeed.portlet.User interface in addition to the getSubject()
method. Please see the Portlet API documentation for the User interface for
more information about UserImpl.
The following are some of the class names that can be used with
Class.forName() on this method
com.ibm.wps.sso.UserDNPrincipal
returns the user's distinguished name from LDAP
com.ibm.wps.sso.GroupDNPrincipal
55
returns the distinguished name of an LDAP group of which the user is a
member. There are as many instances of GroupDNPrincipals as the number
of LDAP groups to which the user belongs.
com.ibm.wps.sso.UserIdPrincipal
returns the ID with which the user logged on to the portal only if an
authentication proxy was not used
com.ibm.wps.sso.PasswordCredential
returns the password with which the user logged on to the portal. Available
only if an authentication proxy was not used.
All Principals descend from java.security.Principal from the standard JDK.
The portlet calls getName() to extract the information contained in these
Principals. Before the portlet can extract any information from the Subject, it
must first get the PortletSession, then the User, and finally the Subject.
getPrivateCredentials(java.lang.Class)
Class is optional. If no argument is specified, this method returns a set
containing all PrivateCredentials. Also use Class.forName(name) to get a
Class object from a classname string for this method. To obtain the user's
CORBA credential, specify org.omg.SecurityLevel2.Credentials as the
argument for Class.forName() .
To compile a portlet that implements single sign-on, the following JARs must be in
your CLASSPATH in addition to the JAR files listed in compiling Java source .
wps_root/app/web/WEB-INF/lib/wpssso.jar;
wps_root/app/web/WEB-INF/lib/um.jar;
app_server/lib/personalization.jar
7.1.
Basic authentication sample
The following example demonstrates how to the extract the Subject from the
PortletSession and then how to extract the user name and password from the
Subject. The portlet then creates a Basic Authentication Header and attempts to
connect to a URL protected by basic authentication. The URL is specified as a
configuration parameter in the portlet deployment descriptor (portlet.xml) . For
output, it displays the user name, password, and the results of the connection
attempt.
In the service() method, the JAAS Subject is extracted from the User object in the
PortletSession by casting the User to
org.apache.jetspeed.portletcontainer.UserImpl . This object has the getSubject()
method.
import
import
import
import
56
java.io.*;
java.net.*;
java.util.*;
java.security.*;
import javax.security.auth.*;
// for the JAAS Subject
import com.ibm.wps.sso.*; // for the Principals contained in the Subject
import com.ibm.ejs.security.util.Base64Coder;
import org.apache.jetspeed.portlet.*;
import org.apache.jetspeed.portlets.*;
public class URLSSOSamplePortlet extends AbstractPortlet {
/** URL To Access */
private String urlSpec = null;
/**
* Initialization Routine
*
* @param PortletConfig Portlet Configuration
* @throws UnavailableException
*/
public void init(PortletConfig portletConfig)
throws UnavailableException {
super.init(portletConfig);
// Get the URL
urlSpec = portletConfig.getAttribute("url");
}
/**
* Retrieves the specified Principal from the provided Subject
*
* @param Subject subject
* @param String Class Name
* @return The values of the Principals for the given class name, null if
*
nothing was returned
* @throws PortletException An error occured retrieving the Principal
*/
private String[] getPrincipalsFromSubject(Subject subject,
String className)
throws PortletException {
try {
// Get the Set of Principals
Object[] principals =
subject.getPrincipals(Class.forName(className)).toArray();
// Do we have any?
if ((null == principals) || (0 == principals.length)) {
// No principals of this class name
return(null);
}
// Create the String Array to hold the values
String[] values = new String[principals.length];
// Populate the values Array
for (int current = 0; current < values.length; current++) {
values[current] = ((Principal) principals[current]).getName();
}
return(values);
} catch (ClassNotFoundException cnfe) {
throw(new PortletException("Class " + className + " could not be found",
cnfe));
}
}
/**
* Extracts the UserID and Password from the JAAS Subject, creates a Basic
* Auth Header from it, and then connects to the resource.
*
* @param PortletRequest Request Object
* @param PortletResponse Response Object
* @throws PortletException
* @throws IOException
*/
public void service(PortletRequest portletRequest,
PortletResponse portletResponse)
throws PortletException,
IOException {
57
// Get the Writer from the Response
PrintWriter writer = portletResponse.getWriter();
// Get the Portlet Session
PortletSession session = portletRequest.getSession();
// Get the User Object from the Portlet Session
org.apache.jetspeed.portletcontainer.UserImpl user =
(org.apache.jetspeed.portletcontainer.UserImpl) session.getUser();
// Next, grab the Subject
Subject subject = user.getSubject();
// Grab the UserID and Password. There will only be one of each
// contained in the Subject.
String[] userId = getPrincipalsFromSubject(subject,
"com.ibm.wps.sso.UserIdPrincipal");
String[] password = getPrincipalsFromSubject(subject,
"com.ibm.wps.sso.PasswordCredential");
// The URL
writer.println("<TABLE>");
writer.println("<TR><TD><B>URL:</B></TD><TD>" +urlSpec +
"</TD></TR>");
// Show the UserId and Password
writer.println("<TR><TD><B>UserID:</B></TD><TD>" + userId[0] +
"</TD></TR>");
writer.println("<TR><TD><B>Password:</B></TD><TD>" + password[0] +
"</TD></TR>");
// Create the Basic Auth Header
String basicAuth = Base64Coder.base64Encode(userId[0] +
":" +
password[0]);
basicAuth = "Basic " + basicAuth;
writer.println("<TR><TD><B>Basic Auth Header:</B></TD><TD>" +
basicAuth +
"</TD></TR>");
writer.println("</TABLE><BR>");
// Create the URL for our protected resource, and get a URLConnection
try {
URL url = new URL(urlSpec);
HttpURLConnection con = (HttpURLConnection) url.openConnection();
// Set our Basic Auth Header
con.setRequestProperty("authorization", basicAuth);
// Connect
con.connect();
// Get the Response Code
String responseMessage = con.getResponseMessage();
int responseCode = con.getResponseCode();
// Were we successful?
if (HttpURLConnection.HTTP_OK == responseCode) {
writer.println("<P>Successfully connected to " +
urlSpec + "!</P>");
} else {
writer.println("<P>Unable to successfully connect to " +
urlSpec + ", HTTP Response Code = " +
responseCode +
", HTTP Response Message = \"" +
responseMessage + "\"</P>");
}
} catch (Exception e) {
writer.println("<P>Exception caught in HTTP Connection:</P><TT>");
writer.println(e.getMessage());
e.printStackTrace(writer);
writer.println("</TT>");
}
}
}
58
7.2.
LTPA example
Lightweight third-party authenticaiton (LTPA) is the mechanism for providing single
sign-on between WebSphere Application Server and Domino servers that reside in
the same security domain. The LTPA token is carried in the HTTP request as a
cookie. The example in this section assumes that:
1. The portlet is accessing a protected resource on the same WebSphere
Application Server that WebSphere Portal Server is installed on.
2. If the resource resides on another WebSphere Application Server or Domino
Server, then single sign-on is enabled between them.
Extracting the LTPA token
Object[] temp =
subject.getPrivateCredentials(LTPATokenCredential.class).toArray();
LTPATokenCredential ltpaToken = (LTPATokenCredential) temp[0];
// Create the LTPA Cookie Header
String cookie = "LtpaToken=" + ltpaToken.getTokenString();
// Create the URL for our protected resource, and get a URLConnection
URL url = new URL("http://myserver.example.com/ltpa_protected_resource");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
// Set our LTPA token Cookie
con.setRequestProperty("cookie", cookie);
// Connect
con.connect();
End of document
59
Download