Overriding MVC Commands

MVC Commands are used to break up the controller layer of a Liferay MVC application into smaller, more digestible code chunks.

Sometimes you’ll want to override an MVC command, whether it’s in a Liferay application or another Liferay MVC application whose source code you don’t own. Since MVC commands are components registered in the OSGi runtime, you can simply publish your own component, and give it a higher service ranking. Your MVC command will then be invoked instead of the original one.

The logical way of breaking up the controller layer is to do it by portlet phase. The three MVC command classes you can override are

  • MVCActionCommand: An interface that allows the portlet to process a particular action request.
  • MVCRenderCommand: An interface that handles the render phase of the portlet.
  • MVCResourceCommand: An interface that allows the portlet to serve a resource.

Find more information about implementing each of these MVC command classes in the tutorials on Liferay MVC Portlets. Here we’re going to focus on overriding the logic contained in existing MVC commands.

Start by learning to override MVCRenderCommand. The process will be similar for the other MVC commands.

Overriding MVCRenderCommand

You can override MVCRenderCommand for any portlet that uses Liferay’s MVC framework and publishes an MVCRenderCommand component.

For example, Liferay’s Blogs application has a class called EditEntryMVCRenderCommand, with this component:

@Component(
    immediate = true,
    property = {
        "javax.portlet.name=" + BlogsPortletKeys.BLOGS,
        "javax.portlet.name=" + BlogsPortletKeys.BLOGS_ADMIN,
        "javax.portlet.name=" + BlogsPortletKeys.BLOGS_AGGREGATOR,
        "mvc.command.name=/blogs/edit_entry"
    },
    service = MVCRenderCommand.class
)

This MVC render command can be invoked from any of the portlets specified by the javax.portlet.name parameter, by calling a render URL that names the MVC command.

<portlet:renderURL var="addEntryURL">
	<portlet:param name="mvcRenderCommandName" value="/blogs/edit_entry" />
	<portlet:param name="redirect" value="<%= viewEntriesURL %>" />
</portlet:renderURL>

What if you want to override the command, but not for all of the portlets listed in the original component? In your override component, just list the javax.portlet.name of the portlets where you want the override to take effect. For example, if you want to override the /blogs/edit_entry MVC render command just for the Blogs Admin portlet (the Blogs Application accessed in the site administration section of Liferay), your component could look like this:

@Component(
  immediate = true,
  property = {
     "javax.portlet.name=" + BlogsPortletKeys.BLOGS_ADMIN,
     "mvc.command.name=/blogs/edit_entry",
     "service.ranking:Integer=100"
  },
  service = MVCRenderCommand.class
)

Note the last property listed, service.ranking. It’s used to tell the OSGi runtime which service to use, in cases where there are multiple components registering the same service, with the same properties. The higher the integer you specify here, the more weight your component carries. In this case, the override component will be used instead of the original one, since the default value for this property is 0.

After that, it’s up to you to do whatever you’d like. You can add logic to the existing render method or redirect to an entirely new JSP.

Adding Logic to an Existing MVC Render Command

Don’t copy the existing logic from the MVC render command into your override command class. This unnecessary duplication of code that makes maintenance more difficult. If you want to do something new (like set a request attribute) and then execute the logic in the original MVC render command, obtain a reference to the original command and call its render method like this:

@Override
public String render(RenderRequest renderRequest, 
                    RenderResponse renderResponse) throws PortletException {

    //Do something here

	return mvcRenderCommand.render(renderRequest, renderResponse);
}

@Reference(target = 
      "(component.name=com.liferay.blogs.web.internal.portlet.action.EditEntryMVCRenderCommand)")
  protected MVCRenderCommand mvcRenderCommand;
}

Sometimes, you might need to redirect the request to an entirely new JSP that you’ll place in your command override module.

Redirecting to a New JSP

If you want to render an entirely new JSP, the process is different.

The render method of MVCRenderCommand returns the path to a JSP as a String. The JSP must live in the original module, so you cannot simply specify a path to a custom JSP in your override module. You need to make the method skip dispatching to the original JSP altogether, by using the MVC_PATH_VALUE_SKIP_DISPATCH constant from the MVCRenderConstants class. Then you need to initiate your own dispatching process, directing the request to your JSP path. Here’s how that might look in practice:

public class CustomEditEntryMVCRenderCommand implements MVCRenderCommand {

