Exploiting the WebSphere Portal 5.1.0.1 programming model: Part 2: Advanced URL generation

Implement portal navigation controls

This is the second part in an article series which can help you to apply the WebSphere Portal V5.1.0.1 programming model to your company's portal. In Part 1 we introduced the model. In this part, we dive into an area that challenges programmers coming from the traditional Web application world: how to create Uniform Resource Locators (URLs). The portal environment poses special requirements on components that need to create URLs; therefore, URL generation in the portal environment is a bit more involved. You see how portal components can generate URLs using WebSphere Portal Version 5.1.0.1 System Programming Interfaces (SPIs) that go beyond what the portal JSP tags. Finally, you see how to use these SPIs in an example which implements a navigation breadcrumb trail.

Stefan Behl (stefan.behl@de.ibm.com), Software Engineer, EMC

Stefan Behl photoStefan Behl is a Software Engineer in the IBM Development Laboratory in Boeblingen, Germany. He joined the Workplace and Portal Foundation Development in 2003 and works in the Portal Engine team. His main areas of focus are navigational state handling and page aggregation. Stefan studied Software Engineering at the University of Stuttgart, Germany, and holds a diploma in Computer Science.



Stefan Hepper, WebSphere Portal Programming Model Architect, EMC

Stefan HepperStefan Hepper is the responsible architect for the WebSphere Portal and Workplace programming model and public APIs. He co-led the Java Portlet Specification V1.0 (JSR 168) and is now leading the V2.0 (JSR 286) effort. Stefan received a Diploma of Computer Science from the University of Karlsruhe, Germany, and in 1998 he joined the IBM Böblingen Development Laboratory.


developerWorks Professional author
        level

Stefan Koch (stefkoch@de.ibm.com), Software Engineer, EMC

Stefan Koch photoStefan Koch is a Software Engineer in the IBM Development Laboratory in Boeblingen, Germany. As part of the WebSphere Portal Engine Team he is responsible for portal tags and URL generation issues. Stefan studied Information Engineering at the University of Applied Sciences Osnabrueck, Germany.



Carsten Leue (CLEUE@de.ibm.com), WebSphere Portal Runtime Architect, EMC

Carsten Leue photoDr. Carsten Leue works as an architect within the WebSphere Portal team in the IBM Development Laboratory in Boeblingen, Germany. He has 6 years of experience in the software development field and holds a PhD in physics from the University of Heidelberg, Germany. He joined IBM in 2000 to apply his scientific background on image processing to address recognition for a postal solution. He moved to WebSphere Portal to work on the WSRP OASIS standard as one of the specification editors. After gaining deep insight into WebSphere Portal as a Chief Programmer, Carsten now focuses on the architecture of the Foundation in the areas of state handling and configuration management.



03 October 2008 (First published 08 March 2006)

Also available in Japanese

Introduction

Developers who create a monolithic Web application are in full control of every aspect of their application. The application developer has the freedom to design the URLs as needed. A portal environment is different. The portal application developer provides a component that is combined with other components into a larger portal application. Therefore, the URLs can no longer be generated in an isolated manner; instead, developers need to create them using the mechanisms that are provided by the portal framework.

In WebSphere Portal, the main portal components that generate markup (which, therefore, need the ability to create URLs) are the following:

  • Themes that create the overall look and feel of the page and the layout of the page.
  • Skins that create the look and feel of a portlet window, including buttons for changing the window state or portlet mode of the portlet.
  • Portlets that render their content inside the portlet window.

This article describes URL generation for themes and skins by using Java APIs. You could create URLs in themes and skins using the URL-tags and the URLGeneration-tags of the portal JSP tag library (see the WebSphere Portal Information Center, Portal tag library section in the Resources list). These tags cover most of the use cases and you should use them for the common use cases. However, for scenarios that require either a Java API or additional functionality, you can use a WebSphere Portal V5.1.0.1 System Programming Interface (SPI) called the Navigational State SPI. One such use case is when you need to switch to another theme template using an URL.

Portlets that need to generate URLs pointing to other portlets should use the Portlet API (see the JSR 168 Portlet Specification link in the Resources list for details). For example, in the use case in which portlet A needs to send parameters to portlet B on a different page and the user should be taken to this page, instead of hard coding the page hierarchy into portlet A by creating an URL pointing to portlet B, portlet A should use the property broker infrastructure and raise an event. The portal administrator can wire the event of portlet A to the input of portlet B. As a result, the portal performs an automatic switch to that page. For more information, see the reference on Developing JSR 168 compliant cooperative portlets in Resources.

To get the most out of this article, you should know the basic portal concepts (see Part 1 ), understand JSP fundamentals, and be familiar with Java programming.


Reviewing state handling

WebSphere Portal V5.1 extends the concept of JSR168 navigational state for portlets to the state of the portal server. The JSR168 model supports the following types of states a portlet can use:

  • Persistent state that is exposed through the PortletPreferences API. Portlet implementers can use preferences to store and retrieve data across end-user sessions with the portal.
  • Session state that is exposed through the PortletSession API. Session state represents data that is kept on the server with a limited lifetime managed by the portlet container. The purpose of session state is to keep information that is transient in nature and that cannot be recomputed from other state or the current interaction (for example, the content of a shopping cart). Themes and skins can use the HttpSession to store session state.
  • Navigational state that is exposed in the form of render or interaction parameters using the PortletRequest. Navigational state represents information which is specific to the current view on the portlet (for example, a selected tab). The lifetime of this state is the same as that of the portlet request. The portlet container makes sure that the navigational state of a portlet is managed across interactions of the end user with other portal artifacts (for example, other portlets on the page or the navigation in the theme). Using navigational state is critical for enabling the browser’s back and forward button for WebSphere Portal. In addition to the render parameters for JSR168 portlets, WebSphere Portal manages navigational state information such as page selection, the expansion states of the page navigation tree, portlet modes, states, and so on. The combined render parameters of all addressed portlets forms the complete navigational state of the system that needs to be encoded into every URL on a portal page.
