Introduction▲
Lucene appartient au fameux projet Jakarta de la fondation Apache, bien connu pour ses outils tels que Struts ou Tomcat. Cette API écrite en Java se destine à la création de puissants moteurs de recherche orientés texte. Parmi les nombreuses qualités de cette bibliothèque, nous retiendrons essentiellement sa rapidité d'exécution et sa simplicité d'utilisation, tant pour le programmeur que pour l'utilisateur final. La découverte de Lucene se déroulera en deux temps. En premier lieu, nous apprendrons à créer un index à partir de nos documents, pour ensuite soumettre des requêtes au moteur. L'application que nous allons étudier, intitulée WebIndex, permet l'indexation d'un document HTML déterminé par une URL.
I. Création de l'index▲
Toutes les requêtes de recherche effectuées par Lucene s'effectuent sur un index. Il s'agit d'une compilation de mots-clés et de propriétés identifiant des documents. Par exemple, nous pourrons indexer une page HTML grâce aux mots-clés contenus dans une balise <meta> et conserver en tant que propriétés son URL complète et son titre.
Lors de la création d'un index, la bibliothèque crée un nouveau répertoire contenant plusieurs fichiers appelés des segments. Vous pourrez vous reporter au site officiel de Lucene (http://jakarta.apache.org/lucene) pour obtenir des informations sur la structure de ces fichiers.
Les classes nécessaires à la création d'un nouvel index, ou à l'extension d'un existant, se trouvent dans les paquetages org.apache.lucene.index et org.apache.lucene.analysis.standard. Le premier contient l'outil de création de l'index tandis que le second accueille les analyseurs de texte. En effet, pendant la création de l'index, Lucene fait appel à des tokenizers et des filtres. Ceux-ci permettent respectivement de découper le texte et de lui appliquer des modifications. Ainsi, le StandardAnalyser découpe le texte mot par mot, les met en minuscules puis retire ceux qui sont inutiles (comme en anglais « the » ou « and »). Vous pouvez bien entendu créer vos propres combinaisons de filtres, et même en créer de nouveaux.
Une fois un analyseur créé, nous pouvons construire une instance d'IndexWriter et lui ajouter des documents. Le listing 1 expose la manière de la créer.
Le premier paramètre du constructeur d'IndexWriter désigne le nom du répertoire dans lequel sera enregistré l'index. Le dernier paramètre permet pour sa part de contrôler le mode de création de l'index. Lorsqu'il vaut true, un nouvel index sera créé, au risque de supprimer celui existant. En choisissant la valeur false, nous demandons à Lucene d'ajouter les informations du nouvel index à celui existant.
Une fois l'IndexWriter créé, il incombe au programmeur de compiler des documents par l'intermédiaire de la classe Document du paquetage org.apache.lucene.document, et de les placer dans l'index. Notre programme réalise cette tâche dans la méthode addDocument(). Un document se compose lui-même de champs, concrétisés par des instances de la classe Field. Le listing 2 présente l'ajout de champs à un document, puis l'ajout dudit document à l'index. Ce code source expose les trois sortes de champs que nous pouvons placer dans un document : non indexé, non stocké et stocké/indexé.
Le type non indexé, que nous pouvons réaliser par l'intermédiaire de la méthode utilitaire UnIndexed(), sert à ajouter des caractéristiques à un document, lesquelles ne seront pas affectées par l'analyseur. Dans notre exemple, nous ajoutons l'URL de cette manière afin de la présenter lors des résultats de recherche. Le deuxième type se voit affecté par l'analyseur, mais nous pouvons par la suite récupérer la valeur employée pour créer le champ. Enfin, nous avons à notre disposition le type Text(), qui est en même temps indexé et accessible.
La méthode addDocument() de WebIndex pousse le concept un peu plus loin en indexant toutes les pages HTML liées depuis celle choisie par l'utilisateur. Ce travail repose sur la classe interne HTTPDocument qui fait un usage intensif des expressions régulières.
II. Requêtes▲
À partir d'un index, notre application peut permettre à l'utilisateur de saisir des requêtes pour lui retourner les résultats pertinents. La syntaxe acceptée par Lucene pour les requêtes se révèle particulièrement puissante, bien que relativement simple. Outre les traditionnels modificateurs « + » et « - », les jokers « * » et « ? », ainsi que les opérateurs booléens « AND », « OR » et « NOT » (que nous pouvons raccourcir en « && », « || » et « ! »), nous avons accès à deux fonctionnalités particulièrement intéressantes. Ainsi, l'accent circonflexe, placé à la fin d'un mot, permet d'augmenter la priorité d'un mot. Ainsi, dans la requête « cpp OR java^ », Lucene choisira en priorité le terme « java ». Cet opérateur peut être suivi d'un chiffre positif définissant la valeur de la priorité. À cela s'ajoute l'opérateur tilde « ~ » qui trouve un emploi dans deux situations. Dans l'exemple « java lucene »~5, le moteur cherchera les termes java et lucene situés à une distance de moins de 5 mots l'un de l'autre. Par contre, la recherche de « form~ » renverra tous les mots similaires à « form », ce qui inclut par exemple « farm » ou « firm ». Enfin, nous pouvons effectuer des requêtes sur des champs particuliers des documents en préfixant les requêtes par le nom du champ suivi du caractère deux-points, comme dans « titre:Lucene ».
III. Effectuer une recherche▲
Les classes relatives aux recherches se situent dans les paquetages org.apache.lucene.search et org.apache.lucene.queryParser. Avant toute chose, le développeur doit charger un index sur lequel les requêtes seront appliquées :
Searcher searcher =
new
IndexSearcher
(
"./webindex/"
);
Ceci fait, nous pouvons interpréter la requête tapée par l'utilisateur et récupérer les résultats. Vous remarquerez que nous faisons une fois de plus appel à l'analyseur standard, ici employé pour déterminer les termes contenus dans la requête. Le second paramètre de la méthode utilitaire parse() désigne le champ de recherche par défaut. La dernière ligne récupère la liste des documents répondant aux exigences de l'utilisateur.
StandardAnalyzer analyzer =
new
StandardAnalyzer
(
);
Query query =
QueryParser.parse
(
searchQuery, "contentKeywords"
, analyzer);
Hits hits =
searcher.search
(
query);
L'objet hits se manipule comme une collection d'objets classique, à ceci près qu'il ne contient que des instances de Document. Pour le classement des résultats, le programmeur pour s'intéresser à la méthode score() de la classe Hits qui retourne le score d'un résultat. L'élégance et la simplicité de cette bibliothèque lui permettront de trouver un emploi sûr dans de nombreux logiciels, particulièrement les applications Web, les forums ou des catalogues d'information comme une encyclopédie.
StandardAnalyzer analyser =
new
StandardAnalyzer
(
);
IndexWriter writer =
new
IndexWriter
(
"./webindex/"
, analyser, true
);
addDocument
(
writer, url);
writer.optimize
(
);
writer.close
(
);
HTTPDocument httpd =
new
HTTPDocument
(
url);
Document doc =
new
Document
(
);
doc.add
(
Field.UnIndexed
(
"url"
, httpd.getURL
(
)));
doc.add
(
Field.UnIndexed
(
"title"
, httpd.getTitle
(
)));
doc.add
(
Field.UnStored
(
"keywords"
, httpd.getKeywords
(
)));
doc.add
(
Field.Text
(
"content"
, httpd.getContent
(
));
writer.addDocument
(
doc);
for
(
int
i =
0
; i <
hits.length
(
); i++
)
{
Document doc =
hits.doc
(
i);
System.out.println
(
doc.get
(
"title"
) +
" - "
+
doc.get
(
"url"
));
}