Liferay 6.2 Integration Tests with Arquillian and maven

At last, after almost two years, I managed finally to make the integration tests work. And it is not so complicated as it seems to be. So straight to the point.

I needed to enabled integration tests in Liferay 6.2EE SP14, but, I think, the steps are the same for all the 6.2 versions (also the CE).

Arquillian is a framework that help us to run our test in the liferay portal, so we don’t need to mock it.

So, we need to configure our installation to work with arguillian. I suppose we have a tomcat installed on our system. We need to make some configurations to that tomcat, so arquillian can communicate with it and deploy its stuff.

Configure Tomcat

Installing Manager
If you look at the webapps directory of your tomcat, per default, the manager web application is missing. You can add it, simple, by downloading the same tomcat version and from its webapps directory you can copy the manager folder back to you tomcats webapps directory.

Setting users
Having the manager web application installed, we need to define a user, that arquillian will be use, to deploy its plugins, by using the manager. To achieve this, we need to define a user with the appropriate roles. So, we need to edit the file tomcat-users.xml in TOMCAT_DIR/conf directory.

In there we need to insert the following xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0"?>
<tomcat-users>
        <role rolename="tomcat" />
        <role rolename="manager-gui" />
        <role rolename="manager-script" />
        <role rolename="manager-jmx" />
        <role rolename="manager-status" />
        <user
              password="tomcat"
              roles="tomcat,manager-gui,manager-script,manager-jmx,manager-status"
              username="tomcat"
       />
</tomcat-users>

Enabling JMX
Now, we need to enable JMX in tomcat. We edit the setenv.sh (or setenv.bat for windows) file in TOMCAT_DIR/bin directory. I replaced the content with the following:

1
CATALINA_OPTS="$CATALINA_OPTS -Dfile.encoding=UTF8 -Djava.net.preferIPv4Stack=true  -Dorg.apache.catalina.loader.WebappClassLoader.ENABLE_CLEAR_REFERENCES=false -Duser.timezone=GMT -Xmx1024m -XX:MaxPermSize=256m -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.port=8099 -Dcom.sun.management.jmxremote.ssl=false"

Now, the tomcat configuration has been set.

Configure Maven

In our plugin we need to add some dependencies.

We will use the arquillian-liferay-maven-extension.

In the plugins pom.xml file I added the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
        ...
        </build>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.jboss.arquillian</groupId>
                <artifactId>arquillian-bom</artifactId>
                <version>1.1.5.Final</version>
                <scope>test</scope>
                <type>pom</type>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.arquillian.liferay.maven</groupId>
            <artifactId>arquillian-liferay-maven-extension</artifactId>
            <version>1.0.0.Alpha2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.jboss.arquillian.junit</groupId>
            <artifactId>arquillian-junit-container</artifactId>
            <version>1.1.5.Final</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.jboss.arquillian.container</groupId>
            <artifactId>arquillian-tomcat-remote-7</artifactId>
            <version>1.0.0.CR7</version>
            <scope>test</scope>
        </dependency>
                ....
       <dependencies>
       ...

Configure Arquillian

We need to give arquillian the information, where it can run the tests. So, we add the file arquillian.xml in the folder src/test/java/integration. In the file we add following XML:

1
2
3
4
5
6
7
8
9
10
11
12
13
<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://jboss.org/schema/arquillian"
xsi:schemaLocation="http://jboss.org/schema/arquillian http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
        <container qualifier="tomcat" default="true">
                <configuration>
                        <property name="jmxPort">8099</property>
                        <property name="host">localhost</property>
                        <property name="httpPort">8080</property>
                        <property name="user">tomcat</property>
                        <property name="pass">tomcat</property>
                </configuration>
        </container>
</arquillian>

Here we are using all the previous things we configured.

The integration test

Now, we are ready to write our integration test. Lets assume, we have a custom Entity with the name SomeEntity. Lets try the following in our plugin project. We create a new class SomeEntityLocalServiceUtilTest in src/test/java/somewhere/service/impl.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import somewhere.service.model.SomeEntity;
import somewhere.service.service.SomeEntityLocalServiceUtil;

import org.junit.After;
import org.junit.Assert;
import org.junit.Before;

@RunWith(Arquillian.class)
public class SomeEntityLocalServiceUtilTest {

    @Test
    public void test() {

        int count = SomeEntityLocalServiceUtil.getSomeEntitiesCount();

        Assert.assertTrue(count == 0);
    }
}

Run the test

At last, we can now see, if our test works. Don’t forget, the tomcat should be running. Let’s run the test:

1
mvn test

We should see how arquillian builds the plugin, deploys it on the portal, run the test and undeploys it again.

