Portlet Lifecycle

The goal of this chapter is not to talk about the Portlet API specification lifecycle but more about eXo UI framework to easily develop portlets.

The web framework used here has been completely developed by eXo and perfectly suits the portal environment, it even allows to send events from the portlet UIComponents to the Portal ones.

Of course using the eXo web framework to build portlet is not necessary and any other web framework that supports portlet environemnt can be used, but all eXo portlets that are part of eXo products are developped using that framework and we provide several UI components that can be used in different abstracted context such as the portal itself or some portlets.

This chapter is not a tutorial on how to write portlets, it will go in the details of the code implementation and logic; hence it is intended for advanced developers. It is also advised to have read the Portal Lifecycle chapter before as the concept are similar and mainly top hierarchy classes are shared and were explained in this other chapter

Portlet init

The main entry point for configuring a portlet is in the portlet.xml file located in the portlet application WAR. Every portlet that will be build using the eXo web framework must reference the PortletApplicationController. The portlet configuration such as the root component is then defined in a configuration.xml file and the path is defined in a porlet.xml init-param.

<portlet>
    <description xml:lang="EN">Content Portlet</description>
    <portlet-name>ContentPortlet</portlet-name>
    <display-name xml:lang="EN">Content Portlet</display-name>
    <portlet-class>org.exoplatform.webui.application.portlet.PortletApplicationController</portlet-class>    
    
    <init-param>
      <name>webui.configuration</name>
      <value>/WEB-INF/conf/portlet/content/ContentPortlet/webui/configuration.xml</value>
    </init-param>

The structure of the configuration.xml file is exactly the same as the one we have already introduced in the Portal Lifecycle chapter. in the case of the content portlet it looks like:

<webui-configuration>
  <application> 
    <ui-component-root>org.exoplatform.content.webui.component.UIContentPortlet</ui-component-root>
    <state-manager>org.exoplatform.webui.application.portlet.ParentAppStateManager</state-manager>
  </application>
</webui-configuration>

The PortletApplicationController class extends the GenericPortlet class defined in the Portlet API specification.

All methods like processAction() or render() are delegated to the PortletApplication. The creation and caching inside the WebController object is described in the following method:

/**
   * try to obtain the PortletApplication from the WebAppController.
   * 
   * If it does not exist a new PortletApplication object is created, init and cached in the
   * controller
   */
  private PortletApplication getPortletApplication() throws Exception {
    PortalContainer container = PortalContainer.getInstance() ;
    WebAppController controller = 
      (WebAppController)container.getComponentInstanceOfType(WebAppController.class) ;
    PortletApplication application = controller.getApplication(applicationId_) ;
    if(application == null) {
      application = new PortletApplication(getPortletConfig()) ;
      application.onInit() ; 
      controller.addApplication(application) ;
    }
    return application ;
  }

Portlet request handle

When a portlet, deployed in eXo Portal, is using the eXo web framework then all methods call go through the PortletApplication object which extends the WebuiApplication one.

ProcessAction phase

The code of the method in PortletApplication is described here. The business logic is shown in the javadoc:

/**
   * The processAction() method is the one modelled according to the Portlet API specification
   * 
   * The process is quite simple and here are te different steps done in the method:
   * 
   * 1) The current instance of the WebuiRequestContext (stored in a ThreadLocal in the class) is referenced
   * 2) A new request context of type PortletRequestContext (which extends the class WebuiRequestContext) is
   *    created as a child of the current context instance
   * 3) The new context is place inside the ThreadLocal and hence overides its parent one there, 
   *    only for the portlet request lifeciclye
   * 4) The method onStartRequest() is called in all the ApplicationLifecycle objects referenced in the webui 
   *    configuration XML file
   * 5) The StateManager object (in case of portlet it is an object of type ParentAppStateManager) is used to get the RootComponent
   *    also referenced in the XML configuration file
   * 6) The methods processDecode(UIApplication, WebuiRequestContext) and processAction(UIApplication, WebuiRequestContext) 
   *     are then called 
   * 7) Finally, a flag, to tell that the processAction phase was done, in the context is set to true and the parent
   *    context is restored in the Threadlocal
   */
  public void processAction(ActionRequest req, ActionResponse res) throws Exception {
    WebuiRequestContext parentAppRequestContext =  WebuiRequestContext.getCurrentInstance() ;
    PortletRequestContext context = createRequestContext(req, res, parentAppRequestContext)  ;
    WebuiRequestContext.setCurrentInstance(context) ;
    try {      
      for(ApplicationLifecycle lifecycle : getApplicationLifecycle())  {
        lifecycle.onStartRequest(this, context) ;
      } 
      UIApplication uiApp = getStateManager().restoreUIRootComponent(context) ;
      context.setUIApplication(uiApp) ;
      processDecode(uiApp, context) ;
      if(!context.isResponseComplete() && !context.getProcessRender()) {
        processAction(uiApp, context) ;
      }
    } finally {
      context.setProcessAction(true) ;
      WebuiRequestContext.setCurrentInstance(parentAppRequestContext) ;
    }
  }