Figure 1. The various states in WebSphere Portal
Figure 1. The various states in WebSphere Portal

When designing a portlet you need to distinguish between the states a portlet manages and to appropriately encode them. In particular, design the navigational state and session state to be orthogonal to each other, because it is possible to access the same session in combination with different navigational states. This typically happens if the user uses the back-button, invokes a bookmark that contains portlet specific state, or opens a new browser window that shares a session with the original window, and then navigates in both windows independently.

Each portlet-generated link falls into one of these two categories:

  • Action links trigger the invocation of the action phase of the portlet. The portlet is free to alter session state or backend state during this phase and associates new render parameters at the end of the action phase. These render parameters become valid in the render request that follows the action request. The disadvantage of this flexibility is a loss in performance, because the action phase will be invoked before the other portlets on a page start their rendering. Only after the action has finished, can these portlets start their rendering. However, they can be rendered in parallel if you enable the Parallel Portlet Rendering feature of WebSphere Portal. Action links should always be encoded as POST links to comply with the W3C architecture recommendations (see Resources) for the WWW1.
  • Render links associate a set of render parameters with the request that is issued when the link is invoked. As a result of a render link invocation the action phase of the portlet is not called and the portlet must render its view with the new set of render parameters. The portlet must not modify session state or backend state as a result of the invocation of a render link. Render links provide the most efficient way (except for raw client side interactions using JavaScript) to interact with a portlet and are appropriate if only the navigational state is modified. Render links should be encoded as GET links (see Resources).

Similar to portlet concepts, themes and skins can also generate links that carry the semantics of action/render links, which are instead related to modifications of the portal state (in contrast to portlet state).

  • Links to Engine Actions are comparable to action links of portlets, because they refer to server side commands that can optionally alter server side state.
  • All other links (for example, page selections or modifications of portlet state/mode) compare to render links of portlets. Such links request different views of the portal server only, without changing the server’s state, and should be represented as GET links (see Resources).

Encoding navigational state

Navigational state represents the view of the portal that is associated with a particular client. The client can request (query) different views by interacting with the Web page (for example, by navigating to a new page). This type of interaction does not change anything on the server, rather only requests for a new view are provided by the server. It is, therefore, a "safe" operation in terms of HTTP. The nature of this interaction is such that the client can navigate back and forward through the recent views; they can also bookmark views and get back to them at a later point in time by invoking the browser bookmark.

Developers implement this behavior by encoding the navigational state of WebSphere Portal into the URL. Different navigational states result in different URLs.


Next, let's discuss the main concepts of the new Navigational State SPI, and see how these concepts are related to each other. In addition, you learn about the Accessor Application Programming Interface, which provides convenience interfaces that enable typed access to navigational state information.

All interfaces and classes discussed in the following subsections are packaged under com.ibm.portal.state.*.


Representing navigational state and URLs

Beginning with WebSphere Portal Version 5.1, navigational state is represented as a hierarchy of state information. The object model is a DOM-like document model containing un-typed state information represented as java.lang.String. This design decision is based on the assumption that a typical portal page contains many URLs and requires the navigational state to be serialized multiple times per request. The String-based memory representation enables efficient serialization of the navigational state into URLs; it avoids time and CPU consuming object-to-String conversions during the serialization process.

From a programmer’s perspective, however, it is more convenient and fail-safe to use strongly typed interfaces to access the state information. So, the state handling API includes a set of easy-to-use interfaces (part of the Accessor API) providing typed read and write access to navigational state information. See Reading and writing navigational state using the Accessor API.

A common pattern used in the design of the new state handling SPIs is to separate read-only interfaces from read-write interfaces (controllers). Therefore, the API offers two interfaces to navigational state:

  • com.ibm.portal.state.StateHolderprovides read access to navigational state information
  • com.ibm.portal.state.StateHolderControllerallows for modifying state information

Figure 2 shows that the state holder is a wrapper enclosing the un-typed document model. This design decision was made because the current navigational state must be cloned before modifying it according to the specific semantics of the generated URL. For example, if the programmer wants to create a URL that points to a particular portal page, the page selection information in the state document model must change; however, this modification must not take effect on all URLs on a page.

Figure 2: The StateHolder wrapping the un-typed DocumentModel
Figure 2: The StateHolder wrapping the un-typed DocumentModel

The lifetime of the state holder is one request. Figure 3 illustrates the lifecycle phases a state holder passes through when processing a request.

  • During URL decoding, the portal creates the state holder and populates it with the navigational state retrieved from the incoming request URL. It transforms the flat, character-based state representation from the URL into a hierarchical object representation (the document model introduced above).
  • During action processing, the portal engine and portlets can modify the state. After the action phase processing completes, the portal consolidates the state to make sure that it can no longer be modified.
  • During rendering, the portlets and navigation controls that are part of the addressed portal page generate URLs and include them into the markup. The generated URLs typically encode their target navigational state, which consists of the consolidated state holder (also called the base state) plus a small piece of state information representing the specific semantics of the respective URL (see Understanding URL encoding) . Because the base state cannot be modified any more at this time, the portal engine creates a (logical) copy of this base state for each new URL. This base state can then be altered according to the desired semantics of the URL. Maintaining the base state ensures that the navigational state of previous interactions does not get lost.
