Accueil > Frameworks > Liferay : Service Builder et Liferay Portlet

Liferay : Service Builder et Liferay Portlet

Bonjour à tous !

Dans cet article, je vais aborder un ensemble de points expliquant comment créer un portlet pour Liferay, les frameworks disponibles, leurs avantages et inconvénients, questions revenant régulièrement lorsqu’un nouveau portlet est à créer. Pour notre exercice, nous allons nous baser sur la création d’un Wiki avec la possibilité d’ajouter, éditer et supprimer des articles.

Je vais vous parler du Service Builder et du Liferay Portlet MVC, framework le plus couramment utilisé dans la construction de portlets pour Liferay. Ready ? … GO !

Wait, wait … Le Service … Qu’est-ce que c’est ?

Le service Builder est un outil de génération de sources créé par Liferay.

Basé sur Spring IoC et Hibernate, il va nous permettre de créer rapidement le socle de notre portlet en générant un ensemble de models, les scripts SQL associés à ceux-ci pour votre base de données, ainsi que les classes et interfaces des couches de persistence, de services et utilitaires nécessaires à la manipulation des entités souhaitées. Le service Builder va également vous générer les configurations Spring nécessaires au bon fonctionnement de l’ensemble.

 Pré-requis

  • Pour cet exercice, nous prendrons la dernière version en date : Liferay 6.1 GA 2
  • Eclipse + Liferay IDE Plugins.
  • Un serveur tomcat 7 (ou tout conteneur de servlet / serveur d’applications qui vous conviendra).
  • Liferay Plugin SDK 6.1.
  • Apache Ant.
  • Base de données : à votre convenance, pour ma part, j’utilise une simple base HSQL.

Vous trouverez tout les outils relatifs à Liferay sur cette page.

Nous partirons du principe que l’environnement est correctement configuré et les outils déjà installés.

 Comment créer un Service Builder ?

Tout d’abord, créons notre plugin de type Portlet :

File > New Liferay Project > New Liferay Portlet

Donnez à votre plugin le nom « liferay-simple » pour le retrouver facilement. Ensuite, laissez selectionné le « framework Liferay MVC » et Terminez.

Nous allons maintenant générer nos entités. Pour cela nous allons créer le fichier service.xml dans le dossier \docroot\WEB-INF et ajouter ce qui suit :


<!--?xml version="1.0" encoding="UTF-8"?-->
<?xml version="1.0" encoding="UTF-8"? >
<!DOCTYPE service-builder PUBLIC "-//Liferay//DTD Service Builder 6.1.0//EN"
  "http://www.liferay.com/dtd/liferay-service-builder_6_1_0.dtd" >
<service-builder package-path="com.liferay.tutorial.model" >
   <author>author</author>
   <namespace>Formation</namespace>
   <entity name="ArticleWiki" local-service="true" uuid="false">
      <column name="articleId" type="long" primary="true"></column>
      <column name="Content" type="String"></column>
      <column name="parentUuid" type="long"></column>
      <column name="articleName" type="String"></column>
      <column name="createdDate" type="Date"></column>
   </entity>
</service-builder>

Ce fichier contient la définition de notre entité Article. Chaque entité est constituée de colonnes, correspondant à leurs attributs.

Enfin, nous définissons nos services afin qu’ils ne soient accessibles que dans un environnement local, c’est à dire, au sein de notre seul contexte. Nous ne souhaitons pas que nos services soient exposés à l’extérieur de notre portail, nous définissons donc le paramètre “local-service” à “true” et laissons le “remote-service” à sa valeur par défaut, “false”, sans avoir à le préciser. Nous garantissons ainsi un niveau minimum d’exposition de nos services. Dans le cas contraire, un ensemble de Web Services auraient été générés.

Afin de rester dans la logique naturelle de Liferay, nous ne ferons aucune jointure explicite, ou clé étrangère, celles-ci n’étant pas supportées par le Service Builder. Dans le cas ou nous en aurions besoin, d’autres solutions existent.

Les classes générées

Maintenant nous allons générer l’ensemble de nos classes via la tâche ANT build-service présente dans le fichier build.xml de la portlet, utilisez à votre convenance votre console en tapant “ant build-service” ou utilisez les commandes de votre Eclipse.

