Jalios Community
Spaces
Content
We have not found any result for your search.
We have not found any result for your search.

Tri de données via le ComparatorManager

1. Introduction

Lors de la réalisation d'un site, un besoin récurrent est de proposer et d'utiliser des ordres de tri personnalisés dans des Portlets Requêtes Itérations (également appelés Portlets Query/Foreach ou PQF).
Préalablement à JCMS 7.1, fournir ces tris nécessitait la modification ou la recopie de JSP natives de JCMS. Ceci ne facilitait ni la maintenance du code ni les migrations.

Cette nouvelle API permet la déclaration de nouveaux ordres de tri facilement, sans modification du code de JCMS.

De plus cette API simplifie et unifie l'accès aux Comparator, préalablement disséminés sur plusieurs classes de l'API JCMS.

2. Principes et utilisation en synthèse

2.1 Déclaration

  • La déclaration d'un Comparator s'effectue dans les propriétés de JCMS avec la syntaxe suivante :
    comparator.{TypeShortName}.{comparatorName}: {comparatorClass.Fully.Qualified.Name}
  • La déclaration d'un ordre de tri accessible dans une PortletQueryForeach s'effectue dans les propriétés de traduction, en fournissant le label associé au Comparator :
    comparator.pqf.{comparatorName}: My Comparator

2.2 Développement

  • Les Comparator doivent concerner un type de donnée dérivant de Storable.
  • Les Comparator doivent proposer un constructeur par défaut sans aucun paramètre.
  • L'approche la plus simple pour développer un nouveau Comparator est d'étendre la classe com.jalios.jcms.comparator.BasicComparator
  • Des informations contextuelles (langue, Locale, JcmsContext, Map de contexte) sont accessibles automatiquement en dérivant de la classe BasicComparator

2.3 Récupération des Comparator

  • Les méthodes statiques Data.getComparator(), Publication.getComparator() et FileDocument.getComparator() sont dépréciées et remplacées par l'appel au singleton ComparatorManager.getComparator(...)
  • Le type de donnée le plus précis possible sur lequel le Comparator va s'appliquer doit être précisé lors de l'appel au ComparatorManager, ceci permet de récupérer le Comparator le plus adapté aux données traitées.
  • Une Map d'informations contextuelles supplémentaires peut être communiquée via les signatures adéquates de ComparatorManager.getComparator(Class, String, boolean, Map)

3. Utilisation détaillée

Notes : Les informations ci-dessous sont reprises et traduites de la JavaDoc des 2 classes suivantes :

  • com.jalios.jcms.comparator.ComparatorManager
  • com.jalios.jcms.comparator.BasicComparator

3.1 Développement d'un Comparator

En plus des règles d'implémentation de l'interface java.util.Comparator, le développement d'un Comparator dans JCMS nécessite certaines spécificités que vous devez suivre pour rendre votre ordre de tri accessible dans JCMS.

Constructeurs par défaut : Il est impératif de fournir un constructeur par défaut sans aucun paramètre dans votre implémentation de Comparator. Si vous dérivez de la classe BasicComparator et que vous ne précisez pas d'autre constructeur c'est automatique.

Informations contextuelles : Si vous souhaitez avoir accès aux informations contextuelles propres à JCMS, lors de l'exécution de votre Comparator, le plus simple est de dériver de la classe com.jalios.jcms.comparator.BasicComparator. Vous bénéficierez alors des variables membres suivantes :

  • Channel channel : cette classe n'a plus besoin d'être présentée...
  • JcmsContext jcmsContext : le contexte JCMS courant, attention cette variable peut être nulle
  • String language : la langue courante de l'utilisateur, ou la langue par défaut du site
  • Locale locale : la Locale courante de l'utilisateur ou la Locale par défaut du site
  • Map<String,Object> contextMap : Une map contenant des informations supplémentaires qui auront pu être mises à disposition par l'appelant lors de l'appel à la méthode getComparator(...);