Figure 3. Lifetime of a StateHolder object
Figure 3. Lifetime of a StateHolder object

URLs are modeled by the com.ibm.portal.state.EngineURL interface. An EngineURL represents an URL that contains navigational state (see Understanding URL encoding). You specify the initial state holder an EngineURL references when requesting a new EngineURL instance from the appropriate URL factory. Typically, it is a copy of the request-specific base state.

Listing 1 provides a brief overview of the EngineURL interface, which is the central object in the API. The crucial method is the getState() method which returns the state holder object that this particular EngineURL instance references. The method returns a modifiable interface (StateHolderController) to the state holder to enable the programmer to modify the state for this EngineURL. See Reading and writing navigational state using the Accessor API for details on how to modify state.

The writeCopy() and writeDispose() methods2 allow for streaming the EngineURL to a given Writer, for example, to the markup PrintWriter obtained from the HTTP response. (Although you could also use toString(), this practice is discouraged because it is a rather expensive operation.)

Listing 1: The EngineURL interface
 public interface EngineURL extends DisposableURL {	
    /** Returns a read-write interface to the state carried by this URL */
    StateHolderController getState();
    /** Specifies whether the URL should point to public /protected area */
    void setProtected(Boolean bFlag);	
    /** Specifies whether a secure connection is requested */
    void setSecure(Boolean bFlag);
    /**
     * Streams the URL to the given writer. Maintains the state of the URL
     * i.e. this method can be called multiple times.
     */
    Writer writeCopy(Writer out)
       throws IOException, OutputMediatorException, 
              PostProcessorException, DocumentModelException;
    /**
     * Streams the URL to the given writer and finally releases the state
     * of the URL. The EngineURL object must not be accessed after 
     * invoking this method
     */
    Writer writeDispose(Writer out)
       throws IOException, OutputMediatorException, PostProcessorException;
}

Understanding URL encoding

The URL encoding implementation introduced in WebSphere Portal V5.1 is optimized for performance to:

  • Minimize the processing time needed to generate an URLl in particular, the time needed to serialize the naviga-tional state that should be included in the URL.
  • Keep the URL as short as possible to reduce markup size and to comply with the URL length restriction of 2 KB which is enforced by some browsers.

To meet these requirements, URL encoding is implemented using an approach called delta encoding. The idea behind delta encoding is that the consolidated base state (available after the action phase) must be – at least conceptually – contained in each URL, unless the programmer explicitly bases the URL on a different state. To avoid serializing this base state for each created URL, it is pre-serialized right after the action phase. During URL generation, the pre-serialized base state is set into each non-relative URL; the state delta, specifying the URL-specific semantics, is encoded separately into the path information of the respective URL.

The example below shows an absolute "delta URL" that contains the serialized base state portion following the codec identifier kcxml, and its state delta portion directly after the delta codec identifier delta/base64xml:

http://myserver.com:9081/wps/portal/kcxml/04_Sj9SPykssy0xPLMnMz0vM0Y_QjHvm5qf
oFuaE6KigCBh9CG/delta/base64xml/Y2BkbGBgYlrDwMDE3srCasTUq!

Things are even better when a relative URL can be generated. The browser appends the relative URL to either the current request URL or to the value of the HTML base tag (if any). WebSphere Portal exploits relative URLs by including the pre-serialized base state into the head of the HTML page using the HTML base tag; the concrete relative URL contains the state delta only. The example below shows how this convention reduces the markup size considerably.

<head>
    <base href="http://myportal.com:9081/wps/portal/!ut/p/kcxml/04_Sj9SPykssy0xPAIuz!
    </base>
</head>
<body>
...
    <td class="wpsToolBar" valign="middle" nowrap>
       <a href="delta/base64xml/L2dJQSEvUUt3QS80SVVFLzZfQkEwN1VTOVMyMzAwR0!"
          class="wpsToolBarLink">Administration</a>
    </td>
...

There are cases in which relative URLs cannot be used. For example, when the protocol switches from http to https, the generated URL must be absolute. Also, in some cases where JavaScript is heavily used in the markup, the browser cannot resolve relative URLs. By default, relative URLs are disabled in WebSphere Portal to avoid such JavaScript problems due to custom markup that the portal page contains (for example, markup produced by portlets) . However, the Navigational State SPI provides a way to explicitly create a relative URL (see Creating URLs using URLAccessorFactory for details).


Reading and writing navigational state using the Accessor API

The Accessor API provides typed access to the state document model. It enables the programmer to easily query and modify navigational state information.

Figure 4 illustrates the Accessor API, an abstraction layer which encapsulates access to particular nodes in the hierarchical document model. For each aspect of navigational state (for example, page selection, expansion states, portlet states, and so on) the Accessor API offers an accessor factory that provides read-only as well as read-write accessors which are tailor-made to the particular state aspect they reference. The accessors read directly from, or write to, the respective positions in the state document model. They also perform required type conversions.

The required navigational state information is located in the state document model and is encapsulated in the dedicated accessor factory implementation. Typically, the accessor factory uses a path expression to locate a particular document node or to create a node (or even a complete path of nodes) in the state document model. After the node is located, the accessor factory passes a node reference to the accessor or accessor controller during their instantiation. Accessor (and accessor controller) implementations are independent from the structure of the state document model; that is, accessors can be reused, even if the required piece of information is moved to another node in the state document model.

Figure 4 shows some important accessors; it omits the corresponding accessor factories.

Figure 4: The Accessor API
Figure 4: The Accessor API

