In this article, we elaborate on Liferay hooks, which are a type of plugin for the Java-based Liferay portal. We discuss what they are and then we build one. We setup the project structure and add all the code. Then we deploy it to Liferay.

Liferay hooks

After playing around a bit with a portal, and maybe writing a few portlets for it, you probably start wondering how you can override some of the portal features. You might want to use a branded theme for the whole portal or integrate the portal login with your own database of users/own authentication system.
Liferay has quite a few types of so called “plugins”, such as themes and layout templates, which enable developers to achieve the aforementioned effects. The most powerful type of plugin is called a “hook”. Liferay hooks let you change the portal functionality itself.

Note: There is also the even more powerful, old EXT Liferay extension model. This EXT model has become rather unpopular during the last few years. It basically allows you to override parts /specific classes of the Liferay source code. Usually, EXT plugins are tied to one version of Liferay, since they rely on specific implementation classes of a particular build, instead of on interfaces that change a lot less. The only common case in which EXT is still used, is to override Liferay Struts action classes, like LoginAction and LayoutAction. However, as we will see, from the next versions of Liferay on, it will be possible to override these action classes with hooks as well, which will almost deprecate the EXT model in my opinion.

Building a hook

We are going to build a simple hook that logs info about every request to Liferay. Before and after every request we will log something. This could be a useful hook if it is necessary to log the ips of the clients or if one wants to calculate how long every Liferay request takes.

Project Structure

The project structure of a hook looks very similar to that of a a regular webapp. Hooks, just like portlets, are packaged as wars. You can even deploy portlets and hooks in the same war file, although this is conceptually not a good idea, because hooks override the behavior of the whole portal, and portlets are supposed to be small pluggable components that don’t impact the portal itself.

Since we are using Maven, we use a typical Maven project structure.

Note: It is possible to build hooks using the Eclipse Liferay IDE plugin, but for this article, we choose to explain building a hook from scratch.

The files in our hook war will be the following:

  • /src/main/java/com/integratingstuff/liferay/hooks/CustomPostEventAction
  • /src/main/java/com/integratingstuff/liferay/hooks/CustomPreEventAction
  • /src/main/resources/portal-hook.properties
  • /src/main/webapp/WEB-INF/liferay-hook.xml
  • /src/main/webapp/WEB-INF/liferay-plugin-package.properties
  • /pom.xml

We see that all the typical Maven directories: src/main/java for the Java files, src/main/resources for the resource file and src/main/webapp for the web content. The file that defines our hook, liferay-hook-xml, resides in this last directory.

Note: It is not necessary to have a web.xml file in your source code. Upon hook deploy however, Liferay will automatically create it for the deployed war.

Defining hooks

Hooks are defined in a liferay-hook.xml file.

The liferay-hook.xml for our portlet looks like this:

<hook>
	<portal-properties>portal-hook.properties</portal-properties>
	<!--
	<custom-jsp-dir>/WEB-INF/jsps</custom-jsp-dir>	-->
	<struts-action>
		<struts-action-path>/portal/layout</struts-action-path>
		<struts-action-impl>com.integratingstuff.liferay.hooks.CustomLayoutAction</struts-action-impl>
	</struts-action>
	<service>
		<service-type>com.liferay.portal.service.UserLocalService</service-type>
		<service-impl> com.integratingstuff.liferay.hooks.CustomUserLocalServiceImpl</service-impl>
	</service>
	 -->
</hook>

It is only overriding some portal properties, but for completeness, we also comment on the most common and important other uses of hooks in this section.

Overriding portal properties

We are only supplying a portal.properties file to override some properties of our portal(this can be done on portal level also, but note that pointing to hook classes in the actual portal.properties file is not a good idea since theses classes are not on the classpath of the portal itself).
Not all portal properties can be overridden with a hook. You can read which properties can be overridden by taking a look at the liferay hook dtd wiki page.

Overriding portal jsps

There are some other tags that can be used in a liferay-hook.xml file.
It is possible to override portal specific jsps by supplying a custom-jsp-dir and then putting jsps in that directory. For example, if we define a custom-jsp-dir “/WEB-INF/jsps” and then we create /WEB-INF/jsps/html/portlet/blogs/view.jsp in our project structure, this portal specific jsp will be overridden. The one from the hook will be used instead of the portal one.

Overriding portal services

Liferay contains a lot of services that are defined as spring beans in … of the portal source code. All these services can be overridden by using the service tag of liferay-hook.xml. For example, we could replace the portal implementation of the com.liferay.portal.service.UserLocalService – the service that enables one to save/update/lookup information about portal users – interface with our own this way.

