Custom simple captcha in custom portlet for Liferay

In the last month we had to add different captchas in some liferay sites. Some customers were ok with the simple captcha, that comes per default with liferay and some others wanted to have the recaptcha of google, that also comes with liferay (not so default). And there was a third category of clients that weren’t happy with a any of the above mentioned captchas and wanted a third solution.

In this post we will show how to add simple captcha in a form. To integrate simple catch in a form of a custom portlet is described in many articles like this one.

But there is not so many articles that describe how to make a captcha with a custom design and a refresh image button. So, here is a why way to implement it:

In the portlet class we need to change the code in two positions.

First in the serveResource method:

1
2
3
public void serveResource(ResourceRequest request, ResourceResponse response) throws PortletException, IOException {
  CaptchaUtil.serveImage(request, response);
}

The code above returns the image of the captcha.

And second in the action method:

1
2
3
4
5
6
7
8
9
10
public void processAction(ActionRequest actionRequest, ActionResponse actionResponse) throws IOException {
  try {
    CaptchaUtil.check(actionRequest);
  } catch (CaptchaTextException e) {
    SessionErrors.add(actionRequest, CaptchaTextException.class.getName());
  } catch (CaptchaMaxChallengesException e) {
    SessionErrors.add(actionRequest, CaptchaMaxChallengesException.class.getName());
  }
  ...
}

The code above checks the value of the captcha value and throws exceptions if the input is invalid.

Next, the jsp code. We need to write the html that shows the image, the refresh button and the text field. In addition, we need a javascript for the refresh button to replace the image with the new one.

Of course, our code is an example of how the html could be. Its is not a ready to use design.

First, the html:

1
2
3
4
5
6
7
8
9
10
11
12
13
  <%-- The url that returns the image --%>
  <portlet:resourceURL var="captchaURL"></portlet:resourceURL>
  <label><liferay-ui:message key="captcha-text" /></label>
  <div style="float:left;">
    <%-- the name of the field should be captchaText --%>
    <aui:input label="" name="captchaText" size="10" type="text" value="">
      <aui:validator name="required" />
    </aui:input>
  </div>
  <%-- the refresh button %-->
  <a href="#" class="refreshCaptcha captcha-reload" style="text-decoration: none;"></a>
  <%-- the image --%>
  <img style="float:left;padding:0px !important;margin-left:10px;" alt='<liferay-ui:message key="text-to-identify" />' class="captcha" border=0 src="<%= captchaURL + "&" + new Random().nextInt(Integer.MAX_VALUE) %>" />

And the js code:

1
2
3
4
jQuery(".refreshCaptcha").click(function(evt){
  jQuery(".captcha").attr('src', '<%=captchaURL%>&force='+encodeURIComponent(Math.floor(Math.random()*Math.pow(2, 53))));
  evt.preventDefault();
});

A tip to solve many asset category problems

In case you are experiencing a strange behaviour in the Categories portlet of the control panel, like creating a category in vocabulary x and finding it in vocabulary y, you try to rebuild the category tree for this site.

The API of the liferay gives us a method for that job: AssetCategoryLocalServiceUtil.rebuildTree

The method has 2 parameters, but you need to find out the value only for the first one. This parameter is the groupId, which is the id of the site. If you don’t know it you can find it following the steps as described here.

But if you are not a programmer, how can execute this method? The solution is simple. As the Administrator, go to the “Server Administration” and then to the “script” tab. Here you can run scripts in different languages. Select “Beanshell” and in the text area give the following code:

1
2
import com.liferay.portlet.asset.service.AssetCategoryLocalServiceUtil;
AssetCategoryLocalServiceUtil.rebuildTree(type_here_the_groupId, true);

and then type run.

Original PortletConfig in Portlet Configuration

Lets assume you have a Project with its own Language properties and a portlet that have a configuration page.

Normally, if you want to get a literal value from the language properties in the doView method of your portlet you can use the following code:

1
String value = LanguageUtil.get(getPortletConfig(), themeDisplay.getLocale(), "literal-key");

But if you want to do the same in the configuration render method then you will encounter a problem with the portletConfig object, that comes as a parameter in the method.

