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.

JCMS 5 : Développer avec DCM et les DataController

L'utilisation de l'API DCM a été mis à jour dans JCMS 5.7. Si vous utilisez cette version, reportez-vous à l'article JCMS 5.7 : Développer avec DCM et les DataController.

JCMS fournit en standard de nombreux contrôles lors de la validation d'un formulaire. Néanmoins, pour certains usages, il est nécessaire d'ajouter des contrôles particuliers sur les champs d'une publication, d'aiguiller automatiquement un contenu dans le workflow selon la valeur de ses champs, d'enrichir un objet juste avant son enregistrement, de déclencher des séries d'écritures en cascade, etc. Tous ces contrôles doivent être effectués non seulement sur les formulaires HTML mais aussi sur tout enregistrement déclenché via l'API JCMS.

JCMS 5 introduit DCM (Data Control Model), un nouveau modèle de contrôle et d'action centralisé sur les objets du store. DCM permet de contrôler l'intégrité des données, de vérifier si une écriture (création, mise à jour, suppression, …) peut être réalisée et, le cas échéant, de déclencher cette écriture ainsi que toutes les traitements annexes. Dans les précédentes version de JCMS, ces traitements étaient pris en charge par les FormHandler mais ne pouvaient être utilisés que dans le cadre de requêtes HTTP. La centralisation de ces traitements simplifie la tâche du développeur qui n'a plus à ce soucier des traitements annexes lorsqu'il effectue des écritures dans le store.

DCM est un modèle extensible. L'interface DataController permet de développer facilement de nouveaux contrôles d'intégrité, de validation et d'exécution des écritures sur n'importe quel type de données géré dans le store.

1. Simplification des écritures dans le store

DCM simplifie l'API d'écriture dans le store. Il se charge de traiter à la fois la validation de l'opération (l'objet est-il correctement rempli, l'utilisateur a-t-il le droit de déclencher ce type d'opération, …) et l'exécution du traitement.

Par exemple, lors de la suppression d'un membre, DCM se charge au préalable de :

  1. supprimer les données propres au membre ;
  2. réassigner ses publications aux administrateurs des espaces de travail ;
  3. mettre à jour les droits d'accès des publications ;
  4. supprimer l'extension sur le membre si elle existe.

Le programmeur n'a alors plus à ce soucier de savoir tout ce qui doit être validé et exécuté pour réaliser une opération d'écriture. Par exemple, pour mettre à jour le login d'un membre, avec JCMS 4.1.1, il fallait écrire :

void updateMember(Member mbr, String login, Member opAuthor) {
if (!loggedMember.isAdmin()) {
System.out.println("Forbidden") ;
return;
}
Member updated = new Member(mbr) ;
if (Util.isEmpty(updated.getLogin())) {
System.out.println("Login is empty") ;
return;
}
if (myMethodToVerifyLoginNotUsed(mbr, login)) {
System.out.println("Login already used") ;
return;
}
updated.setLogin(login) ;
channel.updateData(mbr, update, opAuthor) ;
}

Avec JCMS 5, il suffit d'écrire :

void updateMember(Member mbr, String login, Member opAuthor, String userLang) {
Member updated = new Member(mbr) ;
updated.setLogin(login) ;

ControllerStatus status = updated.checkUpdate(opAuthor) ;
if (status.hasFailed()) {
System.out.println(status.getMessage(userLang)) ;
return;
}
updated.performUpdate(opAuthor)
}

Le code est plus simple mais surtout il décharge le développeur des contrôles qui doivent être fait sur l'objet avant et après son écriture. Lors d'un changement de version de JCMS, il n'y a donc plus besoins de revoir l'ensemble des développements spécifiques pour prendre en compte les nouveaux contrôles internes de JCMS.

2. Contrôles sur les écritures dans le store

2.1 Validation et contrôle d'intégrité

DCM introduit l'interface DataController qui permet d'ajouter des contrôles lors de la validation des formulaires. Les contrôles peuvent porter sur tous les types de données gérés par JCMS (Member, Category, Publication, ...)