The PortletRequestContext extends WebuiRequestContext class and acts as a wrapper on top of all the portlet request information

/**
   * In this method we try to get the PortletRequestContext object from the attribute map of the parent 
   * WebuiRequestContext. 
   * 
   * If it is not cached then we create a new instance, if it is cached then we init it with the correct
   * writer, request and response objects
   * 
   * We finally cache it in the parent attribute map
   * 
   */
  private PortletRequestContext createRequestContext(PortletRequest req, PortletResponse res,
                                                    WebuiRequestContext parentAppRequestContext) throws IOException {
    String attributeName = getApplicationId() + "$PortletRequest" ;
    PortletRequestContext context = 
      (PortletRequestContext) parentAppRequestContext.getAttribute(attributeName) ;
    Writer w  = null ;       
    if(res instanceof  RenderResponse){
      RenderResponse renderRes = (RenderResponse)res;
      renderRes.setContentType("text/html; charset=UTF-8");      
      w = renderRes.getWriter() ; 
    }
    if(context != null) {
      context.init(w, req, res) ;
    } else {
      context =  new PortletRequestContext(this, w, req, res) ;
      parentAppRequestContext.setAttribute(attributeName, context) ;
    }
    context.setParentAppRequestContext(parentAppRequestContext) ;
    return context;
  }

In the PortletApplication, the line UIApplication uiApp = getStateManager().restoreUIRootComponent(context) ; asks the StateManeger defined for the portlet to get the UI root component. In the case of a portlet the root component must extend UIPortletApplication.

public class ParentAppStateManager extends StateManager {
  
