We apologize for untranslated text, you can use the Google Translation button to get an automatic translation of the web page in the language of your choice.

Développer avec les ExtraData et les ExtraDBData

Depuis JCMS 5.7.1, toutes les données de JCMS dérivant de la classe Data disposent du champ extraDataMap, qui permet de stocker des informations additionnelles dans JStore.
Avec le support des bases de données, JCMS 6.0 ajoute une fonctionnalité équivalente pour un stockage en base, les ExtraDBData.

Grâce à ce mécanisme, un module peut ajouter dynamiquement de nouveaux champs à un type de données existant. Il devient par exemple possible pour un module de géolocalisation d’ajouter les champs longitude et latitude aux membres, aux espaces de travail, aux publications...

Ces champs peuvent être utilisés de deux manières :

  1. Pour ajouter dynamiquement de nouveaux champs de saisis dans la plupart des formulaires JCMS (publication, catégorie, membre, groupe, espace de travail, document, …)
  2. Pour stocker programmatiquement des informations spécifiques à une donnée.
    Par exemple :
    • un DataController peut faire une signature MD5 d’un document et la stocker en ExtraData.
    • un AuthenticationHandler peut stocker la date de la dernière connexion au site en ExtraDBData.

1. Ajout de champs aux types existants

La déclaration des nouveaux champs se fait via les propriétés :

  • Une première propriété sert à définir le mode d’édition et la valeur par défaut.
  • Des propriétés de langues associées fournissent les libellés pour le formulaire d’édition.

Pour un stockage dans JStore (ExtraData), le nom de l'ExtraData et sa valeur par défaut se déclare dans les propriétés en respectant la syntaxe suivante :

extra.Type.uid(.editor): defaultValue

Pour un stockage dans la base de donnée (ExtraDBData), respectez la syntaxe suivante :

extradb.Type.uid(.editor): defaultValue

Avec :

  • Type : représente le type de donnée concerné (p. ex. Article, Member, Category, …)
  • uid : représente un identifiant unique pour cette extradata, dans le cas d’un module doit commencer par jcmsplugin. (p. ex. jcmsplugin.geolocator.latitude)
  • editor (optionnel) : représente le type d’éditeur pour ce champ. Par défaut, si cette partie est absente, l’éditeur est un champ texte (sur une ligne). Les autres valeurs possibles sont :
    • number pour un champ texte permettant d’éditer un nombre ;
    • boolean pour un champ permettant de choisir une valeur booléenne (oui/non) ;
    • area pour une zone de texte sur plusieurs lignes ;
    • hidden pour un champ caché.
  • defaultValue (optionnelle) : représente la valeur par défaut du champ.

Les propriétés de langues associées doivent avoir la même structure que la propriété de déclaration, sans la partie éditeur. Elles peuvent être accompagnées d’une seconde propriété avec le suffixe .help pour fournir une aide pour le champ.

Exemples

Dans cet exemple, on ajoute 3 nouveaux champs au membre :

  • Le compte de messagerie instantanée, stocké dans JStore
  • Un booléen indiquant si le membre est externe à la société, stocké dans JStore
  • Le code postal, stocké en base

Pour ce faire, on ajoute les propriétés suivantes (notez la différence de préfixe entre extra et extradb).

Dans le fichier WEB-INF/plugins/DemoPlugin/properties/plugin.prop :

# Membre : ajout du login de messagerie instantanée
extra.Member.jcmsplugin.demo.imlogin:

# Membre : ajout d’un booléen qui indique si le membre est externe
extra.Member.jcmsplugin.demo.external.boolean: false

# Membre : ajout du Code Postal, stocké en base de donnée
extradb.Member.jcmsplugin.demo.zipcode.number: false

Dans le fichier WEB-INF/plugins/DemoPlugin/properties/languages/fr.prop :

extra.Member.jcmsplugin.demo.imlogin:  Messagerie instantanée
extra.Member.jcmsplugin.demo.imlogin.help: Entrez l’identifiant de messagerie instantannée
extra.Member.jcmsplugin.demo.external: Membre externe
extradb.Member.jcmsplugin.demo.zipcode: Code Postal

Dans le fichier WEB-INF/plugins/DemoPlugin/properties/languages/en.prop :

extra.Member.jcmsplugin.demo.imlogin:  Instant Messenger
extra.Member.jcmsplugin.demo.imlogin.help: Enter the login for the Instant Messenger
extra.Member.jcmsplugin.demo.external: External
extradb.Member.jcmsplugin.demo.zipcode: Zip Code