Quelques exemples d'usage :

  • Sur des publications :
    • Le champ légende doit être rempli si le champ photo est rempli et vice-versa.
    • Le champ date de début doit être antérieur au champ date de fin.
    • Le champ résumé doit comporter au moins 100 caractères lorsque le contenu est soumis à validation.
    • Les champs titre et résumé doivent être remplis dans toutes les langues.
    • Autant de champs multivalués TitreSection que de champs ContenuSection.
  • Sur des catégories :
    • Le champ nom doit être inférieur à 30 caractères si cette catégorie appartient à la branche Rubriques.
    • Pas plus de 3 niveaux de profondeur dans la branche Rubriques.
  • Sur des membres :
    • Le champs password doit contenir au moins 8 caractères et au moins 1 chiffre.
    • Interdire qu'un membre appartienne simultanément aux groupes G1 et G2.

Au delà de la validation des formulaires, DCM généralise le contrôle d'intégrité des données. Ceci permet à JCMS 5 fournir une nouvelle fonctionnalité dans l'espace d'administration : le contrôle d'intégrité des données. Cette interface liste l'ensemble des données qui ne respectent pas leurs contraintes d'intégrités (internes à JCMS et spécifique à l'application). Ceci est particulièrement utile lorsqu'un type de publication subit des changements et qu'il faut vérifier l'intégrité des données existantes.

dc dic

2.2 Contrôle de l'exécution des écritures

Les DataController permettent aussi d'agir sur l'objet avant et après que l'opération soit exécutée. Ceci permet au développeur d'ajouter des traitements spécifiques sur les données juste avant qu'elles ne soient écrites mais aussi de déclencher des traitements posterieur à l'écriture.

Quelques exemples d'usage :

  • Signature électronique des publications
  • Aiguillage automatique des messages suspects du forum dans un état spécial du workflow
  • Envoi de mail à l'administrateur lorsqu'un contenu très volumineux est enregistré dans le store.
  • Catégorisation automatique des contenus
  • Résumé automatique

3. L'API DCM

3.1 La classe ControllerStatus

Toutes les méthodes de validation et de contrôle d'intégrité renvoient une instance de la classe ControllerStatus qui possède les méthodes suivantes :

  • hasFailed() indique si le contrôle a échoué.
  • isForbidden() précise si l'échec est lié aux droits du membre.
  • getMessage(userLang) renvoie un message décrivant les raisons de l'échec


3.2 Contrôle d'intégrité

Le contrôle d'intégrité d'un objets JCMS dérivant de la classe Data se fait en invoquant la méthode checkIntegrity(). Celle-ci retourne un ControllerStatus.

Exemple :

Publication pub = channel.getPublication("c_1234") ;
ControllerStatus status = pub.checkIntegrity()
If (status.hasFailed()) {
System.out.println("Integrity error: " + status.getMessage("fr")) ;
}

3.2 Validation des écritures

Tous les objets JCMS dérivant de la classe Data disposent des méthodes de validation suivantes :

  • checkCreate(Member mbr)
  • checkUpdate(Member mbr)
  • checkDelete(Member mbr)

Ces trois méthodes vérifient si les écritures sont autorisées et si le membre (mbr) a le droit de faire ce type d'opération sur cet objet. Les méthodes checkCreate() et checkUpdate() vérifient en plus l'intégrité de l'objet. Le comportement de la méthode checkDelete() dépend de la classe d'objet mais, d'une manière générale, elle vérifie si l'objet n'est pas référencé par d'autres objets.

Selon la classe de l'objet, il peut exister d'autres méthodes de validation. Par exemple, dans la classe Publication, la méthode checkMerge() vérifie si une copie de travail peut-être fusionnée avec la publication originale.

Toutes les méthodes de validation commencent par invoquer leur super-méthode, jusqu'aux méthodes de la classe Data qui invoquent la méthode générale :
checkWrite(int op, Member mbr, boolean checkIntegrity, Map context)

Cette méthode vérifie que :

  1. les écritures sont actives sur le site ;
  2. l'objet est intègre (si checkIntegrity est true) ;
  3. le membre possède les droits nécessaires pour l'exécution de l'action.

Elle délègue ensuite la validation aux méthodes checkWrite() des DataController spécifiques.

La variable context (de type Map) permet d'obtenir des informations sur le contexte des validations et des exécutions. Par exemple, si la méthode est invoquée depuis un FormHandler, celui-ci ajoute la request et la response dans context. Ceci permet de contrôler la valeur de l'un des paramètres de la request ou de placer des informations dans la response (p. ex. un cookie).

3.3 Exécution des écritures

Les méthodes performCreate(), performUpdate() et performDelete() exécutent l'opération quelles représentent. Elles doivent être maintenant utilisées systématiquement à la place des méthodes createData(), updateData() et deleteData() de la classe Channel car l'invocation de ces dernières ne déclenche aucun DataController.

Exemple :

// Create
Category cat = new Category();
cat.setParent(channel.getRootCategory());
cat.setName("My category");
ControllerStatus status = cat.checkCreate(loggedMember);
if (status.hasFailed()) {
System.out.printlnt("Cannot create " + cat + ": " + status.getMessage(userLang));
return;
}
cat.performCreate(loggedMember);

// Update
Updated updated = new Category(cat);
updated.setDescription("My description");
ControllerStatus status = updated.checkUpdate(loggedMember);
if (status.hasFailed()) {
System.out.printlnt("Cannot update " + cat + ": " + status.getMessage(userLang));
return;
}
updated.performUpdate(loggedMember);

// Delete
ControllerStatus status = cat.checkDelete(loggedMember);
if (status.hasFailed()) {
System.out.printlnt("Cannot delete " + cat + ": " + status.getMessage(userLang));
return;
}
cat.performDelete(loggedMember);

Au delà des 3 méthodes performCreate(), performUpdate() et performDelete(), chaque classe peut implémenter ses propres méthodes d'éxécution. Par exemple, la classe Category implémente la méthode performDeepCopy() qui réalise la copie en profondeur d'une branche de catégorie. La classe Publication implémente la méthode performMerge() qui réalise la fusions d'une copie de travail avec la publication originale.

4. Développement de DataController spécifiques

4.1 L'interface DataController

L'ajout de contrôles spécifiques sur une classe donnée, se fait en écrivant une classe qui implémente l'interface DataController. Cette classe doit ensuite être enregistrée via la méthode addDataController(DataController dc, Class clazz) de la classe Channel. Le DataController sera alors invoqué lors des contrôles sur les instances de la classe ou des sous-classes de clazz. Pour associer un DataController à toutes les publications, il suffit donc de l'associer à Publication.class. L'ordre d'appel des DataController n'est pas lié à l'ordre d'enregistrement.

L'interface DataController impose l'implémentation des méthodes suivantes :

  • checkIntegrity(Data data)
  • checkWrite(Data data, int op, Member mbr, boolean checkIntegrity, Map context)
  • beforeWrite(Data data, int op, Member mbr, Map context)
  • afterWrite(Data data, int op, Member mbr, Map context)

Les méthodes beforeWrite() et afterWrite() sont respectivement invoquées avant et après que l'opération soit effectuée.

4.2 la classe BasicDataController

En pratique, il est recommandé de faire dériver son DataController de la classe BasicDataController. Il s'agit d'une implémentation neutre de DataController (et de JcmsConstants). Les méthodes checkIntegrity() et checkWrite() renvoient ControllerStatus.OK. Les méthodes beforeWrite() et afterWrite() ne font aucun traitement. Cette classe permet donc de n'implémenter que les méthodes véritablement utilisées.

Exemple : ce DataController vérifie que le champ résumé contient au moins 300 caractères.

public class LongAbstractController extends BasicDataController {
ControllerStatus checkIntegrity(Data data) {
Publication pub = (Publication)data ;
If (Util.isEmpty(pub.getAbstract()) || pub.getAbstract().length() < 300) {
return new ControllerStatus("Le résumé doit contenir au moins 300 caractères") ;
}
return ControllerStatus.OK ;
}
}

L'enregistrement du DataController se fait dans la méthode dédiée initDataController() de la classe custom.JcmsInit.

Exemple :

public class JcmsInit {
// ...
public static void initDataController() {
Channel channel = Channel.getChannel();
// ...
channel.addDataController(new LongAbstractController(), generated.Article.class);
channel.addDataController(new LongAbstractController(), generated.Interview.class);
}
}

4.3 Construction du ControllerStatus

La classe ControllerStatus contient deux objets prédéfinis :

  • ControllerStatus.OK : doit être retourné lorsque l'opération est valide
  • ControllerStatus.FORBIDDEN : doit être retourné lorsque l'echec de la validation est liée aux droits du membre.

Le message du ControllerStatus peut être fournit soit directement à la construction, soit par les méthodes setProp() pour les message multilingues.

Exemples :

ControllerStatus status1 = new ControllerStatus("Un message en français");
ControllerStatus status2 = new ControllerStatus();
status.setProp("my-data-controller.msg", "param1", "param2");

Avec dans le fichier webapp.prop :
en.my-data-controller.msg: Example of localized message with 2 parameters: {0} and {1}
fr.my-data-controller.msg: Exemple de message localisé avec 2 paramètres : {0} et {1}

5. Exemples de DataController

Les exemples suivants illustrent les différents usages des DataController. Les implémentations des codes de contrôle sont volontairement naïves pour garder le code de l'exemple simple et focalisé sur l'API contrôle. Une archive zip contenant le source de tous les exemples est téléchargeable à la fin de ce document.

Pour illustrer certains exemples, le type ArticlePhoto est utilisé. Sa structure est décrite ci-dessous :

dc articlephoto

5.1 Champs obligatoires conditionnels

Ce DataController impose que, dans les objets ArticlePhoto, le champ legende soit rempli si le champ photo est rempli. L'ensemble du contrôle est réalisé dans la méthode checkIntegrity().

package custom;

import java.util.*;

import com.jalios.jcms.*;
import com.jalios.util.*;

import generated.ArticlePhoto;

public class PhotoLegendeController extends BasicDataController {

public ControllerStatus checkIntegrity(Data data) {
ArticlePhoto pub = (ArticlePhoto)data;

if (Util.notEmpty(pub.getPhoto()) && Util.isEmpty(pub.getLegende())) {
ControllerStatus status = new ControllerStatus();
status.setProp("msg.edit.empty-field", "Legende");
return status;
}

if (Util.isEmpty(pub.getPhoto()) && Util.notEmpty(pub.getLegende())) {
ControllerStatus status = new ControllerStatus();
status.setProp("msg.edit.empty-field", "Photo");
return status;
}

return ControllerStatus.OK;
}
}

Déclaration du DataController :
channel.addDataController(new PhotoLegendeController(), generated.ArticlePhoto.class) ;

5.2 Date de début antérieur à la date de fin

Ce DataController impose que les champs startDate et endDate de la classe CalendarEvent soient correctement ordonnés. L'ensemble du contrôle est réalisé dans la méthode checkIntegrity().

package custom;

import java.util.*;

import com.jalios.jcms.*;
import com.jalios.util.*;

import generated.CalendarEvent;

public class StartEndDateController extends BasicDataController {

public ControllerStatus checkIntegrity(Data data) {
CalendarEvent pub = (CalendarEvent)data;

// check that end date is after the start date
if (pub.getStartDate() != null &&
pub.getEndDate() != null &&
pub.getEndDate().before(pub.getStartDate())) {
return new ControllerStatus("La date de fin doit être postérieure à la date de début.");
}

return ControllerStatus.OK;
}
}

Déclaration du DataController :
channel.addDataController(new StartEndDateController(), generated.CalendarEvent.class) ;

5.3 Contrôle de la profondeur d'une branche de catégorie

Ce DataController limite la profondeur d'une branche de catégorie. Le constructeur permet de choisir la branche de catégorie à contrôler et la profondeur maximum autorisée.

package custom;

import java.util.*;

import com.jalios.jcms.*;
import com.jalios.util.*;

public class CatDepthController extends BasicDataController {

Category root;
int depth;

public CatDepthController(Category root, int depth) {
this.root = root;
this.depth = depth;
}

public ControllerStatus checkIntegrity(Data data) {
Category cat = (Category)data;
List ancestorList = cat.getAncestorList(root, true);
if (cat.hasAncestor(root) && ancestorList.size() > 3) {
return new ControllerStatus("La branche " + root + " ne doit pas dépasser " + depth +
" niveaux de profondeur.");
}

return ControllerStatus.OK;
}

Déclaration du DataController :
Category cat = channel.getCategory("j_5") ;
channel.addDataController(new CatDepthController(cat, 3), Category.class) ;


5.4 Contrôle du format des mots de passe

Ce DataController impose un format sur les mots de passe des membres : entre 8 et 16 caractères avec au moins un chiffre. Toute la difficulté de ce DataController provient du fait que le champ password de l'objet Member est crypté et qu'il n'est donc pas possible de faire le contrôle du format sur ce champ. Le mot de passe en clair peut être obtenu par le paramètre de requête password1. Pour cela, le DataController opère sur la méthode checkWrite() qui reçoit en paramètre le context à partir duquel, on peut accéder à la requête et extraire le paramètre password1.

package custom;

import java.util.*;
import javax.servlet.http.*;
import com.jalios.jcms.*;
import com.jalios.util.*;

public class PasswordController extends BasicDataController {

public ControllerStatus checkWrite(Data data, int op, Member mbr, boolean checkIntegrity, Map context) {

// Control only creation and update
if (op != OP_CREATE && op != OP_UPDATE && op != Member.OP_UPDATE_PROFILE) {
return ControllerStatus.OK;
}

// Get the request to extract the uncrypted password
if (context == null) {
return ControllerStatus.OK;
}

HttpServletRequest request = (HttpServletRequest)context.get("request");
if (request == null) {
return ControllerStatus.OK;
}

// Check if the password has been provided
String password = request.getParameter("password1");
if (Util.isEmpty(password)) {
return ControllerStatus.OK;
}

// Check the password
if (checkPassword(password)) {
return ControllerStatus.OK;
}

return new ControllerStatus("The password must contains between 8 and 16 characters and at least 1 digit.");
}


boolean checkPassword(String password) {

// At least 8 characters (max 16)
if (password.length() < 8 || password.length() > 16) {
return false;
}

// At least 1 digit
for(int i = 0; i < password.length(); i++) {
if (Character.isDigit(password.charAt(i))) {
return true;
}
}

return false;
}
}

Déclaration du DataController :
channel.addDataController(new PasswordController(), Member.class) ;

5.5 Contrôle des messages du forum

Ce DataController vérifie le contenu des messages postés dans le forum. Si le message contient des mots suspects, il est aiguillé dans un état spécial du workflow. Le contrôle se fait dans la méthode beforeWrite() pour pouvoir modifier le champs pstatus du message le cas échéant. Le DataController est associé à la classe AbstractForumMessage qui est la classe mère des objet ForumMessage et ForumDiscussion.

package custom;

import java.util.*;
import com.jalios.jcms.*;
import com.jalios.jcms.forum.*;
import com.jalios.util.*;


public class ForumMessageController extends BasicDataController {

static final int CHECK_TEXT = -80;

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

if (op != OP_CREATE && op != OP_UPDATE) {
return;
}

AbstractForumMessage msg = (AbstractForumMessage)data;

if (!checkMessage(msg)) {
msg.setPstatus(CHECK_TEXT);
}
}


boolean checkMessage(AbstractForumMessage msg) {

// Get all the text of the msg
String text = msg.getTitle() + " " + msg.getText();
text = text.toLowerCase();

// Filter with a very naive algorithm
String[] wordList = new String[] {"sex", "viagra", "divx", "mp3"};
for(int i = 0; i < wordList.length; i++) {
if (text.indexOf(wordList[i]) > 0) {
return false;
}
}

return true;
}
}

Déclaration du DataController :
channel.addDataController(new ForumMessageController(), generated.AbstractForumMessage.class) ;

5.6 Signature électronique d'un contenu

Ce DataController signe un contenu avec l'algorithme MD5. Le contrôle est réalisé dans la méthode beforeWrite(). La signature est calculée sur toutes les chaînes de caractères recherchables de l'objet puis enregistrée dans le champ digest de la publication.

package custom;

import java.util.*;
import java.security.MessageDigest;
import sun.misc.BASE64Encoder;
import org.apache.log4j.Logger;
import com.jalios.jcms.*;
import com.jalios.util.*;
import generated.ArticlePhoto;

public class DigestController extends BasicDataController {

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

if (op != OP_CREATE && op != OP_UPDATE) {
return;
}

ArticlePhoto pub = (ArticlePhoto)data;
pub.setDigest(getDigest(pub);
}

String getDigest(Publication pub) {
// Append all the (searchable) text of this publication
String[] strings = pub.getSearchStrings();
StringBuffer sb= new StringBuffer(1024);
for(int i = 0; i < strings.length; i++) {
sb.append(strings[i]);
}
String text = sb.toString();

// Compute the digest
return base64Encoder.encodeBuffer(md5.digest(text.getBytes())).trim();
}

static BASE64Encoder base64Encoder;
static MessageDigest md5;
private static final Logger logger = Logger.getLogger(DigestController.class);
static {
base64Encoder = new BASE64Encoder();
try {
md5 = MessageDigest.getInstance("MD5");
} catch(java.security.NoSuchAlgorithmException ex) {
logger.warn("An exception occured while retrieving MD5 MessageDigest", ex);
}
}
}

Déclaration du DataController :
channel.addDataController(new DigestController(), ArticlePhoto.class) ;


5.7 Alerte sur les contenus volumineux

Ce DataController envoie un mail à l'administrateur si une publication contient plus d'une certaine quantité de texte. Le contrôle est réalisé dans la méthode afterWrite().

package custom;

import java.util.*;
import com.jalios.jcms.*;
import com.jalios.util.*;

public class HugeContentController extends BasicDataController {

int maxSize;

public HugeContentController(int maxSize) {
this.maxSize = maxSize;
}

public void afterWrite(Data data, int op, Member mbr, Map context) {
if (op != OP_CREATE && op != OP_UPDATE) {
return;
}

Publication pub = (Publication)data;

// Check publication size
if (getSize(pub) < maxSize) {
return;
}

// Alert the administrator
alertAdmin(pub, mbr);
}


int getSize(Publication pub) {

// Compute the size of the (searchable) text of this publication
int size = 0;
String[] strings = pub.getSearchStrings();
for(int i = 0; i < strings.length; i++) {
size += strings[i].length();
}
return size;
}

void alertAdmin(Publication pub, Member mbr) {

// Build the mail
Channel channel = Channel.getChannel();
String subject = "[" + channel.getName() + "] Huge Content Alert";
String content =
"A huge content has been saved by " + mbr + ".\n" +
"Check it out at the following URL:\n" +
channel.getUrl() + "displayWork.jsp?id=" + pub.getId();

String sender = channel.getDefaultAdmin().getEmail();
Member wsAdmin = (Member)pub.getWorkspace().getAdministrators().get(0);
String recipient = wsAdmin.getEmail();

// Send the mail
try {
MailUtil.sendMail(sender,
recipient,
subject,
content);
} catch (Exception ex) {
System.out.println("An exception occured while sending mail alert");
}
}
}

Déclaration du DataController :
channel.addDataController(new HugeContentController(10000), Publication.class) ;

6. Détails sur l'implémentation des DataController

L'ensemble du code de gestion des objets de JCMS 5 exploite DCM. Pour les classes internes (e.g. Category, Member, …) et les classes générées (Article, Faq, Glossaire, …) le code de contrôle est encapsulé dans la classe.

DCM repose sur un modèle de délégation. La classe Data (super classe de tous les objets JCMS) définit les principales méthodes de contrôles :

  • checkIntegrity() déclenche l'appel à la méthode checkIntegrity() des DataController spécifiques
  • checkWrite() contrôle l'intégrité de l'objet (optionnel), l'activation des écriture, invoque la méthode checkMember() et déclenche l'appel à la méthode checkWrite() des DataController spécifiques.
  • checkMember() contrôle les droits du membre à exécuter l'opération
  • checkCreate() invoque la méthode checkWrite()
  • checkUpdate() invoque la méthode checkWrite()
  • checkDelete() invoque la méthode checkWrite()
  • performCreate() et performUpdate() invoquent la méthode beforeWrite() des DataController spécifiques, exécute la création ou la mise-à-jour de l'objet et invoque la méthode afterWrite() des DataController.
  • performDelete() exécute la suppression de l'extension si elle existe et fait un traitement similaire à performCreate() et performUpdate().

Selon leurs besoins, les sous-classes surchargent les méthodes checkIntegrity(), checkCreate(), checkUpdate(), checkDelete(), performCreate(), performUpdate() et performDelete(). Les méthodes de validation (checkXXX()) commencent par invoquer la méthode correspondante de leur superclasse puis, si celle-ci valide l'opération, effectuent leur propre vérification.

Les méthodes d'exécution (performXXX()) appellent leur super méthode selon leurs besoins. Par exemple, la version surchargée de performDelete() effectuera d'abord ses traitements de nettoyage puis invoquera à la fin super.performDelete(). La version surchargée de performUpdate() invoquera d'abord super.performUpdate() puis exécutera son code spécifique ensuite.

Les méthodes de contrôle d'intégrité et de validation des DataController sont donc invoquées avant les méthodes de contrôle interne des objets.

7. Recommandations d'usage

7.1 Choisir la bonne méthode

Lors de l'écriture d'un DataController, il convient de choisir de façon pertinente où doit être placé le code de contrôle. Pour les validations, il est recommandé de placer le code dans la méthode checkIntegrity() si ce contrôle ne dépend pas du type d'écriture ni de son contexte. Dans le cas contraire, on placera le code dans la méthode checkWrite(). Pour les contrôles portant sur l'exécution, on les placera dans la méthode beforeWrite() s'ils doivent modifier l'objet et dans afterWrite() dans les autres cas.

7.2 Granularité des DataController

D'une manière générale il est préférable de développer de petits DataController qui effectuent un contrôle précis. Dans le cas où l'enchaînement des contrôles doit être maîtrisé, il convient de les regrouper dans un même DataController.

7.3 DataController vs. StoreListener

La méthode afterWrite() de l'interface DataController permet d'être alerté après chaque opération d'écriture. Ce comportement est assez semblable aux StoreListener. Cependant, les StoreListener permettent de capturer plus d'évènements :

  • Un StoreListener peut être notifié lors du chargement du store. Ceci permet par exemple de maintenir des index sur les données.
  • Un StoreListener peut être notifié des écritures réalisées sur d'autres réplicas. Ceci permet de déclencher des opérations sur chaque réplica. Par exemple, la suppression du fichier référencé par un FileDocument est réalisée par un StoreListener.

Il est conseiller d'utiliser les DataController lorsque l'action doit être déclenchée une et une seule fois sur l'ensemble des réplicas ; comme par exemple l'envoi d'un mail ou l'enchaînement d'une autre écriture. Les StoreListener doivent être réservés aux seuls cas où un DataController ne peut pas répondre aux besoins.


7.4 DataController vs. Custom FormHandler

Les FormHandler sont les classes Java chargées du traitement des formulaires de création, mise à jour et suppression. Ils utilisent maintenant les méthodes de validation et d'exécution de DCM. Néanmoins, ils ont aussi leurs propres méthodes de validations du format des paramètres reçus (contrôle des valeurs des champs numériques, des dates, des liens, ...)

Lors de la définition d'un type de publication, il est possible de préciser la classe du FormHandler. Cette classe doit dériver de la classe du FormHandler générée pour le type. On parle alors de Custom FormHandler.

L'introduction des DataController restreint l'usage des Custom FormHandler au contrôle des actions spécifiques du formulaire. C'est par exemple le cas pour le type Faq. Une Faq affiche un ensemble de FaqEntry qui référencent la Faq. Les FaqEntry sont listées selon la valeur de leur champ order. Des petites flèches haut/bas présentes, sur le gabarit d'affichage de la Faq, permettent de manipuler l'ordre des FaqEntry. Le traitement des actions opUp et opDown, correspondant aux requêtes émises par les petites flèches, est réalisé dans CustomFaqEntryFormHandler.

References