Introduction au RDF et à l'API RDF de Jena
Date de publication : 25/04/11. Date de mise à jour : 17/09/2011.
Par
Brian McBride
Daniel Boothby
Chris Dollin
Traducteur : Thibaut Cuvelier

Traducteur : Julien Plu

Ceci est un tutoriel introductif à la fois au framework de description de ressources (RDF, Resource Description Framework) du W3C et à Jena,
une API Java pour RDF. Il est écrit pour le développeur peu familier à RDF et qui apprend le mieux en prototypant ou, pour d'autres raisons, désire aller
rapidement à l'implémentation. Une certaine habitude à XML et à Java est requise.
Implémenter trop vite, sans d'abord comprendre le modèle de données de RDF, mène à la frustration, à la déception. Cependant, étudier le modèle de données
seul est assez difficile et mène généralement à des énigmes métaphysiques tortueuses. Il vaut mieux approcher la compréhension du modèle de données et de
son utilisation en parallèle, apprendre un peu du modèle et l'essayer, puis passer au morceau suivant. Ainsi, la théorie complète la pratique et la pratique
complète la théorie. Le modèle de données est assez simple, cette approche ne devrait pas prendre trop de temps.
RDF possède une syntaxe XML et tous ceux qui sont familiers avec XML le prendront pour tel. C'est une erreur. RDF devrait être compris en termes de son modèle
de données. Des données RDF peuvent être représentées en XML, mais comprendre la syntaxe est secondaire à celle du modèle de données.
Une implémentation de l'API Jena, incluant le code source de travail pour tous les exemples de ce tutoriel, peut être téléchargée depuis
le site officiel de Jena.
I. L'article original
II. Introduction
III. Déclaration (statement)
IV. Écriture de RDF
V. Lecture de RDF
VI. Préfixes de contrôle
VI-A. Définitions de préfixes explicites
VI-B. Définitions de préfixes implicites
VII. Paquets RDF de Jena
VIII. Navigation dans un modèle
IX. Interrogation d'un modèle
X. Opérations sur les modèles
XI. Conteneurs
XII. Littérales et types de données
XIII. Glossaire
XIV. Notes
XV. Remerciements
I. L'article original
II. Introduction
RDF est un standard (d'un point de vue purement technique, une recommandation du W3C) pour la description de ressources. Qu'est-ce qu'une ressource ?
C'est une question plutôt profonde et la définition précise est toujours l'objet de tumultueux débats. Pour notre utilisation, on peut imaginer qu'une
ressource est quelque chose que l'on peut identifier. Vous êtes une ressource, au même titre que votre page d'accueil, ce tutoriel ou la baleine blanche
de Moby Dick.
Nos exemples seront des personnes. Ils utilisent une représentation RDF des
VCARDS.
RDF se représente le mieux sous la forme de nœuds et de diagrammes en arcs. Une VCARD simple pourrait ressembler à ceci en RDF :

VCARD en RDF
La ressource, John Smith, est représentée comme une ellipse et est identifiée par un identifiant de ressource uniforme (URI, Uniform Resource Identifiant),
dans ce cas http://.../JohnSmith. Si vous essayez d'accéder à cette ressource dans votre navigateur, vous n'y arriverez probablement pas ;
résistant à la tentation du poisson d'avril, il serait étonnant qu'un navigateur puisse vous amener John Smith. Si vous n'êtes pas familier aux URI, prenez-les
simplement comme des noms d'apparence assez bizarre.
Les ressources possèdent des propriétés. Dans ces exemples, on s'intéresse au type de propriétés qui pourraient apparaître sur la carte de visite de ce John Smith.
La première figure montre une propriété, son nom complet. Une propriété est représentée par un arc, nommé avec la propriété. Le nom de la propriété est aussi
une URI, mais une URI plutôt longue et encombrante, le diagramme montre donc une forme XML. La partie avant : s'appelle le préfixe d'espace
de noms et représente un espace de noms. La partie après : est appelée un nom local et représente un nom dans cet espace. Les propriétés sont
habituellement représentées dans cette forme qname dans du RDF XML et c'est un raccourci pratique pour les représenter dans des diagrammes et du texte.
Strictement parlant, cependant, les propriétés sont identifiées par une URI. La forme nsprefix:localname est un raccourci pour cette URI de
l'espace de noms concaténé au nom local. Il n'y a pas besoin que l'URI de la propriété donne accès à quelque chose par le biais d'un navigateur.
Chaque propriété a une valeur. Dans ce cas, la valeur est littérale, soit, pour le moment, une chaîne de caractères. Ces littérales sont montrées dans des rectangles.
Jena est une API Java qui peut être utilisée pour créer et manipuler des graphes RDF comme celui-ci. Jane possède des classes pour représenter des graphes,
des ressources, des propriétés et des littérales. Les interfaces représentant des ressources, des propriétés et des littérales sont respectivement nommées
Resource, Property et Literal. Dans Jena, un graphe est appelé un modèle et est représenté par l'interface
Model.
Le code pour créer ce graphe (ou modèle) est simple :
static String personURI = "http://somewhere/JohnSmith";
static String fullName = "John Smith";
Model model = ModelFactory.createDefaultModel();
Resource johnSmith = model.createResource(personURI);
johnSmith.addProperty(VCARD.FN, fullName);
|
Il commence avec des définitions de constantes et ensuite crée un modèle vide en utilisant la méthode createDefaultModel() de
ModelFactory pour créer un modèle en mémoire. Jena contient d'autres implémentations de l'interface Model,
par exemple celle qui utilise une base de données relationnelle (SGBDR) : ces types de modèles sont aussi disponibles depuis ModelFactory.
La ressource John Smith est alors créée et une propriété y est ajoutée. La propriété est fournie par une classe « constante »
VCARD, qui détient les objets représentant toutes les définitions dans le schéma VCARD. Jena fournit des classes constantes pour d'autres schémas
bien connus, comme RDF et RDF Schema eux-mêmes, Dublin Core et DAML.
Le code pour créer la ressource et ajouter la propriété peut être écrit d'une manière plus compacte avec un style en cascade :
Resource johnSmith =
model.createResource(personURI)
.addProperty(VCARD.FN, fullName);
|
Le code de travail pour cet exemple peut être trouvé dans le répertoire
/src-examples de la distribution Jena, dans le fichier
Tutorial01. Comme exercice,
prenez ce code et modifiez-le pour créer une VCARD simple vous-même.
Maintenant, ajoutons un peu de détails à cette VCARD, en explorant d'autres fonctionnalités de Jena et de RDF.
Dans ce premier exemple, la valeur de la propriété était littérale ; les propriétés RDF peuvent aussi prendre d'autres ressources comme valeur.
En utilisant une technique RDF courante, cet exemple montre comment représenter les différentes parties du nom de John Smith :
Ici, on a ajouté une nouvelle propriété, vcard:N, pour représenter la structure du nom de John Smith. Il y a plusieurs choses dignes
d'intérêt dans ce modèle. Notez que la propriété vcard:N prend une ressource comme valeur. Notez aussi que l'ellipse représentant le nom
composé n'a pas d'URI. On appelle cela un nœud anonyme.
Le code Jena pour construire cet exemple est encore une fois très simple. Quelques déclarations et puis la création du modèle vide.
String personURI = "http://somewhere/JohnSmith";
String givenName = "John";
String familyName = "Smith";
String fullName = givenName + " " + familyName;
Model model = ModelFactory.createDefaultModel();
Resource johnSmith
= model.createResource(personURI)
.addProperty(VCARD.FN, fullName)
.addProperty(VCARD.N,
model.createResource()
.addProperty(VCARD.Given, givenName)
.addProperty(VCARD.Family, familyName));
|
Le code de travail de cet exemple peut être trouvé comme
tutorial-2 dans le même
répertoire de la distribution Jena.
III. Déclaration (statement)
Chaque arc dans un modèle RDF est appelé une déclaration. Chacune affirme un fait à propos d'une ressource. Une déclaration se décompose en trois parties :
-
le sujet est la ressource que quitte l'arc ;
-
le prédicat est la propriété qui donne un nom à l'arc ;
-
l'objet est la ressource ou la valeur littérale pointée par l'arc.
Une telle déclaration est donc parfois appelée un triplet, à cause de ces trois parties.
Un modèle RDF est représenté comme un ensemble de ces déclarations. Chaque appel à addProperty() ajoute une autre déclaration au modèle.
Puisqu'un modèle est un ensemble de déclarations, ajouter une déclaration en double n'a pas d'effet. Les interfaces de modèle de Jena définissent une méthode
listStatements(), qui retourne un StmtIterator, un dérivé de la classe Java Iterator sur toutes les déclarations
dans un modèle. StmtIterator possède une méthode nextStatement(), qui retourne la déclaration suivante de l'itérateur (le même que
next() retournerait, déjà casté en Statement). L'interface Statement fournit des méthodes d'accès au sujet, au
prédicat et à l'objet de la déclaration.
Maintenant, on va utiliser cette interface pour étendre le deuxième exemple, lister toutes les déclarations créées et les afficher. Le code complet se trouve
dans
tutorial 3.
StmtIterator iter = model.listStatements();while (iter.hasNext()) {
Statement stmt = iter.nextStatement();
Resource subject = stmt.getSubject();
Property predicate = stmt.getPredicate();
RDFNode object = stmt.getObject();
System.out.print(" " + predicate.toString() + " ");
if (object instanceof Resource) {
System.out.print(object.toString());
} else {
System.out.print(" \"" + object.toString() + "\"");
} System.out.println(" .");
}
|
Puisque l'objet d'une déclaration peut être une ressource ou une valeur littérale, la méthode getObject() retourne un objet de type
RDFNode, une superclasse commune à Resource et à Literal. L'objet sous-jacent est du type approprié,
ainsi le code utilise instanceof pour déterminer lequel et le traiter en fonction.
Quand il est lancé, ce programme devrait produire une sortie comme ceci :
http://somewhere/JohnSmith http://www.w3.org/2001/vcard-rdf/3.0#N anon:14df86:ecc3dee17b:-7fff .
anon:14df86:ecc3dee17b:-7fff http://www.w3.org/2001/vcard-rdf/3.0#Family "Smith" .
anon:14df86:ecc3dee17b:-7fff http://www.w3.org/2001/vcard-rdf/3.0#Given "John" .
http://somewhere/JohnSmith http://www.w3.org/2001/vcard-rdf/3.0#FN "John Smith" .
|
Maintenant, vous savez pourquoi il est plus simple de dessiner des modèles. En y regardant de plus près, on peut voir que chaque ligne consiste en trois champs,
représentant le sujet, le prédicat et l'objet de chaque déclaration. Il y a quatre arcs dans le modèle, donc quatre déclarations.
anon:14df86:ecc3dee17b:-7fff est un identifiant interne généré par Jena. Il ne s'agit pas d'une URI et il ne devrait pas être confondu avec une URI.
Il s'agit simplement d'une dénomination interne utilisée par l'implémentation de Jena.
IV. Écriture de RDF
Jena dispose de méthodes pour lire et écrire du RDF comme du XML. On peut les utiliser pour sauvegarder un modèle RDF dans un fichier et le relire après.
Le troisième tutoriel a créé un modèle et l'a ressorti sous forme de triplets.
Le quatrième écrit ce modèle
en RDF XML sur le flux de sortie standard. Le code est, à nouveau, très simple :
model.write() peut prendre un argument
OutputStream.
La sortie devrait ressembler à ceci :
<rdf:RDF
xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'
xmlns:vcard='http://www.w3.org/2001/vcard-rdf/3.0#'
>
<rdf:Description rdf:about='http://somewhere/JohnSmith'>
<vcard:FN>John Smith</vcard:FN>
<vcard:N rdf:nodeID="A0"/>
</rdf:Description>
<rdf:Description rdf:nodeID="A0">
<vcard:Given>John</vcard:Given>
<vcard:Family>Smith</vcard:Family>
</rdf:Description>
</rdf:RDF>
|
Les spécifications RDF disent comment représenter le RDF sous forme de XML. La syntaxe RDF XML est assez complexe. Le lecteur devrait plutôt regarder
du côté de
Primer, en cours de développement par le groupe de travail RDFCore pour
une introduction plus détaillée. Cependant, regardons rapidement comment interpréter ce code.
RDF est habituellement inclus dans un élément <rdf:RDF>. L'élément est optionnel s'il y a d'autres manières de savoir que du XML
est du RDF, mais il est généralement présent. L'élément RDF définit les deux espaces de noms utilisés dans le document. Il y a ensuite un élément
<rdf:description> qui décrit la ressource dont l'URI est http://somewhere/JohnSmith. Si l'attribut rdf:about
était absent, cet élément représenterait un nœud anonyme.
L'élément <vcard:FN> décrit une propriété de cette ressource. Le nom de la propriété est FN dans l'espace de noms
vcard. RDF convertit ceci en une référence à une URI en concaténant la référence d'URI pour le préfixe de l'espace de noms et
FN, la partie du nom local du nom. Ceci donne une référence d'URI de http://www.w3.org/2001/vcard-rdf/3.0#FN.
La valeur de la propriété est la littérale « John Smith ».
L'élément <vcard:N> est une ressource. Dans ce cas, la ressource est représentée par une référence d'URI relative.
RDF convertit cela en une référence d'URI absolue en la concaténant avec l'URI de base du document courant.
Il y a cependant une erreur dans ce RDF XML ; il ne représente pas exactement le même modèle que ce que l'on a créé. Le nœud anonyme a reçu
une référence d'URI. Il n'est donc plus blanc. La syntaxe RDF XML n'est pas capable de représenter tous les modèles RDF ; par exemple, il ne
peut pas représenter un nœud anonyme qui est l'objet de deux déclarations. Le module d'écriture que l'on a utilisé pour écrire ce RDF XML n'essaie
même pas d'écrire correctement le sous-ensemble des modèles qu'il peut écrire correctement dans ce cas. Il donne une URI à chaque nœud anonyme.
Jena a une interface extensible qui permet d'utiliser de nouveaux modules d'écriture pour d'autres langages de sérialisation pour RDF.
Le précédent peut être appelé l'« idiot ». Jena inclut aussi un module d'écriture RDF XML plus sophistiqué, qui peut être invoqué
en passant un autre argument lors de l'appel à la méthode call() :
model.write(System.out, "RDF/XML-ABBREV");
|
Ce module, appelé « PrettyWritter », se base sur les fonctionnalités de syntaxe abrégée de RDF XML pour écrire un modèle de manière
plus compacte. Il peut aussi préserver les nœuds anonymes où cela est possible. Cependant, il n'est pas utilisable pour de très grands modèles, ses performances
étant probablement fort peu acceptables. Pour écrire de grands modèles et préserver de tels nœuds, il faut utiliser le format N-triplet :
model.write(System.out, "N-TRIPLE");
|
Ceci produira alors une sortie similaire à celle du tutoriel 3, qui se conforme maintenant aux spécifications.
V. Lecture de RDF
Le cinquième tutoriel
montre comment les déclarations sont lues depuis le format RDF XML dans un modèle. Avec ce tutoriel, on fournit une petite base de données de VCARD.
Le code suivant va le lire et l'écrire dans le flux standard de sortie. Notez que, pour que cette application se lance, il faut que le fichier d'entrée
soit dans le répertoire courant.
Model model = ModelFactory.createDefaultModel();InputStream in = FileManager.get().open( inputFileName );
if (in == null) {
throw new IllegalArgumentException("File: " + inputFileName + " not found");
}model.read(in, null);model.write(System.out);
|
Le second argument de read() est l'URI qui est utilisée pour résoudre les URI relatives. Comme il n'y a pas de telles URI dans le fichier de test,
il peut être laissé vide. Au lancement, ce tutoriel va produire une sortie XML qui ressemble à ceci :
<rdf:RDF
xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'
xmlns:vcard='http://www.w3.org/2001/vcard-rdf/3.0#'
>
<rdf:Description rdf:nodeID="A0">
<vcard:Family>Smith</vcard:Family>
<vcard:Given>John</vcard:Given>
</rdf:Description>
<rdf:Description rdf:about='http://somewhere/JohnSmith/'>
<vcard:FN>John Smith</vcard:FN>
<vcard:N rdf:nodeID="A0"/>
</rdf:Description>
<rdf:Description rdf:about='http://somewhere/SarahJones/'>
<vcard:FN>Sarah Jones</vcard:FN>
<vcard:N rdf:nodeID="A1"/>
</rdf:Description>
<rdf:Description rdf:about='http://somewhere/MattJones/'>
<vcard:FN>Matt Jones</vcard:FN>
<vcard:N rdf:nodeID="A2"/>
</rdf:Description>
<rdf:Description rdf:nodeID="A3">
<vcard:Family>Smith</vcard:Family>
<vcard:Given>Rebecca</vcard:Given>
</rdf:Description>
<rdf:Description rdf:nodeID="A1">
<vcard:Family>Jones</vcard:Family>
<vcard:Given>Sarah</vcard:Given>
</rdf:Description>
<rdf:Description rdf:nodeID="A2">
<vcard:Family>Jones</vcard:Family>
<vcard:Given>Matthew</vcard:Given>
</rdf:Description>
<rdf:Description rdf:about='http://somewhere/RebeccaSmith/'>
<vcard:FN>Becky Smith</vcard:FN>
<vcard:N rdf:nodeID="A3"/>
</rdf:Description>
</rdf:RDF>
|
VI. Préfixes de contrôle
VI-A. Définitions de préfixes explicites
Dans la section précédente, on a vu que la sortie XML déclarait un préfixe d'espace de noms vcard et utilisait ce préfixe
pour abréger les URI. Alors que RDF n'utilise que des URI complètes, pas cette forme raccourcie, Jena fournit des méthodes pour contrôler
les espaces de noms utilisés à la sortie avec ses mappings de préfixes. Voici un exemple simple :
Model m = ModelFactory.createDefaultModel();
String nsA = "http://somewhere/else#";
String nsB = "http://nowhere/else#";
Resource root = m.createResource( nsA + "root" );
Property P = m.createProperty( nsA + "P" );
Property Q = m.createProperty( nsB + "Q" );
Resource x = m.createResource( nsA + "x" );
Resource y = m.createResource( nsA + "y" );
Resource z = m.createResource( nsA + "z" );
m.add( root, P, x ).add( root, P, y ).add( y, Q, z );
System.out.println( "# -- no special prefixes defined" );
m.write( System.out );
System.out.println( "# -- nsA defined" );
m.setNsPrefix( "nsA", nsA );
m.write( System.out );
System.out.println( "# -- nsA and cat defined" );
m.setNsPrefix( "cat", nsB );
m.write( System.out );
|
La sortie de ce fragment est constituée de trois morceaux de RDF XML, avec trois mappings différents. D'abord, celui par défaut,
sans préfixe autre que les standards :
<rdf:RDF
xmlns:j.0="http://nowhere/else#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:j.1="http://somewhere/else#" >
<rdf:Description rdf:about="http://somewhere/else#root">
<j.1:P rdf:resource="http://somewhere/else#x"/>
<j.1:P rdf:resource="http://somewhere/else#y"/>
</rdf:Description>
<rdf:Description rdf:about="http://somewhere/else#y">
<j.0:Q rdf:resource="http://somewhere/else#z"/>
</rdf:Description>
</rdf:RDF>
|
On a vu que l'espace de nom rdf est déclaré automatiquement, puisqu'il est requis pour des balises comme <RDF:rdf>
et <rdf:resource>. Les déclarations d'espaces de noms sont aussi requises pour l'utilisation des deux propriétés P et Q, mais,
puisque leurs espaces de noms n'ont pas été introduits dans le modèle, ils reçoivent des noms d'espaces inventés : j.0 et j.1.
La méthode setNsPrefix(String prefix, String URI) déclare que l'espace de nom URI peut être abrégé par
prefix. Jena requiert que prefix soit un nom d'espace légal en XML et que URI se termine
par un caractère qui n'est pas un nom. Le module d'écriture RDF XML va transformer des déclarations de préfixes en déclarations d'espaces de
noms XML et les utiliser dans sa sortie :
<rdf:RDF
xmlns:j.0="http://nowhere/else#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:nsA="http://somewhere/else#" >
<rdf:Description rdf:about="http://somewhere/else#root">
<nsA:P rdf:resource="http://somewhere/else#x"/>
<nsA:P rdf:resource="http://somewhere/else#y"/>
</rdf:Description>
<rdf:Description rdf:about="http://somewhere/else#y">
<j.0:Q rdf:resource="http://somewhere/else#z"/>
</rdf:Description>
</rdf:RDF>
|
L'autre espace de nom reçoit toujours un nom construit à la volée, mais le nom nsA est maintenant utilisé dans les balises
de propriétés. Il n'y a pas besoin que le nom du préfixe ait quoi que ce soit à voir avec les variables dans le code Jena :
<rdf:RDF
xmlns:cat="http://nowhere/else#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:nsA="http://somewhere/else#" >
<rdf:Description rdf:about="http://somewhere/else#root">
<nsA:P rdf:resource="http://somewhere/else#x"/>
<nsA:P rdf:resource="http://somewhere/else#y"/>
</rdf:Description>
<rdf:Description rdf:about="http://somewhere/else#y">
<cat:Q rdf:resource="http://somewhere/else#z"/>
</rdf:Description>
</rdf:RDF>
|
Les deux préfixes sont utilisés pour la sortie et aucun préfixe généré n'est requis.
VI-B. Définitions de préfixes implicites
Comme pour les déclarations de préfixes fournies par les appels à setNsPrefix(), Jena se souviendra des préfixes qui ont été
utilisés dans l'entrée avec model.read().
On prend la sortie produite par le fragment précédent et on la colle dans un fichier quelconque, d'URL file:/tmp/fragment.rdf,
par exemple. Ensuite, on lance ce code :
Model m2 = ModelFactory.createDefaultModel();
m2.read( "file:/tmp/fragment.rdf" );
m2.write( System.out );
|
On voit que les préfixes de l'entrée sont préservés dans la sortie. Tous les préfixes sont écrits, même s'ils ne sont utilisés nulle part.
On peut supprimer un préfixe avec removeNsPrefix(String prefix) si on ne veut pas le voir dans la sortie.
Puisque les N-triplets n'ont pas de méthode courte pour écrire des URI, cela n'a pas d'effet sur les préfixes à la sortie et n'en fournit aucun à l'entrée.
La notation N3, aussi supportée par Jena, possède de tels noms de préfixes raccourcis et les enregistre à l'entrée puis les utilise à la sortie.
Jena possède des opérations plus évoluées sur les mappings de préfixes que contient un modèle, comme l'extraction d'une Map Java des
mappings à la sortie ou l'ajout d'un groupe entier de mappings ; voir la documentation de PrefixMapping pour les détails.
VII. Paquets RDF de Jena
Jena est l'API Java pour les applications du Web sémantique. Le paquet-clé RDF pour le développeur de telles applications est
com.hp.hpl.jena.rdf.model. L'API a été définie en termes d'interfaces, de telle sorte que le code de l'application puisse
fonctionner avec différentes implémentations sans changement. Ce paquet contient aussi les interfaces pour représenter des modèles, des ressources,
des propriétés, des littérales, des déclarations et tous les autres concepts-clés de RDF, ainsi qu'une ModelFactory pour la création
des modèles. Ainsi, le code de l'application reste indépendant de l'implémentation, si du moins il utilise les interfaces autant que possible, pas les
implémentations spécifiques.
Le paquet com.hp.hpl.jena.tutorial contient le code source de travail pour tous les exemples de ce tutoriel.
Les paquets com.hp.hpl.jenaimpl contiennent l'implémentation des classes qui peuvent être communes à plusieurs implémentations.
Par exemple, elles définissent les classes ResourceImpl, PropertyImpl et LiteralImpl,
qui peuvent être utilisées directement ou dérivées par les différentes implémentations. Les applications devraient rarement (voire jamais)
utiliser ces classes directement. Par exemple, au lieu de créer une nouvelle instance de ResourceImpl, il vaut mieux utiliser
la méthode createResource() du modèle utilisé. Ainsi, si l'implémentation du modèle utilise une implémentation optimisée de
Resource, aucune conversion de type ne sera nécessaire.
VIII. Navigation dans un modèle
Jusqu'à présent, ce tutoriel a parlé principalement de la création, de la lecture et de l'écriture de modèles RDF. Maintenant, il est temps
de s'occuper de l'accès à l'information stockée dans un modèle.
Avec une URI de ressource, l'objet de ressource peut être récupéré depuis un modèle en utilisant la méthode Model.getResource(String uri).
Cette méthode est définie pour retourner un objet Resource s'il en existe un dans le modèle et, si non, pour en créer un. Par exemple, pour récupérer
la ressource John Smith du modèle lu dans le tutoriel cinq :
Resource vcard = model.getResource(johnSmithURI);
|
L'interface Resource définit un nombre de méthodes pour accéder aux propriétés d'une ressource. La méthode
Resource.getProperty(Property p) permet d'accéder à une propriété de cette ressource. Cette méthode ne suit
pas la convention sur les accesseurs de Java, le type de l'objet retourné étant Statement, pas Property
comme on pourrait s'y attendre. Retourner la déclaration au complet permet à l'application d'accéder à la valeur de la propriété en utilisant
une de ses méthodes d'accession qui retourne un objet de déclaration. Par exemple, pour récupérer la ressource qui est la valeur de la
propriété vcard:N :
Resource name = (Resource) vcard.getProperty(VCARD.N)
.getObject();
|
En général, l'objet d'une déclaration est une ressource ou une littérale, ainsi le code de l'application, sachant que la valeur doit être
une ressource, caste l'objet retourné. L'une des choses que Jena essaie de faire est de fournir des méthodes spécifiques aux types, pour que
l'application ne doive pas caster. La vérification du type peut être faite à la compilation. Le fragment de code ci-dessus peut être mieux écrit :
Resource name = vcard.getProperty(VCARD.N)
.getResource();
|
De la même manière, la valeur littérale d'une propriété peut être récupérée :
String fullName = vcard.getProperty(VCARD.FN)
.getString();
|
Dans cet exemple, la ressource VCARD n'a qu'une propriété vcard:FN et une autre vcard:N. RDF autorise une ressource
à répéter une propriété ; par exemple, Adman pourrait avoir plus d'un surnom. Donnons-lui en deux :
vcard.addProperty(VCARD.NICKNAME, "Smithy")
.addProperty(VCARD.NICKNAME, "Adman");
|
Comme précédemment noté, Jena représente un modèle RDF comme une collection de déclarations, ainsi l'ajout d'une autre déclaration avec un sujet, un prédicat et
un objet dans le modèle n'a pas d'effet. Jena ne définit pas lequel des deux surnoms présents dans le modèle sera retourné. Le résultat de l'appel à
vcard.getProperty(VCARD.NICKNAME) est indéterminé. Jena retournera une des valeurs, mais rien ne garantit que deux appels consécutifs
retourneront la même valeur.
S'il est possible qu'une propriété apparaisse plus d'une fois, la propriété Resource.listProperties(Property p) peut être utilisée et
retourner un itérateur qui va toutes les lister. Cette méthode renvoie un itérateur qui retourne des objets de type Statement. On peut
lister les surnoms comme ceci :
System.out.println("The nicknames of \""
+ fullName + "\" are:");StmtIterator iter = vcard.listProperties(VCARD.NICKNAME);
while (iter.hasNext()) {
System.out.println(" " + iter.nextStatement()
.getObject()
.toString());
}
|
Ce code peut être trouvé dans
le tutoriel six.
L'itérateur de déclarations
iter produit pour chaque déclaration un sujet
vcard et un prédicat
VCARD.NICKNAME.
Ainsi, boucler sur lui permet de récupérer toutes les déclarations, en utilisant
nextStatement(), en récupérant le champ d'objet et en le
convertissant en chaîne de caractères. Le code produit cette sortie :
The nicknames of "John Smith" are:
Smithy
Adman
|
Toutes les propriétés d'une ressource peuvent être listées en utilisant la méthode listProperties() sans argument.
IX. Interrogation d'un modèle
La section précédente traitait du cas de la navigation d'un modèle à partir d'une ressource avec une URI connue. Cette section-ci traite de la recherche
dans un modèle. Le noyau de l'API Jena prend en charge uniquement une requête limitée et primitive. Les implémentations les plus puissantes de RDQL sont
décrites ailleurs.
La méthode Model.listStatements(), qui répertorie tous les états dans un modèle, est peut-être la plus cruelle pour interroger un modèle.
Son utilisation n'est pas recommandée sur les très grands modèles. Model.listSubjects() est similaire, mais renvoie un itérateur sur
toutes les ressources qui ont des propriétés, c'est-à-dire qui font l'objet d'une déclaration.
Model.listSubjectsWithProperty(Property p, RDFNode o) retourne un itérateur sur toutes les ressources qui ont la propriété p
avec la valeur o. Si nous supposons que seulement les ressources de VCARD auront la propriété vcard:FN et que, dans les données,
toutes ces ressources ont une telle propriété, alors on peut trouver toutes les cartes de visite comme ceci :
ResIterator iter = model.listSubjectsWithProperty(VCARD.FN);
while (iter.hasNext())
{
Ressource r = iter.nextRessource();
...
}
|
Toutes ces méthodes d'interrogation sont tout simplement du sucre syntaxique sur une requête primitive de la méthode
model.listStatements(Selector s). Cette méthode renvoie un itérateur sur toutes les déclarations dans le modèle
« sélectionné » par s. L'interface de sélection est conçue pour être extensible, mais, pour l'instant, il n'y a qu'une
seule mise en œuvre de celle-ci, la classe SimpleSelector du paquet com.hp.hpl.jena.rdf.model.
L'utilisation de SimpleSelector est l'une des rares occasions dans Jena où il est nécessaire d'utiliser une classe
spécifique plutôt qu'une interface. Le constructeur de SimpleSelector prend trois arguments :
Selector selector = new SimpleSelector(subject, predicate, object);
|
Il permet de sélectionner tous les états d'un sujet qui correspond à subject, un prédicat qui correspond à predicate
et un objet qui correspond à object. Si la valeur null est fournie dans
toutes les positions, c'est qu'ils ne correspondent à rien, sinon c'est qu'ils correspondent à des ressources égales ou bien à des littérales. Deux ressources sont
égales si elles ont des URI identiques ou bien si elles partagent le même nœud anonyme ; deux littérales sont égales si tous leurs éléments sont égaux. Ainsi :
Selector selector = new SimpleSelector(null, null, null);
|
permet de sélectionner tous les états dans un modèle.
Selector selector = new SimpleSelector(null, VCARD.FN, null);
|
permet de sélectionner tous les états avec VCARD.FN comme prédicat et quels que soient le sujet et l'objet. Comme un raccourci spécial,
est équivalent à
listStatements(new SimpleSelector(S, P, O));
|
Le code suivant, qui peut être consulté dans son intégralité dans le
tutoriel 7,
liste les noms et prénoms sur toutes les cartes de visite dans la base de données.
ResIterator iter = model.listSubjectWithProperty(VCAR.FN);
if (iter.hasNext())
{
System.out.println("La base de données contient les cartes de visite suivantes :");
while (iter.hasNext())
{
System.out.println(" " + iter.nextStatement().getProperty(VCARD.FN).getString());
}
}
else
{
System.out.println("Aucune carte de visite n'a été trouvée dans la base de données");
}
|
Cela devrait produire un résultat similaire à ce qui suit :
La base de données contient les cartes de visite de :
Sarah Jones
John Smith
Matt Jones
Becky Smith
|
Votre prochain exercice est de modifier ce code pour utiliser SimpleSelector à la place de listSubjectsWithProperty.
Voyons comment mettre en œuvre un certain contrôle plus fin sur les états sélectionnés. SimpleSelector peut être sous-classée
et sa méthode selects a été modifiée pour effectuer un filtrage supplémentaire :
StmtIterator iter = modelListStatements(new SimpleSelector(null, VCARD.FN, (RDFNode) null)
{
public boolean selects(Statement s)
{
return s.getString().endsWith("Smith");
}
});
|
Cet exemple de code utilise une technique primordiale propre à Java : une définition de la méthode inline lors de la création d'une instance de la classe.
Ici, la méthode selects() vérifie que le nom se termine par « Smith ». Il est important de noter que le filtrage basé sur les arguments
subject, predicate et object se fait avant que la méthode selects() soit appelée, de
sorte que le test supplémentaire ne sera appliqué qu'aux déclarations correspondantes.
Le code complet peut être trouvé dans le
tutoriel 8 et produit une sortie comme ceci :
La base de données contient les cartes de visite de :
John Smith
Becky Smith
|
Vous pourriez penser que :
StmtIterator iter = model.listStatements(new SimpleSelector(null, null, (RDFNode) null)
{
public boolean selects(Statement s)
{
return (subject == null || s.getSubject().equals(subject))
&& (predicate == null || s.getPredicate().equals(predicate))
&& (object == null || s.getObject().equals(object));
}
});
|
est équivalent à :
StmtIterator iter = model.listStatements(new SimpleSelector(subject, predicate, object);
|
Si elles peuvent être fonctionnellement équivalentes, la première forme listera tous les états dans le modèle et testera chacun d'eux individuellement
tandis que la seconde permettra, par sa mise en œuvre, de maintenir les index afin d'améliorer les performances. Essayez-le sur un grand modèle et voyez
par vous-même, mais préparez d'abord une tasse de café.
X. Opérations sur les modèles
Jena fournit trois opérations de manipulation de modèles dans leur ensemble. Ce sont des opérations ensemblistes d'union, d'intersection et de différence.
L'union des deux modèles est l'union des ensembles d'états qui représentent chaque modèle. C'est l'une des principales opérations que la conception de
RDF soutient. Il permet à des données provenant de sources de données disparates de fusionner. Considérons les deux modèles suivants :
Quand ils sont fusionnés, les deux nœuds
http://.../JohnSmith
le sont en un seul et l'arc
vcard:FN dupliqué est supprimé pour produire :
Regardons le code pour le faire (le code complet est dans le
tutoriel 9) et voyons ce qui se passe.
model1.read(new InputStreamReader(in1), "");
model2.read(new InputStreamReader(in2), "");
model.write(system.out, "RDF/XML-ABBREV");
|
La sortie produite par ce code ressemble à ceci :
<rdf:RDF
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:vcard="http://www.w3.org/2001/vcard-rdf/3.0#">
<rdf:Description rdf:about="http://somewhere/JohnSmith/">
<vcard:EMAIL>
<vcard:internet>
<rdf:value>John@somewhere.com</rdf:value>
</vcard:internet>
</vcard:EMAIL>
<vcard:N rdf:parseType="Resource">
<vcard:Given>John</vcard:Given>
<vcard:Family>Smith</vcard:Family>
</vcard:N>
<vcard:FN>John Smith</vcard:FN>
</rdf:Description>
</rdf:RDF>
|
Même si vous n'êtes pas familier avec les détails de la syntaxe RDF/XML, il devrait être assez clair que les modèles ont fusionné comme prévu.
L'intersection et la différence des modèles peuvent être calculées de la même manière, en utilisant les méthodes
.intersection(Model)
et
.difference(Model), voir la Javadoc de la
différence
et de l'
intersection
pour plus de détails.
XI. Conteneurs
RDF définit un type particulier de ressources pour la représentation des collections. Ces ressources sont appelées des conteneurs.
Les membres d'un conteneur peuvent être soit des littérales, soit des ressources. Il existe trois types de conteneurs :
-
un BAG est une collection non ordonnée ;
-
une ALT est une collection non ordonnée qui a pour but de représenter des solutions de rechange ;
-
une SEQ est une collection ordonnée.
Un conteneur est représenté par une ressource. Cette ressource aura une propriété rdf:type dont la valeur devrait être l'un de ces types,
à savoir rdf:Bag, rdf:Alt ou rdf:Seq ou bien une sous-classe de l'un d'eux, selon le type de conteneur.
Le premier membre du conteneur est la valeur de la propriété rdf:_1 du conteneur ; le deuxième est la valeur de la propriété rdf:_2
du conteneur et ainsi de suite. Les propriétés rdf:_nnn sont connues comme des propriétés ordinales.
Par exemple, le modèle d'un simple Bag contenant les cartes de visite des Smith pourrait ressembler à ceci :
Alors que les membres de Bag sont représentés par les propriétés rdf:_1, rdf:_2, etc., l'ordre des propriétés
n'est pas significatif. On pourrait échanger les valeurs de l'attribut des propriétés rdf:_1 et rdf:_2 et le
modèle résultant représenterait la même information.
Les Alt sont destinées à représenter des alternatives. Par exemple, prenons une ressource qui représente un produit logiciel. Il pourrait avoir
une propriété pour indiquer d'où il pourrait être obtenu. La valeur de cette propriété pourrait être une collection de Alt contenant les différents
sites à partir desquels il peut être téléchargé. Les Alt ne sont pas ordonnés, sauf que l'attribut de la propriété rdf:_1 a une
importance particulière : il représente le choix par défaut.
Alors que les conteneurs peuvent être manipulés en utilisant les mécanismes de base des ressources et des propriétés, Jena a des interfaces et des
classes explicites qui permettent de les traiter. Ce n'est pas une bonne idée d'avoir un objet de manipulation d'un conteneur et en même temps de
modifier l'état du conteneur en utilisant les méthodes de plus bas niveau.
Essayons de modifier le tutoriel 8 pour créer ce sac :
Bag smiths = model.createBag();
StmtIterator iter = model.listStatements(new SimpleSelector(null, VCARD.FN, (RDFNode) null)
{
public boolean selects (Statement s)
{
return s.getString().endsWith("Smith");
}
});
while (iter.hasNext())
{
smiths.add(iter.nextStatement().getSubject());
}
|
Si nous écrivons ce modèle, il contient quelque chose comme ce qui suit :
<rdf:RDF
xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'
xmlns:vcard='http://www.w3.org/2001/vcard-rdf/3.0#'
>
...
<rdf:Description rdf:nodeID="A3">
<rdf:type rdf:resource='http://www.w3.org/1999/02/22-rdf-syntax-ns#Bag'/>
<rdf:_1 rdf:resource='http://somewhere/JohnSmith/'/>
<rdf:_2 rdf:resource='http://somewhere/RebeccaSmith/'/>
</rdf:Description>
</rdf:RDF>
|
qui représente la ressource Bag.
L'interface conteneur fournit un itérateur pour lister le contenu d'un conteneur :
NodeIterator iter2 = smiths.iterator();
if (iter2.hasNext())
{
System.out.println("Le Bag contient :");
while(iter2.hasNext())
{
System.out.println(" " + ((Resource) iter2.next()).getProperty(VCAR.FN).getString());
}
}
else
{
System.out.println("Le Bag est vide");
}
|
Ce code produit la sortie suivante :
Le Bag contient :
John Smith
Becky Smith
|
Un exemple de code exécutable se trouve dans le
tutoriel 10,
qui recolle les morceaux ci-dessus dans un exemple complet.
Les classes de Jena proposent des méthodes pour manipuler les conteneurs, y compris l'ajout de nouveaux membres, l'intersection de nouveaux membres
au milieu d'un conteneur et la suppression de membres existants. Les classes conteneurs de Jena veillent actuellement à ce que l'utilisation de la
liste des propriétés ordinales commence à rdf:_01 et soit contiguë. Le groupe de travail RDF a assoupli cette contrainte, ce qui
permet une représentation partielle du conteneur. Il s'agit donc d'une zone de Jena qui peut être modifiée à l'avenir.
XII. Littérales et types de données
Les littérales RDF ne sont pas seulement des chaînes de caractères simples. Les littérales peuvent avoir une étiquette de langue pour indiquer la langue.
Une valeur littérale « chat » avec une étiquette de langue anglaise est considérée comme différente de « chat » avec une étiquette de langue française.
Ce comportement plutôt étrange est un artefact de la syntaxe originale de RDF/XML.
En outre, il y a deux sortes de littérales. Dans l'une, la composante chaîne de caractères est juste une chaîne de caractères ordinaire. Dans l'autre composante
chaîne de caractères, elle devrait être un fragment de XML bien équilibré. Quand un modèle RDF est écrit en RDF/XML, une construction particulière utilise un attribut
parseType='Literal', qui est utilisé pour le représenter.
Dans Jena, les attributs d'une littérale peuvent être définis dans le littéral qui est construit, par exemple dans le
tutoriel 11 :
Resource r = model.createResource();
r.addProperty(RDFS.label,
model.createLiteral("chat", "en")).addProperty(RDFS.label, model.createLiteral("chat", "fr")).addProperty(RDFS.label, model.createLiteral("<em>chat</em>", true));
model.write(system.out);
|
produit la sortie :
<rdf:RDF
xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'
xmlns:rdfs='http://www.w3.org/2000/01/rdf-schema#'
>
<rdf:Description rdf:nodeID="A0">
<rdfs:label xml:lang='en'>chat</rdfs:label>
<rdfs:label xml:lang='fr'>chat</rdfs:label>
<rdfs:label rdf:parseType='Literal'><em>chat</em></rdfs:label>
</rdf:Description>
</rdf:RDF>
|
Pour que deux littérales soient considérées comme égales, elles doivent être toutes les deux soit des littérales XML, soit de simples littérales.
En outre, soit les deux n'ont aucune étiquette de langue ou, si les étiquettes de langues sont présentes, elles doivent être égales.
Pour les littérales simples, les chaînes de caractères doivent être égales. Les littérales XML ont deux notions d'égalité. La notion simple est
que les conditions mentionnées précédemment soient remplies et les chaînes de caractères le soient aussi. L'autre notion, c'est qu'elles peuvent
être égales si la canonisation de leur chaîne de caractères est égale.
Les interfaces de Jena supportent aussi les types littéraux. Avant (ci-dessus), on traitait les littérales typées comme un raccourci pour les chaînes
de caractères : les valeurs typées sont converties selon la méthode habituelle de Java pour les chaînes de caractères et ces chaînes de caractères sont
stockées dans le modèle. Par exemple, essayez (en notant que, pour les littérales simples, nous pouvons omettre l'appel à model.createLiteral()) :
Resource r = model.createResource();
r.addProperty(RDFS.label, "11").addProperty(RDFS.label, 11);
model.write(system.out, "N-TRIPLE");
|
La sortie produite est :
_:A... <http://www.w3.org/2000/01/rdf-schema#label> "11" .
|
Maintenant, les deux littérales ne sont que la chaîne « 11 », une seule déclaration est alors ajoutée.
Le groupe de travail RDF a défini des mécanismes pour soutenir les types de données en RDF. Jena les supporte en utilisant les mécanismes de littérales typées,
ils ne sont pas cependant abordés dans ce tutoriel.
XIII. Glossaire
Nœud anonyme : représente une ressource, mais n'indique pas son URI. Ils agissent comme des variables existentiellement qualifiées dans la logique
du premier ordre.
Dublin Core : un standard pour les métadonnées sur les ressources Web. De plus amples informations peuvent être trouvées sur le site Web de Dublin Core.
Littérale : une chaîne de caractères qui peut être la valeur d'une propriété.
Objet : la partie d'un triplet qui est la valeur de la déclaration.
Prédicat : la partie de la propriété dans un triplet.
Propriété : une propriété est un attribut d'une ressource. Par exemple, DC.Title est une propriété, comme RDF.type.
Ressources : certaines entités. Ce pourrait être une ressource Web comme une page Web ou une chose concrète et physique telle qu'un arbre ou une voiture.
Elle pourrait être une idée abstraite, comme les échecs ou le football. Les ressources sont nommées par une URI.
Déclaration : un arc dans un modèle RDF, normalement interprété comme un fait.
Sujet : la ressource qui est la source d'un arc dans un modèle RDF.
Triplet : une structure contenant un sujet, un prédicat et un objet. Un autre terme pour une déclaration.
XIV. Notes
L'identification d'une ressource RDF peut inclure un identificateur de fragment, par exemple
http://hostname/rdf/tutorial/#ch-Introduction ; alors, à proprement parler, une ressource RDF
est identifiée par une référence d'URI.
En plus d'être une chaîne de caractères, les littérales ont également un code de langue en option pour représenter la langue de la chaîne de caractères.
Par exemple, la littérale « two » pourrait avoir un code de langue « en » pour l'anglais et la littérale « deux » pourrait avoir un code de langue « fr »
pour le français.
XV. Remerciements


Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other
materials provided with the distribution.
The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.