Overriding and adding struts actions from hook plugins

This is a new cool feature I worked on with Brian and it’s coming on 6.1 as well as 6.0 EE SP2 and 5.2 EE SP6. With this feature you can add new struts actions to portal from a hook plugin and you can override any existing action with it.

There are two interfaces com.liferay.portal.kernel.struts.StrutsAction and com.liferay.portal.kernel.struts.StrutsPortletAction. The StrutsAction is used for regular struts actions like /c/portal/update_password and StrutsPortletAction is used for those that are used from portlets.

Let’s create a new simple hook to test it out. This hook will create a new struts path /c/portal/sample and wraps an existing struts action. Start by creating a new hook plugin in your plugins SDK. I’ll call it sample-struts-action.

./create.sh sample-struts-action

Next edit the liferay-hook.xml and add following fragment:

<?xml version="1.0"?>
<!DOCTYPE hook PUBLIC "-//Liferay//DTD Hook 6.1.0//EN" "http://www.liferay.com/dtd/liferay-hook_6_1_0.dtd">

<hook>
	<portal-properties>portal.properties</portal-properties>
	<custom-jsp-dir>/META-INF/custom_jsps</custom-jsp-dir>
	<struts-action>
		<struts-action-path>/portal/sample</struts-action-path>
		<struts-action-impl>com.liferay.samplestrutsaction.hook.action.SampleStrutsAction</struts-action-impl>
	</struts-action>
	<struts-action>
		<struts-action-path>/message_boards/view</struts-action-path>
		<struts-action-impl>com.liferay.samplestrutsaction.hook.action.SampleStrutsPortletAction</struts-action-impl>
	</struts-action>
</hook>

Next we need to create the struts action like below:

package com.liferay.samplestrutsaction.hook.action;

import com.liferay.portal.kernel.struts.BaseStrutsAction;
import com.liferay.portal.kernel.util.ParamUtil;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @author Mika Koivisto
 */
public class SampleStrutsAction extends BaseStrutsAction {

	public String execute(
		HttpServletRequest request, HttpServletResponse response)
		throws Exception {

		String name = ParamUtil.get(request, "name", "World");

		request.setAttribute("name", name);

		return "/portal/sample.jsp";
	}

}

Next create the second Struts action. This one will actually wrap ViewAction of message boards portlet.

package com.liferay.samplestrutsaction.hook.action;

import com.liferay.portal.kernel.struts.BaseStrutsPortletAction;
import com.liferay.portal.kernel.struts.StrutsPortletAction;

import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.PortletConfig;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import javax.portlet.ResourceRequest;
import javax.portlet.ResourceResponse;

/**
 * @author Mika Koivisto
 */
public class SampleStrutsPortletAction extends BaseStrutsPortletAction {

	public void processAction(
			StrutsPortletAction originalStrutsPortletAction,
			PortletConfig portletConfig, ActionRequest actionRequest,
			ActionResponse actionResponse)
		throws Exception {

		originalStrutsPortletAction.processAction(
			originalStrutsPortletAction, portletConfig, actionRequest,
			actionResponse);
	}

	public String render(
			StrutsPortletAction originalStrutsPortletAction,
			PortletConfig portletConfig, RenderRequest renderRequest,
			RenderResponse renderResponse)
		throws Exception {

		System.out.println("Wrapped /message_boards/view action");

		return originalStrutsPortletAction.render(
			null, portletConfig, renderRequest, renderResponse);
	}

	public void serveResource(
			StrutsPortletAction originalStrutsPortletAction,
			PortletConfig portletConfig, ResourceRequest resourceRequest,
			ResourceResponse resourceResponse)
		throws Exception {

		originalStrutsPortletAction.serveResource(
			originalStrutsPortletAction, portletConfig, resourceRequest,
			resourceResponse);
	}

}

Then we need to create the JSP in docroot/META-INF/custom_jsps/html/portal/sample.jsp

Hello !

And lastly we need to create portal.properties in docroot/WEB-INF/src

auth.public.paths=/portal/sample

Now we are ready to deploy the plugin and see if it works. Just run ant deploy in your plugins sdk to deploy it.

You should see following in your tomcat console:

22:01:29,635 INFO  [AutoDeployDir:167] Processing sample-struts-action-hook-6.1.0.1.war
22:01:29,638 INFO  [HookAutoDeployListener:43] Copying web plugin for /Users/mika/Development/Liferay/git/bundles/deploy/sample-struts-action-hook-6.1.0.1.war
  Expanding: /Users/mika/Development/Liferay/git/bundles/deploy/sample-struts-action-hook-6.1.0.1.war into /Users/mika/Development/Liferay/git/bundles/tomcat-6.0.29/temp/20110117220130299
  Copying 1 file to /Users/mika/Development/Liferay/git/bundles/tomcat-6.0.29/temp/20110117220130299/WEB-INF/classes
  Copying 1 file to /Users/mika/Development/Liferay/git/bundles/tomcat-6.0.29/temp/20110117220130299/WEB-INF/classes
  Copying 1 file to /Users/mika/Development/Liferay/git/bundles/tomcat-6.0.29/temp/20110117220130299/WEB-INF
  Copying 1 file to /Users/mika/Development/Liferay/git/bundles/tomcat-6.0.29/temp/20110117220130299/META-INF
  Copying 12 files to /Users/mika/Development/Liferay/git/bundles/tomcat-6.0.29/webapps/sample-struts-action-hook
  Copying 1 file to /Users/mika/Development/Liferay/git/bundles/tomcat-6.0.29/webapps/sample-struts-action-hook
  Deleting directory /Users/mika/Development/Liferay/git/bundles/tomcat-6.0.29/temp/20110117220130299
22:01:30,486 INFO  [HookAutoDeployListener:49] Hook for /Users/mika/Development/Liferay/git/bundles/deploy/sample-struts-action-hook-6.1.0.1.war copied successfully. Deployment will start in a few seconds.
Jan 17, 2011 10:01:39 PM org.apache.catalina.startup.HostConfig deployDirectory
INFO: Deploying web application directory sample-struts-action-hook
22:01:39,727 INFO  [PluginPackageUtil:1080] Reading plugin package for sample-struts-action-hook
22:01:39,759 INFO  [HookHotDeployListener:432] Registering hook for sample-struts-action-hook
22:01:39,770 INFO  [HookHotDeployListener:717] Hook for sample-struts-action-hook is available for use

Now try to access http://localhost:8080/c/portal/sample. It will ask you to sign in and once you sign in you should see the message Hello World! in your browser. You can add a paramer name to the url to change the message. If you access message boards it will print the message “Wrapped /message_boards/view action” in tomcat console and continue to render message boards as if nothing was changed.

Now our sample was really simple one. The return value from the execute method is the view where the request is dispatched next. This can be path to JSP, an existing struts forward or tiles definition. Returning null means that your action has handled the view already. Now you could try to return for instance portal.terms_of_use to display the terms of use.

You can download this sample plugin from svn://svn.liferay.com/repos/public/plugins/trunk/hooks/sample-struts-action-hook. The username is guest and password is empty.

UPDATE: We changed the API so that the original action is passed in so that you can also wrap it with your own logic instead of replacing. I also added a new hook property auth.public.paths so it allows you to set new public paths from hooks. I also added a StrutsPortletAction into to the sample and that demonstrates wrapping an existing action.

Continue reading...