Implementing UpgradingProcess for your Liferay Portlets!

This is a short 2 step (rather large step) tutorial on how to implement a way to upgrade your Portlets using hooks and Liferay's UpgradeProcess. I'll also assume that you've created your portlet in our Plugins SDK.

Step 1 - Settings

First, we'll need to modify liferay-hook.xml to point to a portal.properties so that we can tell Liferay what the build number of our portlet is and what classes to run as part of our upgrade process. After we define some properties, Liferay will take care of the rest and the upgrade will be trigged when we deploy our portlet. So to begin, navigate to: my-portlet/docroot/WEB-INF/ and find liferay-hook.xml.

If you do not have one create one! Then copy and paste the below lines in. If you already have a liferay-hook.xml. Make sure that the portal-properties element is set.

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

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

Next navigate to your portal.properties. If you've just created your liferay-hook.xml and you've used the code above, you'll need to create one at my-portlet/docroot/WEB-INF/src/ In this file, we'll set the following:

release.info.build.number=110
release.info.previous.build.number=100

upgrade.processes=\
    park.ryan.hook.upgrade.UpgradeProcess_1_1_0

Read below for the descriptions for each property.

release.info.build.number Set this value to the current build number of your portlet. Hopefully your projects build number increases as you release because making this value decrease is specifically coded to not work :) In other words, Liferay assumes that the latest build will have a higher build number and your portlet not upgrade if the build number of the newly deployed portlet is less than that of the old portlet.

release.info.previous.build.number For portlets that do not have a release build number already stored in Liferay, this is the value that your newly deployed portlet will assume the old portlet's build number was. Therefore, set this value to the build number of the previously deployed portlet thereby causing the proper upgrade classes to run. For new deployments, I recommend that you set it to the same value as release.info.build.number so that no upgrade classes run.

upgrade.processes This is a comma delimited list of classes that will run when you deploy your portlet according to the build number associated with the class. Please put your classes from least to greatest as they will run in order. This is important if you want to upgrade from build 100 to 150 because running them out of order will cause errors if the running script depends on the last script.

What we can assume from the sample portal.properties file is that the upgrade process is to assume that the previous build number was 100 if it is not already defined by the last portlet deployed. We also see that the current build number is 110 and that we expect park.ryan.hook.upgrade.UpgradeProcess_1_1_0 to run if the build number is less than 110.

Step 2 - Upgrade Classes

Next we'll discuss the UpgradeProcess_1_1_0 class. This is the class that we told Liferay to run as part of our upgrade process in the property, upgrade.processes. So let us create a Java file in /docroot/WEB-INF/src/park/ryan/hook/upgrade/ and name it UpgradeProcess_1_1_0.java.

package park.ryan.hook.upgrade;

import com.liferay.portal.kernel.upgrade.UpgradeProcess;

public class UpgradeProcess_1_1_0 extends UpgradeProcess {
	public int getThreshold() {
		return 110;
	}

	protected void doUpgrade() throws Exception {
		// your upgrade code here.
	}
}

Without getting into the workings of Java, your upgrade class will extend the Liferay's abstract class UpgradeProcess. 2 methods need to be overwritten for this class to be useful.

getThreshold() This method should simply return the build number that this class is going to upgrade to. If the current build number of your portlet is greater than this value, this upgrade class will be skipped over and will move to the next one defined by upgrade.processes. After successful completion, the release build number for your portlet will be updated with its new build number. In our example and assuming a build number is not saved, this class will run because the property for the previous build number was set to 100 and after completion of the upgrade the build number will be set to 110.

doUpgrade() Don't write anything in this method, this method will never ever run :)

Just kidding! This method, like its name implies, will do the upgrade. So, all your upgrade code will be entered in here. That's pretty much it for using UpgradeProcess in your portlets but as a bonus I'll give an example on how to add a column to your database and to set a default value.

protected void doUpgrade() throws Exception {
	runSQL("alter table sampleUpgradeTable add sampleUpgradeColumn LONG");
	runSQL("update sampleUpgradeTable set sampleUpgradeColumn = 0");
}

Notice that there are keywords such as LONG that will be translated for the appropriate DB connected to Liferay. For example in mysql, "LONG" will become "bigint". Pretty nifty and simple to implement stuff if you ask me.

A Quick Recap

Defining the appropriate properties in portal.properties will trigger the upgrade process for your portlet. When these keys are defined, Liferay will look for the upgrade classes you've set and written to do the upgrade. After successful completion of the upgrade, the portlet will finish deploying (hopefully to a nicely upgraded portlet).

I'd like to thank you for stopping by! If you need good working examples they can be found in Liferay Portal and some plugins, namely so-hook and so-portlet. You'll need Liferay 6.0 for this cool new feature. Leave comments if you have questions!

Blogs
You're funny Ryan emoticon I got stuck on "Don't write anything in this method, this method will never ever run emoticon". I was like, what is he talking about...
Hi Ryan, thank you.

One question: how do you handle different DBMS when using same SQL script? Should it know DBMS first?

protected void doUpgrade() throws Exception {
runSQL("alter table sampleUpgradeTable add sampleUpgradeColumn LONG");
runSQL("update sampleUpgradeTable set sampleUpgradeColumn = 0");
}
One question about this, if I'm on a development stage and I want the upgrade process to run always, is there any property for getting this done?

Thanks!
Hi Juan,

For allowing code to always run, I would not use the upgrade process because it is designed to run only once. If you'd like to run code at the beginning always I would consider using a StartupAction, see http://www.liferay.com/community/wiki/-/wiki/Main/Portal+Hook+Plugins.
Does the UpgradeProcess.doUpgrade() method get executed under full control Permissions (ie. Administrator SecurityContext) for purposes of using XXXLocalServiceUtil methods to manipulate data?

I'm guessing the answer is yes if it allows access to execute custom SQL statements as in your example, but want to make sure.

Thank you
Hi Barry, everything called under *LocalService will run with full privilege.

If you'd like to access any service with permission checking, you'll need to call the *Service (without Local). However, since there are no users running an upgrade process, you'll likely run in to errors if you do not use *LocalService in your upgrade process.
Hello Ryan,

Thanks for the blog. I just implemented an Upgrade Hook using 7cogs as an example. My upgrade hook doesn't seem to be "fired". I want to add a structure and a template.

Can I do this using the UpgradeProcess, or do I need to use a StartupAction instead?

Best regards,
Joris
Hi Joris,

After you've specified the location of your "portal.properties" file in liferay-hook.xml. Make sure that you've specified:

release.info.build.number=
release.info.previous.build.number=

upgrade.processes=

"release.info.build.number" should be greater than "release.info.previous.build.number" and in your upgrade class the threshold should be set to "release.info.build.number".

See Social Office as an example:

https://github.com/liferay/liferay-plugins/blob/master/hooks/so-hook/docroot/WEB-INF/src/portal.properties
Hi Ryan,

Thanks for your quick reply. I already specified those. It turned out I had the directory structure quite wrong. I am using maven artifacts and the structure thus is a little bit different from the plugins SDK structure. I now implemented a startupaction which adds the hook and template.

thanks!

Joris
Is it possible to use a custome spring context xml in an UpgradeProcess? If it is possible, how i can retrieve it?