Wiki

Main | Proposals

Portal Hook Plugins

Warning

The DTD has been updated in Liferay 5.2.

Original Article

Lately, Liferay has been following a trend toward externalizing extensibility features as much as possible. The traditional EXT model, while powerful, often proves too complex for many situations, or too intrusive to the product to retrain a high level of maintainability and a comfortable upgrade path because by it's very nature it inadvertently promotes bad programming practices which can easily introduce difficult migration issues.

There are still use cases for using the EXT extension model (some less component friendly app servers for example), but the plan is to minimize the need and outright eliminate it wherever possible.

That in mind, the external extensibility features of Liferay have been very un-trendily dubbed "plugins". Liferay supports 5 different types of plugins out-of-the-box. They are:

  • Portlets
  • Themes
  • Layout Templates
  • Webs
  • Hooks

Today I'd like to focus on the newest addition, "Hooks". Hooks have been Brian Chan's own personal pet project for the last several months. As the name implies they allow "hooking" into Liferay. Specifically they allow you to hook into the eventing system, model listeners, jsps and portal properties. We'll begin by creating a bare hook config file where we will define some hooks as we review the different types

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hook PUBLIC "-//Liferay//DTD Hook 5.1.0//EN" "http://www.liferay.com/dtd/liferay-hook_5_1_0.dtd">

<hook>
</hook>

Event Handlers

Liferay has a few event handler connection points throughout its lifecycle, designed to allow SI's to conveniently hook-in custom logic. The available events are:

  • Application Startup Events (application.startup.events)
  • Login Events (login.events.pre, login.events.post)
  • Service Events (servlet.service.events.pre, servlet.service.events.post)

Generally speaking your event implementations should extend com.liferay.portal.kernel.events.Action.

For example, presuming we have a custom event handler which should fire when the portal is starting to process any request called me.auge.ray.ServicePreAction, this class placed in the plugin work dir of <sdk_root>/hooks/rays-hook/docroot/WEB-INF/src/me/auge/ray/ServicePreAction.java, I would place the following element in the config file:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hook PUBLIC "-//Liferay//DTD Hook 5.1.0//EN" "http://www.liferay.com/dtd/liferay-hook_5_1_0.dtd">

<hook>
	<event>
<event-class>me.auge.ray.ServicePreAction</event-class>
<event-type>servlet.service.events.pre</event-type>
</event>
</hook>

A couple points to make about events are that an application.startup.events can only extend com.liferay.portal.kernel.events.SimpleAction as that one expects an array of companyIds for which it should be invoked. The second is that you can define as many implementations as you like for each event type. Simply repeat the event element for each one.

Language Bundles

You can install new translations or override few words in existing translations. Point to where your properties files is located, the filename must follow this pattern: Language_$[localeId].properties, where $localeId is the id of the Locale (e.g. en_US, pt_BR, etc). You can add as many <language-properties> as you want:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hook PUBLIC "-//Liferay//DTD Hook 5.1.0//EN" "http://www.liferay.com/dtd/liferay-hook_5_1_0.dtd">

<hook>
	<language-properties>com/liferay/samplelocalized/resources/Language_en_US.properties</language-properties>
</hook>

Model Listeners

Model listeners are similar in behavior to portal event handlers except that these handle events with respect to models built using ServiceBuilder. These listeners implement the com.liferay.portal.model.ModelListener interface.

If you wanted to listen for new Blog posts, you might have a class called me.auge.ray.NewBlogEntryListener which looked like this:

package me.auge.ray;
import com.liferay.portal.ModelListenerException;
import com.liferay.portal.model.BaseModel;
import com.liferay.portal.model.ModelListener;
import com.liferay.portlet.blogs.model.BlogsEntry;
public class NewBlogEntryListener implements ModelListener {
	public void onAfterCreate(BaseModel arg0) throws ModelListenerException {
BlogsEntry entry = (BlogsEntry)arg0;
System.out.println("Woohoo! We got an new one called: " + entry.getTitle());
}
public void onAfterRemove(BaseModel arg0) throws ModelListenerException { } public void onAfterUpdate(BaseModel arg0) throws ModelListenerException { } public void onBeforeCreate(BaseModel arg0) throws ModelListenerException { } public void onBeforeRemove(BaseModel arg0) throws ModelListenerException { } public void onBeforeUpdate(BaseModel arg0) throws ModelListenerException { } }

You'd configure it like so:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hook PUBLIC "-//Liferay//DTD Hook 5.1.0//EN" "http://www.liferay.com/dtd/liferay-hook_5_1_0.dtd">

