Portlet Servant API

Portlet servant communication

Overview

The portlet servant API makes it possible to reuse portlet business logic within another portlet. A client-portlet gets a reference to a portlet servant proxy and calls its methods to incorporate the servant's content or exchange parameters/data. As soon as the inter-portlet communication gets standardized the concept of the portlet servant will probably reconsidered. At the moment the implemenation of the portlet servant API is container specific.

Portlet servant

If the portlet has important functions that could likely be reused by some other portlets it makes sense to use the servant mode. Based on the information received with the PortletRequest such portlets can distinguish the normal modes like VIEW,EDIT,HELP from the "servant" and perform different operations to interact with the client-portlet. To provide portlet-servant communication on the servant side ServantUtil class is used. In the following example the groups manager portlet gets the request attribute sent by the client-portlet in the servant mode:

GroupsManagerPortlet.java
if ( ServantUtil.isServantMode(portletRequest) ) {
   IGroupMember[] groupMembers = (IGroupMember[]) ServantUtil.getAttribute(portletRequest);

   ...

   if ( "Done".equals(request.getParameter("grpCommand") ) {
     // Return result to clients
     ServantUtil.setAttribute(req,getResults(portletRequest)); 
     // Notify clients that the servant is complete
     ServantUtil.setServantDone(portletRequest);
   }
}

Portlet servant locator

The portlet servant locator provides the client-portlet with the reference to IPortletServant instance, internally getting the portlet definition from the definition registry by a functional name and keeping track of client changes to the servant in the portlet session. The client-portlet obtains a reference to the locator by calling the static method of ServantLocatorAccessor utility-class.

Running portlet servant from another portlet

The typical client-servant interaction from the client's point of view is shown below. Every time when the portlet's processAction() or render() is called the client has to get the reference to the IPortletServantLocator instance, then call locate() to return the instance of the IPortletServant and delegate the request processing to the servant:

TestServantPortlet.java
public class TestServantPortlet extends GenericPortlet {
    
/**
 * The internal method that returns the IPortletServant instance.
 */
private IPortletServant getPortletServant(String servantFname, PortletRequest request) {
    IPortletServantLocator servantLocator = ServantLocatorAccessor.getPortletServantLocator(getPortletContext());
    IPortletServant servant = servantLocator.locate(servantFname,request);
    return servant;
}
    
/**
 *  The implemenation of GenericPortlet's processAction() with delegating the action request processing to the servant.
 */
public void processAction(ActionRequest req, ActionResponse res) throws PortletException, IOException {
    
            // Get the servant functional name
            String servantFname = ...
            if ( servantFname != null ) {
               IPortletServant servant = getPortletServant(servantFname,req);
               servant.processAction(req,res);
               if ( servant.isComplete(req) ) {
                  // Get result when the servant is done
                  Object result = servant.getServantData(req);
                  ...
               }
            } 
}

...

}

Portlet servant API

Servant interfaces

ServantLocatorAccessor.java
/**
 * Provides methods for accessing the portlet servant locator
 */
public final class ServantLocatorAccessor {

    /**
     * Creates a new IPortletServantLocator instance that will be used to locate portlet servants.
     **/
    public static IPortletServantLocator getPortletServantLocator(PortletContext portletContext) {
       return ((PortletContextWrapper)portletContext).getPortletServantLocator();
    }

    private ServantLocatorAccessor() { }
}
IPortletServantLocator.java
/**
 * Provides methods for locating portlet servants provided by uPortal.
 */
public interface IPortletServantLocator {

    /**
     * Gets a new IPortletServant instance for running the specified portlet as a servant.
     * @param functionalPortletName is a portlet functional name given during portlet publising
     * @param request a PortletRequest
     **/
    public IPortletServant locate(String functionalPortletName,PortletRequest request);

}
IPortletServant.java
/**
 * The PortletServant interface is a uPortal wrapper around a JSR-168 portlet that implements
 * the Portlet Servant mode. It provides methods for delegating rendering and action processing
 * to the servant portlet, exchanging attribute values with the Client Portlet, to watch for it's completion and to get the results of the servant's
 * operation.
 */
public interface IPortletServant {

    /**
     * Returns the servant ID given by the portlet servant locator.
     * @return a <code>String</code> value
     */
    public String getId();
    
    /** Allows the Client Portlet to ascertain if the Servant has accomplished the requested task 
     *  (Note that the way which a certain task is requested is not specified by this interface; 
     *  normally it will be documented by a particular IPortletServant and require some particular 
     *  configuration paramaters used to initialize the servant)
     *  @return boolean value
     */    
    public boolean isComplete(PortletRequest request);
    
    public Object getServantData(PortletRequest request);

    public void setServantData(PortletRequest request,Object value);

    public void render(RenderRequest request, RenderResponse response) throws PortletException, IOException;
    
    public void processAction(ActionRequest request, ActionResponse response) throws PortletException, java.io.IOException;
}

Servant-side utilities

ServantUtil.java
/**
 * The utility class that provides a portlet with the servant-side methods methods. 
 * 
 * @author Michael Ivanov, mivanov at unicon.net
 * @version $Revision: 1.1 $
 *
 */
public final class ServantUtil {
    
    public static boolean isServantMode(PortletRequest request) {
        return (InternalServantUtil.getServantId(request)!=null);
    }
    
    public static boolean isServantMode(HttpServletRequest request) {
        return (InternalServantUtil.getServantId(request)!=null);
    }
    
    public static void setAttribute(PortletRequest request, Object value) {
        InternalServantUtil.setAttribute(InternalServantUtil.getServantId(request),request,value);
    }
    
    public static Object getAttribute(PortletRequest request) {
        return InternalServantUtil.getAttribute(InternalServantUtil.getServantId(request),request);
    }
    
    public static void setServantDone(PortletRequest request) {
       request.setAttribute(InternalServantUtil.getServantDoneKey(InternalServantUtil.getServantId(request)),"true");
    }
    
    private ServantUtil() {}
}
InternalServantUtil.java
/**
 * 
 * The internal class containing static methods used by the framework to provide client-servant communication .
 * 
 * @author Michael Ivanov, mivanov at unicon.net
 * @version $Revision: 1.4 $
 *
 */
public final class InternalServantUtil {
    
    private static final String PORTLET_SERVANT_DONE =        "javax.portlet.servant.done.";
    private static final String PORTLET_SERVANT_ATTRIBUTE =   "javax.portlet.servant.attribute.";
    private static final String SERVANT_ID_PARAMETER_NAME =   "javax.portlet.servant.id";
    
    public static String getServantId(PortletRequest request) {
        return request.getParameter(SERVANT_ID_PARAMETER_NAME);
    }
    
    public static String getServantId(HttpServletRequest request) {
        return request.getParameter(SERVANT_ID_PARAMETER_NAME);
    }
   
    private static String getServantAttributeKey(String servantId) {
        return PORTLET_SERVANT_ATTRIBUTE + servantId;
    }
    
    protected static String getServantDoneKey(String servantId) {
        return PORTLET_SERVANT_DONE + servantId;
    }

    protected static void setAttribute(String servantId, PortletRequest request, Object value) {
        request.setAttribute(getServantAttributeKey(servantId),value);
    }
    
    protected static Object getAttribute(String servantId, PortletRequest request) {
        return request.getAttribute(getServantAttributeKey(servantId));
    }
    
    public static boolean isServantDone(String servantId, ServletRequest request) {
        return (request.getAttribute(getServantDoneKey(servantId))!=null);
    }
     
    public static boolean isServantDone(String servantId, PortletRequest request) {
         return (request.getAttribute(getServantDoneKey(servantId))!=null);
    }
    
    public static HttpServletRequest prepareServantServletRequest(String servantId, HttpServletRequest request) {
        PortletHttpServletRequestWrapper wrappedRequest = new PortletHttpServletRequestWrapper(request);
        Map requestParams = new HashMap();
        if ( request.getParameterMap() != null ) {
           requestParams.putAll(request.getParameterMap());
        }
        requestParams.put(SERVANT_ID_PARAMETER_NAME,new String[] {servantId});
        wrappedRequest.setParameterValueMap(requestParams);
        return wrappedRequest;
    }
    
    private InternalServantUtil() {}

}