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éveloppement de recherches étendues avec l'API des QueryFilter

1. Introduction

JCMS fournit en standard deux mécanismes de recherche :

  1. Recherche dans les publications gérées par JCMS. Ces recherches peuvent combiner plusieurs critères de différentes natures (texte, type de publication, catégorie, dates, auteur, langues, ...).
  2. Recherche dans les pièces jointes. Il s'agit de recherche en texte intégral sur le contenu des pièces jointes et elles se font en exploitant des indexes Lucene alimentés par le module JCMS Universal.

Dans JCMS, toutes les recherches de publications sont prises en charges par la classe com.jalios.jcms.QueryManager. Celle-ci gère aussi bien les recherches déclenchées par des utilisateurs que les recherches effectuées par une Portlet Requête/Iteration pour afficher ses résultats. Il est important de noter que l'interface de présentation des résultats de JCMS ne sait traiter que des objets Publication. L'ensemble des résultats produits par le QueryManager ne doit donc être composé que d'instance de Publication. Pour les pièces jointes, ce n'est donc pas le fichier qui est retourné mais le FileDocument le référençant (et si ce FileDocument est lui-même référencé par un contenu, c'est ce contenu qui est mis dans la liste des résultats).

L'API QueryFilter permet de compléter ces mécanismes standards en agissant sur les paramètres d'une recherche et en modifiant l'ensemble de résultats obtenus par la méthode standard de recherche. L'API permet aussi d'intégrer les QueryFilter aux interfaces de recherche sans avoir à modifier les JSP.

2. Les QueryFilter

2.1 Principes de fonctionnement

Un QueryFilter permet d'agir en amont (paramétrage de la recherche) et en aval (liste des résultats) de toute recherche. Pour chaque recherche, les QueryFilter sont invoqués par le QueryManager pour 1) agir sur les paramètres de la requête, 2) enrichir ou restreindre l'ensemble des résultats obtenus par la recherche JCMS. Dans le cadre d'une recherche étendue à des sources externes, on enrichira l'ensemble des résultats avec les résultats externes. Mais il peut aussi être souhaitable de réduire l'ensemble des résultats. Par exemple, si les résultats contiennent plusieurs éléments appartenant à un même objet composite (p. ex. des FaqEntry qui composent une Faq, des objets Chapitre qui composent un objet Livre, …), il est possible de retirer ces résultats et de les remplacer par l'objet composite qui leur correspond.

L'extension de la recherche à une source d'information externe, consiste donc à développer un QueryFilter pour cette source. A priori il n'agira pas sur les paramètres de la requête mais enrichira les résultats de la recherche des résultats obtenus en interrogeant la source. Ce QueryFilter étant invoqué à chaque appel de la méthode query() (p. ex. pour l'affichage du contenu d'une portlet Requête/Itération),un paramètre supplémentaire dans la requête de recherche est ajouté pour déterminer si la source externe doit être utilisée. L'API des QueryFilter prend en charge la gestion de ce paramètre.

L'ensemble des résultats retournés par le QueryManager ne pouvant contenir que des instances de la classe Publication, le QueryFilter doit encapsuler les résultats obtenus sur la source externe dans des Publication. Pour cela, on utilise des objets transitoires (c'est-à-dire, non stockés dans le store). Ces objets doivent néanmoins avoir certains attributs tels qu'un identifiant, une cdate, une mdate, ... La méthode prepareExternalResult() de la classe QueryManager sert à remplir les champs d'une instance transitoire. Si les résultats peuvent être exprimés sous forme d'un titre, une description et une URL, il est possible d'utiliser le type WebPage. Sinon, il faut développer un type de contenu dédié à la présentation de ces résultats.

Enfin, lorsque le résultat affiché est un extrait d'une donnée plus détaillée, il convient de fournir à l'utilisateur un moyen de consulter la donnée complète. Si cette donnée est accessible par une URL, il suffit de mettre un lien dans le résultat de recherche, sinon il faut prévoir une JSP capable d'extraire et de présenter la donnée.

2.2 La classe QueryFilter

La classe abstraite com.jalios.jcms.QueryFilter comporte deux groupes de méthodes : celles permettant de filtrer la recherche et celles permettant d'intégrer le QueryFilter dans les interfaces de recherche.

