Month: June 2014

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.

85-15

60-40

40-60

15-85

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.

75-25

25-75

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 {
	TOPLEFT, TOPCENTER, TOPRIGHT, MIDDLELEFT, MIDDLECENTER, MIDDLERIGHT, BOTTOMLEFT, BOTTOMCENTER, BOTTOMRIGHT
}

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 https://github.com/thebuzzmedia/imgscalr 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,
				watermarkSizeMaxPercentage);
		int watermarkHeight = getWatermarkHeight(originalImage, watermarkImage,
				watermarkSizeMaxPercentage);

		// 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);
		g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
		        RenderingHints.VALUE_ANTIALIAS_ON);

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

			case MIDDLELEFT:
				x = 0;
				y = (imageHeight / 2) - (watermarkHeight / 2);
				break;
			case MIDDLECENTER:
				x = (imageWidth / 2) - (watermarkWidth / 2);
				y = (imageHeight / 2) - (watermarkHeight / 2);
				break;
			case MIDDLERIGHT:
				x = imageWidth - watermarkWidth;
				y = (imageHeight / 2) - (watermarkHeight / 2);
				break;

			case BOTTOMLEFT:
				x = 0;
				y = imageHeight - watermarkHeight;
				break;
			case BOTTOMCENTER:
				x = (imageWidth / 2) - (watermarkWidth / 2);
				y = imageHeight - watermarkHeight;
				break;
			case BOTTOMRIGHT:
				x = imageWidth - watermarkWidth;
				y = imageHeight - watermarkHeight;
				break;

			default:
				break;
			}
		}

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

		return bufferedImage;

	}

watermark-full-width

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,
				maxPercentage).getLeft().intValue();

	}

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

		return calculateWatermarkDimensions(originalImage, watermarkImage,
				maxPercentage).getRight().intValue();

	}

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.

<build>
    <pluginManagement>
        <plugins>
	        <plugin>
	            <groupId>org.codehaus.mojo</groupId>
	            <artifactId>versions-maven-plugin</artifactId>
	            <version>2.1</version>
	            <configuration>
	                <includesList>com.codeyouneed</includesList>
	                <generateBackupPoms>false</generateBackupPoms>
	                <allowSnapshots>true</allowSnapshots>
	            </configuration>
	        </plugin>
		</plugins>
	</pluginManagement>
</build>

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:


	<properties>
		<scm.url>scm:git:https://git.codeyouneed.com/git/test/repo</scm.url>
	</properties>
	
	<distributionManagement>
		<repository>
			<id>codeyouneed</id>
			<url>https://codeyouneed/artifactory/libs-releases-local/</url>
		</repository>
	</distributionManagement>

	<scm>
		<url>${scm.url}</url>
		<connection>${scm.url}</connection>
		<developerConnection>${scm.url}</developerConnection>
		<tag>HEAD</tag>
	</scm>

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.

    <scm.url>scm:git:https://git.codeyouneed.com/git/test[/submodulename]</scm.url>

So you should leave out the “/submodulename”.

Special thanks to a blog post that lead me to the right path: http://labs.bsb.com/2014/02/better-maven-releases-with-jenkins/

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:


<portlet>
    <portlet-name>example</portlet-name>
    <icon>/icon.png</icon>
    <user-notification-definitions>example-user-notification-definitions.xml</user-notification-definitions>
    <user-notification-handler-class>com.example.notifications.ExampleUserNotificationHandler</user-notification-handler-class>		
</portlet>

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" "http://www.liferay.com/dtd/liferay-user-notification-definitions_6_2_0.dtd">
<user-notification-definitions>
	<definition>
		<notification-type>${com.example.notifications.ExampleUserNotificationHandler.PORTLET_ID}</notification-type>
		<description>receive-a-notification-when-example-triggered</description>
		<delivery-type>
			<name>email</name>
			<type>${com.liferay.portal.model.UserNotificationDeliveryConstants.TYPE_EMAIL}</type>
			<default>false</default>
			<modifiable>true</modifiable>
		</delivery-type>
		<delivery-type>
			<name>website</name>
			<type>${com.liferay.portal.model.UserNotificationDeliveryConstants.TYPE_WEBSITE}</type>
			<default>true</default>
			<modifiable>true</modifiable>
		</delivery-type>
	</definition>
