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 deStorable
. - 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 classecom.jalios.jcms.comparator.BasicComparator
- Des informations contextuelles (langue,
Locale
,JcmsContext
,Map
de contexte) sont accessibles automatiquement en dérivant de la classeBasicComparator
2.3 Récupération des Comparator
- Les méthodes statiques
Data.getComparator()
,Publication.getComparator()
etFileDocument.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 auComparatorManager
, ceci permet de récupérer leComparator
le plus adapté aux données traitées. - Une
Map
d'informations contextuelles supplémentaires peut être communiquée via les signatures adéquates deComparatorManager.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 nulleString language
: la langue courante de l'utilisateur, ou la langue par défaut du siteLocale locale
: la Locale courante de l'utilisateur ou la Locale par défaut du siteMap<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éthodegetComparator(...)
;
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 dansdoSort.jsp
est déduite des requêtes précisées dans l'instance de la portlet (egSmallNews.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 deComparator
le plus unique possible afin de ne pas rentrer en conflit avec un autreComparator
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 :
- le type de données manipulé est homogène.
- 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é)