Comme vous l’avez constaté, un grand nombre de classes ont été générées, nous pouvons distinguer 4 couches au sein de celles-ci :

  • La Couche Model, contenant l’ensemble de nos entités.
  • La couche de persistence, chargée des requêtes et opérations sur nos entités en base de données.
  • La couche de Service, effectuant toutes les opérations techniques et métiers autres que celles des couches précédentes.
  • La couche Utilitaire, cette couche est celle que nous appellerons au sein de nos portlets.

Mais, je veux ajouter de nouvelles méthodes, comment puis-je faire ?

Pas de soucis, Liferay vous permet d’ajouter vos propres fonctions, pour cela, il vous faut déterminer la couche qui sera impactée par vos changements.

Afin de modifier / ajouter une méthode au sein de nos services, certaines classes générées sont modifiables. Prenons l’exemple de notre entité ArticleWiki, les classes suivantes sont éditables :

  • ArticleWikiImpl
  • ArticleWikiServiceImpl
  • ArticleWikiLocalServiceImpl

Dans le cadre de notre exercice, je souhaite récupérer les articles créés du plus récent au plus ancien, je vais donc modifier la classe « ArticleWikiLocalServiceImpl » :


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public List getLatestsArticles(int start, int end){
   List list = new ArrayList();
   /**
   * Création de la requête
   */

   DynamicQuery query = DynamicQueryFactoryUtil.forClass(ArticleWiki.class)
      .addOrder(OrderFactoryUtil.desc("createdDate"));
   /**
   * on définit la limite selon le Search Container
   */

   query.setLimit(start, end);
   try {
      list = articleWikiPersistence.findWithDynamicQuery(query);
   } catch (SystemException e) {
      // N'hésitez pas à logger votre exception.
   }
   return list;
}

Maintenant que j’ai effectué cette modification, je vais de nouveau générer le service builder par la tâche Ant « build-service » afin de pouvoir y accéder depuis la couche Util.

La Portlet

Nous allons créer notre portlet qui va nous permettre d’exploiter les services nouvellement créés. Pour faire simple, je vais créer un simple CRUD sur nos articleWiki.

Avant de commencer …

Afin d’exploiter au mieux les possibilités de Liferay, nous allons inclure au sein de notre portlet différentes dépendances exploitant notamment les Expressions Languages (JSTL) ou encore différentes taglibs (core, liferay-ui pour certains composants, …).

Editez le fichier /docroot/WEB-INF/liferay-plugin-package.properties et ajoutez ce qui suit à sa fin :


1
2
3
4
5
6
7
8
9
portal-dependency-jars=\
jstl-api.jar,\
jstl-impl.jar
portal-dependency-tlds=\
aui.tld,\
c.tld,\
liferay-aui.tld,\
liferay-ui.tld,\
fn.tld

Vous êtes maintenant fin prêt pour démarrer.

Le Controller

Nous allons créer notre controller à partir de maintenant.

Dans notre fichier portlet.xml, il faut modifier la ligne portlet-class par la suivante :


1
<portlet-class>com.liferay.tutorial.controller.MyClassController</portlet-class>

Dans notre controller, ajoutons la méthode doView afin d’ajouter la méthode affichant les articles les plus récents :


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class MyClassController extends MVCPortlet{
  @Override
  public void doView(RenderRequest renderRequest,
     RenderResponse renderResponse) throws IOException, PortletException {
            List list = new ArrayList();
       
        // Nous récupérons le delta de la pagination ainsi que la page courante
        int delta = ParamUtil.get(renderRequest, "delta", 20);
        int cur = ParamUtil.get(renderRequest, "cur", 1);
        int endPage = cur * delta;
               
        try {
                        list = ArticleWikiLocalServiceUtil.getLatestsArticles(endPage - delta, endPage);
            renderRequest.setAttribute("articles", list);
            renderRequest.setAttribute("nbArticles", ArticleWikiLocalServiceUtil.getArticleWikisCount());
        } catch (SystemException e) {
            SessionErrors.add(renderRequest, "error-view");
        }

        super.doView(renderRequest, renderResponse);
  }
}

Maintenant, notre page d’accueil affichera les derniers articles créés.

Les vues et actions

Pour cette partie, nous allons tout simplement créer un CRUD, mais nous allons tout d’abord parler des différentes phases d’exécution d’une portlet.

Petit intermède : Les phases