Soon: overriding actions

As stated in this blog article, in the next releases of Liferay, it will be possible to override Liferay Struts actions with hooks as well.

Differently from Liferay services, these Struts actions are not defined as Spring beans, and in the past, they required the EXT model to be overridden. These Struts Liferay actions are concrete classes, not interfaces, are part of the internal Liferay code and dependent heavily on its implementation. It often is not a good idea to override them, since changing your version of Liferay would likely break them, but still, it is not uncommon to override some of the important ones like LayoutAction(to get custom rendering behavior of any page fragment) and LoginAction(to get custom portal login functionality that can not be covered by the Liferay event model) if need be.

Soon: adding servlet filters

Apparently, soon it will also become possible to add servlet-filter and servlet-mapping declarations to your hook, making it possible to add custom servlet filters to Liferay itself.

Our hook

Our portal-hook.properties file looks like this:

servlet.service.events.pre=com.integratingstuff.liferay.hooks.CustomPreEventAction
servlet.service.events.post=com.integratingstuff.liferay.hooks.CustomPostEventAction

Basically, we are adding events before Liferay starts to process a request(but after any Liferay servlet filter) and after the processing of every request.

As stated in the comments of the portal.properties documentation(http://www.liferay.com/community/wiki/-/wiki/Main/Portal+Properties+6.0.5), when overriding Portal Events properties, we have to point to classes that extend com.liferay.portal.kernel.events.Action.

So when implementing these classes, we have to extend this Action class:

public class CustomPreEventAction extends Action{
	public void run(HttpServletRequest request, HttpServletResponse response)
			throws ActionException {
		log.info(“Request from ip: ” + request.getRemoteAddr();
		request.setAttribute(“startOfRequest”,new Date());
	}
}

public class CustomPostEventAction extends Action{
	public void run(HttpServletRequest request, HttpServletResponse response)
			throws ActionException {
		Date now = new Date();
		Date startOfRequest = request.getAttribute(“startOfRequest”);
		if (startOfRequest != null){
			log.info(“Request ook: ” + (now.getTime() - startOfRequest.getTime())+ “ms”);
		}
	}
}

Note though that the request and response that are passed in these actions are actually wrapper objects that are managed by Liferay itself.
Some things cannot be done with these wrapper objects. Invalidating or modifying the session of the request for example.

Other files

Optional: liferay-plugin-package.properties

There is also the optional liferay-plugin-package.properties file. It is good practice to add it. It also offers some extra features.

name=IntegratingStuffDemo-hook
module-group-id=liferay-ee
module-incremental-version=1
tags=
short-description=
change-log=
page-url=http://www.liferay.com
author=Liferay, Inc.
licenses=EE

#portal.dependency.jars=portal-impl.jar, struts.jar, commons-logging.jar, log4j.jar, slf4j-api.jar, slf4j-log4j12.jar, commons-codec.jar

Notice the commented line. With portal.dependency.jars you can supply jars that have to be copied from the portal root to the lib folder of the hook. This way, you can easily write a hook that depends on the portal implementation for example(for example, if you want to override one of the Struts actions that are part of portal-impl, with struts-action).

Even if you do not specify any portal.dependency.jars, Liferay will still copy its log4j.jar, commons-logging.jar and util-java.jar into the lib folder of the hook. Adding these this way(or having it in the lib folder of the war) is not necessary.

pom.xml

If you are using Maven, you will need the following dependencies in your pom.xml file, of which some are Liferay specific:

<dependency>
   <groupId>com.liferay.portal</groupId>
   <artifactId>util-java</artifactId>
   <version>6.0.5</version>
   <scope>provided</scope>
</dependency>

<dependency>
   <groupId>com.liferay.portal</groupId>
   <artifactId>portal-service</artifactId>
   <version>6.0.5</version>
   <scope>provided</scope>
</dependency>
<dependency>
   <groupId>javax.servlet</groupId>
   <artifactId>servlet-api</artifactId>
   <version>2.5</version>
   <scope>provided</scope>
</dependency>
<dependency>
   <groupId>javax.servlet.jsp</groupId>
   <artifactId>jsp-api</artifactId>
   <version>2.1</version>
   <scope>provided</scope>
</dependency>

<dependency>
   <groupId>javax.portlet</groupId>
   <artifactId>portlet-api</artifactId>
   <version>2.0</version>
   <scope>provided</scope>
</dependency>

Deploying the hook

Deploying the hook is very straightforward. You can just drop the resulting war in the deploy directory of your Liferay install or add it to your server in your Eclipse/other IDE.