Now let's look at the selection accessor factory, com.ibm.portal.state.selection.SelectionAccessorFactory, and all its related interfaces in more detail. They are illustrated in Listing 2.

Listing 2: The SelectionAccessorFactory interface and related interfaces
public interface SelectionAccessorFactory extends AccessorFactory {
    /** Returns a read-only accessor operating on the given state */
    SelectionAccessor getSelection(StateHolder state);
    /** Returns a read-write accessor operating on the given state */
    SelectionAccessorController getSelectionController(StateHolderController state);
}

public interface SelectionAccessor extends Accessor {
    /** Returns the object id of the currently selected portal page */
    ObjectID getSelection() throws InvalidSelectionNodeIdException;
}
public interface SelectionAccessorController extends SelectionAccessor {
    /** Selects the page that corresponds with the given id */
    void setSelection(ObjectID pageID) throws CannotInsertSelectionNodeException;
    /** Selects the page that corresponds with the given unique name */
    void setSelection(String uniqueName) throws CannotInsertSelectionNodeException;
}

Like other accessor factories, the SelectionAccessorFactory exposes a method that returns a read-only SelectionAccessor and another method that returns a read-write SelectionAccessorController. The getSelectionAccessor() method takes the read-only StateHolder interface as an argument. The getSelectionController() requires the read-write StateHolderController interface.

This flyweight pattern (the state is passed in as an argument) has been chosen because the state on which the accessor operates is not necessarily the request-specific base state that has been retrieved from the request URL. Typically, it is the state clone that has been created for a particular EngineURL. You can obtain the URL-specific state holder by calling getState() on the EngineURL object. Listing 3 shows how to let a created EngineURL point to a certain portal page (for example, the Stock Market page) using the SelectionAccessorController.

Listing 3: Using the SelectionAccessorController to create a page link
 final EngineURL url = ...;
final SelectionAccessorFactory selFct = ...;

final SelectionAccessorController selCtrl = 
    selFct.getSelectionController(url.getState());

try {
    selCtrl.setSelection("wps.StockMarket");
} catch (StateException e) {
    // error handling
    ...
} finally {
    selCtrl.dispose();
}

The base Accessor interface is derived from the com.ibm.portal.Disposable interface. Programmers are strongly encouraged to explicitly indicate when the accessor is no longer needed by invoking the dispose() method on it. This enables accessor factory implementations to store the accessor factories in object pools to achieve better performance (due to less initialization overhead and less garbage collection).


URL generation using PortalStateManagerService

PortalStateManagerService, which is new in WebSphere Portal Version 5.1.0.1, enables programmers to easily create URLs to implement sophisticated portal use cases.

You use this service to:

  • Create URLs within themes (JSPs) in cases where the WebSphere Portal tag libraries are not sufficient (in particular, the URL-tag and URLGeneration-tag)
  • Programmatically create URLs in any portal-related artifact (such as in custom tags, custom portal actions, and so on.)
  • Programmatically create URLs "offline"; that is, in business components which do not have access to the current servlet request (for example, Enterprise JavaBeans)

The following subsections describe how to get access to PortalStateManagerService, and how to create URLs. The last subsection shows how to implement a custom breadcrumb navigation tag, which uses this new service (part of the SPI).

Getting access to PortalStateManagerService

To access PortalStateManagerService, use a JNDI lookup with the lookup name portal:service/state/PortalStateManager. The lookup returns a com.ibm.portal.state.service.PortalStateManagerServiceHome interface with a getter method to retrieve the specific com.ibm.portal.state.service.PortalStateManagerService.

The PortalStateManagerServiceHome instance is valid for the lifetime of the portal. Therefore, you should perform the JNDI lookup once only, and then store the retrieved home object (for example in an instance or static variable).

In contrast to the PortalStateManagerHome object, the PortalStateManagerService has request scope; that is, it can be used for only one servlet request. For all subsequent servlet requests, you need to request a new service instance needs from the home interface. Listing 4 shows how to get access to the service.

Listing 4: How to get access to the PortalStateManagerService
/** the JNDI name to retrieve the PortalStateManagerServiceHome object */
private static final String JNDI_NAME = 
                               "portal:service/state/PortalStateManager";

/**
 * The PortalStateManagerServiceHome object to retrieve the service from 
 */
private static PortalStateManagerServiceHome serviceHome;

/** Any method processing request and response. */
public void anyMethod(final HttpServletRequest request,
                      final HttpServletResponse response) {
    try {
        // get the service from our home interface
        final PortalStateManagerService service
            = getServiceHome().getPortalStateManagerService(request, response);
       // use the service
       ...
       // indicate that we do not need it any longer
       service.dispose();
    } catch (Exception e) {
        // error handling
    }
}
/**
 * Looks up the PortalStateManagerServiceHome being valid
 * for the lifetime of the portal.
 */
private static PortalStateManagerServiceHome getServiceHome() {
    if (serviceHome == null) {
        try {
            final Context ctx = new InitialContext();
            serviceHome =
                (PortalStateManagerServiceHome) ctx.lookup(JNDI_NAME);
        } catch (Exception e) {
            // error handling
        }
    }
    return serviceHome;
}

The PortalStateManagerService interface is derived from the com.ibm.portal.Disposable interface. Programmers are strongly encouraged to explicitly indicate when the service is no longer required by invoking the dispose method. Because the service has request scope, you must dispose of the service at the end of the request, at the latest.

There is one exception to the rule with regard to the request scope of the service. If the service is used in environments or components that are completely decoupled from the request processing (or even from the portal), the service lifetime is undefined. In that case, you can request the service from the home interface, passing in null for request and response, and you can dispose of it as you see fit.