</user-notification-definitions>

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!");

    UserNotificationEventLocalServiceUtil.addUserNotificationEvent(user.getUserId(), 
		com.example.notifications.ExampleUserNotificationHandler.PORTLET_ID, 
		(new Date()).getTime(),
		user.getUserId(),
		payloadJSON.toString(),
		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() {

		setPortletId(com.example.notifications.ExampleUserNotificationHandler.PORTLET_ID);

	}

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

		JSONObject jsonObject = JSONFactoryUtil
				.createJSONObject(userNotificationEvent.getPayload());

		long yourCustomEntityId = jsonObject
				.getLong("yourCustomEntityId");

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

		String bodyText = "Some other text.";

		LiferayPortletResponse liferayPortletResponse = serviceContext
				.getLiferayPortletResponse();

		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()));
		confirmURL.setWindowState(WindowState.NORMAL);

		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()));
		ignoreURL.setWindowState(WindowState.NORMAL);

		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;
	}

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

		JSONObject jsonObject = JSONFactoryUtil
				.createJSONObject(userNotificationEvent.getPayload());

		long yourCustomEntityId = jsonObject
				.getLong("yourCustomEntityId");
		
		LiferayPortletResponse liferayPortletResponse = serviceContext
				.getLiferayPortletResponse();		

		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()));
		viewURL.setWindowState(WindowState.NORMAL);
		
		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=\"");
		sb.append("[$IGNORE_URL$]\">[$IGNORE$]</a></div>");
		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()) %>" />
</span>

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!

Liferay portlet file upload tutorial

Here’s a Liferay portlet file upload tutorial to show you how to handle file uploading by clients.
First we need to define our upload form JSP. This can be shown using Liferay MVCPortlet’s default view.jsp.
We will define our upload form as an AlloyUI form element and w

view.jsp


<%@ taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet" %>
<%@ taglib uri="http://liferay.com/tld/aui" prefix="aui"%>
<%@ taglib uri="http://liferay.com/tld/ui" prefix="liferay-ui"%>
<portlet:defineObjects />

<portlet:actionURL name="upload" var="uploadFileURL"></portlet:actionURL>

<aui:form action="<%= uploadFileURL %>" enctype="multipart/form-data" method="post">

	<aui:input type="file" name="fileupload" />
	
	<aui:button name="Save" value="Save" type="submit" />

</aui:form>

And now we need to handle the upload action that we are calling by submiting this form.
Note some important parts that people often miss in their code, like checking if there’s enough room on the device to actually store the file and refuse additional file uploads when the device has less than 1GB of free space.

Full example class


import java.io.File;

import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;

import org.apache.commons.io.FileUtils;

import com.liferay.portal.kernel.upload.UploadPortletRequest;
import com.liferay.portal.util.PortalUtil;
import com.liferay.util.bridges.mvc.MVCPortlet;

public class RelatedFilesPortlet extends MVCPortlet {

	private final static int ONE_GB = 1073741824;
	
	private final static String baseDir = "/tmp/uploaded/";
	
	private final static String fileInputName = "fileupload";