<hook>
	<event>
		<event-class>me.auge.ray.ServicePreAction</event-class>
		<event-type>servlet.service.events.pre</event-type>
	</event>
	<model-listener>
<model-listener-class>me.auge.ray.NewBlogEntryListener</model-listener-class>
<model-name>com.liferay.portlet.blogs.model.BlogsEntry</model-name>
</model-listener>
</hook>

As with events, add as many as you like even per model.

JSPs

One of the biggest aspects of implementing the portal is of course customization of the user experience. This largely involves modifying portal jsps. The problem is of course migration from version to version where you may end up with code squew, code management, version management, and many other issues. JSP hooks are designed to aleviate some of those issues by providing a way for SI's to easily modify jsps without having to alter the core. Simply specify a folder in the hook plugin from which to obtain jsp files and the portal will automatically use those in place of existing ones in the portal. This works for any jsps in the portal, portlets, servlets, and tags. All you need to do is make sure that you follow the same folder structure off your specified folder.

For example if you specify the folder /WEB-INF/src, the changing the view for the blogs portlet would require a file in /WEB-INF/src/html/portlet/blogs/view.jsp. Configuration would look like this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hook PUBLIC "-//Liferay//DTD Hook 5.1.0//EN" "http://www.liferay.com/dtd/liferay-hook_5_1_0.dtd">

<hook>
	<event>
		<event-class>me.auge.ray.ServicePreAction</event-class>
		<event-type>servlet.service.events.pre</event-type>
	</event>
	<model-listener>
		<model-listener-class>me.auge.ray.NewBlogEntryListener</model-listener-class>
		<model-name>com.liferay.portlet.blogs.model.BlogsEntry</model-name>
	</model-listener>
	<custom-jsp-dir>/WEB-INF/jsps</custom-jsp-dir>
</hook>

Portal Properties

We can alter the portal's configuration properties by specifying an override file. The properties in this file will immediately take effect when deployed thus allowing runtime re-configuration of the portal.

If you had a file /WEB-INF/src/portal.properties, the configuration would look like:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hook PUBLIC "-//Liferay//DTD Hook 5.1.0//EN" "http://www.liferay.com/dtd/liferay-hook_5_1_0.dtd">

<hook>
	<event>
		<event-class>me.auge.ray.ServicePreAction</event-class>
		<event-type>servlet.service.events.pre</event-type>
	</event>
	<model-listener>
		<model-listener-class>me.auge.ray.NewBlogEntryListener</model-listener-class>
		<model-name>com.liferay.portlet.blogs.model.BlogsEntry</model-name>
	</model-listener>
	<portal-properties>portal.properties</portal-properties>
	<custom-jsp-dir>/WEB-INF/jsps</custom-jsp-dir>
</hook>

Finally, all of the above hooks will immediately revert their targetted functionality as soon as they are undeployed from the portal. Also, each type of hook can easily be disabled via portal.properties (Note that if properties hook is disabled, a hook cannot be used to re-enable it). Hooks can be built, packaged, and deployed, like other plugins, using the Liferay plugins SDK.

17057 Views , 0 Attachments 0 Attachments

Average (0 Votes)
Comments Flat View

There isn't any <language-properties> element in the DTD. There should be another version of DTD???

Posted on 10/11/08 8:56 AM.

Top Top
should be liferay-hook_5_2_0.dtd

Posted on 10/12/08 7:34 PM in reply to Van Hoai.

Top Top
It is not clear to me, where I would need to place the hook config file?
Especially in an installation where the ext-environment is not used.

Posted on 10/22/08 12:23 AM.

Top Top
I guess your confusion is like mine, originally... I looked at https://lportal.svn.sourceforge.net/svnroot/lportal/plugins/trunk/hooks/ to figure it out...

There is no create.sh in the hooks folder like other plugins.

Seems to me that:

1- you create a folder in hooks, with an arbitrary name
2- your hooks config goes in yourFolder/docroot/WEB-INF/liferay-hook.xml
3- your overrides go in /docroot/WEB-INF/src and need to be referenced by your hook config
4- you should have a yourFolder/docroot/WEB-INF/liferay-plugin-package.properties
5- you probably want to add a yourFolder/build.xml for ant

Posted on 10/31/08 12:55 PM in reply to Thomas Kellerer.

Top Top
Please, what about Webs plugins?

Thank you in advance!

Ivano Carrara

Posted on 11/5/08 2:33 AM.

Top Top
So this only works with the ext-enviroment, right?

Posted on 11/10/08 3:38 AM in reply to Alex Wallace.

Top Top
It works in both Plugins SDK and Ext.