Using PortalStateManagerService

The com.ibm.portal.state.PortalStateManagerService interface provides two methods:

StateHolderController newState()
Creates a new modifiable state holder. Use this method when a new navigational state is to be constructed programmatically to serve as a base for several EngineURLs.
AccessorFactory getAccessorFactory(final Class cls)
Provides access to the functionality offered by this service. A decisive role is occupied by the so-called URLAccessorFactory which enables programmers to create URLs supporting a variety of use cases. All other accessor factories that can be obtained are needed to express the specific semantics of a URL by modifying the respective navigational state aspect.

To use the service:

  1. Obtain a new EngineURL object using the URLAccessorFactory.
  2. Specify the semantics of the created URL by modifying the StateHolderController that has been associated with the EngineURL object. Use the appropriate accessor factories which you obtain with the method getAccessorFactory(Class).
  3. Write the EngineURL to an output stream (for example, a JSPWriter) to include it into the markup.

Next, let's look at how to use the URLAccessorFactory to create URLs.


Creating URLs using URLAccessorFactory

The com.ibm.portal.state.accessors.url.URLAccessorFactory provides several convenience methods which you can use to get EngineURL instances.

EngineURL newURL(HttpServletRequest request,
                 HttpServletResponse response,
                 Constants.Clone type);

Programmers usually choose this method to create an EngineURL because it is suitable for all prevalent use cases. The method provides an EngineURL which refers to the StateHolder representing the navigational state of the current servlet request. The type argument specifies how the request-specific StateHolder should be cloned for the URL to be created. There are four pre-defined clone constants:

  • SMART_COPY indicates that a shallow StateHolder copy should be created which records the state modifications applied for this particular EngineURL only, instead of copying all nodes in the document model to construct the clone. SMART_COPY represents the default; therefore, passing in null is equivalent to SMART_COPY. This clone method also allows the generation of relative "delta" URLs.
  • DEEP_COPY results in a complete copy of the request-specific StateHolder, i.e. each node and the node hierarchy is cloned. The deep copy prevents the generation of delta URLs.
  • EMPTY_COPY indicates that the contents of the request-specific StateHolder should be cleared, i.e. the created EngineURL will be based on an empty state. Any interaction with such an URL results in losing the navigational state of previous interactions.
EngineURL newURL(HttpServletRequest request,
                 HttpServletResponse response,
                 StateHolder state,
                 Constants.Clone type);

This second newURL method requires that the StateHolder on which the EngineURL is based is explicitly passed in. The type argument refers to this particular StateHolder. You use this method when the URL to be created should encode a StateHolder that was defined programmatically. Clicking on such an URL typically results in losing the navigational state of previous interactions with the portal.

EngineURL newURL(HttpServletRequest request,
                 HttpServletResponse response,
                 URLContext ctx,
                 Constants.Clone type);

This next variant lets you specify whether the created URL should be absolute3, server-relative4, or relative5. You pass in and implement an URLContext interface; for example if the URL must be absolute, then the isAbsolute() method must return true;isRelative() and isServerRelative() must return false. Because this method does not require an explicit StateHolder argument, the EngineURL will encode the StateHolder retrieved from the given request. To reduce markup size, pass in an URLContext that allows for relative URLs6. See Understanding URL encoding for more information on relative URLs.

EngineURL newURL(HttpServletRequest request,
                 HttpServletResponse response,
                 StateHolder state,
                 URLContext ctx,
                 Constants.Clone type)

This method is the counterpart to the previous method, and takes a StateHolder defined programmatically as an explicit argument.

EngineURL newURL(ServerContext request,
                 boolean isSecure,
                 boolean isProtected,
                 StateHolder state,
                 Constants.Clone type)

This last newURL method requires neither a servlet request nor a response. You can use it to generate URLs "offline"; that is, in EJBs or any other Java artifact that does not have access to the current servlet request. You explicitly pass in , the hostname, server port, context path, and servlet path. The abstraction encapsulating this kind of information is the ServerContext interface. The isSecure boolean argument specifies whether the URL will result in a secure connection (https) or not (http). The second boolean argument, isProtected, declares whether or not the URL points to the protected area7 of portal. If null is passed for either of these parameters, then the properties of the current request are preserved.

After you obtain an EngineURL object (using one of the above methods), you can modify the navigational state of the URL as described in Reading and writing navigational state using the Accessor API. In order to get access to the various accessor factories, you use the getAccessorFactory(Class) service method which returns an object of type java.lang.Object. You can cast the returned object to the specific accessor factory interface according to the Class object that is passed in.

Listing 5 shows examples that use these methods. The createPortletLink method illustrates how to create an URL that points to a given portlet and changes its navigational state (portlet mode, window state, and render parameters). The second sample, createOfflinePageLink, shows how to create an URL to a portal page with a specific theme, without having access to the current request and response.

Each sample method involves getting an EngineURL from the URLAccessorFactory using the appropriate newURL method, changing the navigational state of the URL, and streaming the URL to a given writer.