1
2
3
4
public String render(PortletConfig config, RenderRequest request, RenderResponse response) {
    String value = LanguageUtil.get(config, themeDisplay.getLocale(), "literal-key");
    // ...
}

The reason is that the configuration is a portlet from its own, and the portletConfig that you get is the config of the configuration portlet and not from the original portlet. And the configuration portlet is is not part of your plugin project, so it can’t see the custom language properties that you have.

The solution is simple. Just get the portletConfig of your original portlet. For example use the following code:

1
2
3
4
5
6
String origPortletResource = ParamUtil.getString(request, "portletResource");
HttpServletRequest servletRequest = PortalUtil.getHttpServletRequest(request);
ServletContext servletContext = servletRequest.getSession().getServletContext();
Portlet origPortlet = PortletLocalServiceUtil.getPortletById(origPortletResource);
PortletConfig origConfig = PortletConfigFactoryUtil.create(origPortlet, servletContext);
String value = LanguageUtil.get(origConfig, themeDisplay.getLocale(), "literal-key");

How to define an entity in Service Builder for existing tables

Recently we encounter a case, where the tables of an external database were “moved” as synonyms in the schema of the liferay db. This was made to eliminate the extra connections in the same database but on a different schema (liferay’s service builder can’t handle different schemas in the same database with one datasource. You have to define a new datasource as an external database).

So, having now the tables in the liferay db, we can defined the entities in the service.xml as normal entities. But this has a side effect: liferay will try to create the tables in case they don’t exist. And if they are synonyms, liferay will throw an error and say that another object (the synonym) has the same name with the table that is about to create.

There is a workaround: you can create a fake datasource and define it in the entity in service.xml. This datasource can return the null value, so the service builder will think it is an external db and won’t create any scripts for this entity. But because the datasource is null, the default datasource will be used.

Liferay Deployer (Jenkins plugin)

After posting my last post I made some progress in building the liferay deployer plugin. A simple version of it is ready and you can find it here in github.

Some documentation also added in the readme file.

The idea of this plugin is to use the Remote IDE Connector app (see also my last post) to upload the plugin war file and get back the information if the deployment was successful, or not. It looks like the functionality of the tomcat manager.

If anyone tests it, I would be happy to send me a feedback. The plugin is still under development so any suggestion to improve it is welcoming…

Jenkins + Liferay plugin deployment

In the past I wrote an article about how to use jenkins to deploy your plugins in a remote liferay portal server.

But this solution had a problem. You could never be sure that the plugin was successfully deployed in the portal, because the job just copied the war file in the deploy directory. The rest of the deployment procedure was unknown to the jenkins job.

Searching in the internet didn’t help much, I couldn’t find a better solution. So I decided to create a jenkins plugin, that would communicate with liferay in some way, and get the information of the state of a portlet after the deployment.

But the liferay portal as it is, doesn’t provide this information. Luckily, there is a Remote IDE Connector in the liferay Marketplace (for CE and for EE). The description is:

This app provides remote development and deployment support from Liferay IDE. With this app, you can build Liferay projects in Liferay IDE or Liferay Developer Studio, and deploy the projects to a remote host that has this app installed. Liferay IDE is an extension for the Eclipse platform that supports development of app projects for the Liferay Portal platform. It is available as a set of Eclipse apps installable from an update-site. The latest version supports developing 5 Liferay app types: portlets, hooks, layout templates, themes, and EXT-style plug-ins. Liferay IDE requires the Eclipse Java EE developer package using either Galileo or Helios versions. For details on using Liferay IDE or Developer Studio, visit the “Liferay IDE” chapter of the Liferay User Guide, located at http://liferay.com/documentation. This app installs as a Liferay service.

So, installing this app in you liferay portal creates a new web application that lives together with the portal, which is able to accept war files through http and deploy them in the portal. It is something like the manager in tomcat, but as it is a custom web application it works on any web server. Additionally, you can retrieve information about the state of plugins using REST calls.

This app was the missing chain.

So, now, it is possible to have the precious information, if a portlet was successfully deployed or not. In the near future I will make a new post with a prototype jenkins plugin that does exactly the above stuff.

Future note: this is the next post.