Nella II parte, ci eravamo fermati alla presentazione delle API per le ontologie elencando quelle principali per JAVA: JENA, OWL-API, Protege‘ OWL-API. Adesso cominciamo a vedere alcuni esempi basati su JENA, poiché tale framework è il più diffuso e ha una notevole chiarezza di modello.
JENA e l’OntModel
Lo scopo che ci prefiggiamo con questo articolo è di mostrare come una libreria possa essere utilizzata per caricare ontologie, reperire informazione ed eventualmente anche modificarle. Per questo motivo non parlerò di come scaricare JENA o di come creare un progetto in eclipse e configurarlo per usarle.
JENA permette di programmare in termini di risorse e triple RDF usando la sua classe base Model oppure ad un livello più Ontologico in termini di classi, disgiunzione di classi, uniuone, proprietà simmetriche, funzionali, Datatype property piuttosto che Object prorperty e restrizioni. Il modello in questo caso si chiama OntModel ed è del tutto indipendente dal tipo di formalismo utilizzato (OWL, DAML o RDFS). L’interfaccia messa a disposizione quindi sarà sempre la stessa, ciò che cambierà sarà il modo in cui l’ontologia verrà effettivamente salvata.
Partiamo allora subito con un esempio di caricamento di una ontologia.
import java.util.*; import com.hp.hpl.jena.ontology.*; import com.hp.hpl.jena.rdf.model.*; import com.hp.hpl.jena.util.*; ... OntModel onto = ModelFactory.createOntologyModel( OntModelSpec.OWL_MEM, null );
Tramite il ModelFactory otteniamo subito l’OntModel che ci servirà per gestire l’ontologia. I parametri specificano che sarà àdi tipo OWL in memoria (non pesistente su DB) e senza specificare nulla a proposito dell’inferenza.
onto.read("http://downloads.dbpedia.org/3.2/en/dbpedia-ontology.owl"); ExtendedIterator iter = onto.listNamedClasses(); while(iter.hasNext()) { OntClass ontoClass = (OntClass) iter.next(); System.out.println(ontoClass.getLocalName()); }
Con queste poche righe invece carichiamo l’ontologia da remoto e iteriamo su tutte le classi che definisce stampandole a schermo. Si nota la semplicità del codice e l’alto livello di una chiamata come onto.listNamedClasses();
OntClass, Individual, Property e Statement
Proviamo ora ad utilizzare le API di JENA per caricare una particolare ontologia e richiedere la lista delle istanze. Per ogni istanza vogliamo recuperare, oltre ai valori di alcune proprietà generiche OWL (label, namespace, URI e classe di appartenenza), i valori di proprietà specifiche dell’ontologia di modello più ancora una lista di tutte le proprietà che hanno un valore per quella istanza.
L’ontologia presa in considerazione è un elenco di istanze di progetti DOAP [DOAP] del tipo:
... Java version 1.5 Bedework Calendar Bedework 3 Bedework Open Source Calendar System for the Enterprise nome.cognome@email.comhttp: //www.bedework.org/bedework/update.do?artcenterkey=27 Bedework is an open-source, enterprise calendar system designed to conform to current calendaring standards. Made for higher education and built in Java, Bedework has a centralized server architecture allowing immediate update of public and personal calendaring information. Linux, Windows March 27, 2007 <doap:mailing-list rdf_resource="http://www.bedework.org/mailman/listinfo/bedework-users" /> 2009-07-17 <doap:bug-database rdf_resource="http://www.bedework.org/mailman/listinfo/bedework-users" /> Gary Schwartz 3.5 stable ...
Per prima cosa carichiamo l’ontologia nel modello da locale:
public static void main(String[] args) { OntModel onto = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM, null); onto.read("file:src/ontologies/DoapIstanze.rdf"); String doapBaseUri = "http://usefulinc.com/ns/doap#";
Nella stringa doapBaseUri mi salvo il base URI che utilizzerò per riferirmi alle risorse dell’ontologia (classi, istanze e proprietà). A questo punto recupero la lista delle istanze (individuals) della classe doap:Project (http://usefulinc.com/ns/doap#Project).
ExtendedIterator ite = onto.listIndividuals(onto.getOntClass(doapBaseUri+"Project"));
A questo punto posso ciclare sull’iteratore per recuperare le proprietà valorizzate dell’istanza e i suoi valori.
... int counter =0; while (ite.hasNext()) { Individual individual = (Individual) ite.next(); System.out.println(" ########### "+(++counter)+" ###########"); System.out.println("PROPRIETA' GENERICHE:"); System.out.println("URI: "+individual.getURI()); System.out.println("Label: "+individual.getLabel("en")); System.out.println("Local Name: "+individual.getLocalName()); System.out.println("Classe: "+individual.getRDFType().getLocalName()); //Proprietà specifiche dell'ontologia System.out.println(" PROPRIETA' SPECIFICHE DELL'ONTOLOGIA:"); System.out.println("Nome Progetto: "+individual.getPropertyValue(onto.getProperty(doapBaseUri +"name"))); System.out.println("Homepage: "+individual.getPropertyValue(onto.getProperty(doapBaseUri +"homepage"))); System.out.println("Category: "+individual.getPropertyValue(onto.getProperty(doapBaseUri +"category"))); System.out.println("Short Description: "+individual.getPropertyValue(onto.getProperty(doapBaseUri +"shortdesc"))); //Proprietà specifiche dell'ontologia System.out.println(" ALTRE PROPRIETA'..."); //recupero la lista degli statement con individual come soggetto StmtIterator ite2 = individual.listProperties(); System.out.print("["); while (ite2.hasNext()) { Statement statement = (Statement) ite2.next(); //Stamp oil predicato dello statement: ossia la property. System.out.print(" "+statement.getPredicate().getLocalName() +" "); } System.out.print("]"); } ...
Si noti che dato l’individual, tramite metodi come getURI e getLocalName (ereditati dalla classe Resource) possiamo ottenere in maniera immediata i valori. Per ottenere i valori di proprietà specifiche dell’ontologia (che conosciamo a priori) dovremo invece richiamare prima la proprietà tramite il metodo:
onto.getProperty(doapBaseUri+"NOME_DELLA_PROP"))
per poi passarlo al metodo getPropertyValue della classe Individual.
Infine il ciclo finale itera sulla lista di tutti gli statement (triple soggetto-predicato-oggetto) in cui l’istanza è coinvolta come soggetto, che equivale a richiedere tutte le proprietà valorizzate nell’ontologia per quella istanza.
Creare classi, istanze e proprietà
Vediamo ora per completezza come sia semplice utilizzare il modello di JENA per creare nuove classi, istanze e proprietà e successivamente serializzare tutto in un file OWL.
Supponiamo di voler creare una ontologia che estenda DOAP con il concetto di progetto Open Source. Per prima cosa creiamo un modello per DOAP e uno per la nostra ontologia che importerà DOAP:
... String doapBase = "http://usefulinc.com/ns/doap#"; //DOAP NAMESPACE String osDoapBase = "http://imolinfo.it/ontologies/osdoap#"; //OUR NEW NAMESPACE //DOAP MODEL OntModel doapModel = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM, null); doapModel.read("http://usefulinc.com/ns/doap"); //NOSTRO MODELLO OntModel model = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM); model.createOntology(osDoapBase); //IMPORTIAMO DOAP model.getOntology(osDoapBase).addImport(new ResourceImpl("http://usefulinc.com/ns/doap")); //PREFISSI BASE E DOAP model.setNsPrefix("doap", "http://usefulinc.com/ns/doap#"); model.setNsPrefix("", osDoapBase); ...
Vengono quindi creati due modelli: uno (doapModel) per leggere DOAP e uno (model) dove creare la nostra classe di estensione, le istanze e le proprietà (quello di lavoro in pratica);
Per importare l’ontologia doap è necessario creare l’elemento Ontology (come previsto dalle specifiche OWL) in cui specificare l’import tramite il metodo addImport. Infine prima di passare alla creazione dei nuovi concetti dell’ontologia associamo i prefissi per il namepsace doap e quello di base (che verranno poi usati in fase di salvataggio del file).
A questo punto creiamo una nuova classe nel modello di lavoro come sottoclasse di doap:Project:
... OntClass osProjClass = model.createClass(osDoapBase+"OpenSourceProject"); osProjClass.setLabel("OPen source Project", "en"); osProjClass.setComment("A doap project that is Open Source", "en"); osProjClass.setSuperClass(doapModel.getOntClass(doapBase+"Project")); ...
Si noti che per riferirmi alla superclasse doap:Project uso il doapModel da cui ricavo la classe OWL Project come Resource.
Nota: Non scordate label e commenti! Se avete avuto la pazienza di leggere il numero uno di questa stessa serie [SW_GWT-1], sottolineavo quanto fosse importante per un tool di visualizzazione generico poter mostrare almeno proprietà standard come label e commenti per ogni concetto. Ciò favoriva una immediata leggibilità e comprensione dei contenuti dell’ontologia. Proprio per questo motivo tutte le volte che si creano nuovi risorse RDF in una ontologia (classi, proprieà e istanze) sarebbe buona norma ricordarsi di munirle almeno di una rdfs:label e rdfs:comment che rappresentano l’informazione minima preposta alla leggibilità del concetto.
Ora creiamo un’altra classe che rappresenta il concetto di “Maturity Note” ossia un insieme di informazioni che concernono la maturità e bontà di un progetto open source. Creiamo inoltre la proprietà hasMaturityNote che lega in OpenSourceProject ad una MaturityNote settando domain e range della proprietà tramite gli opportuni metodi dell’OntModel.
…
//MaturityNote OntClass matutirtNoteClass = model.createClass(osDoapBase+"MaturityNote"); matutirtNoteClass.setLabel("Maturity Note", "en"); matutirtNoteClass.setComment("A note about the maturity level of a Open source project", "en"); //hasMaturityNote: OpenSourceProject --> MaturityNote OntProperty hasMaturityNoteProp = model.createOntProperty(osDoapBase+"hasMaturityNote"); hasMaturityNoteProp.setDomain(osProjClass); hasMaturityNoteProp.setRange(matutirtNoteClass); hasMaturityNoteProp.setLabel("has Maturity Note", "en"); hasMaturityNoteProp.setComment( "Property of Open source project to have a Maturity Note", "en"); ...
Infine creiamo una istanza di MaturityNote, una di OpenSource Project e le leghiamo insieme… tanto per chiudere il cerchio e mostrare come venga serializzato il relativo file owl.
//Individual JENAProject Individual individualOSProject = osProjClass.createIndividual(osDoapBase+"JENAProject"); //Setto doap_name="JENA" individualOSProject.setPropertyValue(doapModel.getOntProperty(doapBase+"name"), model.createLiteral("Jena")); individualOSProject.setLabel("JENA Project", "en"); individualOSProject.setComment("Jena is a Java framework for building Semantic Web applications. It provides a programmatic environment for RDF, RDFS and OWL, SPARQL and includes a rule-based inference engine.", "en"); //Individual MaturityNoteJENA Individual individualMatNote = matutirtNoteClass.createIndividual(osDoapBase+"maturityNoteAboutJenaProject_01"); individualMatNote.setLabel("Maturity Note about JENA", "en"); individualMatNote.setComment("The project is grown out of work with the HP Labs Semantic Web Programme. Latest release is: 2.6.0", "en"); //Connessione JENAProject a MaturityNoteJENA individualOSProject.setPropertyValue(hasMaturityNoteProp, individualMatNote);
Si noti che per creare le istanze posso partire direttamente dalla classe da cui voglio istanziare usando il metodo createIndividual.
Il risultato verrà quindi salvato su file nel seguente modo:
... PrintStream output=System.out; try { output = new PrintStream("src/ontologies/OSDoap.owl"); } catch (FileNotFoundException e) { e.printStackTrace(); } model.write(output, "RDF/XML-ABBREV", osDoapBase); System.out.println("Ontologia salvata con successo su "+output.toString()); model.close(); ...
Il file OSDoap.owl risultante è il seguente:
<rdf:RDF xmlns_rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns_doap="http://usefulinc.com/ns/doap#" xmlns_owl="http://www.w3.org/2002/07/owl#" xmlns_xsd="http://www.w3.org/2001/XMLSchema#" xmlns_rdfs="http://www.w3.org/2000/01/rdf-schema#"> A note about the maturity level of a Open source project Maturity Note A doap project that is Open Source Open source Project Property of Open source project to have a Maturity Note has Maturity Note The project is grown out of work with the HP Labs Semantic Web Programme. Latest release is: 2.6.0 Maturity Note about JENA Jena is a Java framework for building Semantic Web applications. It provides a programmatic environment for RDF, RDFS and OWL, SPARQL and includes a rule-based inference engine. Jena JENA Project
Interrogazioni SPARQL con JENA
Infine per concludere con una bella “connessione semantica” fra questo JENA e SPARQL diamo una occhiata (non troppo approfondita) a come le API di JENA permettendo di creare e processare query SPARQL.
Consideriamo allora il seguente codice:
public static void main(String[] args) { OntModel model = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM, null); model.read("file:src/ontologies/OSDoap.owl");
Per prima abbiamo caricato il modello partendo dall’ontologia creata prima e salvata in locale.
String queryString = "PREFIX doap: <http://usefulinc.com/ns/doap#>"+ "PREFIX osDoap: <http://imolinfo.it/ontologies/osdoap#>" + "PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> "+ "SELECT ?name ?note_comment "+ "WHERE { " + " ?proj a osDoap:OpenSourceProject ." + " ?proj doap:name ?name ." + " ?proj osDoap:hasMaturityNote ?note ." + " ?note rdfs:comment ?note_comment" + "}";
Ovviamente creiamo la query SPARQL che richiede la lista di tutti i progetti Open Source e per ognuno le relative Maturity Note.
Query query = QueryFactory.create(queryString); QueryExecution qexec = QueryExecutionFactory.create(query, model);
Quindi creiamo la query tramite il QueryFactory e la passiamo a un QueryExecution insieme al modello che rappresenterà il dataset di default per la query. A questo punto lanciamo la query usando il metodo execSelect() e cicliamo sui risultati:
try { ResultSet results = qexec.execSelect(); System.out.println("Elenco dei risultati della query SPARQL: "); System.out.println("==================================="); while (results.hasNext()) { QuerySolution querySolution = (QuerySolution) results.next(); Literal nameLiteral = querySolution.getLiteral("name"); Literal noteLiteral = querySolution.getLiteral("note_comment"); System.out.println("Progetto Open Source: "+nameLiteral.getString() +" ==> Maturity Note: "+noteLiteral.getString()); } } finally { qexec.close(); } }
L’oggetto QuerySolution che otteniamo permette di ottenere il valore di una variabile come Literal, come Resource o anche come RDFNode. Nel nostro caso sono Literal e come tali li salviamo nelle due variabili nomeLiteral e noteLiteral.
Il risultato in console dovrebbe essere il seguente:
Elenco dei risultati della query SPARQL: =================================== Progetto Open Source: Jena ==> Maturity Note: The project is grown out of work with the HP Labs Semantic Web Programme. Latest release is: 2.6.0
Inferenza e recupero informazioni: SWRL, Reasoner e ancora… JENA!
Fino ad ora non abbiamo esplorato una delle grandi potenzialità delle ontologie: quella di poterci fare dell’inferenza sopra. Fare inferenza significa desumere nuova conoscenza a partire dalle informazioni esplicitate nell’ontologia ed eventuali regole utilizzando la Descritpion Logic. OWL-DL nasce proprio per garantire che una ontologia sia compatibile con questi meccanismi inferenziali che vengono implementati dai cosiddetti reasoner. Un reasoner non è altro che una applicazione capace di leggere una ontologia, delle eventuali regole e restituire nuove informazioni come: equivalenza di classi, di istanze, consistenza dell’ontologia ed eventuali nuovi “fatti” derivanti dalle regole.
Senza volerci addentrare troppo in questo mondo (non è negli scopi di questo articolo), facciamo qualche esempio di inferenza. Consideriamo una ontologia che esprima i seguenti fatti:
- Andrea è una persona;
- Giulio è una persona;
- Carlo è il padre di Andrea;
- Carlo è il padre di Giulio;
L’ontologia non dice altro, ma se qualcuno ci chiedesse in che relazione sono Andrea e Giulio noi risponderemmo che sono fratelli. Bene, un reasoner può fare altrettanto se gli è stata spiegata una volta per tutte una regola (che noi abbiamo chiara in testa) del genere:
SE X e Y hanno lo stesso padre ALLORA X e Y sono fratelli
Data questa regola, una volta data in pasto l’ontologia e la regola al reasoner esso potrà rispondere con certezza alla nostra domanda (magari fatta con SPARQL) “chi è il fratello di Andrea?”. Si noti che da nessuna parte è stato esplicitato che i due sono fratelli, ma come si dice… è una logica conseguenza dei fatti!
Questo tipo di approccio alla fine rientra sempre nella problematica di interrogazione di ontologie e recupero di informazione come visto fino ad ora (e quindi API o SPARQL) ma con la piccola variante che non si interroga direttamente a partire dalle informazioni esplicite di modello ma da quelle implicite e inferite dopo un processo, appunto, di inferenza logica.
Detta così fa paura… ma il senso è: non cambia nulla dal punto di vista delle tecniche di accesso alle informazioni, ciò che cambia è il filtro intermedio che risponde in maniera un po’ più intelligente.
Per questo motivo non mostrerò come accedere all’ontologia, ma farò piuttosto un breve esempio pratico di come fare inferenza usando il reasoner integrato in JENA e vederne gli effetti.
Partiamo allora dall’ontologia descritta sopra (Person.owl):
person has sibling is parent of ="http://www.w3.org/2002/07/owl#InverseFunctionalProperty"/> has parent Andrea Giulio Carlo
In un file di testo a parte (che chiamiamo Rules.swrl) inseriremo la seguente regola:
[SiblingRule: (?X rdf:type person:Person), (?Y rdf:type person:Person), (?Z rdf:type person:Person), (?X person:hasParent ?Z), (?Y person:hasParent ?Z), notEqual(?X,?Y) -> (?X person:hasSibling ?Y) ]
La regola SWRL derivano dalle cosiddette regole di Horn [HORN] e sono nella forma di implicazione tra antecedente (head) e conseguente (body). Il senso è che se tutte le clausole dell’antecedente sono vere ALLORA è vero anche il conseguente. Antecedente e conseguente sono sempre composti da così detti Atom i quali sono in AND fra loro. La forma Human Readable di una regola SWRL del tipo:
parent(?x,?y) ∧ brother(?y,?z) ⇒ uncle(?x,?z)
che si legge
"se ?x parent ?y E ?y brother ?z ALLORA ?x uncle ?z"
A questo punto non resta che mostrare il codice per leggere le regole, fare inferenza e leggerne il risultato tramite JENA e il suo GenericReasoner.
String ont_local = "file:src/ontologies/Person.owl"; String person = "http://imolinfo.it/Persons.owl#"; // Setto il prefisso usato nelle regole. PrintUtil.registerPrefix("person", person); OntModel m = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM, null); // Creo una configurazione per il reasoner dove specifico il file delle regole Resource configuration = m.createResource(); configuration.addProperty(ReasonerVocabulary.PROPruleMode, "hybrid"); configuration.addProperty(ReasonerVocabulary.PROPruleSet, "src/swrl/rules.swrl"); // Creo una istanza del reasoner Generico Reasoner reasoner = GenericRuleReasonerFactory.theInstance().create(configuration); // Creo il modello base su cui fare inferenza (l'ontologia) Model baseOntology = FileManager.get().loadModel(ont_local); // Creo il modello inferenziale a partire dal reasoner e dall'ontologia base InfModel infModel = ModelFactory.createInfModel(reasoner, baseOntology); infModel.prepare(); // Dal modello inferito creo un modello ontologico normale su cui recuperare le info OntModel ontMod = ModelFactory.createOntologyModel( OntModelSpec.OWL_MEM, infModel); // Proprietà ObjectProperty hasSiblingProp = ontMod.getObjectProperty(person+ "hasSibling"); ObjectProperty hasParentProp = ontMod.getObjectProperty(person+ "hasParent"); ObjectProperty isParentOfProp = ontMod.getObjectProperty(person+ "isParentOf"); // Istanze Individual Andrea = ontMod.getIndividual(person + "Andrea"); Individual Giulio = ontMod.getIndividual(person + "Giulio"); Individual Carlo = ontMod.getIndividual(person + "Carlo"); // VALORI ESPLICITATI Resource padreAndrea = (Resource) Andrea.getPropertyValue(hasParentProp); Resource padreGiulio = (Resource) Andrea.getPropertyValue(hasParentProp); // VALORI INFERITI Resource fratelloAndrea = (Resource) Andrea.getPropertyValue(hasSiblingProp); System.out.println("FATTI: "); System.out.println("- " + Andrea.getLocalName() + " " + hasParentProp.getLabel("en") + " " + padreAndrea.getLocalName()); System.out.println("- " + Giulio.getLocalName() + " " + hasParentProp.getLabel("en") + " " + padreGiulio.getLocalName()); System.out.println(" NE SEGUE CHE (inferenza): "); if (fratelloAndrea != null) System.out.println("- " + Andrea.getLocalName() + " " + hasSiblingProp.getLabel("en") + " " + fratelloAndrea.getLocalName()); else System.out.println("ERROE! Nessun fratello inferito!"); }
Il cui output se siamo fortunati sarà:
FATTI: - Andrea has parent Carlo - Giulio has parent Carlo NE SEGUE CHE (inferenza): - Andrea has sibling Giulio
Recuperare informazioni da un middleware semantico: OpenAnzo
Un modo alternativo per recuperare informazioni semantiche è quello di interagire con una vera e propria piattaforma middleware che metta a disposizione una serie di servizi (tra qui per esempio anche SPARQL) per reperire e gestire le informazioni semantiche a livello enterprise. Ciò permette di fatto di disaccoppiare le ontologie e quindi in generale la parte di dati RDF, dalle applicazioni (anche molto diverse fra loro) che ne usufruiscono.
La piattaforma che userò per l’esempio si chiama Open Anzo [OANZO] ed è una fork del progetto open source Boca [BOCA] e di altri componenti dell’ IBM Semantic Layered Research Platform.
Il progetto open source unisce ad un sistema di triple-storing RDF organizzati per Named Graph, una piattaforma completa ed estendibile di middleware semantico. Ciò costituisce una possibile base per la creazione di complesse applicazioni enterprise basate sulle tecnologie semantiche (RDF, OWL, SPARQL). Le principali caratteristiche di OpenAnzo sono:
- Supporto per utenti multipli (via LDAP)
- Clients e services distribuiti
- Funzionamento offline
- Real-time notification (via JMS)
- Organizzazione per Named Graph
- Versioning
- Controllo degli accessi e transazioni con precondizioni
Prima di vedere l’esempio ricordo che è necessario scaricarsi OpenAnzo (attualemente alla versione 3.1.0) dal sito e in particolare usare le API del client per accedere al Server. Anche questo però non è negli scopi di questo articolo e verrà eventualmente approfondito in un altro contesto.
Mostriamo ora come sia possibile connettersi al server di OpenAnzo (precedentemente configurato e avviato in locale) per recuperare informazioni dai grafi memorizzati.
... AnzoClient anzoClient = null; // prepare the configuration for the client String username = "default"; String password = "123"; String host = "localhost"; int port = 61616; boolean useSsl = false; // instantiate a anzo client anzoClient = new AnzoClient(AnzoClientConfigurationFactory.createJMSConfiguration(username, password, host, port, useSsl)); // connect the client anzoClient.connect(); ...
Come si nota per connettersi al server basterà creare una istanza di client passando username, password, host del server e numero della porta e lanciare il metodo della classe AnzoClient.connect().
A questo punto supponendo di avere già un grafo (un insieme di triple RDF) memorizzato in OpenAnzo possiamo richiederne l’RDF molto semplicemente in questo modo:
... IAnzoGraph _graph = null; String namedGraphURIStr = "http://example.org/namedgraph1"; String formatStr = request.getParameter(AnzoParameters.FORMAT_PARAM_NAME); // CREO l'URI DEL NAMED GRAPH CHE VOGLIO RECUPERARE URI namedGraphUri = MemURI.create(namedGraphURIStr); // OTTENGO IL GRAFO IN LOCALE DAL SERVER _graph = anzoClient.getCurrentNamedGraphRevision(namedGraphUri); if (_graph != null) { PrintWriter writer = new PrintWriter(System.out); //STAMPO IN OUTPUT IN FORMATO RDF/XML IL CONTENUTO DEL GRAFO ReadWriteUtils.writeGraph(_graph, System.out, format); } ...
Questo tipo di operazione mostra come tutto ciò che viene memorizzato in Anzo come grafo può sempre essere ricondotto a un RDF e quindi di fatto all’accesso ad una normale collezione di triple come visto fino ad ora.
Più interessante invece, risulta il prossimo esempio in cui mostrerò come si possa interrogare direttamente OpenAnzo sia usando SPARQL che tramite delle “quad pattern find” ossia delle quadruple formate dalla tripla di uno statement e il nome del Grafo.
... // CREO LA QUERY SPARQL String query = "SELECT ?s ?p ?o WHERE { ?s ?p ?o }"; // LANCIO IL SERVER QUERY DI OPENANZO SU TUTTI I NAMED GRAPH QueryResults results = anzoClient.serverQuery(Collections.singleton(GRAPHS.ALL_NAMEDGRAPHS), null, null, query); SolutionSet solutions = results.getSelectResults(); System.out.println("Trovate " + solutions.size() + " soluzioni"); //ITERO SULLE SOLUZIONI PER VISUALIZZARE I BINDING DELLA VARIABILE SUBJECT (s) for(PatternSolution solution : results.getSelectResults()) { Value value = solution.getBinding("s"); System.out.println("Binding per S: "+value.toString()); } ...
Come si nota la query SPARQL viene passata come stringa all’Anzo serverQuery indicandogli di usare come grafo standard “tutti in Named Graph” presenti nello store. Una volta lanciata la query (results.getSelectResults()) potrò ovviamente ciclare sul set delle soluzioni e recuperare i valori dei vari binding di variabile.
Un altro modo per effettuare ricerche sui grafi di OpenAnzo è quello di richiamare direttamente delle pattern-based find sul server, ossia di utilizzare direttamente le API messe a disposizione da Open-Anzo nel seguente modo:
... URI prop1 = Constants.valueFactory.createURI("http://example.org/prop1"); Collection stmts = anzoClient.serverFind(null, prop1, null, namedGraphURI); for (Iterator iterator = stmts.iterator(); iterator.hasNext();) { Statement statement = (Statement) iterator.next(); System.out.println(statement.toString()); } ...
Alla chiamata serverFind passo il pattern di ricerca dove posso specificare subject (come Resource), predicate (come URI) e object (come Value). Null sta per “qualunque”. Si noti infine che con questo metodo si ottengono direttamente degli statement RDF e non dei binding. È quindi possibile accedere direttamente al soggetto, predicato e oggetto tramite gli opportuni metodi.
Conclusioni
In questo articolo abbiamo visto l’udo di un’API Java come JENA per caricare e recuperare informazioni da ontologie RDF/OWL. Successivamente abbiamo esplorato in maniera molto superficiale il mondo dell’inferenza logica e in particolare abbiamo visto come si possano recuperare informazioni non esplicitate direttamente in una ontologia ma inferite da un reasoner a partire da delle regole SWRL. Anche questo è stato fatto con JENA. Per finire abbiamo visto come utilizzare una piattaforma di middelware semantico come OpenAnzo per recuperare grafi o effettuare query specifiche sulle informazioni semantiche memorizzate al suo interno.
Concludendo, potremmo dire che non c’è un modo migliore di un altro per recuperare informazioni semantiche ma piuttosto che in base alle esigenze e alla logica di business alcuni risultino più convenienti rispetto ad altri. Per applicazioni desktop standalone per esempio le API sono sicuramente la soluzione più conveniente per semplicità e immediatezza applicativa, mentre per soluzioni di tipo enterprise, la strada migliore da seguire è l’ultima vista. L’adozione di una piattaforma SOA di middelware semantico infatti permette molteplici punti di accesso (API, SPARQL ed eventualmente anche reasoning) e rende possibile l’integrazione di diversi servizi per lo sviluppo di applicazioni in maniera completamente trasparente rispetto allo strato delle ontologie.
Riferimenti
[FOAF] Friend Of A Friends
[SPARQL] Standard W3C SPARQL
[DBPED] DiBpedia
[TURTLE] Turtle Terse RDF Triple Language
http://www.w3.org/TeamSubmission/turtle/
[JSON] Formato JSON
[VIRTUO] Virtuoso
http://virtuoso.openlinksw.com/dataspace/dav/wiki/Main/VOSSparqlProtocol
[XERCES] Xerces Java Parser
http://xerces.apache.org/xerces-j/
[JENA] A Semantic Web Framework for Java
[OWLAPI] OWL API homepage
http://owlapi.sourceforge.net/
[OWL2] OWL 2 Web Ontology Language Profiles
http://www.w3.org/TR/owl2-profiles/
[SWRL] Semantic Web Rule Language
http://www.w3.org/Submission/SWRL/
[POAPI] Protege’-owl api
http://protege.stanford.edu/plugins/owl/api/
[DOAP] Description of a Project Ontology
http://trac.usefulinc.com/doap
[HORN] Clausole di Horn
http://it.wikipedia.org/wiki/Clausola_di_Horn
[OANZO] Progetto Open Anzo
http://www.openanzo.org/index.html
[BOCA] Boca project
http://ibm-slrp.sourceforge.net/
[SW_GWT-1] M. Busanelli, Semantic Web. Esplorazione/visualizzazione di ontologie – I parte: Introduzione e analisi dei tool attuali. MokaByte 142, luglio-agosto 2009 (nella stessa serie)