  /**
   * This method simply delegate the call to the same method of the parent WebuiRequestContext
   */
  @SuppressWarnings("unchecked")
  public UIApplication restoreUIRootComponent(WebuiRequestContext context) throws Exception {
    WebuiRequestContext pcontext = (WebuiRequestContext)  context.getParentAppRequestContext() ;
    return pcontext.getStateManager().restoreUIRootComponent(context) ;
  }

Hence this is the PortalStateManager that will also handle the extraction of the root component.

public UIApplication restoreUIRootComponent(WebuiRequestContext context) throws Exception {
    context.setStateManager(this) ;
    WebuiApplication app  = (WebuiApplication)context.getApplication() ;
    
    /*
     * If the request context is of type PortletRequestContext, we extract the parent context which will
     * allow to get access to the PortalApplicationState object thanks to the session id used as the key for the
     * syncronised Map uiApplications
     */
    if(context instanceof PortletRequestContext) {
      WebuiRequestContext preqContext = (WebuiRequestContext) context.getParentAppRequestContext() ;
      PortalApplicationState state = uiApplications.get(preqContext.getSessionId()) ;
      PortletRequestContext pcontext = (PortletRequestContext) context ;
      String key =  pcontext.getApplication().getApplicationId() ;
      UIApplication uiApplication =  state.get(key) ;
      if(uiApplication != null)  return uiApplication;
      synchronized(uiApplications) {
        ConfigurationManager cmanager = app.getConfigurationManager() ;
        String uirootClass = cmanager.getApplication().getUIRootComponent() ;
        Class type = Thread.currentThread().getContextClassLoader().loadClass(uirootClass) ;
        uiApplication = (UIApplication)app.createUIComponent(type, null, null, context) ;     
        state.put(key, uiApplication) ;
      }
      return uiApplication ;
    }

Render phase

The render method business logic is quite similar to the processAction() one.

/**
   * The render method business logic is quite similar to the processAction() one.
   * 
   * 1) A PortletRequestContext object is created (or extracted from the cache if it already exists) 
   *    and initialized
   * 2) The PortletRequestContext replaces the parent one in the WebuiRequestContext ThreadLocal object
   * 3) If the portal has already called the portlet processAction() then the call to all onStartRequest of
   *    the ApplicationLifecycle has already been made, otherwise we call them
   * 4) The ParentStateManager is also used to get the UIApplication, as we have seen it delegates the call 
   *    to the PortalStateManager which caches the UI component root associated with the current application
   * 5) the processRender() method of the UIPortletApplucaton is called
   * 6) Finally, the method onEndRequest() is called on every ApplicationLifecycle referenced in the portlet
   *    configuration XML file and the parent WebuiRequestContext is restored
   *    
   */
  public  void render(RenderRequest req,  RenderResponse res) throws Exception {    
    WebuiRequestContext parentAppRequestContext =  WebuiRequestContext.getCurrentInstance() ;
    PortletRequestContext context = createRequestContext(req, res, parentAppRequestContext)  ;
    WebuiRequestContext.setCurrentInstance(context) ;
    try {
      if(!context.hasProcessAction()) {
        for(ApplicationLifecycle lifecycle : getApplicationLifecycle())  {
          lifecycle.onStartRequest(this, context) ;
        }
      }      
      UIApplication uiApp =  getStateManager().restoreUIRootComponent(context) ;
      context.setUIApplication(uiApp) ;
      if(!context.isResponseComplete()) {
        UIPortletApplication uiPortletApp = (UIPortletApplication)uiApp;
        uiPortletApp.processRender(this, context) ;
      }
      uiApp.setLastAccessApplication(System.currentTimeMillis()) ;
    } finally {
      try {
        for(ApplicationLifecycle lifecycle :  getApplicationLifecycle()) {
          lifecycle.onEndRequest(this, context) ;
        }
      } catch (Exception exception){
    	log.error("Error while trying to call onEndRequest of the portlet ApplicationLifecycle", 
    		exception);
      }
      WebuiRequestContext.setCurrentInstance(parentAppRequestContext) ;
    }
  }

The processRender() call made on the UIPortletApplication is shown now:

/**
   * The default processRender for an UIPortletApplication handles two cases:
   * 
   *   A. Ajax is used 
   *   ---------------
   *     If Ajax is used and that the entire portal should not be re rendered, then an AJAX fragment is 
   *     generated with information such as the portlet id, the portlet title, the portlet modes, the window 
   *     states as well as the HTML for the block to render
   *   
   *   B. A full render is made
   *   ------------------------
   *      a simple call to the method super.processRender(context) which will delegate the call to all the 
   *      Lifecycle components
   *   
   */
  public void  processRender(WebuiApplication app, WebuiRequestContext context) throws Exception {
    WebuiRequestContext pContext = (WebuiRequestContext)context.getParentAppRequestContext();
    ExoPortletRequest renderRequest = (ExoPortletRequest) context.getRequest();
    WindowID windowID = renderRequest.getWindowID();
    if(context.useAjax() && !pContext.getFullRender()) {
      Writer w =  context.getWriter() ;
      w.write("<div class=\"PortletResponse\" style=\"display: none\">") ;
      w.  append("<div class=\"PortletResponsePortletId\">"+windowID.getUniqueID()+"</div>") ;
      w.  append("<div class=\"PortletResponsePortletTitle\"></div>") ;
      w.  append("<div class=\"PortletResponsePortletMode\"></div>") ;
      w.  append("<div class=\"PortletResponsePortletState\"></div>") ;
      w.  append("<div class=\"PortletResponseData\">") ;
      List<UIComponent> list = context.getUIComponentToUpdateByAjax() ;
      if(list == null) list = app.getDefaultUIComponentToUpdateByAjax(context) ;      
      for(UIComponent uicomponent : list) {
        renderBlockToUpdate(uicomponent, context, w) ;
      }
      w.  append("</div>") ;
      w.  append("<div class=\"PortletResponseScript\"></div>") ;
      w.write("</div>") ;
    }  else {
      super.processRender(context) ;
    }
    
  }


Creator: Benjamin Mestrallet on 2007/08/19 10:56
Copyright (c) 2000-2009. Allright reserved - eXo platform SAS
1.6.13286