MVCRenderCommand as default

So, I heard some people like MVC commands so much they try to make it for their default view. Normally, when I see this topic they are looking for a way to keep their portlet classes short and have consistency with regards to where the different views are coded and how. (of course I am using the term view rather informally).

Just to put some points in order before going further. We have several ways to implement a portlet, normally, the portlet main class is acting like a controller while you have JSPs to be rendered and actions are executed by the portlet action methods, or delegated to action commands. Still a simplified view, but enough to get to the part we will discuss in this post. 

(I will be using the word portlet to mean the class that declares it as a OSGi component or the portlet as a whole, I will let you know when it is not evident)

Let's say you have a portlet and you want it to be displayed, the most common flow if you are using Liferay's MVC framework is to define a JSP as the default view, while you can use the portlet's render and doView methods to guide the rendering process. render and doView will both execute, as the portlet needs to first prepare itself to be rendered before passing to the desired mode. Most people do not care for the other modes, so those methods in some cases are interchangeable. 

( I am getting to the point as fast as I can...)

In this simplified view, until now we have: The render method will execute, then the doView. Simple enough, but when complexity grows, one might want to code extra logic for rendering, and this is where the MVC render commands come into play. Each command in its own class, each one registering itself and being able to grow freely. As they register themselves it is easy to have as many as you want, you just keep creating them.

Let's suppose now you are in a process where you are creating render and action commands for your use cases, but something starts to bother you: your controller has its logic implemented in different ways, some of the render logic is in your main portlet class and some pieces are in command classes. Further, the logic that is in your main class is affecting your commands as its render method executes anyway. You then realize you can use the portlet's class to hold a common piece of code while all the rest is left to commands. Except for the default view, which is still in your doView method. 

Of course, each case is one, and each architect is one. I will show you a simple way to have your command executed as the default, if you wish to do so.

First case is the simplest, you have a panel app. Some people forget that the PanelApp is the one responsible for generating the URL used by the side menu.


@Component( immediate = true,
         property = {
               "panel.app.order:Integer=100",
               "panel.category.key=" + EXAMPLE_PORTLET_SITE_ADMINISTRATION,
         },
         service = PanelApp.class )
public class ExamplePanelApp extends BasePanelApp {

   @Override
   public PortletURL getPortletURL( HttpServletRequest request ) throws PortalException {

      PortletURL url = super.getPortletURL( request );
      url.setParameter( "mvcRenderCommandName", RENDER_COMMAND_EXAMPLE);
      return url;
   }

   @Override
   @Reference( target = "(javax.portlet.name=" + EXAMPLE_PORTLET + ")" )
   public void setPortlet( Portlet portlet ) {

      super.setPortlet( portlet );
   }

   @Override
   public String getPortletId( ) {

      return EXAMPLE_PORTLET;
   }

}
    

Ok, this is just changing where you land if you have a panel app. More interrestly, though, is that one can use the render method to check if a MVC command was requested as parameter, or if a MVCPath was sent.

Let's define for that goal two methods, one will be our regular render method and the second one a private overload that accepts a default render command name to call in case none is given.


@Override
public void render( RenderRequest request, RenderResponse response ) throws IOException, PortletException {
   render( request, response, RENDER_COMMAND_DEFAULT );
}

private void render( RenderRequest request, RenderResponse response, String defaultMvcRenderCommand ) throws PortletException, IOException {

   String mvcPath = ParamUtil.getString( request, "mvcPath" );
   Optional< String > mvcRenderCommand = fetchParameterString( request, "mvcRenderCommandName" );

   if ( PortletMode.VIEW.equals( request.getPortletMode( ) ) && !mvcRenderCommand.isPresent( ) && isNull( mvcPath ) ) {
      MVCCommandCache renderCache = getRenderMVCCommandCache( );
      MVCRenderCommand command = ( MVCRenderCommand ) renderCache.getMVCCommand( defaultMvcRenderCommand );

      mvcPath = command.render( request, response );
      request.setAttribute( getMVCPathAttributeName( response.getNamespace( ) ), mvcPath );
   }

   super.render( request, response );
}

One will notice this is more like a teaching example, but it has a couple of interesting facts here. First, will chose to execute our logic only if we target the view mode and we check if when executing for this mode, if a command or path was defined. If they were, we do not need the default. Otherwise, by calling the getMVCPathAttributeName method, we are telling liferay that we want this command to be executed.

The mode needs to be tested, as the portlet will pass through the render method before dispatching to any available and requested mode. The render will also be executed before the render commands, which will allow you to code common logic in your regular render method.


@Override
public void render( RenderRequest request, RenderResponse response ) throws IOException, PortletException {
   // Common logic
   render( request, response, RENDER_COMMAND_DEFAULT );
}

@Override
public void doEdit( RenderRequest request, RenderResponse response ) throws IOException, PortletException {

   super.doEdit( request, response );
}

@Override
public void doView( RenderRequest request, RenderResponse response ) throws IOException, PortletException {

   super.doView( request, response );
}

Some would suggest taking advantage of the "/" test inside Liferay's source code that allows us to define a MVC command named "/" but this is kind of awkward, especially if all your commands have a more descriptive prefix before the actual command name, while the name is always descriptive. It is also awkward if you are following an internal naming convention that "/" breaks. 

There you go, it was an informal and short explanation but my goal was to show that the panel app and the render commands are more flexible than they might look, and if you find that you need to put all your render logic in render commands, you can do so.

More Blog Entries

0 Comments