Type de données traité : Il est très fortement recommandé d'utiliser les génériques Java pour préciser le type de données traité.

3.2 Déclaration

La déclaration d'un Comparator s'effectue dans les propriétés de JCMS (via custom.prop ou plugin.prop) en respectant la syntaxe suivante :

comparator.{TypeShortName}.{comparatorName}: {comparatorClass.Fully.Qualified.Name}

Dans le cas où le type de données concerné n'est pas dans le package generated ou com.jalios.jcms., il est nécessaire de préciser le nom qualifié complet de la classe concernée en ajoutant la ligne suivante en plus de la déclaration du comparateur :

comparator.{TypeShortName}: {typeClassFullyQualifiedName}

Par exemple, un Comparator qui tri les AbstractCalendarEvent sur leur durée peut être déclaré ainsi :

comparator.AbstractCalendarEvent: com.jalios.jcms.calendar.AbstractCalendarEvent
comparator.AbstractCalendarEvent.duration: com.example.jcmsplugin.CalendarEventDurationComparator

3.3 Déclaration pour les Portlets QueryForeach

Pour proposer un tri supplémentaire dans tous les PortletQueryForeach, il faut ajouter un label associé au comparator dans les propriétés de traduction, en respectant la syntaxe suivante :

comparator.pqf.{comparatorName}: My Comparator

Ce nouveau tri apparaîtra à la fin du menu de sélection de l'ordre de tri.
Pour modifier l'ordre des tris proposés, vous pouvez modifier la propriété suivante :

# Comparator order for Portlet Query Foreach 
comparator-pqf.prefered-order: \
  cdate mdate pdate edate sdate adate udate \
  title author opauthor rating workspace reader \
  reviewCount type pstatus contentType dimensions size duration  

Les Comparator qui ne se trouvent pas dans la propriété précédente seront à la fin de la liste dans le formulaire d'édition de la PQF.

4. Exemple : ArticleIntroComparator

Cet exemple de Comparator fait appel à l'ensemble des notions évoquées ci-dessus.
Il permet la comparaison des Article en utilisant le texte d'introduction dans la langue courante.

4.1 Déclaration

On déclare la propriété suivante dans le fichier plugin.prop d'un module pour rendre le Comparator disponible depuis l'API :
WEB-INF/plugins/MyPlugin/properties/plugin.prop

comparator.Article.intro: com.example.jcmsplugin.ArticleIntroComparator

On déclare les 2 propriétés de traduction suivantes pour mettre à disposition l'ordre de tri dans les Portlets Query Foreach :
WEB-INF/plugins/MyPlugin/properties/language/en.prop

comparator.pqf.intro: introduction field

WEB-INF/plugins/MyPlugin/properties/language/fr.prop

comparator.pqf.intro: champ introduction

4.2 Code

 // Sorts Article by alphabetical order of their "intro" field, using current language.
// Declare this comparator using the following property :
// comparator.Article.articleintro: com.example.jcms.comparator.MyArticleIntroComparator
public class MyArticleIntroComparator extends BasicComparator<Article> {

public int compare(Article p1, Article p2) {
// object nullity check
if (p1 == null) {
return (p2 == null) ? 0 : -1;
}
if (p2 == null) {
return 1;
}

// Retrieve Article introduction
String t1 = p1.getIntro(language);
String t2 = p2.getIntro(language);
if (t1 == null) {
return (t2 == null) ? 0 : -1;
}
if (t2 == null) {
return 1;
}

t1 = Util.unaccentuate(t1);
t2 = Util.unaccentuate(t2);

int res = t1.compareToIgnoreCase(t2);
if (res != 0) {
return res;
}
return super.compare(p1, p2);
}
}

4.3 Utilisation dans l'API

On récupère une nouvelle instance du Comparator comme suit :

Comparator<? super Article> articleComparator = ComparatorManager.getComparator(Article.class, "title");

5. Limites et précisions techniques