Selon la JSR-168 (Portlet Specification), 2 phases sont définies, chacune correspondant à un cycle de vie particulier d’une requête :

  • Render Phase : Cette phase est appelée à chaque fois que nous souhaitons définir une vue particulière, un écran, nous l’appellerons suite à une succession de traitements, par exemple.
  • Action Phase : Cette phase permet d’effectuer des traitements spécifiques avant d’avoir à redéfinir la présentation.

La JSR-286 ( Portlet Specification 2.0) va, quand à elle ajouter 2 phases supplémentaires :

  • Event Phase : Elle permet à une portlet d’envoyer et recevoir des événements lors d’une Action Phase. Elle permet, par exemple, d’effectuer de la communication entre portlets.
  • Resource Phase : Grâce à cette phase nous pouvons effectuer des appels au serveur  sans avoir à appeler une render ou une action phase. On s’en servira notamment en cas d’appel serveur en Ajax pour récupérer une information partielle, par exemple.
 On continue…

Tout d’abord, nous allons créer une page commune à toutes les autres pages, elle contiendra tous les imports communs aux différentes pages que nous allons créer dans le dossier /WEB-INF/html (créez le si non présent).

init.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<%@ taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet" %>
<%@ taglib uri="http://java.sun.com/jstl/core_rt" prefix="c" %>
<%@ taglib uri="http://liferay.com/tld/portlet" prefix="liferay-portlet" %>
<%@ taglib uri="http://liferay.com/tld/security" prefix="liferay-security" %>
<%@ taglib uri="http://liferay.com/tld/theme" prefix="liferay-theme" %>
<%@ taglib uri="http://liferay.com/tld/ui" prefix="liferay-ui" %>
<%@ taglib uri="http://liferay.com/tld/util" prefix="liferay-util" %>
<%@ taglib uri="http://alloy.liferay.com/tld/aui" prefix="aui" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>

<%@ page import="com.liferay.portal.util.PortalUtil" %>
<%@ page import="com.liferay.tutorial.model.model.ArticleWiki"%>

<portlet:defineObjects />

Maintenant, la page de visualisation, nous allons utiliser la taglib Liferay Search-Container qui nous permet de créer une table à partir d’une liste de données, chaque ligne contiendra un certain nombre d’informations ainsi qu’un bouton d’action (edit / delete), pour plus de détails sur cette taglib, je vous invite à lire la documentation officielle.

Nous mettons en place la pagination, je vous invite à lire la documentation la concernant.

Nous implémenterons également un bouton de redirection par renderURL afin de permettre à l’utilisateur de créer un nouvel article.

view.jsp

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
36
37
<%@include file="/html/init.jsp" %>

<liferay-ui:success key="success-add" message="article-added"></liferay-ui:success>
<liferay-ui:success key="success-delete" message="article-deleted"></liferay-ui:success>
<liferay-ui:error key="error-add" message="error-adding"/>
<liferay-ui:error key="error-delete" message="error-deleting"/>
<liferay-ui:error key="error-view" message="error-view"/>

<liferay-ui:search-container>
   <liferay-ui:search-container-results
     results="${articles}"
     total="${nbArticles}"
     />
      <liferay-ui:search-container-row
        className="com.liferay.tutorial.model.model.ArticleWiki"
        keyProperty="articleId"
        modelVar="article">
         <liferay-ui:search-container-column-text name="article-name"
           property="articleName" orderable="true"/>
         <liferay-ui:search-container-column-text name="date-parution"
           property="createdDate" orderable="true"/>
         <liferay-ui:search-container-column-text name="content"
           property="content"/>
         <liferay-ui:search-container-column-jsp path="/html/tutorial/actions.jsp" >
         </liferay-ui:search-container-column-jsp>
      </liferay-ui:search-container-row>
      <liferay-ui:search-iterator />
</liferay-ui:search-container>

<aui:button-row>
   <portlet:renderURL var="addArticleURL">
      <portlet:param name="jspPage" value="/html/edit.jsp" />
      <portlet:param name="redirect" value="<%= PortalUtil.getCurrentURL(renderRequest) %>"/>
   </portlet:renderURL>

   <aui:button value="add-Article" onClick="${addArticleURL}"/>
</aui:button-row>

Définissons maintenant la page contenant nos actions.