2.2.1 Les méthodes agissant sur la recherche

  1. public QueryHandler filterQueryHandler(QueryHandler qh, Map context)
    Cette méthode reçoit en paramètre le QueryHandler (qh) et doit fournir en retour un QueryHandler. Un QueryHandler est un objet contenant l'ensemble des critères de recherche. L'implémentation par défaut renvoie le qh reçu. Cette méthode ne doit être surchargée que si les paramètres de recherches doivent être modifiés (par exemple pour modifier le texte recherché, pour agir sur les catégories, les types, ...)
    Le paramètre contexte permet de passer des informations entre l'appel à cette méthode et l'appel à filterResultSet().

  2. public QueryResultSet filterResultSet(QueryHandler qh, QueryResultSet set, Map context)
    Cette méthode reçoit en paramètre le QueryHandler qui a été utilisé pour traiter la requête (qh) et l'ensemble des résultat obtenus (set) par JCMS. Elle doit fournir en retour un ensemble de résultat. L'implémentation par défaut renvoie le set reçu. C'est typiquement dans cette méthode que l'on interroge la source d'information externe et que l'on ajoute des résultats à l'ensemble des résultats fournit.
    Le paramètre context permet de récupérer des informations positionnées par la méthode filterQueryHandler().

La classe QueryResultSet dérive de la classe java.util.HashSet. Elle contient non seulement les résultats d'une recherche mais aussi le score permettant de calculer les indices de pertinence.

La méthode utilitaire prepareExternalResult(Publication pub) de la classe QueryManager, remplit les champs (id, cdate, mdate, pdate, author) de la publication reçue afin de la rendre exploitable lors de l'affichage des résultats.

2.2.2 Les méthodes d'insertion dans l'interface de recherche

Ces méthodes doivent être surchargée pour le QueryFilter soit proposé de façon optionnelle dans les interfaces de recherche de JCMS.

  1. public String getSearchParam()
    Cette méthode doit renvoyer le nom du paramètre HTTP indiquant que le QueryFilter devra être pris en compte pour cette recherche.

  2. public String getSearchLabel(String lang)
    Cette méthode doit renvoyer le libellé du QueryFilter (le libellé peut contenir du code HTML).

  3. public String getSearchDesciption(String lang)
    Cette méthode doit renvoyer la description du QueryFilter.

  4. public boolean getSearchDefault()
    Cette méthode doit renvoyer true si le QueryFilter doit être sélectionné par defaut, false sinon.

2.2.3 Déclaration des QueryFilter

A partir de JCMS 5.7, il est fortement recommandé de développer en module (plugins). La déclaration du QueryFilter dans le fichier plugin.xml se fait par la balise <queryfilter> :

  <plugincomponents>
<queryfilter class="com.package.MyQueryFilter" />
</plugincomponents>

2.2.4 Limites

A partir de JCMS 6, certaines publications sont stockées dans JcmsDB. Lorsque la recherche est paginée, ces publications ne doivent pas être filtrées (sinon la pagination sera erronée).

3. Exemple : extension de la recherche à Google

3.1 Principes de fonctionnement

Cet exemple décrit le fonctionnement du Module Recherche Google qui ouverture de la recherche textuelle à Google. L'interface de recherche propose une nouvelle option permettant d'étendre ou non la recherche à Google.

google search

Fig. 1. Formulaire de recherche avec l'option de recherche dans Google

Lorsque cette option est cochée lors d'une recherche textuelle, les résultats comprendront non seulement des contenus de JCMS mais aussi les 10 premiers sites retournés par Google.

google results

Fig. 2. Exemple de résultats de recherche

 

Pour présenter les résultats issus de Google, on utilise des objets WebPage avec un gabarit de recherche dédié. Ce gabarit est déclaré dans le fichier plugin.xml :

  <types>
<templates type="WebPage">
<queryTemplate name="google" file="doGoogleResultDisplay.jsp">
<label xml:lang="en">Google Template</label>
<label xml:lang="fr">Gabarit Google</label>
</queryTemplate>
</templates>
</types>

Ce gabarit est assez proche du gabarit de présentation synthétique des WebPage mais ajoute le logo Google pour distinguer ces résultats des autres et le lien du titre amène sur le site référencé au lieu d'amener sur la vue détaillée de la WebPage.

3.2 Google Web APIs

Les recherches dans Google sont réalisées via la Google Web APIs. Il s'agit d'une bibliothèque Java permettant d'effectuer des recherches sur les sites indexés par Google. Bien qu'elle soit distribuée gratuitement, son utilisation requiert une clé qui peut être obtenue lors du téléchargement de l'API. Cette clé donne droit à 1000 requêtes/jour. Néanmoins, depuis le 5 décembre 2006 Google ne fournit plus de clé pour son API de recherche. Les clés obtenues précédement sont encore valides.