5.1 Comparateur par défaut

Si un Comparator inexistant est demandé (nom court non déclaré), ou que le Comparator demandé ne s'applique pas sur la donnée précisée, alors c'est le Comparator CdateComparator qui sera utilisé (tri par date de création).

Par exemple, si le tri "duration" est demandé (tri des calendrier par durée, en exemple de la section 3.2), alors que la classe spécifiée à getComparator() est Publication.class, ce comparator ne sera pas récupéré et le CdateComparator sera utilisé.

5.2 Conflits de nom de comparateur

Historiquement, certains comparateur totalement différents utilisent le même nom court, (eg : "name" pour tri des noms de Group ou pour le tri des noms de Workspace).

  • Ces conflits ne peuvent être résolus que si la Class des données à comparer est suffisamment précise lors de l'appel à ComparatorManger.getComparator()
  • Dans le cas des PortletQueryForeach, la classe qui est utilisée dans doSort.jsp est déduite des requêtes précisées dans l'instance de la portlet (eg SmallNews.class si une seule requête sur les Brève, Content.class si requête sur Article et Brève)
  • Lors du développement de nouveaux Comparator il est fortement recommandé d'utiliser un nom court de Comparator le plus unique possible afin de ne pas rentrer en conflit avec un autre Comparator concernant le même type de données.

5.3 Cas particulier du tri par pertinence (ScoreComparator)

Le tri par pertinence ("relevance") ne peut s'effectuer qu'après une recherche textuelle.
Ce comparator requiert l'accès au QueryResultSet contenant les scores des publications, le Comparator récupère ce Set via la map de contexte de ComparatorManager.getComparator().
En l'absence du QueryResultSet, le ScoreComparator délègue le tri à la classe BasicComparator (qui trie par date de création CdateComparator)

Exemple

L'appel suivant est faux : getComparator() renverra bien un ScoreComparator, mais celui-ci ne pourra pas faire le tri par pertinence et un tri par cdate sera effectué :

// THIS CODE DOES NOT WORK
Comparator comparator = ComparatorManager.getComparator(Publication.class, "relevance", false);
SortedSet<Publication> resultSet = new TreeSet<Publication>(comparator);
resultSet.addAll(queryResultSet)

Il faut passer par les méthodes getAsSortedSet(...) de haut niveau fourni dans QueryResultSet qui se chargent de fournir les informations de contextes requises :

SortedSet<Publication> resultSet = queryResultSet.getAsSortedSet(Publication.class, "relevance", false);

5.4 Cas particulier du tri des données en base

Seuls les tris SQL natif sur les champs des types de contenus sont possibles pour les requêtes en base.
Si un comparateur personnalisé est configuré, il ne s'appliquera qu'après la requete en base, c'est-à-dire sur les n premiers résultats trouvés avec le tri demandé dans la requete SQL.

Un comparateur personnalisé peut être mis en œuvre si et seulement si :

  1. le type de données manipulé est homogène.
  2. le tri que vous souhaitez appliquer correspond à celui d'une colonne en base du type recherché

Alors, il est possible de procéder de la façon suivante :

  • créer et enregistrer un comparateur personnalisé, dont le nom DOIT être celui du champ
  • utiliser cette déclaration dans la Portlets QueryForeach

Alors les QueryHandler et les PublicationCriteria qui seront créées pour faire la recherche utiliseront ce champ à la fois

  • pour la requete en base (car le champ a été trouvé dans le type recherché en base)
  • et pour le tri en mémoire (car un comparateur a été identifié)

In brief...

Cet article présente l'API de déclaration de comparateurs introduite à partir JCMS 7.1. Cette API permet de fournir rapidement des nouveaux ordres de tri dans les Portlets Requêtes Itérations pour les Publications, ou dans tout autre code Java pour les autres types de données (Member, Group, Workspace, ...).

Subject
Products
Published

3/18/12

Writer
  • Olivier Jaquemet