Liferay Layout Templates finally approved!

Today our Liferay Layout Templates were finally approved. While Liferay Marketplace team took a little bit of time to finally approve them we are grateful that they finally did. Although the templates are not highly original they still fill the void when someone is missing a 15/85 or 25/75 or 40/60 ratio 2 column basic template.

While we don’t actually want to charge money for them, they did took us a small amount of time to make and getting them approved on Liferay Marketplace took also a considerable time considering all the somewhat awkward bumps we met on the road.

Basic 25 / 75 ratio layout


15/85 and 85/15 Basic layout for extra thin portlets


Liferay Birthday List portlet


Just wanted to let you guys know that our very basic version of Liferay Birthday List portlet is now available for FREE on Liferay Marketplace.

You can download and install it straight from the Marketplace.
liferay birthday list portlet

The portlet relies on the Liferay’s internal user database and displays the birthdays of Yesterdays, Todays and Tomorrows birthdays.

Can’t really say that’s an achievement, but at least it’s our first Portlet on the Marketplace.
We plan to start publishing many useful Portlets to make the Liferay Ecosystem a little bit friendlier to people who don’t have development teams just hanging around, waiting for work…

We will probably publish a more feature-rich Liferay Birthday List portlet that would also show Work Anniversaries and allow sending quick Congratulations using Liferay Notifications.

dijit.Tree and Sorting

Today I want to talk about the Dojo Toolkit 1.6 version dijit.Tree and Sorting feature.
This will be a very very short tutorial on how to make it work. I recently had to make it work on an already existing application that had a few years old version of Dojo installed. Although looking at the documentation then it doesn’t seem like the new version is much better in this particular area.


To get the sorting working nicely you need 3 main components: store, model and the tree. Here’s my examples on those. I’ll be using an ItemFileWriteStore to get my tree data with Ajax query to in a JSON format. We then need a TreeStoreModel that basically is just a wrapper for our tree nodes.