Pour cet exemple, la clé est déclarée dans le fichier plugin.prop :

# GoogleSearch - the key for the GoogleAPI
jcmsplugin.googleSearch.key: abcdefg1234

3.3 La classe GoogleQueryFilter

La classe GoogleQueryFilter est chargée d'étendre les recherches textuelle à Google.

package com.jalios.jcmsplugin.googleSearch;

import java.util.*;
import org.apache.log4j.Logger;
import com.google.soap.search.*;
import com.jalios.jcms.*;
import com.jalios.jcms.handler.*;
import com.jalios.jcms.plugin.Plugin;
import com.jalios.jcms.plugin.PluginComponent;
import com.jalios.util.*;
import generated.WebPage;

public class GoogleQueryFilter extends QueryFilter implements PluginComponent {

private Logger logger;

private static final String GOOGLE_KEY_PROP = "jcmsplugin.googleSearch.key";
private static final String DESCRIPTION_PROP = "jcmsplugin.googleSearch.description";

QueryManager queryMgr;
String key;
int maxResults = 10;

public GoogleQueryFilter() {
}

public boolean init(Plugin plugin) {
logger = plugin.getLogger();
logger.debug("Init");
queryMgr = Channel.getChannel().getQueryManager();
key = Channel.getChannel().getProperty(GOOGLE_KEY_PROP, null);
if (Util.isEmpty(key)) {
logger.warn("No Google key");
return false;
}
logger.debug("GoogleQueryFilter is enabled");
return true;
}

public QueryResultSet filterResultSet(QueryHandler qh, QueryResultSet set, Map context) {

// Check the Google key
if (Util.isEmpty(key)) {
logger.warn("No Google key");
return set;
}

// Skip non-textual search
String text = qh.getText();
if (Util.isEmpty(text)) {
return set;
}

// Check if the request must be extended to Google
if (qh.getRequest() != null && Util.isEmpty(qh.getRequest().getParameter("google"))) {
return set;
}

try {
// Prepare Google search
GoogleSearch search = new GoogleSearch();
search.setKey(key);
search.setQueryString(text);
search.setMaxResults(maxResults);

// Start Google search
logger.info("Start google search for: " + text);
GoogleSearchResult googleResults = search.doSearch();

// Add Google results
GoogleSearchResultElement[] elts = googleResults.getResultElements();
for(int i = 0; i < elts.length; i++) {
GoogleSearchResultElement elt = elts[i];

WebPage result = new WebPage();
queryMgr.prepareExternalResult(result);
result.setTitle(elt.getTitle());
result.setDescription(elt.getSnippet());
result.setUrl(elt.getURL());
result.setQueryTemplate("google");
set.add(result);
}
} catch (GoogleSearchFault ex) {
logger.warn("The call to the Google Web APIs failed:" + ex.toString());
}

return set;
}

public String getSearchParam() {
return "google";
}

public String getSearchLabel(String lang) {
return "<img src='http://www.google.com/logos/Logo_25wht.gif' align='absmiddle' alt='Google'>";
}

public String getSearchDescription(String lang) {
return JcmsUtil.glp(lang, DESCRIPTION_PROP);
}

public boolean getSearchDefault() {
return false;
}

}

Le constructeur récupère la clé via la propriété jcmsplugin.googleSearch.key. La méthode filterResultSet() commence par vérifier qu'il s'agit bien d'une recherche textuelle qui doit être étendue à Google. Si c'est le cas, une requête de recherche est envoyée à Google. Pour chaque résultat obtenu, on construit une WebPage qui décrit le site (titre, URL et description) et qui utilise le gabarit d'affichage synthétique google (associé au JSP doGoogleResult.jsp). Cette WebPage est ensuite ajoutée dans l'ensemble des résultats.

Enfin, la classe GoogleQueryFilter est déclarée dans le fichier plugin.xml :

  <plugincomponents>
<queryfilter class="com.jalios.jcmsplugin.googleSearch.GoogleQueryFilter"/>
</plugincomponents>

In brief...

Cet article décrit les possibilités offertes par les QueryFilter de JCMS 5.7 pour étendre les recherches à des sources d'information externes (base de données, bases documentaires, moteurs de recherche, …) et, d'une manière plus générale, pour agir sur les recherches et les résultats obtenus. Ces principes sont illustrés avec l'ouverture des recherches textuelles à Google.

Subject
Published

6/30/11

Writer
  • Olivier Dedieu