Sunday 20 November 2011

Hooks Implimentation

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>

Overriding Services #

Starting with Liferay 6.0 and 5.2 EE SP4, you can override services using hooks as opposed to doing so in the EXT. An example would be:
<?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>
        <service>
                <service-type>com.liferay.portal.service.RoleLocalService</service-type>
                <service-impl>me.michael.han.NewRoleLocalServiceImpl</service-impl>
        </service>
        <service>
                <service-type>com.liferay.portal.service.UserLocalService</service-type>
                <service-impl>me.michael.han.NewUserLocalServiceImpl</service-impl>
        </service>
</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.

Application adapters #

Application Adapters are hooks that can be used just for certains Sites and can chosen from the UI when creating new sites. They were introduced in Liferay 6.1

No comments:

Post a Comment