Notes

  • It seems to be correct to have a test server to run your tests. Not only for the db data, but arquillian deploys and then undeploys the plugins you want to test. So, while developing, perhaps you make also your own deployments in the portal, and this could create unwanted conflicts.
  • Sometimes I get java.lang.OutOfMemoryError: PermGen space. I didn’t solve it yet. It seems, that arquillian starts a jvm (with surefire), and I couldn’t find a way to define memory parameters there.
  • Running the tests from jenkins, there was a problem with a library, so I needed to add the following dependency in the pom.xml file:
    1
    2
    3
    4
    5
            <dependency>
                <groupId>biz.aQute.bnd</groupId>
                <artifactId>bnd</artifactId>
                <version>2.4.0</version>
            </dependency>

Generating preview image thumbnails with liferay default configuration

This seems to be, even now, a subject without a specific documentation. The need of thumbnails is very common especially if you are showing content with images in a public url with a lot of traffic.

Liferay comes with PDFBox build in, that can create images for certain image types. Of course, you can use a lot of extra tools like ImageMagick, or Xuggler to create more sophisticated thumbnails, not only for images, but also for pdfs, documents etc. Have a look here.

In our case, the needs are small. We have jpg images, and we need to have two custom thumbnails for them:

  • 180 x 252
  • 280 x 187

Also, we have already a URL in some of our portlets, that has the form:

1
/portal/documents/178201/495116/image.jpg/ac34c05e-5ad5-4704-b006-7cdd182dc1a3?version=1.0&t=1464246512402

This url is generated by the class

1
String com.liferay.portlet.documentlibrary.util.DLUtil.getPreviewURL(FileEntry fileEntry, FileVersion fileVersion, ThemeDisplay themeDisplay, String queryString, boolean appendVersion, boolean absoluteURL)

After a lot of reading in the forum, and after a lot of trying, I came to this conclusion (without being very confident about it):

  • First thing is to define your thumbnails in the portal-ext.properties:

    1
    2
    3
    4
    dl.file.entry.thumbnail.custom1.max.width=180
    dl.file.entry.thumbnail.custom2.max.height=252
    dl.file.entry.thumbnail.custom2.max.width=280
    dl.file.entry.thumbnail.custom2.max.height=187
  • We add the parameter imageThumbnail in out url with the value 2 for the custom1 thumbnail and with the value 3 for the custom2 thumbnail. So our url now, looks like

    1
    /portal/documents/178201/495116/image.jpg/ac34c05e-5ad5-4704-b006-7cdd182dc1a3?version=1.0&t=1464246512402&imageThumbnail=2

It is so simple.

Questions:

Ok, but in all articles I ‘ve read, everybody is defining only 2 custom thumbnails. What if I want to defined 6 of them?
Hm, I don’t know the answer. Everything I tried, didn’t worked out. Defining another custom3 thumbnails in the properties, didn’t work with adding the value 4 in the imageThumbnail parameter.

Havig a better look at the class com.liferay.portlet.documentlibrary.util.DLPreviewableProcessor, it seems that you can use only those two properties, and no more than this. So, the number of the optionally custom thumbnails are exact 2.

There are two properties dl.file.entry.thumbnail.max.width and dl.file.entry.thumbnail.max.height. What if I change them?
Changing them, would affect the thumbnails in the document & media portlet in the control panel. It would eventually break the layout.

How can I recreate the thumbnails of my images?
There is a button in the control panel / Configuration / Server Administration / Resources with the name “Reset preview and thumbnail files for Documents and Media portlet.”
The description for that is:

Reset preview and thumbnail files for the Documents and Media portlet: You can send in a request to reset the preview and thumbnail files for each item in your portal’s Documents and Media libraries.

The code that is running in the background. deletes two directories in the file system:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    public static void deleteFiles() {
        long[] companyIds = PortalUtil.getCompanyIds();

        for (long companyId : companyIds) {
            try {
                DLStoreUtil.deleteDirectory(
                    companyId, REPOSITORY_ID, PREVIEW_PATH);
            }
            catch (Exception e) {
            }

            try {
                DLStoreUtil.deleteDirectory(
                    companyId, REPOSITORY_ID, THUMBNAIL_PATH);
            }
            catch (Exception e) {
            }
        }
    }

If you want to generate them programmatically, you could use the following method:

1
ImageProcessorUtil.generateImages(null, imageFileEntry.getFileVersion());

If you need to check the existence of the thumbnails for a specific image you could call the following method:

1
ImageProcessorUtil.hasImages(imageFileEntry.getFileVersion())

Some sources from the forum

