Jahia : External Data Provider

Développé par Eventbrite

Jahia 7 a introduit une nouvelle API : External Data Provider. Elle permet la création de connecteurs entre Jahia et une source de données externe (base de données, système de fichiers, API externes). Ainsi les données seront stockées dans Jahia et seront facilement manipulables.
Avant de présenter comment connecter Jahia avec une source de données, nous allons voir comment sont stockées les données sous Jahia.

JCR (Java Content Repository)

Le CMS Jahia Digital Factory stocke toutes les données de façon structurée dans le JCR (Java Content Repository) sous forme de nœuds. Tout contenu Jahia est donc représenté par un nœud. Par exemple :

  • une page est représentée par un nœud de type [jnt:page],
  • un fichier par un nœud de type [jnt:file],
  • un répertoire de contenu par un nœud de type [jnt :contentFolder].

On peut ainsi interroger le JCR pour retrouver des données avec différents types de requêtes :

  • SQL-2 (langage très proche du SQL disposant d’extensions pour réaliser des requêtes imbriquées complexes),
  • JQOM (Java Query Object Model),
  • XPATH.

Le JCR utilise une base de données SQL de type MySQL, PostgreSQL, Oracle ou Apache Derby pour stocker les données des nœuds sous forme sérialisée pour des raisons de performance.

External Data Provider

Le but d’un connecteur de type External Data Provider est de faire le lien entre une source de données externe et le JCR de Jahia.
Le connecteur va devoir transformer les données provenant du système tiers en nœuds pour le JCR, permettant ainsi de pouvoir manipuler ces données à l’intérieur du CMS.
Jahia offre la possibilité aux utilisateurs de créer leurs propres types de nœuds par l’intermédiaire du fichier de configuration definitions.cnd. Cela est nécessaire pour réaliser le mapping entre une donnée et sa représentation sous forme de nœud Jahia.
Le connecteur va être déclaré sous forme de bean dans un fichier Spring de type XML. Ce bean implémentera différentes interfaces selon le besoin (search, write, lazy loading). De plus, ce bean permettra d’expliciter à quels types de nœuds on applique le traitement.

Strava