	public void upload(ActionRequest request, ActionResponse response)
			throws Exception {

		UploadPortletRequest uploadRequest = PortalUtil.getUploadPortletRequest(request);

		long sizeInBytes = uploadRequest.getSize(fileInputName);

		if (uploadRequest.getSize(fileInputName) == 0) {
			throw new Exception("Received file is 0 bytes!");
		}

		// Get the uploaded file as a file.
		File uploadedFile = uploadRequest.getFile(fileInputName);

		String sourceFileName = uploadRequest.getFileName(fileInputName);

		
		// Where should we store this file?
		File folder = new File(baseDir);

		// Check minimum 1GB storage space to save new files...
		
		if (folder.getUsableSpace() < ONE_GB) {
			throw new Exception("Out of disk space!");
		}

		// This is our final file path.
		File filePath = new File(folder.getAbsolutePath() + File.separator + sourceFileName);

		// Move the existing temporary file to new location.
		FileUtils.copyFile(uploadedFile, filePath);
	}

}

Java tutorial: Add Portlet to Liferay Control Panel

You can add portlet to liferay control panel. For your custom control panel portlet you have to modify liferay-portlet.xml file and add 2 lines there:

<control-panel-entry-category>portal</control-panel-entry-category>
<control-panel-entry-weight>1.0</control-panel-entry-weight>

The control-panel-entry-category has to be one of “my”, “content”, “portal” or “server” and specifies under which category should the portlet be displayed.
The weight specifies the ordering or the elements under the different categories. It’s a double number, and the higher you specify the lower your portlet is placed. So if you want to appear in the bottom, add “99.0”.
The portlet has to be non-instanceable (the Control Panel may have only such portlets) so you also have to have this line:

<instanceable>false</instanceable>

Please note that Control Panel by default does not allow you to enter CONFIG mode of the portlet. For a work-around you can use

<preferences-unique-per-layout>false</preferences-unique-per-layout>

Full example liferay-portlet.xml

<portlet>
 <portlet-name>hello-world-portlet</portlet-name>
 <icon>/icon.png</icon>
 <control-panel-entry-category>portal</control-panel-entry-category>
 <control-panel-entry-weight>99.0</control-panel-entry-weight>
 <action-url-redirect>true</action-url-redirect>
 <instanceable>false</instanceable>
 <preferences-unique-per-layout>false</preferences-unique-per-layout>
</portlet>

add portlet to liferay control panel

Service Builder String column length in Liferay

Service Builder generated entity String columns have a standard length of 75 characters. Here’s a tutorial on how to change the Service Builder String column length.
If you are looking at the most obvious place like service.xml file then you are wrong. You have to look into portlet-model-hints.xml file.
Now this file is updated when you run service builder, but it will keep your modifications to column entries.

service.xml

<!DOCTYPE service-builder PUBLIC "-//Liferay//DTD Service Builder 6.2.0//EN" "http://www.liferay.com/dtd/liferay-service-builder_6_2_0.dtd">

<service-builder package-path="com.example" auto-namespace-tables="false">
    <author>CodeYouNeed</author>
	<namespace>Example</namespace>
	
	<entity name="Entity" local-service="true" human-name="Entity" table="my_entity" remote-service="true">

		<column name="entityId" type="long" primary="true" />

		<column name="groupId" type="long" />
		<column name="companyId" type="long" />
		<column name="userId" type="long" />
		<column name="userName" type="String" />
		<column name="createDate" type="Date" />
		<column name="modifiedDate" type="Date" />

		<column name="veryLongField" type="String"></column>
		<column name="approvalDate" type="Date"></column>
		<column name="comments" type="String"></column>

	</entity>
	
</service-builder>

portlet-model-hints.xml

Now assuming we have an Entity with fields like veryLongField and comments and approvalDate we can modify the auto-generated portlet-model-hints.xml file to look something like this:

<?xml version="1.0"?>
<model-hints>
	<hint-collection name="TEXTAREA">
		<hint name="display-height">100</hint>
		<hint name="display-width">500</hint>
		<hint name="max-length">32768</hint>
	</hint-collection>
	<hint-collection name="DATEFIELD">
		<hint name="month-nullable">true</hint>
		<hint name="day-nullable">true</hint>
		<hint name="year-nullable">true</hint>
		<hint name="show-time">false</hint>
	</hint-collection>
	<model name="com.example.Entity">
		<field name="entityId" type="long" />
		<field name="groupId" type="long" />
		<field name="companyId" type="long" />
		<field name="userId" type="long" />
		<field name="userName" type="String" />
		<field name="createDate" type="Date" />
		<field name="modifiedDate" type="Date" />
		<field name="veryLongField" type="String">
			<hint name="max-length">255</hint>
		</field>
		<field name="approvalDate" type="Date">
			<hint-collection name="DATEFIELD" />
			<hint name="year-range-delta">90</hint>
			<hint name="year-range-future">true</hint>
		</field>
		<field name="comments" type="String">
			<hint-collection name="TEXTAREA" />
		</field>
	</model>
</model-hints>

Notice how we are defining a hint for max-legth so that the database column would also be 255 characters.
We are also defining a hint-collection to re-use the same hints multiple times.
Once we have run service builder again, the corresponding SQL table files and entity classes are updated to reflect our changes.
In case you are wondering, yes there are few more hints that you can use.

Model Hint Values and Descriptions

Name Value Type Description Default
auto-escape boolean sets whether text values should be escaped via HtmlUtil.escape true
autoSize boolean displays the field in a for scrollable text area false
day-nullable boolean allows the day to be null in a date field false
default-value String sets the default value for a field (empty String)
display-height integer sets the display height of the form field rendered using the aui taglib 15
display-width integer sets the display width of the form field rendered using the aui taglib 350
editor boolean sets whether to provide an editor for the input false
max-length integer sets the maximum column size for SQL file generation 75
month-nullable boolean allows the month to be null in a date field false
secret boolean sets whether hide the characters input by the user false
show-time boolean sets whether to show include time along with the date true
upper-case boolean converts all characters to upper case false
year-nullable boolean allows the year to be null in a date field false
year-range-delta integer specifies the number of years to display from today’s date in a date field rendered with the aui taglib 5
year-range-future boolean sets whether to include future dates true
year-range-past boolean sets whether to include past dates true
Here be dragons
default-day-delta integer ??? ???
default-month-delta integer ??? ???
default-year-delta integer ??? ???
check-tab boolean ??? ???

For the full list of Model Hints for Liferay 6.2 you can take a look at this page in the developer guide: http://www.liferay.com/documentation/liferay-portal/6.2/development/-/ai/use-model-hints-liferay-portal-6-2-dev-guide-04-en

As you can see it’s actually rather simple to modify Service Builder String column length.

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!

Java tutorial on how to make a Portlet

Java tutorial on how to create a simple Hello world Portlet. Let’s start with a simple overview of files. Depending on your project structure and IDE your source code can be structured differently but in all normal cases you will have a WEB-INF folder and inside of that you should have a portlet.xml file. This is the file that defines what Portlets are included in our  application. You can have more than one portlet in one application.

portlet.xml


<?xml version="1.0"?>

<portlet-app xmlns="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd"
	version="2.0">
	<portlet>
		<portlet-name>hello-world-tutorial</portlet-name>
		<display-name>Hello World Tutorial</display-name>
		<portlet-class>com.example.package.HelloWorldPortlet</portlet-class>
		<expiration-cache>0</expiration-cache>
		<supports>
			<mime-type>text/html</mime-type>
			<portlet-mode>view</portlet-mode>
			<window-state>normal</window-state>
		</supports>
		<portlet-info>
			<title>Hello World Tutorial</title>
			<short-title>Hello World Tutorial</short-title>
			<keywords>hello world, tutorial</keywords>
		</portlet-info>
		<security-role-ref>
			<role-name>administrator</role-name>
		</security-role-ref>
		<security-role-ref>
			<role-name>power-user</role-name>
		</security-role-ref>
		<security-role-ref>
			<role-name>user</role-name>
		</security-role-ref>
	</portlet>
</portlet-app>	