The development of hooks should be merged with that of portlets. That is, we should develop portlets in plugins SDK with hooking feature. Right?

Thanks

Jonas Yuan

Posted on 12/29/08 5:38 PM in reply to Thomas Kellerer.

Top Top
Yes, in 5.2, there are only two elements:

<portal-properties>portal.properties</portal-properties>
<custom-jsp-dir>/META-INF/custom_jsps</custom-jsp-dir>

place all other values (including language properties) as properties in:

<portal-properties>portal.properties</portal-properties>

Posted on 12/29/08 5:43 PM in reply to Arcko Duan.

Top Top
So how does one put something like
1
2    <event>
3        <event-class>com.mycompany.liferay.ActionClass</event-class>
4        <event-type>application.startup.events</event-type>
5    </event>   

which is valid in the 5.1 dtd into portal.properties? That doesn't seem right..

Posted on 4/27/09 4:16 PM in reply to Jonas Yuan.

Top Top
Nevermind on my last comment, it appears one can specify
application.startup.events=com.mycompany.liferay.ActionClass
to emulate
1
2<event>
3        <event-class>com.mycompany.liferay.ActionClass</event-class>
4        <event-type>application.startup.events</event-type>
5</event>   

but now the question is how can I have multiple plugins that have callbacks for the same hook? For example, I assumed that previously I could have two separate hook plugins declaring two separate event-class values for the same hook and have both execute; was I wrong on that? Will something like:
1
2application.startup.events=${application.startup.events},com.mycompany.liferay.ActionClass

work?

Posted on 4/27/09 4:39 PM in reply to Eugene Rachitskiy.

Top Top
Hi,

I am trying to implement A listener class for Journal Article, in ext when I am using this properies 'value.object.listener.com.liferay.portlet.journal.model.JournalArticle=com.liferay.portlet.journal.model.JournalArticleListener' I am able to do it. Can you help me how can i achieve this using a hook, I am using 5.2.2 version of Liferay(model-listener tag is no more 5.2 hook dtd).

Thanks in Advance!

Posted on 5/24/09 11:06 PM in reply to Jonas Yuan.

Top Top
I tried something similar on the lines of the above and in portal.properties, added the following key-value pair:

value.object.listener.com.liferay.wol.model.WallEntry=org.inexistent.wolportlet.listeners.WallEntryListener

Server startup clearly shows that the property file has been successfully read, but the listener still does not appear to fire up. Also, I noted that in the DTD at http://www.liferay.com/dtd/liferay-hook_5_2_0.dtd, the comments section say that:

"Not all portal properties can be overridden via a hook."

What were the steps that you had taken to create and deploy the listener correctly? I am trying to create a listener for WallEntry.

Thanks!

Posted on 6/18/09 5:31 AM in reply to Faisal K.

Top Top
I tried to create my own JSP hook. I downloaded the Plugins-SDK and made a copy of the custom-jsphook directory.

I then added the file WEB-INF/jsps/html/portlet/document_library/view.jsp and left the other files from the example unchanged (especially liferay-hook.xml)

But when I deploy the hook, I see an error message on the Tomcat console window:

 1
 211:00:59,635 ERROR [HotDeployUtil:111] com.liferay.portal.kernel.deploy.hot.HotDeployException: Error registering hook f
 3or my-jsphook-5.2.3.1
 4com.liferay.portal.kernel.deploy.hot.HotDeployException: Error registering hook for my-jsphook-5.2.3.1
 5        at com.liferay.portal.deploy.hot.BaseHotDeployListener.throwHotDeployException(BaseHotDeployListener.java:58)
 6        at com.liferay.portal.deploy.hot.HookHotDeployListener.invokeDeploy(HookHotDeployListener.java:101)
 7        at com.liferay.portal.kernel.deploy.hot.HotDeployUtil._doFireDeployEvent(HotDeployUtil.java:108)
 8        at com.liferay.portal.kernel.deploy.hot.HotDeployUtil._fireDeployEvent(HotDeployUtil.java:153)
 9        at com.liferay.portal.kernel.deploy.hot.HotDeployUtil.fireDeployEvent(HotDeployUtil.java:43)
