Liferay - The Missing Parts: web content article priority

When working with the web content management part of Liferay you quickly start using the Asset Publisher because of its versatility. This portlet enables you to gather and present different kinds of content in a multitude of ways. It can gather content in a dynamic way, by means of a search in a certain scope, on one or more asset type, filtering on tags/categories, or manually, where you select a fixed set of content items yourself.

When selecting content dynamically you're able to define how the results will be sorted depending on certain fields and when selecting manually you define the order yourself. What you will quickly discover however is that sometimes you need something in between: you want to do a dynamic selection, but have a more specify way to define the order. The easiest way would be to use an already existing field where you could enter some sort of number, a weight, that will define how content needs to be sorted. When looking at the Asset Publisher configuration it seems that there is already something for that: a field with the name priority. Only when you try to use this, you'll notice that there is no way, except directly in the database, to fill in the priority on a web content article.

This blog post will show how you can easily provide a way to fill in this field so that it can be used in the Asset Publisher to get articles sorted in the way we want. A simple hook can be used to add this missing functionality to Liferay. In this hook we'll need to 2 of the most used Liferay extension mechanisms, JSP overrides & service overrides, to reach our goal. The JSP override consists of two parts: one adds a priority menu item to the list of actions you see on the right of an article when editing it:

...
if (Validator.isNotNull(toLanguageId)) {
    mainSections = PropsValues.JOURNAL_ARTICLE_FORM_TRANSLATE;
} else if ((article != null) && (article.getId() > 0)) {
    mainSections = PropsValues.JOURNAL_ARTICLE_FORM_UPDATE;
} else if (classNameId > JournalArticleConstants.CLASSNAME_ID_DEFAULT) {
    mainSections = PropsValues.JOURNAL_ARTICLE_FORM_DEFAULT_VALUES;
}

// Add priority section
mainSections = ArrayUtil.append(mainSections, "priority");

String[][] categorySections = {mainSections};
...

and the other is a small JSPs that is used to show the actual priority input field that can be filled in by the user (that also has some validation as the priority is supposed to be a positive double).

<%@ include file="/html/portlet/journal/init.jsp" %>
<%
   JournalArticle article = (JournalArticle) request.getAttribute(WebKeys.JOURNAL_ARTICLE);

   String priority = "0";
   if (article != null) {
      AssetEntry assetEntry = AssetEntryLocalServiceUtil.getEntry(JournalArticle.class.getName(), article.getResourcePrimKey());
      priority = Double.toString(assetEntry.getPriority());
   }
%>

<aui:input name="priority" type="text" value="<%= priority %>">
   <aui:validator name="number" />
   <aui:validator name="min">[0]</aui:validator>
</aui:input>

Once these JSPs are in place we also need to override Liferay's JournalArticleLocalService so that we can actually store the priority value once the user has filled it in. This means providing an implementation that extends from JournalArticleLocalServiceWrapper (some method parameters have been replaced by ... in the code below for readability reasons):

package be.aca.hook.service;

import com.liferay.portal.kernel.exception.PortalException;
import com.liferay.portal.kernel.exception.SystemException;
import com.liferay.portal.service.ServiceContext;
import com.liferay.portlet.asset.model.AssetEntry;
import com.liferay.portlet.asset.service.AssetEntryLocalServiceUtil;
import com.liferay.portlet.journal.model.JournalArticle;
import com.liferay.portlet.journal.service.JournalArticleLocalService;
import com.liferay.portlet.journal.service.JournalArticleLocalServiceWrapper;
import java.io.File;
import java.util.Locale;
import java.util.Map;

public class JournalArticleLocalServiceImpl extends JournalArticleLocalServiceWrapper {

    private static final String PRIORITY = "priority";

    public JournalArticleLocalServiceImpl(JournalArticleLocalService journalArticleLocalService) {
        super(journalArticleLocalService);
    }

    @Override
    public JournalArticle addArticle(long userId, long groupId, long folderId, long classNameId, ..., ServiceContext serviceContext) throws PortalException, SystemException {
        JournalArticle article = super.addArticle(userId, groupId, folderId, classNameId, ..., serviceContext);
        setPriority(article, serviceContext);
        return article;
    }
   
    @Override
    public JournalArticle updateArticle(long userId, long groupId, long folderId, String articleId, ..., ServiceContext serviceContext) throws PortalException, SystemException {
        JournalArticle article = super.updateArticle(userId, groupId, folderId, articleId, ..., serviceContext);
        setPriority(article, serviceContext);
        return article;
    } 

    @Override
    public JournalArticle updateArticle(long userId, long groupId, long folderId, String articleId, ..., ServiceContext serviceContext) throws PortalException, SystemException {
        JournalArticle article = super.updateArticle(userId, groupId, folderId, articleId, ..., serviceContext);
        setPriority(article, serviceContext);
        return article;
    }
 
    private double getPriority(ServiceContext serviceContext) {
        String priority = (String) serviceContext.getAttribute(PRIORITY);
        return priority != null ? Double.parseDouble(priority) : 0;
    }  

    private void setPriority(JournalArticle article, ServiceContext serviceContext) throws SystemException, PortalException {
        double priority = getPriority(serviceContext);
        AssetEntry assetEntry = AssetEntryLocalServiceUtil.getEntry(JournalArticle.class.getName(), article.getResourcePrimKey());
        assetEntry.setPriority(priority);
        AssetEntryLocalServiceUtil.updateAssetEntry(assetEntry);
    }
} 

and reference it in the liferay-hook.xml together with the location of the overridden JSPs