Now, we also need that class that we reference in portlet.xml. Every portlet needs a class that acts as the Controller in MVC pattern.

HelloWorldPortlet.java

import java.io.*;
import javax.portlet.*;

public class HelloWorldPortlet extends GenericPortlet {

	protected void doView (RenderRequest request, RenderResponse response) throws PortletException, IOException {
		String url = "/view.jsp";
		getPortletContext().getRequestDispatcher(url).include(request,response);  
	}
}

Finally, we also need the view.jsp file, this should go to the same level with WEB-INF folder.

view.jsp


<%@ taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet" %>
<portlet:defineObjects />

<h2>Hello World</h2>
This is a tutorial portlet. <br />
Your browser seems to be :<b><%=request.getHeader("user-agent")%></b><br />
And we think that your current language is: <b><%=request.getLocale().getDisplayLanguage()%></b> <br />

Have a lovely day.

Please note that we used portlet_2_0 taglib which is JSR 286. If your environment requires an older version, just leave out the _2_0.

Now while this is a perfectly nice portlet, it’s a little bit too simple. For more complex applications I would suggest you use some framework to get a head start. Spring has a lovely Portlet support but you can also use Liferay provided MVCPortlet as your basis for new Portlets, but you can only do that when you develop your applications for Liferay Portal. Frameworks provide both functionality and structure to a project. Struts 2 enforces the MVC pattern and provides lots of functionality, such as custom tags and validating functionality. With Liferay you can use almost any frameworks in the JavaEE space, including Struts, Spring Framework, and JSF.

The benefit of doing a completely vendor-free(Not using any portal specific stuff) portlet with open-source frameworks is that you can deploy the same portlet in either Liferay, JBoss, Websphere Portal or other JSR 286 compliant portals.

Java tutorial

Why use Liferay?

The Good side of Liferay

Liferay is NOT just a Java based CMS. It’s a complete portal environment that’s suitable for large enterprise usage. It should be used as a platform to develop in-house applications or intranet. If you need just a small website to promote something then Liferay is not for you.

Some people compare Liferay with PHP based CMS systems like Joomla or Wordpress but that’s completely wrong. Liferay does contain CMS and it’s not a bad one. It’s rather useful for providing small applications and even better for big applications.

Supporting JSR 168 and JSR 286 means you can deploy Portlet applications that others have developed. Liferay also has a very good support for groups and communities allowing you to build team sites and custom environments for different user-groups with ease. Some cool features that you get with Liferay out of the box are:

  • User and Group management
  • Multi-site support for having different pages and themes for different user groups
  • Proper Access rights and Role management
  • Internationalization
  • Document library management
  • Wiki, Blogging and Forums
  • Service Oriented Architecture
  • Theme support
  • Integrates with other existing systems using LDAP, RSS, iFrames and other technologies

Liferay

 

The Bad side of Liferay

Developing applications and portals for the first time is a taunting task and comes with a rather massive learning curve. If you plan to use Liferay Service Builder and do everything “the Liferay way” then you have to plan probably at least couple of weeks for learning it first.

If you need to develop a small application quickly AND you don’t have an existing Liferay installation already in-house then you can get your application done way faster by NOT using Liferay. But once you have learned Liferay and have enough need for proper portal then Liferay becomes rather irresistible.

When it comes to documentation and tutorials then it’s not the best situation either. It does have a massive developer’s guide. But the Wiki is filled with tons of content that’s rather old and not updated for the latest releases.

The official developer and administration guides are good but often lack information about some crucial parts or fail to provide decent examples. While they claim to be developer friendly I have to admit that some parts of the development platform is severely lacking. Also note that releases are rather infrequent. Also one should be very cautious because early versions are always rather buggy.

AlloyUI that’s provided with Liferay for Javascript and UI is rather difficult to learn and seems also a very large investment time-wise – save yourself the trouble and use jQuery or some other favorite JS library instead.