Lors de l’édition, ces nouveaux champs apparaissent à la suite des champs standards :

Screenshot PubCtxMenu

Fig. 1. Exemple de nouveaux champs ajouter sur les membres

2. API

2.1 ExtraData

Le champ extraDataMap est une Map dont les clés et les valeurs sont des String.

La classe Data fournit plusieurs méthodes pour manipuler ce champ :

  • getExtraData(String name) : retourne la valeur associé à l’ExtraData name.
  • setExtraData(String name, String value) : affecte value à l’ExtraData name.
  • removeExtraData(String name) : supprime l’ExtraData name.

Nomenclature :

Dans le cas d’une utilisation purement programmatique et si l’utilisateur n’a pas à avoir connaissance de l'ExtraData, il n’est pas nécessaire de la déclarer dans les propriétés. De plus, dans ce cas, il ne faut pas préfixer le nom de votre ExtraDBData par extradb.<Type>, mais par exemple par (recommandé) jcmsplugin.pluginName. Exemple :

data.setExtraData("jcmsplugin.demoplugin.mailSent", "true"); 

Autrement, si vous manipulez une ExtraData déclarée préalablement via les propriétés, utilisez le nom de la propriété, sans les options d'édition (boolean, number, ... ). Exemple :

data.setExtraData("extra.Member.jcmsplugin.demo.external", "true"); 

Usages :

Attention ! Lors de la mise-à-jour de ce champ, il faut penser à dupliquer l’extraDataMap :

Publication copy = (Publication)pub.clone()
Map extraDataMap = pub.getExtraDataMap() ;
If (extraDataMap ! = null) {
copy.setExtraDataMap(new HashMap(extraDataMap));
}
copy.setExtraData(key, value);

Limitations :

Les ExtraData étant stockés dans JStore, ils ne peuvent pas être utilisés sur des instances de DBData.

Exemple :

Dans un DataController, on enregistre en ExtraData le fait qu'un mail a bien été envoyé pour ne pas effectuer de traitement en double.
Il s'agit d'une ExtraData à usage programmatique, non déclaré dans les propriétés. On utilise donc simplement le préfixe "jcmsplugin.pluginName." suivi d'une chaîne unique. Pour notre exemple "jcmsplugin.demoplugin.mailSent".

package com.jalios.jcmsplugin.demo;
import java.util.HashMap;
import java.util.Map;

import org.apache.log4j.Logger;

import com.jalios.jcms.BasicDataController;
import com.jalios.jcms.Data;
import com.jalios.jcms.Member;
import com.jalios.jcms.Publication;