Make your control panel custom portlet visible per site

Lets us assume we want to have an admin portlet in the content area in our site. We can do this by adding the following xml elements in the liferay-portlet.xml

1
2
3
4
5
6
...
        <control-panel-entry-category>
            content
        </control-panel-entry-category>
        <control-panel-entry-weight>1.5</control-panel-entry-weight>
...

But then we notice that this portlet exists also in the other sites. And we don’t want that.

The solution to this problem is to add a class that implements the ControlPanelEntry interface.

This interface declares 3 methods that can allow you to restrict the visibility of the menu item:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
        @Override
    public boolean hasAccessPermission(PermissionChecker permissionChecker, Group group, Portlet portlet)
        throws Exception {

        if (MY_GROUP_ID == group.getGroupId()) {
            return true;
        }
        return false;
    }

    @Override
    public boolean isVisible(PermissionChecker permissionChecker, Portlet portlet)
        throws Exception {

        return true;
    }

    @Override
    public boolean isVisible(Portlet portlet, String category, ThemeDisplay themeDisplay)
        throws Exception {

        if (MY_GROUP_ID == themeDisplay.getScopeGroupId()) {
            return true;
        }

        return false;
    }

This class must be also declared in liferay-portlet.xml like following

1
2
3
4
5
6
7
...
        <control-panel-entry-category>
            content
        </control-panel-entry-category>
        <control-panel-entry-weight>1.5</control-panel-entry-weight>
        <control-panel-entry-class>your.entry.ClassHere</control-panel-entry-class>
...

Getting the preview URL of a File Entry

If you are working in a custom portlet and with FileEntries, it is very likely that you will need the preview url of this file, to use it in your UI.

There is a class with the name DLUtil that provide you with a method for this.

But, is you don’t have the themeDisplay (because, for example, you are writing the code of a scheduler job), you can’t use this class. You have to do this alone:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
         public String getFileUrl(FileEntry fileEntry, boolean appendToken) throws Exception{

        try {
            StringBundler sb = new StringBundler();

            sb.append("/documents/");
            sb.append(fileEntry.getRepositoryId());
            sb.append(StringPool.SLASH);
            sb.append(fileEntry.getFolderId());
            sb.append(StringPool.SLASH);
            sb.append(HttpUtil.encodeURL(HtmlUtil.unescape(fileEntry.getTitle()), true));
            sb.append("?version=");
            sb.append(fileEntry.getFileVersion().getVersion());

            if (appendToken) {
                sb.append("&t=");

                Date modifiedDate = fileEntry.getFileVersion().getModifiedDate();

                sb.append(modifiedDate.getTime());
            }

            return sb.toString();
        }
        catch (PortalException | SystemException e) {
            throw e
        }

        return null;
    }

Sass::SyntaxError: File to import not found or unreadable: compass.

If you see the error

1
Sass::SyntaxError: File to import not found or unreadable: compass.

while building your theme, search to find your temp folder and empty it. In the next build, the error should be gone.

But, what is this error? The building process unzips a ruby-gems.jar in the temp dir to get some scripts for the parsing of the css files. This alone is not a problem. But if you have two liferay instances in your system, and they have two different versions of this jar, this could be a problem, because the build script will try to use the version of the other liferay instance (of an old build from the other instance).

P.S. The temp directory in a linux machine is per default the “/temp”. You can change it using

1
-Djava.io.tmpdir=/new/tmp/dir

In our case (at least in liferay 6.1) we tried to give ant this parameter but the task that does the work with ruby-gems.jar was running a new java vm and didn’t pass the temp parameter to it, so this new vm was using the machines default temp directory. Perhaps somebody could change the build.xml to use a specific temp directory.

Liferay Kaleo workflows: How to get rid of no-reply@liferay.com

When a kaleo workflow in Liferay 6.2 sends an email, it adds automatically as the sender the email no-reply@liferay.com. If you want to change that, you can’t… at least not directly through the UI of control panel.

You have to go to the workflow XML and change some things there. In the actions element, and above the notification element add a new action element like this:

1
2
3
4
5
6
7
8
9
10
11
12
<action>
    <name>setNotificationStuff onEntry</name>
    <script>

workflowContext.put(Packages.com.liferay.portal.kernel.workflow.WorkflowConstants.CONTEXT_NOTIFICATION_SENDER_ADDRESS,­"liferay@cosmo-one.gr");  

workflowContext.put(Packages.com.liferay.portal.kernel.workflow.WorkflowConstants.CONTEXT_NOTIFICATION_S­ENDER_NAME,"DEI-DSA");

    </script>
    <script-language>javascript</script-language>
    <execution-type>onEntry</execution-type>