Après avoir vu la théorie, nous allons développer notre propre connecteur. Pour cela, nous allons nous baser sur l’API de Strava. Strava est un réseau social pour les sportifs désireux de centraliser toutes leurs informations sportives (activités, données physiques, matériels, etc.). Il est disponible à cette adresse : https://www.strava.com/
Une fois inscrit sur le site, Strava offre la possibilité à leurs utilisateurs de récupérer un token de connexion pour pouvoir effectuer des requêtes leur permettant de récupérer leurs données au format JSON (http://strava.github.io/api/).
Pour récupérer ce token, il faut se rendre sur le site http://www.strava.com/developers. Ensuite il faut créer une application en remplissant un simple formulaire et de là on obtient notre token d’identification.

Exemples de requêtes :

$ curl -G https://www.strava.com/api/v3/athlete -d access_token=xxx > athlete.json $ curl -G https://www.strava.com/api/v3/activities -d access_token=xxx > activities.json

La première requête permet de récupérer les informations courantes sur l’utilisateur (nom, prénom, email, ville, …), c’est sur cette requête que nous allons nous baser pour notre exemple. La deuxième permet de récupérer toutes les activités (données GPS, distance, temps, …) réalisées par l’athlète.

On peut aussi se servir de l’utilitaire jq qui permet de récupérer un flux JSON formaté directement depuis le terminal.

$ curl -G https://www.strava.com/api/v3/athlete -d access_token=xxx | jq . $ curl -G https://www.strava.com/api/v3/activities -d access_token=xxx | jq .

jq

Cas concret

Nous allons donc développer un connecteur entre Jahia et Strava.
Pour cela, créons un nouveau type de nœud Jahia, que nous nommerons stravaAccount. Ce noeud contiendra certaines informations apportées par la requête précédente :

[jnt:stravaAccount] > jnt:content - lastname (String) - firstname (String) - city (String) - state (String) - country (String) - sex (String) - email (String) - weight (String)

Notre but est donc maintenant que le JCR dispose d’un nœud de type stravaAccount avec nos propres informations pour pouvoir ensuite afficher ces données sur une page du CMS.
Définissons notre provider sous forme de bean :

<beanid="StravaProvider"class="org.jahia.modules.external.ExternalContentStoreProvider"parent="AbstractJCRStoreProvider" > <propertyname="key"value="StravaProvider"/> <propertyname="mountPoint"value="/sites/strava-site/contents/strava"/> <propertyname="externalProviderInitializerService"ref="ExternalProviderInitializerService"/> <propertyname="extendableTypes"> <list> <value>jnt:contentFolder</value> <value>jnt:stravaAccount</value> </list> </property> <propertyname="dataSource"ref="StravaDataSource"/> </bean> <beanname="StravaDataSource"class="org.jahia.modules.strava.StravaDataSource"init-method="start"> <propertyname="apiKeyValue"value="${access_token}"/> </bean>

Le StravaProvider dispose de plusieurs propriétés :

  • une clé,
  • un point de montage, c’est-à-dire le lieu où seront stockés les nœuds créés par notre connecteur (ici ils se trouveront sous le site de nom strava-site et dans un dossier contents/strava),
  • le service Jahia partagé par tous les External Data Provider
  • la liste des types de nœuds supportés (ici le nœud de type stravaAccount que l’on souhaite créer ainsi que les nœuds de type de contentFolder permettant de créer des sous dossiers),
  • la data source qui va contenir l’implémentation de notre connecteur (et qui se connectera à Strava grâce au token de l’utilisateur).

Détaillons maintenant la classe StravaDataSource qui contient l’ensemble de l’implémentation de notre connecteur.
Tout d’abord elle implémente les classes ExternalDataSource et ExternalDataSource.Searchable, permettant ainsi de gérer une source de données externe et de pouvoir effectuer des recherches sur ces données dans le JCR :

publicclassStravaDataSourceimplements ExternalDataSource, ExternalDataSource.Searchable

Voici la méthode la plus importante que l’on doit implémenter sur l’interface ExternalDataSource :

publicExternalData getItemByIdentifier(String identifier) throws ItemNotFoundException { try { if (identifier.equals("root")) { returnnewExternalData( identifier, "/", "jnt:contentFolder", newHashMap<String, String[]>() ); } JSONObject stravaAccount = getCacheStravaAccount(); Map<String, String[]> properties =newHashMap<String, String[]>(); if (stravaAccount.getString("lastname") !=null) properties.put("lastname", newString[]{stravaAccount.getString("lastname")}); if (stravaAccount.getString("firstname") !=null) properties.put("firstname", newString[]{stravaAccount.getString("firstname")}); ... ExternalData data =newExternalData( identifier, "/"+ identifier, "jnt:stravaAccount", properties ); return data; } catch (Exception e) { thrownewItemNotFoundException(e); } }

Cette méthode permet de définir un nœud avec son chemin unique dans le JCR. On voit que si l’on est à la racine, on créé un nœud de type dossier et sinon on reçoit l’identifiant de notre nœud de type stravaAccount, on récupère alors les données strava par l’intermédiaire de la méthode getCacheStravaAccount() et l’on crée notre nœud avec un chemin de type /id.
Le code de la méthode getCacheStravaAccount() permet de récupérer les informations provenant de strava, en voici une version simplifiée :

HttpsURL url =newHttpsURL("www.strava.com", 443, "/api/v3/athlete"); Map<String, String> m =newLinkedHashMap<String, String>(); m.put("access_token", apiKeyValue); url.setQuery(m.keySet().toArray(newString[m.size()]), m.values().toArray(newString[m.size()])); GetMethod httpMethod =newGetMethod(url.toString()); httpClient.executeMethod(httpMethod); returnnewJSONObject(httpMethod.getResponseBodyAsString());

Il s’agit simplement de faire un appel http (basé sur la requête expliquée plus haut) sur le site de Strava avec le token d’identification (apiKeyValue).
Notre connecteur va aussi implémenter l’interface ExternalDataSource.Searchable ce qui nous permettra d’effectuer des recherches dans le JCR sur nos nouvelles données. Une seule méthode est à implémenter :

publicList<String> search(ExternalQuery query) throws RepositoryException { List<String> results =newArrayList<String>(); String nodeType =QueryHelper.getNodeType(query.getSource()); try { if (NodeTypeRegistry.getInstance() .getNodeType("jnt:stravaAccount").isNodeType(nodeType)) { JSONObject stravaAccount = getCacheStravaAccount(); String path ="/"+ stravaAccount.getString("id"); results.add(path); } } catch (JSONException e) { thrownewRepositoryException(e); } return results; }

Cette méthode permet de définir précisément le chemin dans le JCR de notre nœud de type stravaAccount qui sera ici /id où id est notre identifiant unique sur la plateforme strava.

Déploiement du module

On peut maintenant déployer notre module strava-provider sous Jahia grâce à une commande Maven ou grâce à l’interface Studio de Jahia.
Une fois notre module déployé, on va pouvoir visualiser notre nœud par l’intermédiaire de l’interface du gestionnaire de contenu de Jahia.

Content Manager

On voit ici que notre site strava dispose d’un dossier contents/strava dans lequel un nœud de type stravaAccount est présent et rempli avec mes informations.

Recherche des données

Notre nœud est bien créé, maintenant on souhaite récupérer les données stockées sur ce nœud. Jahia met à disposition des utilisateurs une interface permettant d’effectuer des requêtes sur le JCR.

Tools Jahia

La requête SQL-2 suivante :

select*from [jnt:stravaAccount]

recherche tous les nœuds de type stravaAccount dans le JCR et remonte bien un résultat. En cliquant sur le lien, on obtient le détail du nœud :

Result Query JCR

Intégration sur le site

On sait désormais comment récupérer les informations sur notre nœud, il nous faut maintenant pouvoir les afficher sur notre site. Pour cela, on ajoute à notre fichier definitions.cnd un nouveau contenu :

[jnt:athlete] > jnt:content, jmix:structuredContent

Créons la vue (athlete.jsp) associée à notre contenu :

<jcr:sqlvar="res"sql="select * from [jnt:stravaAccount]"/><h3>Strava account</h3><ul><c:forEachitems="${res.nodes}"var="stravaAccount"><li>Lastname : ${stravaAccount.properties['lastname'].string}</li><li>Firstname : ${stravaAccount.properties['firstname'].string}</li><li>City : ${stravaAccount.properties['city'].string}</li><li>State : ${stravaAccount.properties['state'].string}</li><li>Country : ${stravaAccount.properties['country'].string}</li><li>Sex : ${stravaAccount.properties['sex'].string}</li><li>Email : ${stravaAccount.properties['email'].string}</li><li>Weight : ${stravaAccount.properties['weight'].string}</li></c:forEach></ul>

Cette vue va lancer la requête précédente pour récupérer tous les nœuds de type stravaAccount sachant qu’il n’y en a qu’un dans notre JCR. On peut ensuite afficher toutes les propriétés du nœud.
On peut donc ajouter le composant athlete sur une page de notre site :

Athlete content

Et voici le rendu final sur le site :

Site Jahia

Conclusion

Les External Data Provider sont l’un des points forts du CMS Jahia par rapport à beaucoup d’autres CMS qui n’offrent pas cette possibilité. On peut imaginer des cas d’utilisation très pratiques pour des sites de e-commerce souhaitant lier leurs sources de données à Jahia.
Voici un exemple d’implémentation d’un connecteur Jahia sur lequel je me suis appuyé.
Le code complet de mon exemple est disponible sur mon GitHub.