public class MailDataController extends BasicDataController {

private static final Logger logger = Logger.getLogger(MailDataController.class);

static int PSTATUS_DONE = -70;
static String WORKFLOW_ID = "wffomulaire";

public void afterWrite(Data data, int op, Member mbr, Map context) {

// Only process update
if (op != OP_UPDATE) {
return;
}

// Only process publication
if (!(data instanceof Publication)) {
return;
}

Publication pub = (Publication)data;

// Only process the submission workflow
if (!WORKFLOW_ID.equals(pub.getWorkflow().getId())) {
return;
}

// Only process publication in the DONE state
if (pub.getPstatus() != PSTATUS_DONE) {
return;
}

// Only process publication not already notified
if ("true".equals(pub.getExtraData("
jcmsplugin.demoplugin.mailSent"))) {
return;
}


// Send the mail
sendMail(pub);

// Update the data
Publication copy = (Publication)pub.clone();
Map extraDataMap = pub.getExtraDataMap();
if (extraDataMap != null) {
copy.setExtraDataMap(new HashMap(extraDataMap));
}
copy.setExtraData("jcmsplugin.demoplugin.mailSent", "true");
copy.performUpdate(mbr);

}

private void sendMail(Publication pub) {
logger.debug("Send the mail ...");

// ...
}
}

2.2 ExtraDBData

L'API des ExtraDBData est sensiblement la même que celle des ExtraData. Les ExtraDBData sont également des clés/valeurs de type String. et les méthodes de la classe Data sont similaires.

La classe Data fournit les méthodes suivantes pour manipuler les ExtraDBData :

  • getExtraDBData(String name) : retourne la valeur associé à l’ExtraDBData name.
  • setExtraDBData(String name, String value) : affecte value à l’ExtraDBData name.
  • removeExtraDBData(String name) : supprime l’ExtraDBData name.

Attention ! Lorsque vous travaillez avec les ExtraDBData vous devez toujours être dans une transaction Hibernate. C'est systèmatiquement le cas, si votre code est appelé suite à une requête JSP. Par contre si votre code est appelé dans une alarme JDring, vous devez utiliser un TransactionalAlarmListener. Dans les autres cas, comme par exemple les tests unitaires, c'est à vous d'ouvrir et de commiter la transaction.

Nomenclature :

La nomenclature est la même que pour les ExtraData :

Dans le cas d’une utilisation purement programmatique et si l’utilisateur n’a pas à avoir connaissance de l'ExtraDBData, il n’est pas nécessaire de la déclarer dans les propriétés. Dans ce cas, préfixez le nom de votre ExtraDBData par "jcmsplugin.pluginName.". Exemple :

data.setExtraDBData("jcmsplugin.demoplugin.last-login-date", String.valueOf(System.currentTimeMillis())); 

Autrement, si vous manipulez une ExtraDBData déclarée préalablement via les propriétés, utilisez le nom de la propriété, sans les options d'édition (boolean, number, ... ). Exemple :

data.setExtraDBData("extradb.Member.jcmsplugin.demo.zipcode", "78150"); 

Usages :

Contrairement au ExtraData, lors de la mise-à-jour d'une ExtraDBData, il n'est pas nécéssaire de dupliquer le champ extraDBDataMap (et c'est préférable, pour des raisons de performance).

La modification d'une ou plusieurs ExtraDBData peut s'effectuer directement sur le clone de la donnée concernée.
La modification de l'ExtraDBData est alors effective au moment du performUpdate().

Publication copy = (Publication) original.clone();  
copy.setExtraDBData("jcmsplugin.myplugin.foobar", "hello world");
copy.performUpdate(mbr);

Si aucune autre modification ne doit être effectuée sur la donnée, il est également possible d'effectuer une modification directement sur la donnée original, sans clone préalable.
Dans ce cas, la modification de l'ExtraDBData est effective immédiatement.

original.setExtraDBData("jcmsplugin.myplugin.foobar", "hello world"); 

Exemple :

Dans un AuthenticationHandler, on stocke la date de la dernière connexion au site en ExtraDBData :

package com.jalios.jcmsplugin.demo;

import java.io.IOException;

import com.jalios.jcms.Member;
import com.jalios.jcms.authentication.AuthenticationContext;
import com.jalios.jcms.authentication.AuthenticationHandler;
import com.jalios.jcms.authentication.handlers.LoggingAuthenticationHandler;

public class DemoAuthenticationHandler extends AuthenticationHandler {

public DemoAuthenticationHandler() {
// on se place avant le tout premier handler de JCMS pour observer le résultat d'authentification
super(LoggingAuthenticationHandler.getInstance().getOrder());
}

@Override
public void login(AuthenticationContext ctxt) throws IOException {
super.login(ctxt);

// Login Success, update last login date
if (ctxt.isLogged() && ctxt.getRequest().getSession().isNew()) {
Member loggedMember = ctxt.getLoggedMember();
loggedMember.setExtraDBData("jcmsplugin.demoplugin.last-login-date", String.valueOf(System.currentTimeMillis()));
}
}

}

3. ExtraData vs. ExtraDBData

Priviligiez les ExtraData pour les données editoriale ou devant être saisies principalement en production.

Priviligiez les ExtraDBData pour les données techniques ou devant être modifiées fréquement.

4. ExtraData vs. ExtraInfo

La classe Data dispose d’un autre champ, extraInfoMap, qui peut paraître similaire au champ extraDataMap. Néanmoins, ils diffèrent sur 2 points :

  • extraInfoMap, contrairement à extraDataMap, n’est pas limitée aux String mais peut stocker n’importe quel type de données.
  • extraInfoMap est une Map non persistante. Son contenu est donc perdu à chaque redémarrage de JCMS. Elle est typiquement destinée à mettre en cache des informations associées à la donnée.

5. ExtraData vs. Extension

Jusqu’à JCMS 5.7.1, les extensions étaient la seule manière disponible pour ajouter des champs aux types non-extensibles (Member, Group, Category, Workspace, FileDocument). Avec les extraData, l’ajout de champ devient plus simple et surtout plus extensible. En particulier, il ne peut y avoir qu’une extension par type, ce qui pose problème si deux modules veulent déployer des extensions différentes pour un même type. En reposant sur une déclaration par les propriétés, les extraData sont mieux adaptées aux modules.

Néanmoins, les extraData sont plus limitées dans les types de champ qu’elles peuvent supporter à l’édition.

Pour les types de champs suivant, on préférera les extensions aux extraData :

  • Les champs multivalués
  • Les champs multilingues
  • Champs textuels typés (wiki, wysiwyg, image, média, couleur, requête)
  • Les champs Date ; sauf à stocker une représentation textuelle de la date.
  • Les champs Liens sur donnée (Membre, Catégorie, Publication, Document, …) ; sauf à stocker l’identifiant de la donnée et à en accepter les conséquences (liaison faible sans contrôle d’intégrité)
  • Les champs liés aux bases de données (SQL query, SQL result et DB Record)

6. Recherche textuelle sur les ExtraData et les ExtraDBData

Le contenu des ExtraData et des ExtraDBData n'est pas utilisé lors d'une recherche textuelle.
Si vous souhaitez permettre à vos utilisateurs de rechercher ces données, c'est possible grâce au développement d'une LuceneSearchEnginePolicyFilter.
Le rôle de cette PolicyFilter sera d'ajouter les valeurs à rechercher dans l'index lucene des publications, pour permettre au moteur de recherche de JCMS de les trouver.

Exemple :

Dans l'exemple suivant, une ExtraDBData a été ajouté sur le type Article. On souhaite que la recherche textuelle inclue les valeurs saisies dans cette ExtraDBData.

Pour cela on crée une nouvelle classe DemoLuceneSearchEnginePolicyFilter qui dérive de BasicLuceneSearchEnginePolicyFilter et on déclarre cette classe dans le fichier plugin.xml de notre plugin.

<plugincomponents>
   <policyfilter class="com.example.jcmsplugin.demoplugin.DemoLuceneSearchEnginePolicyFilter" />
</plugincomponents>

On implémente la méthode filterPublicationDocument(...) qui est invoquée lors de l'indexation d'une publication.
C'est dans cette méthode que l'on enregistre la valeur de l'ExtraDBData dans le champ lucene commun à toutes les recherches textuelles.

public class DemoLuceneSearchEnginePolicyFilter extends BasicLuceneSearchEnginePolicyFilter {

  @Override
  public void filterPublicationDocument(Document doc, Publication pub, String lang) {
    if (!(pub instanceof Article)) {
      return;
    }
    String str = pub.getExtraDBData("extradb.Article.jcmsplugin.demo.sometext");
    if (Util.isEmpty(str)) {
      return;
    }

    // Add to ALLFIELDS_FIELD in order to allow our extradata text to be searched
    Field allField = new Field(LucenePublicationSearchEngine.ALLFIELDS_FIELD, str, Field.Store.NO, Field.Index.TOKENIZED);  
    doc.add(allField);  

    
  }
 
}

Consultez la JavaDoc des classes LuceneSearchEnginePolicyFilter et LucenePublicationSearchEngine ainsi que la JavaDoc de l'API lucene pour plus d'informations sur les possibilités d'indexation lucene dans JCMS.

7. Recommandations

7.1 ExtraInfo 

Les ExtraInfos d'une Data permettent généralement de stocker en mémoire une information que l'on ne souhaite pas calculer fréquement (par exemple parcequ'elle est couteuse) mais qu'il n'est pas pour autant nécessaire de stocker définitivement. un cache en somme.

Mais il ne faut en aucun cas y stocker des Data (ou tout objet référençant des Data, comme une collection), sans quoi vous provoquerez une fuite de mémoire à un moment ou un autre.
Pour ces besoins, stockez systématiquement l'identifiant de la donnée (ou une collection d'identifiant).

La classe JcmsTestCase met à disposition une une méthode pour vérifier l'absence de fuite mémoire sur une donnée en particulier :assertObjectIsGarbageCollected(). En cas de fuite, tous les éléments nécessaires seront générés et indiqués dans les logs pour une analyse rapide dans un outil tel que Memory Analyzer (MAT).
Utilisez cette méthode lorsque vous réalisez de nouveaux développements. Consultez la JavaDoc de la méthode ou le code existant dans les test unitaires de JCMS pour son usage, et demandez moi si vous avez besoin de plus d'informations.

 

References

In brief...

Cet article décrit les ExtraData (disponibles à partir de JCMS 5.7.1) et les ExtraDBData (disponibles à partir de JCMS 6) qui permettent d’ajouter dynamiquement de nouveaux champs à un type de données existant.

Subject
Published

11/26/08

Writer
  • Olivier Jaquemet