10        at com.liferay.portal.kernel.servlet.PortletContextListener.portalInit(PortletContextListener.java:113)
11        at com.liferay.portal.kernel.util.PortalInitableUtil.init(PortalInitableUtil.java:48)
12        at com.liferay.portal.kernel.servlet.PortletContextListener.contextInitialized(PortletContextListener.java:109)
13        at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:3843)
14        at org.apache.catalina.core.StandardContext.start(StandardContext.java:4342)
15        at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:791)
16        at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:771)
17        at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:525)
18        at org.apache.catalina.startup.HostConfig.deployDirectory(HostConfig.java:926)
19        at org.apache.catalina.startup.HostConfig.deployDirectories(HostConfig.java:889)
20        at org.apache.catalina.startup.HostConfig.deployApps(HostConfig.java:492)
21        at org.apache.catalina.startup.HostConfig.check(HostConfig.java:1217)
22        at org.apache.catalina.startup.HostConfig.lifecycleEvent(HostConfig.java:293)
23        at org.apache.catalina.util.LifecycleSupport.fireLifecycleEvent(LifecycleSupport.java:117)
24        at org.apache.catalina.core.ContainerBase.backgroundProcess(ContainerBase.java:1337)
25        at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.processChildren(ContainerBase.java:1601)
26        at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.processChildren(ContainerBase.java:1610)
27        at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.run(ContainerBase.java:1590)
28        at java.lang.Thread.run(Thread.java:619)
29Caused by: com.liferay.portal.kernel.xml.DocumentException: Error on line 2 of document  : Content is not allowed in pro
30log. Nested exception: Content is not allowed in prolog.
31        at com.liferay.portal.xml.SAXReaderImpl.read(SAXReaderImpl.java:377)
32        at com.liferay.portal.xml.SAXReaderImpl.read(SAXReaderImpl.java:388)

As the liferay-hook.xml is the only xml file involved I assume that is the problem, but when looking at the file I cannot see any error in there (and it is an unchanged copy of the file from the SDK)

Even though the hot deployer claims "ready to use" the hook is not working (I added an additional label to the jsp to see if it's working) and in the control panel the hook is not listed as well.

What am I missing?

Posted on 7/14/09 2:28 AM.

Top Top
Hi Amarjeet, were you able to solve your problem? I'm having this same problem with a different portlet.

Posted on 7/14/09 11:20 AM in reply to Amarjeet Singh.

Top Top
"Content is not allowed in prolog is an xml exception. I think it occurs when you have some text in the <? ... ?> header.

Posted on 7/29/09 1:25 PM in reply to Thomas Kellerer.

Top Top
isn't it portal-ext.properties if i am using ext environment

Posted on 8/27/09 6:05 PM in reply to Jonas Yuan.

Top Top
Thomas, How did you resolve this issue? I want to do modify the same file to stop the teasing in the Document Library, as detailed here: http://www.liferay.com/web/guest/community/forums/-/message_boards/message/3370122

Posted on 9/11/09 12:38 PM in reply to Thomas Kellerer.

Top Top
See my forum message for a solution:
http://www.liferay.com/web/guest/community/forums/-/message_boards/message/3552876

Posted on 10/5/09 6:24 AM in reply to Markus Khouri.

Top Top
I'm trying to implement a language hook, but as soon as I deploy it (with just three keys) I cannot' switch the language in the portal anymore. When I change the user's language, the whole portal stays in the same language. Only web-content and the navigation changes to the new language (but all Liferay dialogs, stick with e.g. with english)

Posted on 10/22/09 6:11 AM.

Top Top
Hi

I hate to see that I am the only one who seem to have problem with deploying.
Nobody mentions anything about how to deploy.
I presumed I must run "ant deploy", but it only gives me :
liferay_sdk/hooks/my-hooks/build.xml (No such file or directory)
Alex Wallace, mentions :
"you probably want to add a yourFolder/build.xml for ant " but, where or how to I get the proper build.xml ??!!!

Armaz

Posted on 11/5/09 3:57 AM.

Top Top
Answering my own question emoticon

Found the answer on this link:
http://kamalkantrajput.blogspot.com/2009/05/using-hooks-in-liferay-for-customizing.html
A nice step by step walk-through.

Posted on 11/5/09 4:22 AM in reply to Armaz Mellati.

Top Top
I'm using webspace. Dont have ext environment for developing.
I need to customize the login portlet by changing the struts action path..

Is it achievable through hooks plugin and how..

Can anyone plz guide me ..

Posted on 1/20/10 7:57 AM in reply to Jonas Yuan.

Top Top
Faris,

Forgive me if i lead you down the wrong path since i haven't tried this specifically with the struts-config.xml.

If you look at the structure of a hook, it looks something like :

<hook name>
|-docroot
|- WEB-INF
|- classes
|- various xml files

i believe you want to copy the struts-config.xml from the liferay-portal source to here, and add/modify your changes inside the file.

hope this helps.

Posted on 1/26/10 3:17 PM in reply to Faris Abdulla.

Top Top