Package com.ibm.portal.resolver

Contains the main interfaces that extenders of the URI resolution infrastructure must implement.

See:
          Description

Interface Summary
Binding Tagging interface for bindings during the resolution process
Constants Multi purpose constants that support the POC framework
Lookup Constructs an XML source document that represents an individual ATOM entry.
LookupService Service that allows to map between the URI to a resource and an ATOM feed that describes this resource, including its entry URL.
PocURL The PocURL is a URL that addresses a piece of content (POC).
PortletContext Extension of the COR Context for use in a JSR168 portlet.
ResolutionService Service that allows to resolve a URI to navigational state in the context of WebSphere Portal.
Resolved Identification of the result of the resolution process.
ServletContext Extension of the COR context for use in a servlet.
 

Package com.ibm.portal.resolver Description

Contains the main interfaces that extenders of the URI resolution infrastructure must implement.

Package Specification

The URI addressability framework consists of thow following functional groups
  1. Resolution of URIs to views in WebSphere Portal
  2. Mapping of URIs to URLs to services for such URIs
  3. Tag API that can be used from both portlets and servlets

Resolution of WebSphere Portal views

How to extend the resolver infrastructure?

Content providers need to provider a resolver implementation that resolves URIs and locates the target page and the target portlet window ID that contains a viewer. In addition there needs to be a viewer portlet implementation. Both pieces are decoupled and can reside in different bundles.

Implementing the viewer

The viewer needs to be a portlet packaged in a WAR file. The implementation of the portlet is arbitrary. In addition to fulfilling the portlet specification the portlet.xml needs to specific a special init-param to link from the portlet to its resolver.

<init-param>
  <name>com.ibm.portal.resolver.ResolutionService</name>
  <value>%YOUR_UNIQUE_SERVICE_ID%</value>
</init-param>

The parameter name com.ibm.portal.resolver.ResolutionService is predefined by the resolver infrastructure, the value references a valid location service handler for a COR service that extends the com.ibm.portal.resolver.portlet location type (defined again by the resolver infrastructure).

The actual service handler implementation may be implemented by any bundle, however it is advised to include the handler in the same WAR file that also contains the portlet. So the WAR file needs to contain a valid plugin.xml in the META-INF directory of the WAR file. This plugin has as a minimum to define the extension.

<?xml version="1.0" encoding="UTF-8"?>

<plugin provider-name="%YOUR_PROVIDER_NAME%" version="1.0.0" 
name="%YOUR_PLUGIN_NAME%" id="%YOUR_PLUGIN_ID%">
  <extension point="com.ibm.content.operations.registry. 
  locationServiceHandler">
    <serviceHandler class="%YOUR_HANDLER_IMPLEMENTATION_CLASS%" 
        locationTypeId="com.ibm.portal.resolver.portlet" id="% 
        YOUR_UNIQUE_SERVICE_ID%"/>

  </extension>
</plugin>

Implementing the resolver

The implementation class of the viewer resolver needs to implement the com.ibm.portal.resolver.ResolutionService interface. The purpose of the implementation is to initialize the state with portlet specific information that the portlet requires to get initialized. The URI that is passed to the resolver is of the format portlet:<portletWindowID>:<pageID>:<pocURI>, so the portlet can use the portletWindowID in conjunction with the pocURI and the state handling API in WebSphere Portal to modify the state.

In the simplest case the portlet is implemented as a JSR168 portlet and accepts the pocURI as a JSR168 render parameter. For this case the resolver can be implemented as follows:


        /*
         * (non-Javadoc)
         *
         * @see com.ibm.portal.resolver.ResolutionService#resolve(com.ibm.portal.resolver. 
         Resolved,
         *      java.net.URI, java.util.Set, java.util.Map,
         *      com.ibm.portal.resolver.ResolutionContext)
         */
        public void resolve(final Resolved res, final URI uri,
                        final Set acceptedBindings, final Map params, final Context ctx)
                        throws ResolutionException, StateException {
                // lookup the required services first
                final Identification idSvc = services.getIdentification(ctx);
                final StateManagerService stateSvc = services.getStateManager(ctx);

                // decode the URI
                final PortletURI portletURI = new PortletURI(uri, idSvc);
                final ObjectID portletWindowID = portletURI.getPortletWindowID();
                final URI pocURI = portletURI.getPocURI();

                /*
                 * This is the critical piece of code that knows how to pass parameters
                 * to the target portlet. There is a tight coupling between this code
                 * and the code in the portlet that reads the parameters. That's why the
                 * ViewerResolver and the portlet code should reside in the same bundle
                 * (WAR file) and the extra indirection is required.
                 *
                 * This implementation makes use of a JSR168 render parameter to
                 * communicate state between the resolver and the portlet. As both the
                 * portlet code and the resolver have access to the same java classes,
                 * they can use a java constant for the parameter name. This in turn
                 * makes long parameter names unnecessary (the parameter is already
                 * scoped to the portlet instance by the state handling API). Short
                 * parameter names in turn help to reduce the URL lengths.
                 */
                final PortletAccessorFactory portletFct = (PortletAccessorFactory) stateSvc
                                .getAccessorFactory(PortletAccessorFactory.class);
                final PortletAccessorController portletCtrl = portletFct
                                .getPortletAccessorController(portletWndId, stateCtrl);
                try {
                        // current set of render parameters that can be modified
                        final Map renderParams = portletCtrl.getParameters();
                        /*
                         * It's critical to remember that the values for the render
                         * parameter map are of type String[] per JSR168 spec (not just
                         * plain strings!).
                         */
                        renderParams.put(PARAM_URI, new String[] { pocURI.toString() });
                } finally {
                        // done
                        portletCtrl.dispose();
                }
        }