    @Override
    public String render
        (RenderRequest renderRequest, RenderResponse renderResponse) throws
        PortletException {

        System.out.println("Rendering custom_edit_entry.jsp");

        RequestDispatcher requestDispatcher =
            servletContext.getRequestDispatcher("/custom_edit_entry.jsp");

        try {
            HttpServletRequest httpServletRequest = 
                PortalUtil.getHttpServletRequest(renderRequest);
            HttpServletResponse httpServletResponse = 
                PortalUtil.getHttpServletResponse(renderResponse);

            requestDispatcher.include
                (httpServletRequest, httpServletResponse);
        } catch (Exception e) {
            throw new PortletException
                ("Unable to include custom_edit_entry.jsp", e);
        }

        return MVCRenderConstants.MVC_PATH_VALUE_SKIP_DISPATCH;
    }

    @Reference(target = "(osgi.web.symbolicname=com.custom.code.web)")
    protected ServletContext servletContext;

}

In this approach, there’s no reference to the original MVC render command because the original logic isn’t reused. Instead, there’s a reference to the servlet context of your module, which is needed to use the request dispatcher.

A servlet context is automatically created for portlets. It can be created for other modules by including the following line in your bnd.bnd file:

Web-ContextPath: /custom-code-web

Once we have the servlet context we just need to dispatch to the specific JSP in our own module.

Overriding MVCActionCommand

You can override MVC action commands using a similar process to the one presented above for MVC render commands. Again, you’ll register a new OSGi component with the same properties, but with a higher service ranking. This time the service you’re publishing is MVCActionCommand.class.

For MVC action command overrides, extend the BaseMVCActionCommand class, and the only method you’ll need to override is doProcessAction, which must return void.

As with MVC render commands, you can add your logic to the original behavior of the action method by getting a reference to the original service, and calling it after your own logic. Here’s an example of an MVCActionCommand override that checks whether the delete action is invoked on a blog entry, and prints a message to the log, before continuing with the original processing:

@Component(
    property = { 
        "javax.portlet.name=" + BlogsPortletKeys.BLOGS_ADMIN, 
        "mvc.command.name=/blogs/edit_entry",
        "service.ranking:Integer=100" 
    }, 
    service = MVCActionCommand.class)
public class CustomBlogsMVCActionCommand extends BaseMVCActionCommand {

    @Override
    protected void doProcessAction
        (ActionRequest actionRequest, ActionResponse actionResponse) 
        throws Exception {

        String cmd = ParamUtil.getString(actionRequest, Constants.CMD);

        if (cmd.equals(Constants.DELETE)) {
            System.out.println("Deleting a Blog Entry");
        }

        mvcActionCommand.processAction(actionRequest, actionResponse);
    }

    @Reference(
        target = "(component.name=com.liferay.blogs.web.internal.portlet.action.EditEntryMVCActionCommand)")

    protected MVCActionCommand mvcActionCommand;

}

It’s straightforward to override MVC action commands while keeping your code decoupled from the original action methods. You can also override MVC resource commands.

Overriding MVCResourceCommand

There are fewer uses for overriding MVC resource commands, but it can also be done.

The process is similar to the one described for MVCRenderCommand and MVCActionCommand. There’s a couple things to keep in mind:

  • The service to specify in your component is MVCResourceCommand.class

  • As with overriding MVCRenderCommand, there’s no base implementation class to extend. You’ll implement the interface yourself.

  • Keep your code decoupled from the original code by adding your logic to the original MVCResourceCommand’s logic by getting a reference to the original and returning a call to its serveResource method:

      return mvcResourceCommand.serveResource(resourceRequest, resourceResponse);
    

The following example overrides the behavior of com.liferay.login.web.portlet.action.CaptchaMVCResourceCommand, from the login-web module of the Login portlet. It simply prints a line in the console then executes the original logic: returning the Captcha image for the account creation screen.

@Component(
    property = { 
        "javax.portlet.name=" + LoginPortletKeys.LOGIN,
        "mvc.command.name=/login/captcha" }, 
    service = MVCResourceCommand.class)
public class CustomCaptchaMVCResourceCommand implements MVCResourceCommand {

    @Override
    public boolean serveResource
        (ResourceRequest resourceRequest, ResourceResponse resourceResponse) {

        System.out.println("Serving login captcha image");

        return mvcResourceCommand.serveResource(resourceRequest, resourceResponse);
    }

    @Reference(target = "(component.name=com.liferay.login.web.internal.portlet.action.CaptchaMVCResourceCommand)")
    protected MVCResourceCommand mvcResourceCommand;

}

And that, as they say, is that. Even if you don’t own the source code of an application, you can override its MVC commands just by knowing the component class name.

« Overriding Portal Properties using a HookOverriding lpkg files »
Este artigo foi útil?
Utilizadores que acharam útil: 0 de 0