<?xml version="1.0"?>
<!DOCTYPE hook PUBLIC "-//Liferay//DTD Hook 6.1.0//EN" "http://www.liferay.com/dtd/liferay-hook_6_1_0.dtd">
<hook>
    <custom-jsp-dir>/jsps</custom-jsp-dir>
    <service>
        <service-type>com.liferay.portlet.journal.service.JournalArticleLocalService</service-type>
        <service-impl>be.aca.hook.service.JournalArticleLocalServiceImpl</service-impl>
    </service>
</hook>

You can see the service is pretty simple and just takes the article that was just created/edited, finds the corresponding AssetEntry and sets the priority that was extracted from the ServiceContext on it. In a similar way, by using an overridden JSP/service combination, you could also add something similar to documents (or any other Liferay object that has a linked AssetEntry). When deploying this hook you now have the option to fill in a priority on an article when creating or editing it by going to the Priority menu on the right hand side of the screen and filling in the input field. When you now configure the Asset Publisher to use this value for sorting you will actually get the behavior you would expect of the portlet in the first place (without customisation that is).

The code for this post can be found on GitHub: priority-hook

Blogs
Good, finally someone smart like you solves this annoying missing feature.
But the mistery remains..why it was missing?
Thank you and that's a great question! I guess they haven't had a need for it and ordered/sorted their articles in some other way? Would be nice to hear from someone at Liferay what the actual reason is.
Hi guys,

I am sorry I can not solve the mistery either, but I guess that the priority was added to asset publisher at some point as part of the asset framework but none of the apps is leveraging it so far.

As you mentioned, this would be really easy to include in the product itself. Jan, would you be willing to contribute this feature to the Portal for 7.0? I would be very happy to review your pull request! emoticon
@Julio:

I definitely would like to give it a try, but I guess you want it integrated in Liferay and not as a hook like it is now?
Hi Jan,

yes, that would be awesome.

Otherwise, if it gets complicated, send me what you can do and I will adapt it. I have created a Story so that we can keep track of this improvement: https://issues.liferay.com/browse/LPS-58412

Thank you!
Hi Julio,

It took me quite some time to get liferay-portal cloned... but I got there in the end. Now I'm looking for some information about building Liferay 7, but can't seem to find a lot.

I think I found a good place to add the priority stuff in the existing code, the updateAsset method in the Journal Service stuff, but now I'm wondering how to only get my small changes deployed without needing to do ant all? Is there a way to build and update/refresh a certain module?
Hi Julio,

I managed to get a bit further and found out how to only build/deploy a module. I think I can solve the priority stuff by changing the updateAsset method signature in the Journal module to have a priority parameter. This also means I have to change the Asset service a bit so that it accepts a Double priority instead of an Integer (strange choice as the priority field is already a double).

With these changes I'm able to retrieve the priority from the serviceContext when a journal article is added/updated, pass it onto the updateAsset which will pass it on to the existing AssetEntry stuff (now just as a Double instead of an Integer).

I also found out I can add the priority menu item using 2 portal properties instead of overwriting a JSP (something I will have to change in my blog post).

So getting there... slowly.
And thanks to Jan, this feature will be part of Liferay 7!! Thanks a ton Jan, your contribution was awesome! emoticon

https://issues.liferay.com/browse/LPS-58412

It will actually be available in the Alpha Release that will be published in the next weeks.
By the way, if anyone wants to contribute the priority field to other entities (documents, bookmarks, blog entries...) we will be extremely happy to accept that contribution ;)
I have added support in my own project for documents priority, but it was much trickier because of the order Liferay creates asset entries after each document.
However the big problem in 6.2.x is that the values for priority is not imported / exported in LAR (therefore staging is affected as well). I would really appreciate a backport of this feature for 6.2.x. Cheers
Hi Olivier, thanks for your feedback!

Can you explain the problem you had with documents? Would you be willing to contribute that? It should be much easier after Jan's changes.

Regarding the Export/Import, that is a very good point. I am not sure we even did that for 7.0... I will check that out!

Regarding the backport, we never backport features to previous branches. Only bug fixes. You can achieve this feature using a hook for 6.2.x though, as Jan explained in this post.

Thanks a lot!!
Hi Julio,

The case with web content is straightforward just override the method addArticle and updateArticle of JournalArticleLocalService.
For Documents, while updateFileEntry in DLFileEntryLocalService does the trick, overriding addFileEntry does not work because when the method finishes the asset related to the file entry does not yet exist. The assets are created in a higher level service, so I add to override addFileEntry of DLAppService.
Anyway, this feature is unusable for me since I use staging, these values are not kept. Unless you know a way to extend the behavior of staging/LAR ?

Thank you.
Thank you Olivier, I think you did the right thing with the DLApp Service. Will you be willing to contribute that as Jan did with Web Content? We will review your changes and make sure they work, so don't worry if you are not sure if they are correct.

Regarding the import/export, I just verified it is currently not exported/imported in 7.0 either. We are going to implement that as part of this Story: https://issues.liferay.com/browse/LPS-59863

In 6.2 you can extend the behavior of the Data Handlers using an EXT Plugin. You will ned to override the liferay-portlet.xml to use a new JournalArticleStagedModelDataHandler in the Journal Portlet. This new data handler can extend the existing one and you only need to override the methods you want with the custom logic for the priority.

Thanks a lot!
By default the priority value is 0. I would like that the value would be 1. In this way you can increase or decrease the value, not only increase.

hi sir

i use 7.2 and i need to change the value of priority to character not number such us "high" instead of "0"

how i can do this please?