var myStore = new{url:"/datasource.php?format=json"});
var treeModel = new dijit.tree.TreeStoreModel({store:myStore,query:{id: "0"}});
var myTree = new dijit.Tree({
    id: 'myTree',
    model: treeModel,
    labelAttrs: "label",
    betweenThreshold: "8",
    dndController: dijit.tree.dndSource,
    checkAcceptance: dragAccept,
    checkItemAcceptance: dropAccept

By now you should have a working dijit.Tree with sorting functions, but no actual storage is happening yet. We’ll get to that in a second. My personal show-stopper was the fact that I tried to use dijit.tree.ForestStoreModel initially which is wrong. No matter how hard I tried – the tree just didn’t allowed sorting nodes with it.

Also, special note on the “betweenThreshold” flag – You need this property(nr of pixels) to tell the tree that the pages can be sorted within it’s current parent, not only dragged between parents. If you don’t have betweenThreshold then Dojo Toolkit 1.6 will not allow you to sort within your current parent.

So far so good. Now you might have noticed I defined dragAccept and dropAccept methods as checkAcceptance and checkItemAcceptance. You can add custom logic to those methods to define what items can be dragged to what parent nodes. This allows you to make some items incompatible with others and can prevent you from mixing Apples with Oranges in your tree. For the purpose of this tutorial we keep them VERY simple and allow sorting between all nodes. This is probably the typical case:

function dragAccept(source,nodes){ return true; }
function dropAccept(target,source,position) { return true; }

In order to store the new sorting order after you release the item from dragging you need a method to save it to the server. You could do it by saving the whole order of the whole tree into array of inputs or some other hack, but I chose the option of sending an ajax POST request on each sorting action. This means every time we release an item we will send a background POST request to the server. This means we don’t need a special SAVE button in the end of our tree. Do achieve that we have to connect to the onPaste method of the treeModel.

If we want to allow modifications to our data via drag-n-drop, we can implement the pasteItem() method and set the drag-n-drop controller for the tree.

dojo.connect(treeModel, 'pasteItem', function(childItem, oldParentItem, newParentItem, bCopy, insertIndex) {
    entityID =[0]; // The node that was sorted
    parentID =[0]; // The new(could be the old one) parent of the node
    order = 0; // We set the order to 0 first
    if (insertIndex != undefined) { // And only override it when the order is defined.
        order = insertIndex;
    // Send POST to server side.
        url: "/saveorder.php?id="+entityID+"&parent="+parentID+"&order="+order,
        load: function(data, ioargs) {
            // Notify user about successful save here.
            alert('Your order was saved!');

That’s IT! You should be done now from the javascript side. The server and JSON parts should be easy as the internet is full of documentation on those. If you are using a PHP backend then it’s just a matter of using json_encode method and reading some $_POST params.

Some more reference material can be found here:

If you have a question or having trouble getting it working then feel free to write a comment and I’ll do my best to help you out. If you would like to read more about Dojo Toolkit related subjects then also make sure to comment on that!

DB2 Backup using TSM

In this example we are using a DB2 database called WPSDB. At one point I needed to automate a backup from DB2 into Tivoli Storage Manager. IBM Tivoli Storage Manager is a data backup platform that gives enterprises a single point of control and administration for storage management needs.

Get current config:

db2 => connect to WPSDB

There will be a big long list and you are trying to find LOGARCHMETH1.

First log archive method (LOGARCHMETH1) = OFF
Options for logarchmeth1 (LOGARCHOPT1) =
Second log archive method (LOGARCHMETH2) = OFF

Now let’s change this into TSM:

db2 => update db cfg using LOGARCHMETH1 TSM
DB20000I The UPDATE DATABASE CONFIGURATION command completed successfully.
SQL1363W One or more of the parameters submitted for immediate modification
were not changed dynamically. For these configuration parameters, all
applications must disconnect from this database before the changes become

At this point ignore warning messages.
Let’s look if the change was OK:


First log archive method (LOGARCHMETH1) = TSM
Options for logarchmeth1 (LOGARCHOPT1) =
Second log archive method (LOGARCHMETH2) = OFF
Options for logarchmeth2 (LOGARCHOPT2) =
Failover log archive path (FAILARCHPATH) =
Number of log archive retries on error (NUMARCHRETRY) = 5
Log archive retry Delay (secs) (ARCHRETRYDELAY) = 20
Vendor options (VENDOROPT) =

Look for “First log archive method (LOGARCHMETH1)” parameter. It should now be set to “TSM”.

Restart DB2 instance:

db2 force applications all
db2 terminate

Start DB/2


Run full DB2 backup manually as administrator:

db2 backup db WPSDB use tsm
Backup successful. The timestamp for this backup image is : 20140411144830

Proceed with enabling automated DB2 TSM backup by creating a administrative schedule that call the script (db2backup_full.cmd). You can then schedule this backup from TSM:

"C:\IBM\ProductName\db2\BIN\db2cmd.exe" /c DB2.EXE backup db WPSDB user <DOMAINUSERNAME> using <DOMAINUSERPASSWORD> online use tsm include logs

So there you go, we now have a DB2 backup using TSM and it’s working automatically.

Parameterized JUnit test

Today I needed to do a quick test for a custom NameUtils class that capitalized human and business names based on some very specific rules. To do that I decided that the best way is to use a Parameterized JUnit test and the best and fastest way of doing it is by using Junit with JUnitParams. This project adds a new runner to JUnit and provides much easier and readable parameterized tests for JUnit >=4.6. According to their own JUnitParams homepage the main differences to standard JUnit Parameterized runner are:

  • more explicit – params are in test method params, not class fields
  • less code – you don’t need a constructor to set up parameters
  • you can mix parameterised with non-parameterised methods in one class
  • params can be passed as a CSV string or from a parameters provider class
  • parameters provider class can have as many parameters providing methods as you want, so that you can group different cases
  • you can have a test method that provides parameters (no external classes or statics anymore)
  • you can see actual parameter values in your IDE (in JUnit’s Prameterized it’s only consecutive numbers of parameters)

So first, since we are using maven, then we need our dependencies:


While JUnit is very common, JunitParams is probably not so popular yet. It’s a package of small helper annotations that allow you to better write your tests.

So now – what are these parametrized tests I’m talking about? Here’s a quick example:

import static org.junit.Assert.assertEquals;
import junitparams.FileParameters;
import junitparams.JUnitParamsRunner;

import org.junit.Test;
import org.junit.runner.RunWith;

public class NameUtilsTest {

	public void testNameCapitalization(String input, String expected) {
		assertEquals(expected, NameUtils.capitalize(input));


This test asserts that our NameUtils.capitalize method returns expected results.
As you can see, we use @RunWith that tells JUnit to this test using JUnitParamsRunner and then we use @FileParameters that tells junit to run this test once for every single line in that CSV file.

On each of the CSV file lines we have our input and expected output, like this:

john doe, 					John Doe
DR. JOHN DOE, 				Dr. John Doe
ACME Corporation Ltd.,		Acme Corporation Ltd.
Miriam Torrence, 			Miriam Torrence
Shawanna Melody, 			Shawanna Melody
    SPACE     Tuma, 		Space Tuma
Jeraldine Ownbey, 			Jeraldine Ownbey
Nilda Cavaliere, 			Nilda Cavaliere
bUffy Yearta, 				Buffy Yearta,
AMANDA skoog, 				Amanda Skoog
a,							A
a b			, 				A B
Elias johnny cash Rash,		Elias Johnny Cash Rash

And now when we run our code, we get:
parameterized junit test results

Note that the test was ran for every single line in that CSV file and this allows you to quickly add new cases or testing material to constantly improve your code verifications.

If you have a question or having trouble getting it to work on your machine then feel free to write in the comment section and I’ll do my best to help you out. If you would like to read more about Unit Testing related subjects then also make sure to comment on that and if you liked what you read then make sure to subscribe – It’s FREE!

New Liferay Layout Templates

Today we submitted 6 new Liferay Layout Templates. Although they are not very original they still fill the void when someone is missing a 15 / 85 or 25 / 75 or 40 / 60 ratio 2 column basic template.

At the moment of writing this they are not approved yet but we hope everything will go smoothly and you’ll be able to get them from Liferay Marketplace very soon!

Here’s a couple of screenshots of 15/85 and 85/15 Basic layout for extra thin portlets. 40/60 and 60/40 Basic Layouts for when you need just a little bit of extra space.





Basic 25 / 75 ratio layout that uses Liferay 6.2 built in span3 span9 ratios. Real-world percentages will depend on the theme and window sizes.



Java watermark image

Today I needed to add a watermark on my client images. I had a look around for available libraries. I didn’t find a good solutions that I could use straight out of the box in Java so I just created my own ImageUtil class and made my own Java watermark image method.

First things first – If we want to watermark an image then we have to decide on where should we anchor our watermark. For that I decided to use a simple ENUM called PlacementPosition:

public enum PlacementPosition {

When you want to add a watermark then you probably need to resize it so you might want a good image resizer. That one I found and we can use an already existing one. I personally prefer to use Scalr named image library from because it’s very good, pretty damn fast and has controllable quality. Also it’s available on Maven repository.

So now the actual code of watermarking an image:

	 * Generate a watermarked image.
	 * @param originalImage
	 * @param watermarkImage
	 * @param position
	 * @param watermarkSizeMaxPercentage
	 * @return image with watermark
	 * @throws IOException
	public static BufferedImage watermark(BufferedImage originalImage,
			BufferedImage watermarkImage, PlacementPosition position,
			double watermarkSizeMaxPercentage) throws IOException {

		int imageWidth = originalImage.getWidth();
		int imageHeight = originalImage.getHeight();

		int watermarkWidth = getWatermarkWidth(originalImage, watermarkImage,
		int watermarkHeight = getWatermarkHeight(originalImage, watermarkImage,

		// We create a new image because we want to keep the originalImage
		// object intact and not modify it.
		BufferedImage bufferedImage = new BufferedImage(imageWidth,
				imageHeight, BufferedImage.TYPE_INT_RGB);
		Graphics2D g2d = (Graphics2D) bufferedImage.getGraphics();
		g2d.drawImage(originalImage, 0, 0, null);

		int x = 0;
		int y = 0;
		if (position != null) {
			switch (position) {
			case TOPLEFT:
				x = 0;
				y = 0;
			case TOPCENTER:
				x = (imageWidth / 2) - (watermarkWidth / 2);
				y = 0;
			case TOPRIGHT:
				x = imageWidth - watermarkWidth;
				y = 0;

				x = 0;
				y = (imageHeight / 2) - (watermarkHeight / 2);
				x = (imageWidth / 2) - (watermarkWidth / 2);
				y = (imageHeight / 2) - (watermarkHeight / 2);
				x = imageWidth - watermarkWidth;
				y = (imageHeight / 2) - (watermarkHeight / 2);

				x = 0;
				y = imageHeight - watermarkHeight;
				x = (imageWidth / 2) - (watermarkWidth / 2);
				y = imageHeight - watermarkHeight;
				x = imageWidth - watermarkWidth;
				y = imageHeight - watermarkHeight;


		g2d.drawImage(Scalr.resize(watermarkImage, Method.ULTRA_QUALITY,
				watermarkWidth, watermarkHeight), x, y, null);

		return bufferedImage;



That method uses some specific methods to calculate the watermark size. I wanted the user to be able to upload their own watermarks but that caused a problem where I didn’t want to hard-code the size of the watermark. So we ended up using a solution where we have defined a maximum percentage of size that the watermark can be relative to the original image.

For that we use these methods:

	 * @param originalImage
	 * @param watermarkImage
	 * @param maxPercentage
	 * @return
	private static Pair<Double, Double> calculateWatermarkDimensions(
			BufferedImage originalImage, BufferedImage watermarkImage,
			double maxPercentage) {

		double imageWidth = originalImage.getWidth();
		double imageHeight = originalImage.getHeight();

		double maxWatermarkWidth = imageWidth / 100.0 * maxPercentage;
		double maxWatermarkHeight = imageHeight / 100.0 * maxPercentage;

		double watermarkWidth = watermarkImage.getWidth();
		double watermarkHeight = watermarkImage.getHeight();

		if (watermarkWidth > maxWatermarkWidth) {
			double aspectRatio = watermarkWidth / watermarkHeight;
			watermarkWidth = maxWatermarkWidth;
			watermarkHeight = watermarkWidth / aspectRatio;

		if (watermarkHeight > maxWatermarkHeight) {
			double aspectRatio = watermarkWidth / watermarkHeight;
			watermarkHeight = maxWatermarkHeight;
			watermarkWidth = watermarkHeight / aspectRatio;

		return Pair.of(watermarkWidth, watermarkHeight);

	 * @param originalImage
	 * @param watermarkImage
	 * @param maxPercentage
	 * @return
	public static int getWatermarkWidth(BufferedImage originalImage,
			BufferedImage watermarkImage, double maxPercentage) {

		return calculateWatermarkDimensions(originalImage, watermarkImage,


	 * @param originalImage
	 * @param watermarkImage
	 * @param maxPercentage
	 * @return
	public static int getWatermarkHeight(BufferedImage originalImage,
			BufferedImage watermarkImage, double maxPercentage) {

		return calculateWatermarkDimensions(originalImage, watermarkImage,


And now we have our scaled logo, this time it’s aligned to middle-left:
Java watermark image

If you have a question or having trouble getting it to work then feel free to write me a comment and I’ll do my best to help you out. If you would like to read more about general Java related subjects then also make sure to comment on that and if you liked what you read then make sure to subscribe – It’s FREE!

Liferay allPortletsReady event not firing

Today I stumbled upon a problem where I noticed that on some of my pages my portlet was acting weird and the culprit of the problem was the Liferay specific javascript “allPortletsReady” event not firing. I was using the latest version of Liferay 6.2 CE.

So I debugged and Googled around and went through a pretty large array of possible problems for why the allPortletsReady was not working for other people.

Some results suggested that the Related Assets portlet might cause the problem – That’s not the case this time though as I wasn’t using that portlet on my pages.

Next up on the list is Notifications Portlet and sure enough I had no Notification Portlet on those pages either.

Finding no obvious solutions using quick searches on Google and StackOverflow I decided to start testing and debugging. So I created a new clean page and added my portlet on that one. This time the portlet had it’s “allPortletsReady” event nicely firing so I now know that my portlet works on new and clean pages.

Then I deleted all portlets, including my own, from an old page where the portlet was not getting it’s allPortletsReady Javascript event called and then I added my portlet back in there. This time the Javascript event remained unfired and problem was still around the corner.

So by now we know that it works on clean new pages and does not work on old pages even after removing all portlets. I even tried switching themes which had no result.

Liferay allPortletsReady Solution (One of many)

Finally it dawned upon me – there has to be a difference on the pages between the new and old page – I checked the page’s embedded portlets section and discovered that even though I no longer had a Login portlet on those pages, they still had Liferay built-in Login portlet ID 58 listed there with some preferences saved. I removed those preferences and sure enough the allPortletsReady started firing again as normal.

Here’s a screenshot of the embedded portles section (I already removed the Login portlet from the list):
Liferay page embedded portlets

So it seems Liferay has some kind of an issue with old embedded Portlet preferences sticking around and causing issues with our Javascript events.

Solution: Remove old unused embedded portlet preferences from the page.

If you have a question or having trouble getting this thing working then feel free to write me a comment and I’ll do my best to help you out. If you would like to read more about Liferay related subjects then also make sure to comment on that and if you liked what you read then make sure to subscribe – It’s FREE!

Jenkins and Maven Release plugin

OK, So here’s a quick tutorial on how to make a Jenkins and Maven Release plugin build that automatically creates a release build using Jenkins and we are assuming your code is in Git.

Step 1 is easy, get your code from Git. You just configure the Jenkins build as always.

Now, We need one additional plugin for Jenkins which is “Workspace Cleanup Plugin”. This allows us to enable
“Delete workspace before build starts” in Build Environment section.

Now the tricky part: Under Pre Steps you must add a new shell command:

git checkout master

This is because otherwise your git is checked out without a specific branch and maven release plugin will fail.

Build command

-B -Dusername=jenkins -Dpassword=JenkinsPasswordForGit release:prepare release:perform

Important notice – you can configure jenkins user to have automatic access to git in other ways also, but I decided to keep this info in the build.

Once you start releasing your project with Jenkins, you quickly discover that manually managing the version numbers inside your own multi-module project will become a hassle. Simple solution would be to configure Jenkins to upgrade your own dependencies to the latest releases (requires you to limit versions plugin to your own packages):

-B versions:use-latest-releases scm:checkin -Dmessage=$BUILD_NUMBER -Dusername=jenkins -Dpassword=JenkinsPasswordForGit release:prepare release:perform

Now we might want to limit versions plugin to our own com.codeyouneed package since having it upgrade ALL dependencies all the time is really bad.


Now that’s pretty much it. That git checkout master command took me a while to figure out.

Of course, for maven release plugin to work you actually need to add some stuff to your pom.xml also:



Important notice if you are dealing with multi-module maven projects – Submodules have to leave out their name from scm.url since release plugin will add the name by itself into the end of the URL.


So you should leave out the “/submodulename”.

Special thanks to a blog post that lead me to the right path:

Jenkins Logo

Liferay Custom Notifications

Java tutorial on how to handle Liferay custom notifications. There is very little good information on this topic but I’ve seen a good need for a proper documentation so here’s a nice example of how to handle Liferay User Notifications based on Liferay 6.2 Notifications API. The official documentation on Liferay notifications is rather weak but then again the feature is also rather new and it’s not even 100% part of the core Liferay experience. Liferay 6.2 notifications have improved quite well and I feel it’s time to make them a first-class feature.

If you download the Liferay 6.2 bundle with Tomcat then you have the Notifications portlet installed already. If you installed Liferay on your own Tomcat or other Application Server using the WAR installation method then you need to install Notifications Portlet from the Marketplace.
Here’s a link to the Notifications CE Portlet on Liferay Marketplace.

Liferay custom notifications require a User Notification Handler class that turns the notification into a nice HTML fragment that is displayed to the user. Also Liferay wants us to define the notification types that our portlet or application is creating in a definition file upfront.

So the first thing we need to do is to define the location of our class that extends BaseUserNotificationHandler class and definitions inside liferay-portlet.xml. This can be done in liferay-portlet.xml like this:


The important part is user-notification-definitions and user-notification-handler-class tags. Now we need to actually define our notification inside that example-user-notification-definitions.xml. This file should go into your resources folder. Since I’m using a maven based project then this file goes into src/main/resources/:

location of example-user-notification-definitions.xml

If you are running an Ant based project then you have to place this file inside docroot/WEB-INF/src/ folder.

Here’s the example-user-notification-definitions.xml I created:

<?xml version="1.0"?>
<!DOCTYPE user-notification-definitions PUBLIC "-//Liferay//DTD User Notification Definitions 6.2.0//EN" "">

Notice how we are using some public final Strings from class files like this: ${com.example.notifications.ExampleUserNotificationHandler.PORTLET_ID}. This allows us to define our Portlet ID in one location and keep things a little bit simpler.
Ok, so now we have our example defined and we can actually Add a new notification for the user. We can do so by using the built-in UserNotificationEventLocalServiceUtil in either our Service Builder class or one of our portlet classes.

Here’s how to add a new notification event:

    JSONObject payloadJSON = JSONFactoryUtil.createJSONObject();
    payloadJSON.put("userId", user.getUserId());
    payloadJSON.put("yourCustomEntityId", exampleEntity.getEntityId());
    payloadJSON.put("additionalData", "Your notification was added!");

		(new Date()).getTime(),
		false, serviceContext);		

If you are doing this inside a portlet then you need the serviceContext, you can get one like this:

ServiceContext serviceContext = ServiceContextFactory.getInstance(portletRequest);

So now that we have our notification inside the database we want to actually show it to the user. When we look at the dockbar our notification number gets increased so we can be pretty sure that the notification was added. When you attempt to actually look at the notification you will probably see a blank white box. We need the notification handler class to actually handle the display side of our notification. This current example also handles the behavior like providing Approve and Reject buttons right in our example notification so that our example user could perform an action straight from the dockbar notification and not waste extra time when it’s a trivial task. I don’t have the correct screenshot but the look we are going after is similar to that you can see on this picture:
liferay custom notifications

Basic idea being we have an Approve and Reject buttons on our notification, so that you can click on the notification or it’s background to go and View more details, but you can also directly perform some custom actions like Approve or Reject in this Liferay custom notification tutorial. The buttons could also be “Report as Spam” or “Notify the Security”. It’s all up to you where that link takes the user.
So here’s our handler class:

import javax.portlet.ActionRequest;
import javax.portlet.PortletURL;
import javax.portlet.WindowState;

import com.liferay.portal.kernel.json.JSONFactoryUtil;
import com.liferay.portal.kernel.json.JSONObject;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.notifications.BaseUserNotificationHandler;
import com.liferay.portal.kernel.portlet.LiferayPortletResponse;
import com.liferay.portal.kernel.util.StringBundler;
import com.liferay.portal.kernel.util.StringPool;
import com.liferay.portal.kernel.util.StringUtil;
import com.liferay.portal.model.UserNotificationEvent;
import com.liferay.portal.service.ServiceContext;
import com.liferay.portal.service.UserNotificationEventLocalServiceUtil;

public class ExampleUserNotificationHandler extends
		BaseUserNotificationHandler {
	public static final String PORTLET_ID = "example_WAR_exampleportlet";
	public ExampleUserNotificationHandler() {



	protected String getBody(UserNotificationEvent userNotificationEvent,
			ServiceContext serviceContext) throws Exception {

		JSONObject jsonObject = JSONFactoryUtil

		long yourCustomEntityId = jsonObject

		String title = "<strong>Example notification for entity ID "
				+ yourCustomEntityId
				+ "</strong>";

		String bodyText = "Some other text.";

		LiferayPortletResponse liferayPortletResponse = serviceContext

		PortletURL confirmURL = liferayPortletResponse.createActionURL(com.example.notifications.ExampleUserNotificationHandler.PORTLET_ID);

		confirmURL.setParameter(ActionRequest.ACTION_NAME, "doSomethingGood");
		confirmURL.setParameter("redirect", serviceContext.getLayoutFullURL());
		confirmURL.setParameter("yourCustomEntityId", String.valueOf(yourCustomEntityId));
		confirmURL.setParameter("userNotificationEventId", String.valueOf(userNotificationEvent.getUserNotificationEventId()));

		PortletURL ignoreURL = liferayPortletResponse.createActionURL(com.example.notifications.ExampleUserNotificationHandler.PORTLET_ID);
		ignoreURL.setParameter(ActionRequest.ACTION_NAME, "cancelForExample");
		ignoreURL.setParameter("redirect", serviceContext.getLayoutFullURL());
		ignoreURL.setParameter("yourCustomEntityId", String.valueOf(yourCustomEntityId));
		ignoreURL.setParameter("userNotificationEventId", String.valueOf(userNotificationEvent.getUserNotificationEventId()));

		String body = StringUtil.replace(getBodyTemplate(), new String[] {
				"[$CONFIRM$]", "[$CONFIRM_URL$]", "[$IGNORE$]",
				"[$IGNORE_URL$]", "[$TITLE$]", "[$BODY_TEXT$]" }, new String[] {
				serviceContext.translate("approve"), confirmURL.toString(),
				serviceContext.translate("reject"), ignoreURL.toString(),
				title, bodyText });
		return body;

	protected String getLink(UserNotificationEvent userNotificationEvent,
			ServiceContext serviceContext) throws Exception {

		JSONObject jsonObject = JSONFactoryUtil

		long yourCustomEntityId = jsonObject
		LiferayPortletResponse liferayPortletResponse = serviceContext

		PortletURL viewURL = liferayPortletResponse.createActionURL(com.example.notifications.ExampleUserNotificationHandler.PORTLET_ID);
		viewURL.setParameter(ActionRequest.ACTION_NAME, "showDetails");
		viewURL.setParameter("redirect", serviceContext.getLayoutFullURL());
		viewURL.setParameter("yourCustomEntityId", String.valueOf(yourCustomEntityId));
		viewURL.setParameter("userNotificationEventId", String.valueOf(userNotificationEvent.getUserNotificationEventId()));
		return viewURL.toString();

	protected String getBodyTemplate() throws Exception {
		StringBundler sb = new StringBundler(5);
		sb.append("<div class=\"title\">[$TITLE$]</div><div ");
		sb.append("class=\"body\">[$BODY_TEXT$]<a class=\"btn btn-action ");
		sb.append("btn-success\" href=\"[$CONFIRM_URL$]\">[$CONFIRM$]</a>");
		sb.append("<a class=\"btn btn-action btn-warning\" href=\"");
		return sb.toString();


NB Important Information: The com.example.notifications.ExampleUserNotificationHandler.PORTLET_ID string that you use as your notification type has to match an actual portlet ID. It doesn’t actually need to be YOUR portlet ID but that would be the right thing to have there. The reason being that Notifications display portlet uses it to display a small portlet icon next to your notification to help the user identify the source of the notification. Providing a bad Portlet ID or something like null leads to a hard-to-trace NullPointerException in the JSP. Took me an hour to track it down.

The problematic part is this code from notifications-portlet view_entries.jsp source

<span class="portlet-icon">
<liferay-portlet:icon-portlet portlet="<%= PortletLocalServiceUtil.getPortletById(company.getCompanyId(), userNotificationEvent.getType()) %>" />

If you have a question or having trouble getting it working then feel free to write me a comment and I’ll do my best to help you out. If you would like to read more about Liferay related subjects then also make sure to comment on that and if you liked what you read then make sure to subscribe – It’s FREE!