[#if coverImage??]
    [#if coverImage?is_hash]
        [#if coverImage.alt??]
            ${coverImage.alt}
        [/#if]
    [/#if]
[/#if]

Objectifs

Dans cet article, nous allons nous intéresser à la migration de données d’une version d’un portail Liferay source (v6.0) à un portail Liferay de destination généralement plus récent (v7.4).

Liferay étant en perpétuelle évolution, il n’est pas rare que les différentes plateformes aient du mal à suivre le rythme de progression des différentes versions de Liferay. De plus, il est vrai que Liferay est très complexe et il peut s’avérer fastidieux de migrer des données sans connaître les différents liens entre les classes qu’utilise le portail. C’est pourquoi plusieurs outils sont mis à disposition pour faire des migrations de données très importantes.

Ici, nous nous appuierons sur l’outil Talend, qui est spécialisé dans l’intégration de données et va nous permettre de construire des services capables de faire l’exportation de nos données simplement et proprement.

Pour ce faire, je vais m’appuyer sur les données provenant des articles de blog du Liferay source que l’on va migrer dans le Liferay de destination. Bien entendu, il est possible d’adapter les différents composants et les données à exporter. Je précise que ce qui sera présenté ici n’est pas une unique solution.

Pré-requis

  • Quelques bases sur Talend
  • Bonnes connaissances de Liferay

 

Environnement technique utilisé

  • Serveur Liferay de destination (v.7.4)
  • Serveur Liferay source (v.6.0)
  • Talend Open Studio (v.7.1)
  • MySQL 5.1 (source) et 5.7 (detination)
Ceci n'étant pas un cours sur Talend, je n’entrerai pas dans les détails quant à l’utilisation de contexte sur Talend qui permettrait d’utiliser des variables communes entre plusieurs Jobs et donc variabiliser nos composants avec différentes configurations. Si cela peut vous intéresser, n’hésitez pas à poster un commentaire pour un nouvel article !

 

Mise en place d’un job Talend appuyé sur l’exemple de BlogsEntry

 

Etape 1 : Récupération des données du BlogsEntry depuis la base de données source

Dans un premier temps, il est nécessaire de récupérer les données que l’on souhaite importer depuis la base de données source. Pour cela, nous allons utiliser un premier composant Talend : tDBInput

Configurez le tDBInput de manière à ce qu’il se connecte à la base de données de votre Liferay source.

Configuration du composant tDBInput :

Configuration du composant tDBInput

Récupérez tous les champs suivants via une requête SQL :

"SELECT
  `blogsentry`.`uuid_` as uuid,
  `blogsentry`.`entryId`,
  `blogsentry`.`groupId`,
  `blogsentry`.`companyId`,
  `blogsentry`.`createDate`,
  `blogsentry`.`modifiedDate`,
  `blogsentry`.`title`,
  `blogsentry`.`urlTitle`,
  `blogsentry`.`content`,
  `blogsentry`.`displayDate`,
  `blogsentry`.`allowPingbacks`,
  `blogsentry`.`allowTrackbacks`,
  `blogsentry`.`trackbacks`
FROM `blogsentry`
WHERE `blogsentry`.`createDate` > '2022-01-01'"

Pour finir, n’oubliez pas de modifier le schéma du composant pour qu’il puisse envoyer en sortie les données qui nous intéressent.

Schéma final du composant tDBInput :

Schéma final du composant tDBInput

Ce composant va permettre d’initialiser un flux sur lequel nous allons itérer.

Ajoutons maintenant un composant tMap et lions les 2 composants avec une row main. Le tMap va permettre de transformer et de diriger les données reçues vers notre prochaine étape.

Faire l’association de toutes les données reçues dans la tMap avec l’éditeur de mapping.

Données d’entrées du composant tMap :

Données d’entrées du composant tMap

Données de sortie du composant tMap :

​​​​​​​Données de sortie du composant tMap

Modèle conceptuel à la fin de l'étape 1 :

 

Etape 2 : Récupération des catégories et des tags pour le BlogsEntry courant

Dans cette étape, nous allons récupérer les catégories et les tags Liferay du BlogsEntry sur lequel nous sommes en train d’itérer.

Pour cela, ajoutons un composant tDBRow que l’on va configurer de la même manière que le tDBInput… à l’exception du schéma et de la requête.

"SELECT
GROUP_CONCAT(c.categoryId SEPARATOR ', ') as categoryIds,
GROUP_CONCAT(t.name) as tagNames
FROM blogsentry b
LEFT JOIN assetentry ae ON b.entryId = ae.classPK
LEFT JOIN assetentries_assetcategories ac ON ae.entryId = ac.entryId
LEFT JOIN assetcategory c ON ac.categoryId = c.categoryId
LEFT JOIN assetentries_assettags at ON ae.entryId = at.entryId
LEFT JOIN  assettag t ON at.tagId = t.tagId
WHERE
b.entryId = ?
GROUP BY b.entryId"

Schéma du composant tDBRow :

Schéma du composant tDBRow

Maintenant, nous pouvons utiliser un tParseRecordSet pour parser l’enregistrement précédemment obtenu avec le composant tDBRow et ainsi pouvoir récupérer les catégories et les tags.

Schéma de notre composant tParseRecordSet : le schéma de notre composant tParseRecordSet

Nous allons maintenant ajouter à notre flux courant les catégories et les tags correspondants au BlogsEntry que nous sommes en train de traiter. Pour cela ajoutons le composant tMap et ajoutons au flux courant les différentes valeurs qui nous intéressent.

Données en entrées du tMap :

​​​​​​​Données en entrées du tMap

Données en sorties du tMap :

Données en sorties du tMap

Pour terminer cette étape, nous pouvons utiliser le composant tConvertType qui convertira les types de données réceptionnées en types Talend et évitera ainsi toutes les erreurs lors du traitement de celles-ci. Concernant le schéma du tConvertType, l’entrée et la sortie sont identiques.

Modèle conceptuel à la fin de l'étape 2 :

Modèle conceptuel à la fin de l'étape 2

Etape 3 : Préparation des données pour l’appel API du Liferay de destination

Durant cette étape, nous allons préparer les données que l’on utilisera lors de l’appel de l’API Rest de notre Liferay de destination.

Pour cela pour itérer sur notre flux courant nous utilisons un composant tJavaRow dans lequel nous créons le ServiceContext et ajoutons les catégories.

// variables
JSONObject serviceContext = new JSONObject();
String categoryIds = input_row.categoryIds;
String tagNames = input_row.tagNames;

// parsing displayDate
Calendar calendar = GregorianCalendar.getInstance();
calendar.setTime(input_row.displayDate);
int displayDateMonth = calendar.get(Calendar.MONTH);
int displayDateDay = calendar.get(Calendar.DAY_OF_MONTH);
int displayDateYear = calendar.get(Calendar.YEAR);
int displayDateHour = calendar.get(Calendar.HOUR_OF_DAY);
int displayDateMinute = calendar.get(Calendar.MINUTE);

// building serviceContext
serviceContext.put("uuid", input_row.uuid);
serviceContext.put("companyId", input_row.companyId);
serviceContext.put("scopeGroupId", input_row.groupId);
serviceContext.put("createDate", input_row.createDate);
serviceContext.put("modifiedDate", input_row.modifiedDate);
if (categoryIds != null) {
    serviceContext.put("assetCategoryIds", categoryIds.split(","));
}
if (tagNames != null) {
    serviceContext.put("assetTagNames", tagNames.split(","));
}
System.out.println(input_row.uuid);
System.out.println(input_row.groupId);
System.out.println(input_row.urlTitle);
// return
output_row.title = input_row.title;
output_row.content = input_row.content;
output_row.displayDateMonth = String.valueOf(displayDateMonth);
output_row.displayDateDay = String.valueOf(displayDateDay);
output_row.displayDateYear = String.valueOf(displayDateYear);
output_row.displayDateHour = String.valueOf(displayDateHour);
output_row.displayDateMinute = String.valueOf(displayDateMinute);
output_row.allowPingbacks = String.valueOf(input_row.allowPingbacks);
output_row.allowTrackbacks = String.valueOf(input_row.allowTrackbacks);
output_row.trackbacks = input_row.trackbacks;
output_row.serviceContext = serviceContext.toString();
output_row.urlTitle = input_row.urlTitle;
output_row.groupId = input_row.groupId;
output_row.uuid = input_row.uuid;

​​​​​Cela étant fait, nous utilisons le composant tFlowIterate qui permet d’effectuer une itération sur les données d'entrée et de générer des variables globales.

Et pour terminer cette étape, nous ajoutons un lien d’itération sur le tFlowIterate et un nouveau composant tJavaFlex dans lequel nous pouvons insérer du code java si nécessaire, juste avant de faire appel à l’API Liferay.

Modèle conceptuel à la fin de l'étape 3 :

Modèle conceptuel à la fin de l'étape 3

Etape 4 : Vérification de l’existence du BlogsEntry dans le Liferay de destination

Il s’agit ici d’une étape optionnelle, mais qui peut être intéressante à explorer. Il est important de noter que Liferay va utiliser le même uuid_ lors de l’ajout de la donnée entre le Liferay source et le Liferay de destination.

Nous pouvons donc requêter la base de données source et vérifier si la donnée que nous sommes en train de traiter n’est pas déjà existante.

Si c’est le cas un traitement spécifique peut être fait (suppression, modification, affichage d’un log, etc.)

Pour ce faire, nous utilisons le composant tDBInput et faisons une requête sur la base de données de destination avec le uuid_ que nous sommes en train de traiter.

"SELECT
  `blogsentry`.`entryId` AS resultSet
FROM `blogsentry`
WHERE `blogsentry`.`uuid_` LIKE '"+globalMap.get("blogapi.uuid").toString()+"'"

Cela étant fait, nous pouvons lier notre composant tDBInput avec un tMsgBox et afficher un message si la donnée est déjà présente. Nous pouvons utiliser pour cela un lien RunIf avec comme condition :

((Integer)globalMap.get("tDBInput_2_NB_LINE")) != 0

Modèle conceptuel à la fin de l'étape 4 :​​​​Modèle conceptuel à la fin de l'étape 4

 

Etape 5 : Utilisation de l’API du Liferay de destination pour publier le BlogsEntry

Nous allons maintenant terminer cette importation de données avec le composant tRestClient. Il va nous permettre de faire un appel api vers l’API JSON Web Service de Liferay, plus particulièrement vers les BlogsEntry.

Pour plus d’informations sur l’API JSON Web Service : Invoking JSON Web Service

Nous allons alors configurer notre composant tRestClient de manière à ce que les paramètres de la requête soient ceux attendus par Liferay.

Exemple de configuration du composant tRestClient :

Exemple de configuration du composant tRestClient

De plus, n’oubliez pas l'autorisation dans l’en-tête HTTP, ce qui provoquerait une erreur 403 Forbidden.

Pour cela, convertissez vos identifiants d’utilisateur ayant les droits suffisants de Liferay en base 64.

Voici un exemple sur un shell linux :

base64 <<< identifiant:mot_de_passe

​​​​​​​Insérez l’en-tête dans les paramètres avancés du composant tRestClient.

En tête HTTP du composant tRestClient :

​​​​​​​

 

Faire appel à l’API Liferay va permettre de passer directement par le module blogs-api du portail Liferay qui fera lui appel au service du BlogsEntry. GitHub BlogsEntryLocalService

Cette pratique va permettre d’ajouter les données correctement via le service Liferay et par là-même donnera l'occasion de vérifier les permissions, les liens, etc.

Modèle conceptuel final :

Modèle conceptuel final

Pour terminer, il peut être intéressant maintenant d’avoir une gestion des erreurs des différents appels, notamment de l’API et de la base de données et d'effectuer un traitement si nécessaire. Mais cela sera le sujet d’un autre article… Stay tuned!

Merci de m’avoir lu.

BEDIA Julien - Développeur Beorn Technologies.