</action>

Add as many execution types you need.
Have a look also here.

How to lose time with something you can’t see…

While working I encounter a strange exception:

java.net.URISyntaxException: Illegal character in query at index 79: http:// ...

Strange, because there was no special character in my URL. The index 79 was here:

&v​4003=
  ^

I couldn’t figure out what the problem was… Then I decided to try something out:

System.out.println(URLEncoder.encode("v​4003=", "UTF-8"));

And the output was

v%E2%80%8B4003%3D

So, what is this %E2%80%8B? After so many years I learned for the first time that this is the zero width space character. A very devious character, really!

So, how did this character came in my URL? It was a result of (of course) a copy paste from the google docs. I was too lazy to write 4000 with the keyboard. And laziness is something that always creates strange problems.

Internal Compiler Error while building a liferay 6.1.10 plugin

The problem
I recently encounter a strange problem on a functioning project. I made a svn branch of it, and then I tried to build the branch in the same sdk, but it failed. And it failled with an internal compiler error: NullPointerException. This was very strange. The exact error was:

[javac] 1. ERROR in /XXXX/liferay-plugins-sdk-6.1.10-ee-ga1/portlets/XXXX-portlet/docroot/WEB-INF/src/XXXX/ObjectFactory.java (at line 0)
[javac]
[javac]     ^
[javac] Internal compiler error
[javac] java.lang.NullPointerException
[javac]     at org.eclipse.jdt.internal.compiler.ast.SingleTypeReference.getTypeBinding(SingleTypeReference.java:44)
[javac]     at org.eclipse.jdt.internal.compiler.ast.TypeReference.internalResolveType(TypeReference.java:130)
[javac]     at org.eclipse.jdt.internal.compiler.ast.TypeReference.resolveType(TypeReference.java:197)
[javac]     at org.eclipse.jdt.internal.compiler.ast.TypeReference.resolveType(TypeReference.java:193)
[javac]     at org.eclipse.jdt.internal.compiler.ast.Annotation.resolveType(Annotation.java:231)
[javac]     at org.eclipse.jdt.internal.compiler.ast.ASTNode.resolveAnnotations(ASTNode.java:594)
[javac]     at org.eclipse.jdt.internal.compiler.apt.dispatch.AnnotationDiscoveryVisitor.resolveAnnotations(AnnotationDiscoveryVisitor.java:143)
[javac]     at org.eclipse.jdt.internal.compiler.apt.dispatch.AnnotationDiscoveryVisitor.visit(AnnotationDiscoveryVisitor.java:131)
[javac]     at org.eclipse.jdt.internal.compiler.ast.TypeDeclaration.traverse(TypeDeclaration.java:1198)
[javac]     at org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration.traverse(CompilationUnitDeclaration.java:687)
[javac]     at org.eclipse.jdt.internal.compiler.apt.dispatch.RoundEnvImpl.(RoundEnvImpl.java:56)
[javac]     at org.eclipse.jdt.internal.compiler.apt.dispatch.BaseAnnotationProcessorManager.processAnnotations(BaseAnnotationProcessorManager.java:148)
[javac]     at org.eclipse.jdt.internal.compiler.Compiler.processAnnotations(Compiler.java:794)
[javac]     at org.eclipse.jdt.internal.compiler.Compiler.compile(Compiler.java:423)
[javac]     at org.eclipse.jdt.internal.compiler.batch.Main.performCompilation(Main.java:3543)
[javac]     at org.eclipse.jdt.internal.compiler.batch.Main.compile(Main.java:1645)
[javac]     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
[javac]     at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
[javac]     at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
[javac]     at java.lang.reflect.Method.invoke(Method.java:597)
[javac]     at org.eclipse.jdt.core.JDTCompilerAdapter.execute(JDTCompilerAdapter.java:79)

NullPointerException and at line 0? Where should I search…? The liferay sdk is using per default the eclipse java compiler, which is the ecj.jar included in the lib dir of the sdk directory. And it seems to be a very old version.
Even the eclipse guys didn’t know about it.

A simple update of the ecj.jar to the newest version was the solution of the problem of the compilation error.

The solution
Updating the ecj.jar in the liferay SDK includes the following steps:

  1. replace the file ecj.jar with the newest one,
  2. delete the ecj.jar from the ant lib directory,
  3. run the ant war task to copy the new ecj.jar int the ant lib directory (it will seem like an error, but it isn’t),
  4. run the ant war again to build your plugin.
But…
Although the problem is solved I am not happy. Something is still bothering me: Why did the original version of the same plugin compile without errors? It had the same code/files. If someone has an idea, please tell me.