The constants PARAM_URI that appears in the code represents shared, internal knowledge between this resolver and the portlet implementation. As both are implemented in the same bundle however, this dependency is not fragile.

Implementing the portlet

The portlet needs to be aware of the way it receives its initialization parameters. In the simplest case the portlet is a JSR168 portlet and uses a JSR168 render parameter to receive the pocURI. In this case the portlet can just read the parameter via the standard JSR168 APIs and only needs to share the parameter name with the resolver (ideally via a java constant that represents a very short parameter name).

<%@ page import="com.ibm.wps.resolver.viewer.ViewerConstants" 
language="java"
    pageEncoding="UTF-8"%>
<div class="portlet-section-body">

<p>The portlet displays the render parameter <b>PARAM_URI</b> 
that has been initialized
by the <b>ViewerResolver</b>. </p>

<p>

URI = <%= request.getParameter(ViewerConstants.PARAM_URI) %>
</p>
</div>

The portlet does not need to implement any action semantics in this example, because the render parameter is just navigational state that can be transfered in render requests. A real portlet with more logic would now generate markup containing (render-)links that modify or enhance these render parameters.

Implementing the custom business component lookup

In general providers need to implement the com.ibm.portal.resolver.ResolutionService interface by defining a service handler in the Content Operations Registry infrastructure with the ID com.ibm.portal.resolver.ResolutionService. This service handler needs to be associated (via the locationTypeId with a content location factory that in turn is associated with a URI scheme.

The responsibility for the resolver implementation is to modify the navigational state such that it points to a page with a portlet that contains properly initialized initialization parameters.

Implementing a resolver from scratch

Using the CAI infrastructure

The CAI infrastructure greatly supports content providers in correctly implementing a resolver by maintaining mappings from business components to page/portlets. The CAI infrastructure provides a resolver that is associated width the cai URI scheme and that handles already many details of the state manipulation. CAI URIs have the format cai:<pocURI>. The CAI resolver has no dependency on the actual provider of the pocURI other than it needs to do a callback to pocURI specific resolution service to locate the page and portletWindowID based on the POC ID. The CAI resolver will then use these IDs to do the appropriate state manipulations automatically.

In order to do the page resolution CAI looks via the COR infrastructure for a service handler for the pocURI with the service id com.ibm.portal.resolver.cai.BusinessComponentService. The resulting service needs to implement the com.ibm.portal.resolver.cai.BusinessComponentService interface.

For most CAI based providers it will be simpler to determine the hosting business component ID than to find the target page and target portlet. So providers can implement he BusinessComponentService interface by first doing a lookup for the hosting business component ID and then dispatching to CAI's implementation of the BusinessComponentService interface that finds page and portlet based on the business component ID.

In this prototype code the business component lookup does not use any CAI APIs, yet. Instead it uses the pocURI scheme and just searches all deployed pages and all portlet windows until it finds a portlet window that has a title that matches the pocURI scheme. For the example implementation this means that in order to find the correct viewer portlet for the URI SampleProvider:test there needs to be a portlet installed on a page with a portlet title of SampleProvider.

This is how a simple business component lookup can be implemented

        public DomainObjectRef resolve(final URI uri, final Map params, final Context 
        ctx) throws ResolutionException {
                LOGGER.info("finding the page: uri = " + uri);
                try {
                        /*
                         * just chose a dummy type/id for the business component in this demo.
                         *
                         * This is the line of code that real components needs to override. These
                         * components need to do a lookup based on the uri to the type and the
                         * URI of a business component. Then they can use the same
                         * mechanism to dispatch to the business component lookup
                         *
                         */
                        final URI bcURI = BusinessComponentURI.newInstance("dummyType", " 
                        dummyID", uri);
                        LOGGER.info("dispatching: bcURI = " + bcURI);
                        // dispatch to the resolver for the BC URI
                        final ContentIntrospector ci = ctx.getContentIntrospector();
                        final ServiceHandler handler = ci.getServiceHandler(bcURI, 
                        BusinessComponentService.class.getName(), null, ctx);
                        LOGGER.info("handler = " + handler);
                        if (handler != null) {
                                // get the actual operation
                                final BusinessComponentService bizSvc = (BusinessComponentService) handler. 
                                getService(bcURI, ctx);
                                LOGGER.info("bizSvc = " + bizSvc);
                                // dispatch
                                return bizSvc.resolve(bcURI, params, ctx);
                        } else {
                                // TODO throw the correct exception
                                throw new ServiceNotFoundException("service not found");
                        }
                } catch (ResolutionException ex) {
                        throw ex;
                } catch (Exception ex) {
                        // TODO throw the correct exception
                        throw new ResolutionProxyException(null, ex);
                }
        }

In addition there needs to be a plugin.xml that is bundled with the lookup class that registers the business component lookup with the appropriate URI scheme (see the COR documentation for details).

<?xml version="1.0" encoding="UTF-8"?>
<plugin provider-name="%YOUR_PROVIDER_NAME%" version="1.0.0" 
name="%YOUR_PLUGIN_NAME%" id="%YOUR_PLUGIN_ID%">

  <extension point="com.ibm.content.operations.registry. 
  locationTypeContribution">
    <contentLocationType title="%YOUR_TITLE%"
        match.uri.scheme="%YOUR_URI_SCHEME%"
        class="%YOUR_CONTENT_LOCATION_FACTORY%"
        id="%YOUR_LOCATION_TYPE_ID%"/>

  </extension>
  <extension point="com.ibm.content.operations.registry. 
  locationServiceHandler">
    <serviceHandler
        class="%YOUR_HANDLER_IMPLEMENTATION_CLASS%"
        locationTypeId="%YOUR_LOCATION_TYPE_ID%"
        id="com.ibm.portal.resolver.cai.BusinessComponentService"/>

  </extension>
</plugin>

How to write a browser portlet?

Writing a browser portlet is fortunately very simple. The portlet needs to be a standard conformant portlet that can use a resolver specific tag library to easily generate its URLs.

In this prototype the portlet needs to ship the tag library on its own, but it can reference the tag implementation in the basic resolver infrastructure.

        <tag>

        <name>url</name>
        <tag-class>com.ibm.wps.resolver.tags.ResolvedUrlTag</tag-class>
        <body-content>JSP</body-content>
            <attribute>

            <name>uri</name>
            <required>true</required>
            <rtexprvalue>true</rtexprvalue>
            <type>java.lang.String</type>

        </attribute>
    </tag>

Currently the tag only supports the generation of late binding URLs.

The portlet would then just use this tag to create links to the pocURIs.

<%@ taglib uri="http://www.ibm.com/xmlns/prod/websphere/portal/v60/ 
resolver/browser" prefix="res" %>

<%@ page language="java" pageEncoding="UTF-8"%>

<div class="portlet-section-body">
<p>
Demo of late binding to the following URLs
</p>
<ul>

<li><a href='<res:url uri='cai:SampleProvider:test1'/>'> 
SampleProvider:test1</a></li>
<li><a href='<res:url uri='cai:SampleProvider:test2'/>'> 
SampleProvider:test2</a></li>
</ul>

</div>

Lookup of service URLs for URIs

Content handlers that feature custom servlets to expose their functionality via HTTP need to extend the resolver infrastructure to expose a mapping between URI and their custom URLs. It is possible to specify arbitrary mappings, including mappings to servlets in different web applications, different servers, etc Handlers provide a COR extension that is registered with the handler specific URI scheme. This extension must implement the com.ibm.portal.resolver.LookupService interface. It maps a URI and a parameter map to a com.ibm.portal.resolver.Lookup interface that in turn generates a JAXP source object. The contract for the XML stream represented by the source object is that it is a valid ATOM entry document for the URI/parameter combination. In case the lookup servlet is requested to only lookup one single URI, the XML source provided by the respective handler will potentially be streamed directly through to the client. In case the lookup servlet needs to lookup more than one URI in a single request, it will aggregate the individual feeds of ATOM entries from multiple handlers into one single ATOM feed.

Tag API for the resolver infrastructure

The following tags can be used to generate URLs that result in a resolution step to a URI. The namespace of the taglibrary is http://www.ibm.com/xmlns/prod/websphere/portal/v6.0.1/resolver.
Tag resolvedURL
required uri URI of the piece of content to be addressed
optional allowRelativeURL If set to "true", the generation of relative URLs is explicitly allowed (though not enforced). If set to "false", no relative URLs will be generated. If missing, the server default setting for the generation of relative URLs will be used.
optional keepNavigationalState If set to "true" the URLs contain the current navigational state. If set to "false" the URLs will be generated without navigational state. If missing the URLs will contain navigational state if possible.
optional mode Specification of the way the system should interact with the URI. Default modes are VIEW, DOWNLOAD and LOOKUP.
optional escapeXml If set to "true" the URLs will be markup escaped. Per default this value is set to "true".
optional protected If set to "true" the URLs will point to the protection version of the target servlet, if set to "false" they point to the unprotected version, if missing the URLs point to the same flavor of the target servlet as the current request.
optional secure If set to "true" the URLs will use HTTPS as the protocol, if set to "false" they use HTTP, if missing the URLs point to the same flavor of the target servlet as the current request.
Tag urlParam To be used in the body of the resolvedURL tag.
required name Name of the parameter to attach to the URL. If there was already a parameter with the given name, the values will be merged.
required value Value of the parameter to attach to the URL. If the value is of type Object[] the array entries will be converted to String and appended to the list of parameters for the given key. In all other cases the value is converted to a String and appended.