Afin de pouvoir éditer un Article, nous allons rediriger l’utilisateur vers une autre page prévue à cet effet, edit.jsp, par l’utilisation d’une RenderURL, appelant donc uniquement la phase RENDER.

Quand à la suppression, nous effectuerons uniquement une actionURL, afin d’appeler directement la méthode concernée.

Afin d’effectuer les actions concernant l’article en cours, nous récupérons par la Request, la ligne sur laquelle nous sommes situés au sein de notre Search Container.

actions.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<%@page import="com.liferay.portal.kernel.dao.search.ResultRow"%>
<%@page import="com.liferay.portal.kernel.util.WebKeys"%>
<%@include file="/html/init.jsp" %>
<%
  ResultRow row = (ResultRow) request.getAttribute(WebKeys.SEARCH_CONTAINER_RESULT_ROW);
  ArticleWiki article = (ArticleWiki) row.getObject();
%>
<liferay-ui:icon-menu>
   <portlet:renderURL var="editURL">
      <portlet:param name="jspPage" value="/html/edit.jsp" />
      <portlet:param name="articleId" value="<%= String.valueOf(article.getArticleId()) %>"/>
      <portlet:param name="redirect" value="<%= PortalUtil.getCurrentURL(renderRequest) %>"/>
   </portlet:renderURL>
   <liferay-ui:icon image="edit" url="${editURL}" />

   <portlet:actionURL name="deleteArticle" var="deleteURL">
      <portlet:param name="articleId" value="<%= String.valueOf(article.getArticleId()) %>"/>
      <portlet:param name="redirect" value="<%= PortalUtil.getCurrentURL(renderRequest) %>"/>
   </portlet:actionURL>

   <liferay-ui:icon-delete url="${deleteURL}" />
</liferay-ui:icon-menu>

Créons par la suite, la page d’édition qui nous servira à la fois à créer et éditer un article, en fonction de si nous lui transmettons un articleId ou non.

edit.jsp

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
36
37
38
<%@page import="com.liferay.tutorial.model.service.ArticleWikiLocalServiceUtil"%>
<%@page import="com.liferay.portal.kernel.util.ParamUtil"%>
<%@page import="com.liferay.portal.kernel.util.StringPool" %>
<%@include file="/html/init.jsp" %>

<%
   long articleId = ParamUtil.getLong(request, "articleId", -1);
   ArticleWiki article = null;
   if(articleId > -1)
      article = (ArticleWiki) ArticleWikiLocalServiceUtil.getArticleWiki(articleId);

   request.setAttribute("article", article);
%>

<liferay-ui:header
   backURL="${redirect}"
   title='${ article != null ? article.articleName : "new-article" }'
   />

<portlet:actionURL name='${ article != null ? "updateArticle" : "addArticle" }'
   var="articleUpdateURL" windowState="normal" />

<aui:form action="${articleUpdateURL}" method="POST" name="fm">
   <aui:fieldset>
      <aui:input type="hidden" name="articleId" value='${ article != null ? article.articleId : "" }'/>
      <aui:input type="hidden" name="createdDate"
           value='${ article != null ? article.createdDate : "" }'/>
      <aui:input name="articleName" type="text"
           value='${ article != null ? article.articleName : "" }' />
      <aui:input name="content" type="textarea"
           value='${ article != null ? article.content : "" }'/>
   </aui:fieldset>

   <aui:button-row>
      <aui:button type="submit" />
      <aui:button type="cancel" onClick="${redirect}" />
   </aui:button-row>
</aui:form>

Ajoutons maintenant le resource-bundle nécessaire à nos clés de traduction :


1
<resource-bundle>language_fr</resource-bundle>

Sans oublier au passage de créer nos clés de traduction dans le language_fr.properties définit dans le portlet.xml dans le docroot/WEB-INF/src :


1
2
3
4
5
6
7
8
9
10
article-added=Article Ajouté
 article-deleted=Article Supprimé
 error-adding=Erreur lors de l'ajout
 error-deleting=Erreur lors de la suppression
 error-view=Une erreur est survenue
 add-Article=Ajouter un article
 article-name=Nom de l'article
 date-parution=Date de parution
 content=Contenu
 new-article=Nouvel Article

Ajoutons maintenant les méthodes nécessaires pour ajouter, éditer et supprimer dans notre Controller.