Listing 5: Creating a URL that changes the navigational state of a portlet
public Writer createPortletLink(
    final HttpServletRequest req, final HttpServletResponse res,
    final ObjectID portletWindowID, final PortletMode newMode,
    final WindowState newState, final Map renderParameters,
    final Writer writer) throws StateException, IOException {
    
    final PortalStateManagerService service
        = getServiceHome().getPortalStateManagerService(req, res);
    
     // get a URL from the URL accessor factory using request and response
    final URLAccessorFactory urlFct = (URLAccessorFactory) 
          service.getAccessorFactory(URLAccessorFactory.class);
    // the URL should be based on the current request state
    final EngineURL url = urlFct.newURL(request, response, null);
    
    // change the portlet's navigational state
    final PortletAccessorFactory portletFct = (PortletAccessorFactory)  
        service.getAccessorFactory(PortletAccessorFactory.class);
    final PortletAccessorController portletCtrl
        = portletFct.getPortletController(portletWindowID, url.getState());
    try {
        portletCtrl.setPortletMode(newMode);
        portletCtrl.setWindowState(newState);
        portletCtrl.getParameters().putAll(renderParameters);
    } finally {
        portletCtrl.dispose();
    }
    // stream the URL using the given writer
    return url.writeDispose(writer);
}
Listing 6: Creating a localized portal page link offline
public Writer createOfflinePageLink(
    final ServerContext serverCtx, final boolean isSecure,
    final boolean isProtected, final ObjectID pageID,
    final Locale locale, final Writer writer)
    throws StateException, IOException {
    
    // get the service via the home interface
    final PortalStateManagerService service
        = getServiceHome().getPortalStateManagerService(null, null);
    
    // get a URL from the URL accessor factory
    final URLAccessorFactory urlFct = (URLAccessorFactory)
        service.getAccessorFactory(URLAccessorFactory.class);
    final EngineURL url = urlFct.newURL(serverCtx, isSecure, isProtected,
        service.newState(), null);
    
    // change the page selection and the locale
    final SelectionAccessorFactory selFct = (SelectionAccessorFactory) 
        service.getAccessorFactory(SelectionAccessorFactory.class);
    final ThemeTemplateAccessorFactory themeTemplateFct = 
      (ThemeTemplateAccessorFactory) 
      service.getAccessorFactory(ThemeTemplateAccessorFactory.class); 
    final SelectionAccessorController selCtrl
        = selFct.getSelectionController(url.getState());
    final ThemeTemplateAccessorController themeTemplateCtrl = 
      themeTemplateFct.getThemeTemplateAccessorController(url.getState()); 

    
    try {
        selCtrl.setSelection(pageID);
        themeTemplateCtrl.setThemeTemplate(themeTemplate); 
    } finally {
        // dispose the controllers
        selCtrl.dispose();
        themeTemplateCtrl.dispose();
    }

In order to support more sophisticated use cases, the PortalStateManagerService offers additional accessor factories. The list below describes the available accessor factories. All accessor factories are part of the package com.ibm.portal.state.accessors.*.

SelectionAccessorFactory
The SelectionAccessorFactory provides accessors to read and write portal page selection information. To create an URL that points to another page the SelectionAccessorController needs to be requested from the factory in order to set the new selection upon the state the created EngineURL is associated with.
PortletAccessorFactory
The PortletAccessorFactory provides accessors to read and write portlet-related navigational state information. This includes portlet mode, window state, and render parameters. In particular the PortletAccessorController may be used to change the navigational state of a given portlet (for example, the portlet mode).
PortletTargetAccessorFactory
The PortletTargetAccessorFactory provides accessors to read and write portlet action-related information. In particular the PortletAccessorController may be used to declare a portlet as the target of an action. This enables the programmer to create URLs that trigger portlet actions.
SoloAccessorFactory
The SoloAccessorFactory provides accessors referring to the Solo view of portlets. The SoloAccessorController can be used to create URLs that activate /deactivate the Solo view of a particular portlet.
ThemeTemplateAccessorFactory
The ThemeTemplateAccessorFactory supports reading and writing theme template information. In particular the ThemeTemplateAccessorController may be used to create URLs that switch to a certain theme template.
ExpansionStatesAccessorFactory
The ExpansionStatesAccessorFactory provides accessors to read and write expansion states information i.e. to determine whether a given navigation node in a navigation tree control is expanded or collapsed. The ExpansionStatesAccessorController is typically used to generate twisty-URLs that toggle the expansion state of a certain navigation node.
ShowToolsAccessorFactory
The ShowToolsAccessorFactory provides accessors to read and write "show tools"-related information. The ShowToolsAccessorController is typically used to create a URL that blends in the tool icons for portlet windows offering function such as moving /deleting the respective portlet.
StatePartitionAccessorFactory
The StatePartitionAccessorFactory provides accessors to read and write state partition identifiers. The StatePartitionAccessorController can be used to include a state partition identifier into the navigational state. A new state partition identifier should be included into URLs that open new browser windows.
EngineActionAccessorFactory
The EngineActionAccessorFactory provides controllers that should be used to create engine action URLs. The EngineActionAccessorController in particular allows for setting action parameters. Note that this accessor factory does not offer a read-only accessor because the execution of engine actions is exclusively managed by the portal.

Example: A breadcrumb navigation control for themes

In order to illustrate how to use the API, let's look at how to use it in custom JSP tags.

Figure 5 shows a sample page which includes a breadcrumb navigation control which can be used within themes or skins. The breadcrumb navigation locates the page which is currently selected and displays the titles of all pages that are part of the navigation path from the content root to the selected page. The titles are rendered as page links enabling the user to directly navigate to the parent pages of the current page. In Figure 5, the user has selected the page Company Tracker.

Figure 5: Using the breadcrumb navigation in a theme
Figure 5: Using the breadcrumb navigation in a theme

Listing 7 illustrates how to include the breadcrumb navigation in a theme JSP. In this example, the WebSphere Portal default theme (Default.jsp) was chosen.

Listing 7: Including the breadcrumb into a JSP
<td colspan="2" class="breadcrumb" >
    <crumbtrail:loop var="selection">
        &nbsp;>&nbsp;;
        <a href='<crumbtrail:url node="<%= selection %>" 
                                 allowRelative="true"/>'>
            <wps:title varname="<%=selection%>"/>
        </a> 
    </crumbtrail:loop><br>
</td>

The listing shows that two custom JSP-tags are used to put the breadcrumb trail into practice. Both tags are part of the tag library that has been included with the crumbtrail prefix.

  • The LoopTag (crumbtrail:loop) encapsulates the lookup of the navigation path.

    Internally, it traverses the identified navigation path and stores each navigation node visited in a scripting variable called selection. LoopTag does not produce any markup output.

  • The UrlTag (crumbtrail:url) is responsible for creating the URL that points to the current navigation node (that is, the page). It gets the respective navigation node using the tag attribute node. You can just assign it the value of the scripting variable selection (node="<%= selection %>").

    Internally, the page URL is created using the PortalStateManagerService and the URL is written to the markup using the appropriate JSPWriter. Therefore, the URL can be directly included into the markup as an HTML href attribute. The localized display name of the created link is determined using the portal title tag.

In the following sections we look at the UrlLTag in more detail. To simplify matters, the LoopTag is not described in detail because it does not use the new SPI; it merely uses the Model SPI to traverse the navigation model to build the breadcrumb trail. See the WebSphere Portal V 5.1.0.1 Information Center, Model SPI Overview section link in Resources for details.

Accessing PortalStateManagerService within the URL tag

As discussed in Getting access to PortalStateManagerService, you can obtain the PortalStateManagerService from the PortalStateManagerHome object retrieved using JNDI.

Listing 8 demonstrates how the service retrieval has been implemented using a custom tag called UrlTag.

Listing 8: Getting access to the PortalStateManagerService in a tag
public class UrlTag implements Tag

  /** the JNDI name to retrieve the PortalStateManagerServiceHome object */
  private static final String JNDI_NAME = 
      "portal:service/state/PortalStateManager";

  /** the PortalStateManagerServiceHome object to retrieve the service from */
  private static PortalStateManagerServiceHome serviceHome;

  /** the PortalStateManagerService for the current request */
  private PortalStateManagerService service;

  /**
   * Initializes the tag on a per page basis. Gets the request-specific
   * PortalStateManagerService from the service home.
   */
  public void setPageContext(final PageContext pc) {
      pageContext = pc;
      try {
          service = getServiceHome().getPortalStateManagerService(
              (HttpServletRequest)pageContext.getRequest(),
              (HttpServletResponse)pageContext.getResponse());
      } catch (Exception e) {
          logger.log(Level.WARNING,"Exception occured", e);
      }
  }
/**
   * Looks up the PortalStateManagerServiceHome being valid
   * for the lifetime of the portal.
   */
  private static PortalStateManagerServiceHome getServiceHome() {
      if (serviceHome == null) {
          try {
              final Context ctx = new InitialContext();
              serviceHome =
                  (PortalStateManagerServiceHome) ctx.lookup(JNDI_NAME);
          } catch (Exception e) {
              logger.log(Level.WARNING,"Exception occured",e);
          }
      }
      return serviceHome;
  }

  /**
   * @see javax.servlet.jsp.tagext.Tag#release()
   */
  public void release() {
      service.dispose();
      service = null;
  }

The PortalStateManagerServiceHome object is valid for the lifetime of the portal. Therefore, it is retrieved only once using JNDI and then stored in the static variable serviceHome. The lookup logic has been implemented in the private method getServiceHome(). PortalStateManagerService is requested within the setPageContext() method, which is invoked once per page visit. Therefore, the same service instance can be reused when the URLTag is invoked multiple times per page (as it is the case in our example). This solution is correct because page scope implies request scope as required.

Within the release() method the tag explicitly indicates that it does not need the service any more by invoking its dispose() method. The release() method is called by the JSP implementation after all invocations of the URLTag have been processed to indicate the end of the tag’s lifecycle.

Each URL representing a page link in the breadcrumb navigation is created within the doStartTag() method of the URLTag. Listing 9 shows the required code.

Listing 9: Creating the navigation links within doStartTag
 /** The navigation node to operate on */
private NavigationNode iNode;

/** Flag indicating whether this tag may create a relative URL */
private Boolean iAllowRelativeURL;

/** URL context that allows for relative URLs */
private static final URLContext ALLOW_RELATIVE_URL = new RelativeURLContext();

/** URL context that disallows relative URLs */
private static final URLContext DISALLOW_RELATIVE_URL = new NonRelativeURLContext();

/** Sets the navigation node to operate on. */
public void setNode(final NavigationNode node) {
    iNode = node;
}

/** Specifies whether the URL to be created may be relative or not. */
public void setAllowRelativeURL(final String allowRelative) {
    iAllowRelativeURL = Boolean.valueOf(allowRelative);
}

/**
 * Create an EngineURL which points to the portal page represented by iNode.
 * Finally write URL to the responsible JSPWriter and indicate that no tag
 * body processing is required.
 */
public int doStartTag() throws JspException {
    if (iNode != null) {
       try {
            // get the factory providing EngineURLs
            final URLAccessorFactory urlAccessorFactory = 
                (URLAccessorFactory) service.getAccessorFactory 
                    (URLAccessorFactory.class);            
            // request an EngineURL 
            final EngineURL selectionURL;
            // request and response
            final HttpServletRequest request =
                (HttpServletRequest) pageContext.getRequest();   
            final HttpServletResponse response =
                (HttpServletResponse) pageContext.getResponse();   
            // check whether we can create a relative URL
            if (iAllowRelativeURL == null) {
                // the server should decide
                selectionURL = urlAccessorFactory.newURL(
                    request, response, null);
            } else if (iAllowRelativeURL.booleanValue()) {
                // relative URLs are allowed
                selectionURL = urlAccessorFactory.newURL(
                    request, response, ALLOW_RELATIVE_URL, null);
            } else {
                // relative URLs are not allowed
                            selectionURL = urlAccessorFactory.newURL(
                    request, response, DISALLOW_RELATIVE_URL, null);
            }
            // get the selection accessor factory    
            final SelectionAccessorFactory selectionAccessorFactory = 
                (SelectionAccessorFactory) service.getAccessorFactory
                    (SelectionAccessorFactory.class);           
            // get the selection controller which operates on the URL-
            // specific state which has been derived from the request state 
            final SelectionAccessorController selectionAccessorController = 
                selectionAccessorFactory.getSelectionController
                    (selectionURL.getState());
            // set the ObjectID of iNode
            selectionAccessorController.setSelection(iNode.getObjectID());
            // we do not need the controller any more
            selectionAccessorController.dispose();				
            // stream the URL to the obtained JSPWriter and finally dispose
            // the EngineURL object
            selectionURL.writeDispose(pageContext.getOut());
        } catch (Exception e) {
            logger.log(Level.WARNING,"Exception occured", e);
        }
    }
    // no body processing required	
    return SKIP_BODY;
}

/** Reset our instance variables to enable tag pooling. */
public int doEndTag() throws JspException {
    iNode = null;
    iAllowRelativeURL = null;
    return EVAL_PAGE;
}

/** URL context implementation that also allows for relative URLs */
private static class RelativeURLContext implements URLContext, Serializable {
    /** @see com.ibm.portal.state.accessors.url.URLContext#isAbsolute() */
    public boolean isAbsolute() {
        return true;
    }
    /** @see com.ibm.portal.state.accessors.url.URLContext#isRelative() */
    public boolean isRelative() {
        return true;
    }
    /** @see com.ibm.portal.state.accessors.url.URLContext#isServerRelative() */
    public boolean isServerRelative() {
        return true;
    }
}

/** URL context implementation that enforces non-relative URLs */
private static class NonRelativeURLContext extends RelativeURLContext {
    /** @see com.ibm.portal.state.accessors.url.URLContext#isRelative() */
    public boolean isRelative() {
        return false;
    }
}

The URLTag has two instance variables: iAllowRelative and iNode. The iAllowRelative Boolean instance variable indicates whether or not the tag can create a relative URL. The corresponding (optional) tag attribute is allowRelative. By default, iAllowRelative is not set (equals null), indicating that the server should decide whether a relative URL can be generated or not8. Deciding whether or not a URL can be relative depends strongly on how the URL is included in the markup. In our example, relative URLs can be used because the created navigation URLs are included as href attributes of anchor tags (see Listing 9). Use relative URLs wherever possible to reduce the markup size of the breadcrumb control.

The code which evaluates the iAllowRelative variable is part of the doStartTag method. The EngineURL is requested from the URLAccessorFactory using either the newURL(HttpServletRequest, HttpServletResponse, Constants.Clone)method in the case that the decision is up to the portal server, or the newURL(HttpServletRequest, HttpServletResponse, URLContext, Constants.Clone)method in the case that the generation of a relative URL is explicitly allowed or disallowed. In the latter case, the URLContext argument that is passed to the newURL method is either an instance of the RelativeURLContext inner class (to allow for generating relative URLs) or NonRelativeURLContext(to enforce non-relative URLs).

The instance variable iNode of type NavigationNode represents the page the tag references. The corresponding (mandatory) tag attribute used in the JSP is node. In order to let the created EngineURL select this page, the setSelection method of the SelectionAccessorController is invoked, passing in the ObjectID of the navigation node iNode.

At the end of the doStartTag method, the created page selection URL is directly streamed to the JSP writer by calling selectionURL.writeDispose(pageContext.getOut()).


Conclusion

Navigational state represents the view of the portal that is associated with a particular client. Starting with WebSphere Portal V5.1, navigational state is encoded in the URL, enabling a variety of important features. Users can navigate back and forward through the views visited previously using the browser’s Back or Forward button. Users can click browser bookmarks to get back to a certain portal view at a later point in time.

The efficient encoding of navigational state into the URL is done internally by the Navigational State SPI implementation. Depending on the use case, the programmer just needs to set the respective navigational state on the created URL object using the Accessor API. The Accessor API provides intuitive, typed interfaces to read and write certain navigational state aspects (such as page selection, theme, portlet-related state, and so on) to relieve the programmer from performing type conversions.

PortalStateManagerService is an easy-to-use portal service providing access to the Navigational State SPI for theme programmers to enable them to implement a variety of use cases, such as developing custom tags that enhance the functionality provided by the default portal URL tags, creating URLs offline (for example, in EJBs), and so forth.

In the next part of this series, we explain how to integrate WebSphere Portal into your existing security environment. We cover such topics as Single-Sign-On (SSO) and customization of the login/logout behavior.


Known problems

To exploit relative URLs in WebSphere Portal Version 5.1.0.1 and Version 5.1.0.2, you need to install the fixes to APAR PK15894.


Download

DescriptionNameSize
Code samplescrumbtrail.zip  ( HTTP | FTP )12 KB

Resources

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into WebSphere on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=WebSphere
ArticleID=105095
ArticleTitle=Exploiting the WebSphere Portal 5.1.0.1 programming model: Part 2: Advanced URL generation
publish-date=10032008