Jena
Unterpunkte dieser Seite
Links
Einführung
RDF Modelle und Programmierung mit RDF Modellen
RDFS Reasoning
OWL Reasoning
OWL Reasoning mit Pellet
Jena Architecture
Kontaktstellen von Pellet mit Jena
Links
http://jena.sourceforge.net/
Einführung
Jena ist ein toolkit für Ontologien, das auf RDF basiert. Da OWL des w3c mit RDF formuliert wird, kann man OWL mit Jena bearbeiten. Jena lädt serialisierte Ontologien, die mit DAML+OIL, RDFS (RDF Schema), OWL Lite, OWL DL und OWL Full formuliert worden sind. Um über die Ontologiesprache (DAML+OIL, RDFS, OWL Lite, OWL DL, OWL Full) zu abstrahieren, gibt es in Jena eine Menge von Klassen. Eine Klasse aus dieser Abstraktionsschicht ist OntClass. OntClass steht z.B. für Klassen in DAML+OIL, RDFS oder OWL Ontologien. Die Bindung an die konkreten Ontologiesprachen wird über sogenannte Profile geregelt. Profile beschreiben, welche Konstrukte in der entsprechenden Ontologiesprache erlaubt sind. Das Profil wird von einer Instanz der Jena OntModel Klasse verwaltet. OntModel ist von Model abgeleitet. Model verwaltet einen RDF-Graphen. RDF ist keine Ontologiesprache sondern eine Model für Aussagen über Resourcen nach dem Subjekt-Prädikat-Objekt Triple Prinzip. RDFS ist eine Erweiterung von RDF um Ontologien beschreiben zu können, also eine Ontologiesprache auf Basis von RDF zu erhalten. Ein RDF Triple wird in Jena Statement genannt.
Jena arbeitet intern auf einem RDF-Graphen und verändert diesen Graphen nicht. Der RDF Graph wird durch die Klasse Graph im Quellcode dargestellt. Die Jena API bietet lediglich Klassen an, die im Sinne einer Ontologie auf den RDF-Graphen zugreifen und die Arbeit mit dem RDF-Graphen vereinfachen. Dazu dient die Klasse OntModel, die von Model abgeleitet ist und Objekte im RDF-Graphen verarbeiten kann, die in einer Ontologie erwartet werden. OntModel kennt z.B. Klassen, Properties und Individuen. Jede Jena Operation auf dem RDF-Graphen, erzeugt wieder einen RDF-Graphen. Jena interpretiert den intern gespeicherten RDF-Graphen allerdings als Ontolgie wenn man mit OntModel auf ihm arbeitet und erleichtert die Arbeit durch spezielle Funktionen für Ontologien.
Die Arbeit auf dem RDF Graphen geschieht folgendermaßen. Es gibt beispielsweiße die Jena OntClass Klasse. Sie abstrahiert über Klassen einer Ontologie. Wenn man eine Instanz von OntClass für eine Klasse in der Ontologie besitzt, kann man Methoden auf dieser Instanz ausführen. Beispielsweiße kann man eine subClassOf Property erzeugen. Dies löst eine Änderung am RDF-Graph aus, er wird nun um ein RDF-Tripple für diese Property erweitert. Bei der Programmierung eines Ontologie Editors muss also das Model als RDF-Graph gestaltet sein und die View muss den Graph, also das Model, darstellen. Jedes mal, wenn sich der Graph geändert hat, muss das Model die View updaten. Dies sollte über den Controller geschehen, der Model und View trennt.
Die OntClass Klasse speichert aber selbst keine Information über den Graphen, sie ruft lediglich Methoden des Graphen-Management auf. Sie dient als Facade-Pattern und gibt Aufrufe an die richtige Stelle weiter. Wenn man Informationen über eine OntClass Instanz abruft, indem man beispielsweiße die listSuperClasses() Methode aufruft, wird die OntClass Instanz nicht in eigenen Attributen nach Superklassen suchen, sondern eine Query auf dem RDF-Graphen starten, indem Sie eine Query über das Graphenmanagement durchführt. Die Ergebnisse gibt sie dann als Returnwert der listSuperClasses() Methode zurück.
Modelle: Modelle sind von der Klasse com.hp.hpl.jena.rdf.model.Model oder von ihr abgeleitet (s. OntModel). Ein Model besitzt ein Profil um zu beschreiben, welche Konstrukte erlaubt sind, welche Speicherart verwendet wird (InMemory, DB) und welcher Reasoner verwendet wird und welcher DocumentManager verwendet werden soll. Es gibt Profile für (DAML+Oil, RDFS, OWN Lite, OWL DL und OWL Full). Im SourceCode werden Profile durch Specifications (com.hp.hpl.jena.ontology.OntModelSpec) dargestellt. Ein Model arbeitet auf einem RDF-Graph.
Modelle werden von der com.hp.hpl.jena.rdf.model.ModelFactory erzeugt. Ein RDF-Model erhält man durch die Methode createDefaultModel(). Ein OntModel erhält man durch createOntologyMode(). Das erzeugte Model ist zunächst leer. Eine Ontology oder ein RDF Dokument muss von der Ontology eingelesen werden. Die Klasse Model besitzt eine ganze Menge von read() Methode um RDF Dokumente zu lesen und dann intern zu speichern. Die Klasse Model kann intern verwaltete RDF-Graphen auch durch eine ganze Menge an write() Methoden ausgeben.
RDF Support und RDF-Graph-Management ist durch das Interface Graph gegeben. Das Graph Interface verwaltet eine Menge von RDF-Tripeln. Ein RDF-Graph besteht aus einer Menge von RDF-Trippeln, daher verwaltet das Graph Interface einen RDF-Graph vollständig.
Inference Intro
Jena 2 Inference Support
Reasoning bedeutet automatisches Ableiten von Wissen aus Ontologien. Inference ist ein Synonym für Reasoning. Programme, die Inference durchführen werden synonym Rules Engine, Prover, Therem Prover, Reasoner, Logical Inference Engine genannt.
Der Reasoning Support in Jena ist wie folgt ausgeprägt. Jena beinhaltet selbst einige Reasoner (Transitive Reasoner, RDFS rule reasoner, OWL, OWL Mini, OWL Micro Reasoner, DAML Micro Reasoner, Generic Rule Reasoner). Von http://jena.sourceforge.net/inference/index.html stammt diese Auflistung:
Available reasoners. Included in the Jena distribution are a number of predefined reasoners:
- Transitive reasoner
Provides support for storing and traversing class and property lattices. This implements just the transitive and symmetric properties of rdfs:subPropertyOf and rdfs:subClassOf.
- RDFS rule reasoner
Implements a configurable subset of the RDFS entailments.
- OWL, OWL Mini, OWL Micro Reasoners
A set of useful but incomplete implementation of the OWL/Lite subset of the OWL/Full language.
- DAML micro reasoner
Used internally to enable the legacy DAML API to provide minimal (RDFS scale) inferencing.
- Generic rule reasoner
A rule based reasoner that supports user defined rules. Forward chaining, tabled backward chaining and hybrid execution strategies are supported.
Jena besitzt ein Reasoner Interface um einen externen Reasoner einzubinden, der das Interface implementiert. Durch das Interface wird der Reasoner von der konkreten Ausprägung der Ontologiesprache abgekapselt. Jena wird den Reasoner verwenden, wenn in Jena die Inference Capability aktiviert ist. Falls die Capability aktiviert ist, werden die Aufrufe auf der Jena API den Reasoner verwenden um die Funktionalität von Jena zu erweitern. Die Reasoner werden also wie Plug-Ins in Jena eingefügt. Ein Reasoner nimmt einen RDF-Graph als Base Model, betrachtet ihn als Ontologie und vervollständigt den Graphen nach bestimmten Algorithmen. Es entsteht ein neuer RDF-Graph, der wieder eine Ontologie darstellt und auch von Jena weiterverarbeitet und durch OntModel verwaltet werden kann.
RDF Modelle und Programmierung mit RDF Modellen
Dieser Abschnitt beschreibt den Umgang mit RDF-Modellen des Jena Toolkit. Dieser Abschnitt beschreibt nicht den Umgang mit der OntModel Klasse für Ontologien.
Modelle für RDF werden in Jena durch die com.hp.hpl.jena.rdf.model.ModelFactory und deren Methode createDefaultModel() erzeugt. Das Model ist leer und muss erst eine .rdf Datei laden um den internen Graph zu füllen. Alternativ können RDF-Tripple über API aufrufe im internen Model erzeugt werden und der Graph kann auf diese Weise gefüllt werden. Das Modell kann dann über seine write() Methoden geschrieben, also serialisiert werden.
Im Jena CVS Repository gibt es Tutorials, die auch auf http://jena.sourceforge.net/tutorial/index.html beschrieben werden. Die Tutorials befinden sich unter jena/jena2/src-examples/jena/examples/rdf. Wenn man das jena/jena2/src-examples Verzeichnis in den Build-Path aufnimmt, kann man diese Beispiele ausführen. (Schließen Sie alle Editoren und refreshen Sie das Projekt sobald Sie den BuildPath angepasst haben). Prinzipiell muss jeweils der einem Packet übergeordnete Ordner in den Eclipse Build Path aufgenommen werden, damit das Packet übersetzt werden kann und die package Deklarationen in den Java Dateien korrekt vom Compiler aufgelöst werden.
RDFS Reasoning
http://jena.sourceforge.net/inference/index.html
Jena bietet das Interface com.hp.hpl.jena.reasoner.Reasoner, das alle Reasoner implementieren müssen. (Dieses Interface wird von org.mindswap.pellet.jena.PelletReasoner implementiert).
Reaoner werden in Jena über eine eigene ReasonerFactory erstellt, jeder Reasoner braucht seine eigenen Reasoner Factory. Jena bietet das Interface com.hp.hpl.jena.reasoner.ReasonerFactory. Von diesem Interface muss man die ReasonerFactory des eigenen Reasoners ableiten.
Jena bietet eine ReasonerRegistry, in die man ReasonerFactory einstellen kann und dann über die Registry Reasoner erzeugen kann. Alternativ kann man die eigene Factory auch direkt nutzen um einen Reasoner zu erstellen.
Wenn man den Reasoner hat, muss man nun ein Model laden und den Reasoner mit dem Model verbinden. Modelle werden in Jena über ModelFactoryS erzeugt. Dazu gibt es die Klasse com.hp.hpl.jena.rdf.model.ModelFactoryBase und die daraus abgeleitete Klasse com.hp.hpl.jena.rdf.model.ModelFactory. Die ModelFactory besitzt drei wichtige Methoden: CreateDefaultModel(), CreateInfModel() und CreateOntologyModel(). createDefaultModel erzeugt ein RDF Model, das keine Inference erlaubt, da RDF zu wenig Mächtig für Inference ist.
Ein Reasoner führt Inference durch. Ein Reasoner kann also nicht in jedem Modell verwendet werden, sondern nur in solchen Modellen, die Inference erlauben. Daher gibt es in der ModelFactory eine Methoden namens createInfModel(). Der Name dieser Methode enthält Inf um auszudrücken, das dieses Model Inference unterstützt. Der Methode kann ein Reasoner und ein Model übergeben werden. Sie verbindet den Reasoner mit dem Model. Falls kein Reasoner übergeben wird, erzeugt die ModelFactory selber einen Reasoner. Der Reasoner wird aus dem Reasonervorrat des Jena Toolkits genommen.
Man erzeugt einen Reasoner und ein DefaultModel. Dann erzeugt man ein InfModel und übergibt der ModelFactory dazu den Reasoner und das DefaultModel.
Das InfModel steht nun für alle RDF-Tripple im RDF-Graph. Auf dem InfModel kann man nun Resourcen abfragen. Über die Resource kann man nun Properties abfragen. Wenn man bestimmte Methoden von Resourcen oder Properties aufruft, muss inference durchgeführt werden, um die Anfragen beantworten zu können. Diese Inference wird nun von der Jena-API automatisch an den eingesetzten Reasoner delegiert. Die Antwort des Reasoners wird als return-wert der aufgerufenen Methode zurückgegeben werden.
Ein Beispiel für einen Methodenaufruf, der Reasoning benötigt um beantwortet zu werden ist durch folgenden Code gegeben:
String NS = "urn:x-hp-jena:eg/";
// Build a trivial example data set
Model rdfsExample = ModelFactory.createDefaultModel();
Property p = rdfsExample.createProperty(NS, "p");
Property q = rdfsExample.createProperty(NS, "q");
rdfsExample.add(p, RDFS.subPropertyOf, q);
rdfsExample.createResource(NS+"a").addProperty(p, "foo");
Reasoner reasoner = ReasonerRegistry.getRDFSReasoner();
InfModel inf = ModelFactory.createInfModel(reasoner, rdfsExample);
Resource a = inf.getResource(NS+"a");
System.out.println("Statement: " + a.getProperty(q));
In der allerletzen Zeil wird gefragt, ob a ein q hat. Da a ein p hat und p immer ein q ist, hat a ein q. Vergleichbar wäre das mit folgendem Beispiel. Jedes Fahrrad p ist ein Fortbewegungsmittel q. Der Junge (resource a) hat ein Fahrrad. Eine Anfrage wäre: Hat der Junge a ein Fortbewegungsmittel. Der Computer findet durch den RDF-Graph heraus: Junge a hat Fahrrad p. Er findet auch im RFG-Graph: Fahrad p ist Fortbewegungsmittel q. Durch den Reasoner und inference bastelt er neues wissen zusammen nämlich der Junge a hat ein Fortbewegungsmittel. Der letzte Aufruf in obigen Beispiel löst intern implizit diese Denkleistung aus und ruft den Reasoner auf.
OWL Reasoning
OWL Reasoning funktioniert ähnlich wie RDFS Resoning. Die ModelFactory hat eine createOntologyModel() Methode. Dieser Methode übergibt man auch ein DefaultModel aber zusätzlich keinen Reasoner wie beim RDFS Reasoning sondern eine Instanz der com.hp.hpl.jena.ontology.OntModelSpec Klasse. Bei der Spec handelt es sich um das Profil des Models. Die Profile wurden bereits in der Einführung
erwähnt. Das Profil beschreibt die ontology language, den reasoner, den document manager and das storage model, welche für das OntologyModel verwendet werden.
Die Spec speichert in einem ihrer Attribute bereits eine Instanz einer Reasoner Factory. Man muss also seinen eigenen Reasoner für das OWL Reasoning nicht instantiieren, sondern nur seine Factory in eine Spec einsetzen und diese Spec an die ModelFactory.createOntologyModel() Methode übergeben. In Pellet kann die PelletReasonerFactory eine Spec herausgeben, in der sie sich selbst als ReasonerFactory eingesetzt hat. Folgender static block aus der org.mindswap.pellet.jena.PelletReasonerFactory erstellt die statische Spec und registriert sie zusätzlich auch noch in der Registry.
static {
theInstance = new PelletReasonerFactory();
THE_SPEC = new OntModelSpec( OntModelSpec.OWL_MEM );
THE_SPEC.setReasonerFactory( theInstance );
ReasonerRegistry.theRegistry().register( PelletReasonerFactory.theInstance() );
}
Ein Ontology Model wird wie folgt erzeugt wobei plainModel aus ModelFactory.getDefaultModel() enstand:
model = ModelFactory.createOntologyModel( PelletReasonerFactory.THE_SPEC, plainModel );
Der Reasoner kann abgefragt werden, da der Code ganz genau weiß, das der Graph eine Instanz der bestimmten PelletInfGraph Klasse ist
reasoner = ((PelletInfGraph) model.getGraph()).getOWLReasoner();
OWL Reasoning mit Pellet
Erzeugen Sie ein neues Java Projekt in Eclipse und fügen Sie diese Main Klasse hinzu.
import java.io.FileNotFoundException;
import org.mindswap.pellet.KnowledgeBase;
import org.mindswap.pellet.jena.PelletInfGraph;
import org.mindswap.pellet.jena.PelletReasonerFactory;
import com.hp.hpl.jena.rdf.model.InfModel;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.reasoner.Reasoner;
import com.hp.hpl.jena.util.FileManager;
import com.hp.hpl.jena.vocabulary.RDF;
public class Main {
/**
* @param args
* @throws FileNotFoundException
*/
public static void main(String[] args) throws FileNotFoundException {
Model schema = FileManager.get().loadModel("file:data/owlDemoSchema.xml");
Model data = FileManager.get().loadModel("file:data/owlDemoData.xml");
//Reasoner reasoner = ReasonerRegistry.getOWLReasoner();
Reasoner reasoner = PelletReasonerFactory.theInstance().create();
reasoner = reasoner.bindSchema(schema);
InfModel infmodel = ModelFactory.createInfModel(reasoner, data);
// ABox consistency
PelletInfGraph graph = (PelletInfGraph)infmodel.getGraph();
graph.prepare();
KnowledgeBase kb = graph.getKB();
// ensureConsistency ensures by throwing an exception if the graph
// is not consistent
kb.ensureConsistency();
Resource gamingComputer = infmodel.getResource("urn:x-hp:eg/GamingComputer");
Resource whiteBox = infmodel.getResource("urn:x-hp:eg/whiteBoxZX");
if (infmodel.contains(whiteBox, RDF.type, gamingComputer)) {
System.out.println("White box recognized as gaming computer");
} else {
System.out.println("Failed to recognize white box correctly");
}
}
}
Erzeugen Sie im Verzeichnis des Java Projekts einen Ordner namens data. Legen Sie dort owlDemoSchema.xml und owlDemoData.xml ab. Sie finden diese Dateien hier zum Download.
ACHTUNG: in dem verlinkten Dokument aus der Jena Dokumentation haben die beiden Dateien die Endungen owl und rdf! Man lädt jedoch die Dateien mit der Endung xml herunter. Die Endung spielt keine Rolle, da der Inhalt von den Jena Klassen untersucht und korrekt geladen wird. Für das öffnen der Dateien von der Festplatte spielt die Endung allerdings sehr wohl eine Rolle. Achten Sie immer auf die Angabe eines korrekten Dateinamens im Quellcode und benennen Sie die Dateien auf der Festplatte dann zum Quellcode passend.
Fügen Sie dem Projekt den Quellcode von Pellet und Jena2 hinzu. Projekt im Package Explorer markieren > Kontext Menu > Properties > Java Build Path > Reiter Projects > Add > wählen Sie die jena und pellet Projekte aus (Sie müssen diese Projekte zuvor erfolgreich in ihrem Eclipse workspace eingerichtet haben).
Binden Sie alle jars aus dem Pellet lib Verzeichnis ein. Erzeugen Sie dazu eine Variable. Markieren Sie das Java Projekt im Package Explorer > Kontext Menu > Properties > Java Build Path > Reiter Libraries > Add Variable > Configure Variables... > New > Name: PELLET_LIB > Folder ... > Wählen Sie das lib Verzeichnis der Pellet Distribution aus (Die Pellet Distribution muss zuvor erfolgreich von Ihnen erstellt worden sein. Dazu müssen Sie die ant build.xml des trunk Verzeichnisses des Pellet Repositories ausgeführt haben) > OK > OK > Markieren Sie im New Variable Classpath Entry die soeben erstellte PELLET_LIB Variable > Extend ... > markieren Sie alle Jars in dem Dialog, auch die Jars in den angezeigten Unterverzeichnissen des Baums. > OK > OK.
Schalten Sie den Debug Modus von log4j an. Markieren Sie das Java Projekt im Package Explorer > Kontext Menu > Debug As > Debug Configurations ... > Reiter Arguments > VM Arguments: fügen Sie -Dlog4j.debug=true ein > Run.
Betrachten Sie den log4j output und ermitteln Sie, welche log4j.xml oder log4j.properties Datei verwendet wird. Editieren Sie diese Datei und stellen Sie alle Log Level auf trace. Damit sehen Sie alle Ausgaben aller Logger aller Projekte. Sie können erkennen, was das Programm tut.
Die Main Methode lädt eine OWL Ontology aus data/owlDemoSchema.xml und Individuen zu den Klassen aus data/owlDemoData.xml. Danach initialisiert sie den Graph durch graph.prepare(). Der Graph überprüft nun ob er konsistent ist und setzt bezüglich dieser Eigenschaft ein Flag. Dabei passt der Graph die KnowledgeBase an. Die Methode ensureConsistency() der KnowledgeBase fragt das Konsistenzflag ab und wirft eine Exception falls diese Flag auf false steht.
Diese Exception wird mit den angegebenen Dateien im data Verzeichnis geworfen, da der Graph nicht konsistent ist. Die Ontology beschreibt PCs mit einem Constraint. Jeder PC darf nur genau ein Motherboard haben. In data/owlDemoData.rdf wird ein PC mit zwei Motherboards definiert. Dieses Individuum verstößt also gegen den Contstraint der OWL Ontology. Wenn Sie das zweite Motherboard des Individuums auskommentieren, haben Sie einen konsistenten Graphen. In diesem Fall läuft das Programm über ensureConsistency() hinaus und führt Reasoning durch. Das Individuum WhiteBox wird als zugehörig zur Klasse der gaming computer identifiziert.
Diese Tatsache wurde durch Reasoning aka Inference identifiziert, da diese Tatsache nirgends in den Dateien im data Verzeichnis explizit notiert ist, sondern sich nur implizit aus den Regeln der OWL Ontologiesprache in Kombination mit der gegebenen Ontologie und den gegebenen Individuen ergibt.
Jena Architecture
Ausgangspunkt ist folgendes Programm:
Model schema = FileManager.get().loadModel("file:data/owlDemoSchema.owl");
Model data = FileManager.get().loadModel("file:data/owlDemoData.rdf");
//Reasoner reasoner = ReasonerRegistry.getOWLReasoner();
Reasoner reasoner = PelletReasonerFactory.theInstance().create();
reasoner = reasoner.bindSchema(schema);
InfModel infmodel = ModelFactory.createInfModel(reasoner, data);
// ABox consistency
PelletInfGraph graph = (PelletInfGraph)infmodel.getGraph();
graph.prepare();
KnowledgeBase kb = graph.getKB();
// ensureConsistency ensures by throwing an exception if the graph
// is not consistent
kb.ensureConsistency();
Resource gamingComputer = infmodel.getResource("urn:x-hp:eg/GamingComputer");
Resource whiteBox = infmodel.getResource("urn:x-hp:eg/whiteBoxZX");
if (infmodel.contains(whiteBox, RDF.type, gamingComputer)) {
System.out.println("White box recognized as gaming computer");
} else {
System.out.println("Failed to recognize white box correctly");
}
Wir sehen, dass am Anfang des Programms Model Instanzen erstellt werden um darin das Ergebnis eines Ladevorgangs zu speichern. Aus diesen Model Instanzen wird dann einen Instanz der abgeleiteten InfModel Klasse erzeugt. Wir sehen also, das aus Modellen komplexere Modelle entstehen und Modelle auch als Speicher dienen.
Auf der Festplatte liegt eine RDF/XML Datei. Ein FileManager kann diese Datei laden. Er versucht das Format zu erraten. Dazu verwendet er die Zeile
String syntax = FileUtils.guessLang(mappedURI) ;
Der FileManager hat ein DefaultModel erzeugt
Model model = ModelFactory.createDefaultModel() ;
und verwendet nun dieses Model um die Datei einzulesen.
model.read(in.getInput(), baseURI, syntax) ;
Er speichert dieses Model in seinem Cache (wozu auch immer) falls Model Loads gecached werden sollen und gibt dann das Model zurück.
if ( this.cacheModelLoads )
modelCache.put(filenameOrURI, model) ;
return model;
Interessant ist nun, was sich in einer Instanz der Klasse Model befindet. Der Java Kommentar zur read() Methode der Model Klasse lautet:
Add RDF statements represented in language lang to the model.
Predefined values for lang are "RDF/XML", "N-TRIPLE",
"TURTLE" (or "TTL") and "N3".
read() liest also prinzipiell nur RDF statements ein. Statements ist Jena-Jargon für RDF-Tripple. RDF/XML, N-TRIPLE, TURTLE und N3 sind nur gängige Serialisierungen von RDF. Die Frage ist nun, was wird mit den RDF Statements im Model gemacht?
Der Kommentar des Model Interfaces lautet:
An RDF Model.
<p>
An RDF model is a set of Statements. Methods are provided for creating
resources, properties and literals and the Statements which link them,
for adding statements to and removing them from a model, for
querying a model and set operations for combining models.
<p>
Models may create Resources [URI nodes and bnodes]. Creating a Resource does
<i>not</i> make the Resource visible to the model; Resources are only "in" Models
if Statements about them are added to the Model. Similarly the only way to "remove"
a Resource from a Model is to remove all the Statements that mention it.
<p>
When a Resource or Literal is created by a Model, the Model is free to re-use an
existing Resource or Literal object with the correct values, or it may create a fresh
one. [All Jena RDFNodes and Statements are immutable, so this is generally safe.]
<p>
This interface defines a set of primitive methods. A set of
convenience methods which extends this interface, e.g. performing
automatic type conversions and support for enhanced resources,
is defined in {@link ModelCon}.</P>
Es handelt sich also um einen Speicher für RDF-Resourcen, RDF-Properties und RDF-Literals, sowie Statements (= RDF Tripple), die diese drei Dinge in Subjekt Prädikat Objekt Art verbinden. Wie folgender Code zeigt, dient das Model auch als Obserable, in das man einen Listener registrieren kann.
// create an empty model
Model model = ModelFactory.createDefaultModel();
model.register(new LogModelChangedListener());
// create the resource
Resource johnSmith = model.createResource(personURI);
// add the property
johnSmith.addProperty(VCARD.FN, fullName);
Der Change Listener wird über das hinzufügen der Resource und der Property informiert.
Das Model Interface ist Vaterinterface des InfModel Interface, welches wiederum Vaterinterface vom OntModel Interface ist.
Parallel zu den Interfaces gibt es eine Hierarchie von Implementierungen. ModelCom implementiert Model. von der Klasse ModelCom implementiert. ModelCom ist von EnhGraph abgeleitet. Von ModelCom ist InfModelImpl abgeleitet, welches InfModel implementiert. Von ModelCom ist auch OntModelImpl abgeleitet, welches OntModel implementiert. (OntModelImpl erbt nicht von IntModelImpl). Model, InfModel, OntModel. Model wird von der ModelFactory oder vom FileManager erzeugt. Man kann Modelle instantiieren und deren read Methoden direkt aufrufen.
ModelCom ist synchronized und bietet Transactionsfunctionalität. Es ist ein Observable mit Listener Interface. Es bietet Methoden zum Hinzufügen und Entfernen von RDF-Resourcen, RDF-Properties und RDF-Literals, sowie Statements (= RDF Tripple). Es bietet Iteratoren und Zugriff auf seine Objekte über Listen und Arrays.
Beziehung zwischen Model und Graph: ModelCom ist von EnhGraph abgeleitet. Die Klasse EnhGraph besitzt ein protected field graph welches vom Interface Graph ist. Die ModelFactory instantiiert ModelCom und erzeugt dabei mittels der com.hp.hpl.jena.graph.Factory Klasse eine Instanz der Klasse com.hp.hpl.jena.mem.faster.GraphMemFaster, die das Graph Interface implementiert. Diese GraphMemFaster Instanz wird der ModelCom Instanz in deren Konstruktor übergeben. Die ModelCom Klasse ruft den Konstruktor ihrer SuperKlasse EnhGraph auf und übergibt dort die Instanz von GraphMemFaster. EnhGraph speichert die Instanz im protected field graph. Der Graph kann über die Methode ModelCom.getGraph() abgefragt werden.
public Graph getGraph()
{ return graph; }
Die Größe von ModelCom wird wie folgt beantwortet:
public long size()
{ return graph.size(); }
Ob ModelCom leer ist, wird wie folgt beantwortet:
public boolean isEmpty()
{ return graph.isEmpty(); }
Graph speichert lediglich RDF-Tripple, also Statements. Ein Statement kann aus ModelCom abgefragt werden, was wie folgt implementiert ist:
public boolean contains( Statement s )
{ return graph.contains( s.asTriple() ); }
Ob eine Resource in ModelCom gespeichert ist wird beantwortet, indem alle Knoten des graphs untersucht werden. Sobald ein Knoten die Resource als Subjekt oder als Objekt speichert, enthält der Graph die Resource und damit ModelCom. Eine Resource ist in ModelCom also nur enthalten, wenn es ein Statement über sie gibt. Dies wird wie folgt implementiert:
// containsNode(): true iff the graph contains a triple in which
// n appears somewhere. if n is a fluid node, it is not defined
// whether true or false is returned, so don't do that.
public boolean containsResource( RDFNode r )
{ return graph.queryHandler().containsNode( r.asNode() ); }
Das Einfügen eines Tripples in ModelCom ist wie folgt implementiert:
public Model add( Resource s, Property p, RDFNode o ) {
modelReifier.noteIfReified( s, p, o );
graph.add( Triple.create( s.asNode(), p.asNode(), o.asNode() ) );
return this;
}
Wenn mit ModelCom eine Resource erzeugt wird, wird diese nicht im Graph gespeichert. Ein Graph enthält nur Trippel. Erst wenn ein Trippel mit dieser Resouce in ModelCom eingerichtet wird, landet die Resource auch im Graph. ModelCom speichert eine Resource nicht. Wenn das Programm die Resource verwirft, kann die Resource nicht mehr aus ModelCom abgefragt werden. Sie ist unwideruflich verschwunden. Es sein denn die Resource ist Teil eines Statements. Dann befindet sie sich im Graph und kann von dort wieder abgefragt werden.
Rendering des Graph:
Aus diesem Dokument:
In the case of RDF, one natural technique is to translate the RDF statements
into a graph notation and then view that using standard graph visualization
tools (such as GraphViz developed at AT&T [1]). There have been a number
of implementations of this approach - see for example Pietriga’s IsaViz [9]
or Brinkley’s RDFViz [2]. There are also several graph visualization tools
designed for use with ontologies - see for example the range of plugins for
the Prot´eg´e ontology editor [10].
Es wird in diesem Dokument aber ein Visualisierer basierend auf Web Technology entwickelt. Das Tool ist hier erhältlich.
Graph und InfGraph sind interne Objekte, mit denen der API Benutzer eigentlich nicht in Berührung kommen sollten.
Kontaktstellen von Pellet mit Jena
Der Pellet Reasoner ist durch die Klasse PelletReasoner gegeben. Er implementiert das Jena interface com.hp.hpl.jena.reasoner.Reasoner.
Die PelletFactory implementiert das Jena interface com.hp.hpl.jena.reasoner.ReasonerFactory und liefert das Jena Interface Reasoner zurück.
public Reasoner create() {
return new PelletReasoner( this );
}
Was passiert, wenn ein PelletReasoner an ein Model gebunden (reasoner.bindSchema(schema)) wird?
Zunächst muss man zwischen Schema und Data unterscheiden. Beide werden über ein Model (Statements, RDF-Tripple) dargestellt haben aber unterschiedliche Zwecke. Das Schema ist ein RDF-Graph, der über OWL Klassen spricht (also tbox Axiome enthält). Manche Programmierer formulieren ihren Code nach tbox und abox um diesen Umstand hervorzuheben:
Model tbox = ModelLoader.loadModel("schema.owl");
Model abox = ModelLoader.loadModel("data.rdf");
Das Schema wird mit den Methoden bindSchema an den Pellet Reasoner gebunden.
Data enthält Daten zu Individuuen der OWL Klassen (also ABox Axiome).
Model tbox = ModelLoader.loadModel("schema.owl");
Model abox = ModelLoader.loadModel("data.rdf");
Data ist ebenfalls ein RDF-Graph. Data wird mit den bind() Methoden an den Reasoner gebunden.
Der PelletReasoner interessiert sich nur für den Graph eines Models. Er besitzt die Methoden bindSchema(Graph graph) und bindSchema(Model model), die er aus dem Jena Interface Reasoner übernimmt. Bei der bindSchema(model) Variante wird lediglich der Graph des Models mit getGraph abgefragt und dann die bindSchema(graph) Variante aufgerufen. Beide Methoden erzeugen eine neue PelletReasoner Instanz, die alte Instanz ist hinfällig.
public Reasoner bindSchema( Graph graph ) throws ReasonerException {
return new PelletReasoner( graph, factory );
}
public Reasoner bindSchema( Model model ) throws ReasonerException {
return bindSchema( model.getGraph() );
}
Da eine neue Instanz erzeugt wird, kommet es zu folgender Zeile:
reasoner = reasoner.bindSchema(schema);
Bei Data sehen die Methoden bind(Model model) und bind(Graph graph) anders aus. Während bind(graph) einen PelletInfGraph zurückgibt, gibt bind(model) ein InfModelImpl zurück. Was soll das?!?
public InfGraph bind( Graph graph ) throws ReasonerException {
log.debug("In bind!");
return new PelletInfGraph( graph, this );
}
public InfModel bind( Model model ) throws ReasonerException {
log.debug("In bind!");
return new InfModelImpl( bind( model.getGraph() ) );
}
Eine ModelFactory wird verwendet um einen Reasoner an Data zu binden.
InfModel infmodel = ModelFactory.createInfModel(reasoner, data);
createInfModel ruft bind(graph) des reasoners auf. Pellet als Resoner erzeug daraufhin einen PelletInfGraph, wobei der PelletInfGraph den Pellet Reasoner kennt. Dieser PelletInfGraph wird von Jena als InfGraph abstrahiert und in der ModelFactory an den InfModelImpl() Konstruktor übergeben.
public static InfModel createInfModel( Reasoner reasoner, Model model ) {
InfGraph graph = reasoner.bind(model.getGraph());
return new InfModelImpl(graph);
}
Wir sehen also, das sowohl der InfGraph als auch das InfModelImpl über den InfGraph Zugriff auf den Reasoner haben. Wir erinnern uns, das das InfModel als Facade vor dem Graph steht und alle Aktionen am Graph über das Model vorgenommen werden. Es ist nun also möglich, das eine Aktion am Model automatisch und implizit eine Inferenz auf dem Graph ausführt, indem sie den Reasoner verwendet.
Der PelletInfGraph Konstruktor ist sehr interessant (zumindest für Nerds). Er erzeugt eine Instanz von OWLReasoner! OWLReasoner ist der HauptReasoner von Pellet für Jena. Man sollte denken PelletReasoner ist die Hauptklasse, das ist aber falsch. OWLReasoner ist die Hauptklasse.
public PelletInfGraph(Graph graph, PelletReasoner pellet) {
super(graph, pellet);
reasoner = new OWLReasoner();
nodeMapper = new ATermToNodeMapper();
// we want a union of the input graph and reasoner schema
if (pellet.getSchema() != null) {
DisjointMultiUnion union = new DisjointMultiUnion( graph );
union.addGraph( pellet.getSchema() );
fdata = new FGraph( union );
}
kb = reasoner.getKB();
rebind();
}
Der OWLReasoner ist hauptsächlich ein Wrapper um die KnowledgeBase (Dies steht genau so im Java Kommentar des OWLReasoners). Die KnowledgeBase kann sich selbst auf Konsistenz überprüfen. Die KnowledgeBase besteht aus 3192 Zeilen Java Code. Der Autor dieses Dokuments ist sich nicht sicher, aber er vermutet, dass die KnowledgeBase die eigentliche DescriptionLogic DL Intelligenz von Pellet enthält und nur durch OWLReasoner an Jena bzw. durch andere Klassen an die Manchester OWL API und das DIG Protokoll gebunden wird, also durch Adapter und Wrapper konvertiert und wiederverwendet wird, wie es in diesem Diagramm dargestellt ist.
Eine Diskussion auf einer Mailinglist unterstützt diese Theorie:
Let me first answer the second question which will help you to solve the
first problem. KnowledgeBase is the internal Pellet representation for a
knowledge base. The information it stores is equivalent to a Jena Model
but uses different internal data structures (based on ATerm library).
KnowledgeBase class caches various different information (satisfiability
status of classes, subsumption relation between classes, etc.) to
optimize query answering. However, these caches are invalidated when
KnowledgeBase is changed and needs to be recomputed. OWLReasoner is a
wrapper around the KnowledgeBase class that simply converts the
parameters and results between Jena and Pellet. If you load a model to
OWLReasoner and then change the model then you need to reload the model
again. More specifically, you will want to reasoner.clear() and then
reasoner.load(model) again. Then you can use the functions provided by
OWLReasoner to query the KB. You will never want to use KnowledgeBase
unless you want go into the specifics of Pellet. PelletReasoner on the
onther hand is an implementation of Jena Reasoner interface. You hook
PelletReasoner to an existing Jena model and when you call any Jena
function, e.g. c1.listSubClasses(), the answer is generated by Pellet.
The changes to the model are automatically recognized and internal
structures are updated accordingly. PelletReasoner provides much easier
integration with Jena but what you lose is the control over when and how
reasoning is done. PelletReasoner will classify the whole ontology
whenever you make a change (actually when you ask a question after a
change). With OWLReasoner you can determine when you want to classify or
realize (find the instances of each class).
Wenn man wissen möchte, wie Pellet funktioniert, muss man lernen, wie ein Tableau Reasoner funktioniert, da Pellet ein Tableau Reasoner ist. (Quelle). Auch gut:
@ARTICLE{Baader00anoverview,
author = {Franz Baader and Ulrike Sattler},
title = {An overview of tableau algorithms for description logics},
journal = {Studia Logica},
year = {2000},
volume = {69},
pages = {2001}
}
Im Folgenden wird ein Programm vorgestellt, das beschreibt, wie man den Pellet spezifischen Code weitesgehend durch die Jena API kapselt:
package main;
import java.io.FileNotFoundException;
import org.mindswap.pellet.jena.PelletReasonerFactory;
import com.hp.hpl.jena.ontology.OntModel;
import com.hp.hpl.jena.ontology.OntModelSpec;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.reasoner.ReasonerFactory;
import com.hp.hpl.jena.reasoner.ReasonerRegistry;
import com.hp.hpl.jena.util.FileManager;
import com.hp.hpl.jena.vocabulary.RDF;
public class Main {
/**
* @param args
* @throws FileNotFoundException
*/
public static void main(String[] args) throws FileNotFoundException {
// The following code is performed by the part that hooks up the
// pellet specific classes with the jena api
//
// this uri might go into some reasoner selection combo box or whatever ...
String pelletURI = PelletReasonerFactory.theInstance().getURI();
// insert the PelletReasonerFactory into the registry
// using the uri
ReasonerRegistry.theRegistry().register(pelletURI, PelletReasonerFactory.theInstance());
// from here on, every call is unrelated to pellet. Only jena API
// calls are used. The factory pattern allows the creation of the
// pellet instance without knowing the pellet peculiarities
//
// retrieve the factory via url of the pellet reasoner
ReasonerFactory factory = ReasonerRegistry.theRegistry().getFactory(pelletURI);
// create a spec for the model factory
// specs describe which constructs are allowed,
// which type of persistence to use (InMemory, DB)
// which reasoner and which DocumentManager to use
OntModelSpec spec = new OntModelSpec(OntModelSpec.OWL_MEM);
spec.setReasonerFactory(factory);
// read ABox, instance information
Model abox = FileManager.get().loadModel("file:data/owlDemoData.xml");
// read TBox aka schema, concept information
Model schema = FileManager.get().loadModel("file:data/owlDemoSchema.xml");
// Retrieve reasoner and bind it to the TBox via the bindSchema()
// method. The reasoner could perform work on the TBox which could
// be reused for several ABoxes which can be changed via the
// bind() method. This is implementation dependand ...
Reasoner reasoner = spec.getReasoner();
// WARNING: bindSchema() creates a new reasoner!
spec.setReasoner(reasoner.bindSchema(schema));
// createXXX() uses the factory stored inside the spec to create
// the reasoner and stores it within the ontModel.
// The ABox is bound to the reasoner via its bind() method. The
// bind() method is used for ABox data whereas the bindSchema()
// method is used for TBox data
OntModel ontModel = ModelFactory.createOntologyModel(spec, abox);
// validate() checks the current ABox data against the TBox data
ValidityReport validity = ontModel.validate();
if (validity.isValid()) {
System.out.println("OK");
} else {
System.out.println("Conflicts");
for (Iterator i = validity.getReports(); i.hasNext(); ) {
System.out.println(" - " + i.next());
}
}
// Perform reasoning, inference
//
Resource gamingComputer = ontModel.getResource("urn:x-hp:eg/GamingComputer");
Resource whiteBox = ontModel.getResource("urn:x-hp:eg/whiteBoxZX");
// call to contains() uses the reasoner which is stored somewhere inside
// the ontModel
if (ontModel.contains(whiteBox, RDF.type, gamingComputer)) {
System.out.println("White box recognized as gaming computer");
} else {
System.out.println("Failed to recognize white box correctly");
}
}
}
Das Programm besteht aus drei Teilen (Pellet specific hook up, Jena2 code, reasoning). Im ersten Teil wird die PelletReasonerFactory in die Jena Registry eingefügt. Die Factory ist dann unter einer URI abzufragen. Die Factory besitzt eine URI und kann diese mittel getURI() mitteilen. Dies ist der erste Schritt zur Kapselung von Pellet in Jena, er wird HookUp genannt. Die Jena Registry erlaubt nun nur noch Zugriffe auf das Jena Interface ReasonerFactory. Damit ist die PelletReasonerFactory gekapselt.
Im zweiten Teil wird die Factory abgefragt und eine OntModelSpec wird formuliert. Die Spec speichert eine Factory und damit die Möglichkeit einen Reasoner zu erzeugen. Die Spec erzeugt aber nur eine Instanz mit der ReasonerFactory und gibt diese Instanz nun bei jedem Aufruf von getReasoner() heraus. Falls man einen Reasoner einsetzt, wird dieser Reasoner nun bei jedem Aufruf von getReasoner() zurückgegeben. Der obige Code verwendet dieses Feature um den aktuellen Reasoner an eine TBox zu binden. Dazu verwendet er die Methode bindSchema(), die speziell für TBox Daten vorgesehen ist. Die Methode bindSchema() erzeugt einen neuen Reasoner, der zurück in die Spec gespeichert wird und im folgenden von jedem Aufrufer aus der Spec abgefragt werden kann. Die Spec wird verwendet um ein Model zu erstellen. Bei der Erstellung des Models wird ein Reasoner aus der Factory mithilfe der Spec generiert und im Model gespeichert, falls die Spec noch keinen Reasoner gepuffert hat. Im obigen Fall jedoch wird der an die TBox gebundene Reasoner abgefragt, da er bereits in der Spec enthalten ist. Der Reaoner ist nun also im Model gespeichert. Dies hat den Grund, das man nun durch Aufrufe der Jena API auf dem Model indirekt Aufrufe des Reasoners auslösen kann, ohne den Reasoner zu kennen. Dies ist ein weiterer wichtiger Schritt um Pellet oder jeden anderen Reasoner in Jena zu kapseln.
ABox Information wird bei der Erstellung des Models durch die ModelFactory übergeben, da die Erstellung des Models das übergebene ABox Model per bind() Methode an den Reasoner bindet. Der Reasoner besitzt zwei Methoden bindSchema() für TBox Daten und bind() für ABox Daten. Da die sich die ABox Daten von Anwendungsfall zu Anwendungsfall unterscheiden, aber die TBox Daten als Schema eher statischer Natur sind, können beide Daten durch Jena getrennt werden. Eine Implementierung des Reasoner Interface kann nun aufgrund eines bindSchema() Aufrufs rechenintensive Vorgänge auf der TBox ausführen und diese dann zur Wiederverwendung speichern. Wenn eine andere ABox eingesetzt wird, bleibt die TBox Information erhalten und kann unverändert übernommen werden. Es wird Rechenzeit gespart. Allerdings ist das Verhalten wie bereits gesagt Implementierungsabhängig und eine solche Wiederverwendung muss nicht unbedingt stattfinden.
Die letzte Aktion im zweiten Teil des Programms ist die Validierung der ABox gegen die TBox.
Nachdem TBox und ABox Information eingefügt wurden, kann man Jena API Methoden auf dem OntModel aufrufen. Diese Methoden verwenden nun eventuell den Reasoner. Die Methode contains() wird im dritten Teil des Programms aufgerufen und nutzt den Reasoner.
zum Seitenanfang
zur Hauptseite
Letzte Änderung: 13.04.2009