Tout d’abord, céons une methode qui va générer un Article Wiki en fonction de notre PortletRequest:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* méthode de Mapping de notre ArticleWiki
* @param request
* @return un article Wiki contenant les valeurs adéquats
* @throws SystemException
*/

private ArticleWiki createArticleFromRequest(PortletRequest request) throws SystemException {
   ArticleWiki articleWiki = new ArticleWikiImpl();

   articleWiki.setArticleId(ParamUtil.getLong(request, "articleId", CounterLocalServiceUtil.increment()));
   articleWiki.setArticleName(ParamUtil.getString(request, "articleName"));
   articleWiki.setContent(ParamUtil.getString(request, "content"));
   articleWiki.setCreatedDate(ParamUtil.getDate(request, "createdDate", null, new Date()));

   return articleWiki;
}

Nous allons maintenant ajouter toutes nos méthodes d’ajout, d’édition et de suppression dans notre controller.


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
36
37
38
39
40
41
42
43
44
45
46
public void addArticle(ActionRequest request, ActionResponse response) throws IOException{

   ArticleWiki article;
   try {
      article = createArticleFromRequest(request);
      ArticleWikiLocalServiceUtil.addArticleWiki(article);
      SessionMessages.add(request, "success-add");
   } catch (SystemException e) {
      /**
      * On va ajouter en session des messages d'erreurs
      */

      SessionErrors.add(request, "error-add");
   }

   sendRedirect(request, response);
}

public void updateArticle(ActionRequest request, ActionResponse response) throws IOException{

   ArticleWiki article;
   try {
      article = createArticleFromRequest(request);
      ArticleWikiLocalServiceUtil.updateArticleWiki(article);
      SessionMessages.add(request, "success-update");
   } catch (SystemException e) {
      /**
      * On va ajouter en session un message d'erreur
      */

      SessionErrors.add(request, "error-update");
   }

   sendRedirect(request, response);
}

public void deleteArticle(ActionRequest request, ActionResponse response){
   long articleId = ParamUtil.getLong(request, "articleId");

   try {
      ArticleWikiLocalServiceUtil.deleteArticleWiki(articleId);
      SessionMessages.add(request, "success-delete");
   } catch (PortalException e) {
      SessionErrors.add(request, "error-delete");
   } catch (SystemException e) {
      SessionErrors.add(request, "error-delete");
   }
}

Et voilà, nous venons de créer notre premier CRUD. Félicitations !

Bonus

Si vous souhaitez changer la catégorie dans laquelle votre portlet doit apparaître, editez le fichier /docroot/WEB-INF/liferay-display.properties et changez la valeur de name de la catégorie associées à votre portlet.

 

Mais quels sont les intérêts à utiliser le Service Builder ou le Liferay MVC Portlet ?

Avantages :
  • Framework intrinsèquement lié à Liferay.
  • Rapidité de développement.
  • Configuration simple et intuitive ne nécessitant pas de grandes connaissances des Frameworks Spring ou Hibernate pourtant utilisés par Liferay.
Inconvénients :
  • Nécessite une nouvelle génération des services à chaque changement de ceux-ci.
  • Difficultés à maintenir dans le cas d’une portlet suffisamment complexe.
  • Réadaptation complexe lors d’une migration d’une version du portail à une autre. Par exemple, les différentes couches et noms de méthodes diffèrent entre les versions 6.0 et 6.1 de Liferay. Les modifications à impacter sur l’ensemble du portail par la suite peuvent être importantes.

Maintenant, vous possédez toutes les clés pour développer efficacement et rapidement vos services Liferay.

Share
  1. TUSHAR PATEL
    22/09/2014 à 11:04 | #1

    GOOD article can you upload source code for it?

  1. 25/11/2015 à 10:01 | #1
  2. 28/12/2015 à 10:01 | #2
  3. 18/10/2016 à 00:10 | #3
  4. 04/12/2016 à 16:58 | #4
  5. 09/12/2016 à 06:49 | #5
  6. 23/12/2016 à 08:20 | #6
  7. 31/12/2016 à 12:08 | #7
  8. 04/01/2017 à 15:27 | #8
  9. 16/01/2017 à 20:42 | #9
  10. 17/02/2017 à 14:22 | #10
  11. 17/02/2017 à 16:50 | #11
  12. 18/04/2017 à 05:18 | #12