In questa quarta e ultima parte della serie vedremo come creare una applicazione GWT che effettui il caricamento di una ontologia OWL a scelta e ne visualizzi la tassonomia ad albero navigabile e cliccabile sfruttando un opportuno widget semantico sviluppato appositamente. Il tutto verrà effettuato sfruttando il plugin di Google per Eclipse.
Introduzione
Siamo arrivati all’ultimo articolo di questa serie semantica e vorrei fare una summa dei concetti principali visti durante la serie.
Come suggerisce il titolo uno degli scopi principali della serie era quello di analizzare i modi di fare “rendering” delle complesse informazioni derivanti dalle ontologie. A questo scopo nel primo articolo [MOKA1] abbiamo visto una panoramica dei vari tool open source e dei relativi sistemi di navigazione, interrogazione delle ontologie. Successivamente nella serie abbiamo visto la cosa da un punto di vista più programmatico o come direbbero in gergo MokaByte, “del programmatore e delle sue api”.
Arrivati a questo punto non ci resta che mettere insieme i vari pezzi e provare a sviluppare una applicazione per la visualizzazione, esplorazione di una “un po’ di ontologia” utilizzando i potenti mezzi messi a disposizione dal mondo open source Java e Google.
L’idea è quindi di sfruttare l’amatissimo (parlo per me ovviamente) GWT 2 [MOKA2] per creare un bel componente semantico riutilizzabile per il rendering di tassonomie OWL (solo il nome ispira simpatia non vi pare?). Il componente sarà testato su una applicazione client-server con GWT RPC usando il plugin Google per Eclipse nella sua ultima versione [GECLIPSE]. Ovviamente tutto questo a partire dalle nostre ormai solide conoscenze di framework e API semantici (leggasi JENA nello specifico) per gestire lato server la logica di business di accesso alle ontologie.
Insomma, se siete un po’ smanettoni e fino ad ora la serie vi è risultata un tantino soporifera… credo sia venuto il momento di scrocchiarsi le dita e svegliarsi perche’ stavolta ci si diverte.
Una nota: i lettori di Moka hanno già sentito parlare molto di GWT e sicuramente avranno anche già potuto apprezzarne le caratteristiche leggendo i molti articoli che Moka ha pubblicato in proposito. Per questo motivo non mi addentrerò nella spiegazione del framework (vi rimando ad altri articoli come [MOKA2] o [MOKA3]) ma piuttosto porrò l’attenzione sugli aspetti che mi sembrano più interessanti come il servizio semantico e lo sviluppo del widget custom della tassonomico.
Overview dell’architettura
Prima di addentrarci sulla parte tecnica di questo articolo è necessario avere una idea generale di come siano tra loro integrati gli aspetti della parte client (principalmente il widget semantico), di RPC/serializzazione, e di quella server di business logic semantica (JENA per intenderci). A questo scopo ho perso 2 ore della mia vita per realizzare questo diagramma:
Lato client abbiamo un semplice browser web con JavaScript abilitato su cui gireranno i componenti grafici GWT compilati in JavaScript. In particolare in questo caso ho scritto un componente custom, che chiameremo TaxonomyTree, che implementa la logica di visualizzazione delle informazioni semantiche derivate dall’ontologia. Il client comunica con un GWT Remote Service (una servlet che gira su Tomcat) chiamato OntologyService. L’Ontology Service ha il compito di rispondere al client interfacciandosi con l’ontologia tramite le API JENA. Ciò non sarebbe possibile direttamente sul client in quanto JENA è una libreria nativa JAVA e non è possibile portarla su JavaScript per renderla disponibile direttamente al browser.
Il client per prima cosa richiederà all’OntologyService di caricare l’ontologia specificata dall’utente tramite una opportuna comboBox nell’interfaccia di input (una semplice URL). Il server quindi caricherà l’ontologia richiesta nel JENA model in modo da essere pronto a servire le successive richieste del client.
Quando il client riceverà la conferma di caricamento avvenuto, eseguirà la seconda richiesta asincrona sul server per richiedere la tassonomia da visualizzare. La tassonomia (come qualsiasi oggetto scambiato via RPC) deve essere serializzabile secondo GWT (ora anche secondo il più standard java.io.Serializable). Ciò significa che il server non potrà inviare direttamente le informazioni del modello JENA al client, ma che prima dovrà wrapparle in un apposito DTO (Data Transfer Object). Per questo motivo ho creato il TaxonomyDTO e il TaxonomyTreeItemDTO che non sono altro che Javabean che trasportano l’albero tassonomico.
Il widget TaxonomyTree quindi sarà in grado di generare l’albero delle classi a partire proprio dal TaxonomyDTO che gli viene passato. Il widget semantico accetta anche gli handler per gli eventi di Click e MouseOver sui nodi dell’albero. In questo modo il widget implementerà solo la logica di visualizzazione dell’albero delle classi a partire dal DTO, lasciando all’applicazione che lo usa la scelta su come gestire l’interazione con gli item.
Di seguito mostriamo una schermata di come risulterà alla fine l’interfaccia con un TaxonomyTree già caricato.
Creazione del progetto e step preliminari
Come ho già detto il progetto è stato sviluppato in Eclipse Galileo utilizzando il Google Plug-in. Ho creato quindi un Web Application Project chiamato TaxonomyTreeTest in cui ho importato subito la libreria di JENA (bisogna importare tutti i jar contenuti nella cartella lib della di Jena)[JENA]. Come si è già detto, solo il server usufruirà di queste classi mentre la parte client non sarà interessata. Successivamente ho creato i vari sotto-package in modo da organizzare le classi rispetto a client e server (cosa indispensabile come già sapete quando si lavora con GWT):
Nella parte client (quella che GWT compilerà in JavaScript) vi sono:
- la classe di EntryPoint TaxonomyTreeTest
- le interfacce asincrone e sincrone del servizio
- le classi DTO e il widgets TaxonomyTree
Nella parte server invece vi è solo l’implementazione dell’OntologyService (OntologyServiceImpl.java).
Ontology Service e Taxonomy DTO
Per prima cosa, creo i DTO in modo da poter dopo definire le interfacce del servizio in funzione dei DTO scambiati. Il TaxonomyDTO non sarà altro che una classe Java serializzabile contenente una TaxonomyTreeItemDTO come root. A sua volta un TaxonomyTreeItemDTO rappresenta ogni singolo nodo della tassonomia e come tale racchiude le informazioni basilari di una classe OWL (id, raf:label e rdf:comment) più ovviamente la lista di suoi TaxonomyTreeItemDTO figli. Tutte le informazioni saranno attributi privati accessibili tramite getter e setter proprio come un Javabean.
Nota: Il DTO non racchiude mai nessuna logica di business!
Di seguito riporto il codice di entrambe le classi:
public class TaxonomyDTO implements Serializable { TaxonomyItemDTO root; public TaxonomyItemDTO getRoot() { return root; } public void setRoot(TaxonomyItemDTO root) { this.root = root; } } public class TaxonomyItemDTO implements Serializable { public static String INSTANCE_TYPE_NAME = "instance"; public static String CLASS_TYPE_NAME = "class"; private String id; private String label; private String comment; private String nodeType; private Collection subItems = new ArrayList(); public TaxonomyItemDTO(String id, String label, String comment, String nodeType) { super(); this.id = id; this.label = label; this.comment = comment; this.nodeType = nodeType; } public TaxonomyItemDTO() { super(); } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getLabel() { return label; } public void setLabel(String label) { this.label = label; } public String getComment() { return comment; } public void setComment(String comment) { this.comment = comment; } public String getNodeType() { return nodeType; } public void setNodeType(String nodeType) { this.nodeType = nodeType; } public Collection getSubItems() { return subItems; } public void setSubItems(Collection subItems) { this.subItems = subItems; } public void addSubItem(TaxonomyItemDTO subItem) { this.subItems.add(subItem); } }
A questo punto possiamo definire l’Ontology Service. Come si è detto il servizio dovrà eseguire due operazioni per il client:
- caricare l’ontologia sul server (loadOntology)
- restituire la tassonomia (getCompleteTaxonomy)
In base a questi requisiti possiamo immediatamente definire la sua interfaccia sincrona. In questo modo potremo far fare tutto il lavoro di generazione della asincrona e relativo aggiornamento al plug-in di eclipse. Di seguito riporto l’interfaccia OntologyService.java:
package org.example.gwt.semantic.client; import org.example.gwt.semantic.client.dto.TaxonomyDTO; import com.google.gwt.user.client.rpc.RemoteService; import com.google.gwt.user.client.rpc.RemoteServiceRelativePath; @RemoteServiceRelativePath("ontoservice") public interface OntologyService extends RemoteService { // Carica l'ontologia RDF/OWL sul server public String loadOntology(String url); // Ritorna la tassonomia completa. public TaxonomyDTO getCompleteTaxonomy(); }
A partire da questa genero l’interfaccia asincrona usando il plug-in:
package org.example.gwt.semantic.client; import org.example.gwt.semantic.client.dto.TaxonomyDTO; import com.google.gwt.user.client.rpc.AsyncCallback; public interface OntologyServiceAsync { void getCompleteTaxonomy(AsyncCallback callback); void loadOntology(String url, AsyncCallback callback); }
Finalmente è arrivato il momento di scrivere un po’ di logica e quindi di attivare qualche neurone in più: nel package server creo quindi la classe di implementazione del servizio. Di seguito riporto il codice della classe OntologyServiceImpl.java:
import org.example.gwt.semantic.client.OntologyService; import org.example.gwt.semantic.client.dto.TaxonomyDTO; import org.example.gwt.semantic.client.dto.TaxonomyItemDTO; import java.util.Iterator; import com.google.gwt.user.server.rpc.RemoteServiceServlet; import com.hp.hpl.jena.*; public class OntologyServiceImpl extends RemoteServiceServlet implements OntologyService { private static String LOAD_SUCCESS_MSG = "Ontology loaded succesfully"; private static String LOAD_FAILURE_MSG = "ERROR ON LOADING ONTOLOGY: "; private OntModel ontModel = null; public TaxonomyDTO getCompleteTaxonomy() { TaxonomyDTO taxonomyDTO = new TaxonomyDTO(); taxonomyDTO.setRoot(generateRootItem()); return taxonomyDTO; } private TaxonomyItemDTO generateRootItem() { if (ontModel==null) return null; else { Iterator i = ontModel.listHierarchyRootClasses() .filterDrop( new Filter() { public boolean accept( OntClass r ) { return r.isAnon(); }} ); TaxonomyItemDTO rootItem = new TaxonomyItemDTO(); rootItem.setId("owl:Thing"); rootItem.setLabel("owl:Thing"); rootItem.setComment("OWL Ontology root class"); while (i.hasNext()) { rootItem.addSubItem(generateSubItemTreeFromClass(i.next())); } return rootItem; } } private TaxonomyItemDTO generateSubItemTreeFromClass(OntClass ontClass) { Iterator ite = ontClass.listSubClasses() .filterDrop( new Filter() { public boolean accept( OntClass r ) { return r.isAnon(); }} ); TaxonomyItemDTO item = new TaxonomyItemDTO(ontClass.getLocalName(), ontClass.getLabel(null), ontClass.getComment(null), TaxonomyItemDTO.CLASS_TYPE_NAME); while (ite.hasNext()) { OntClass subClass = (OntClass) ite.next(); item.addSubItem(generateSubItemTreeFromClass(subClass)); } return item; } public String loadOntology(String url) { this.ontModel = ModelFactory.createOntologyModel( OntModelSpec.OWL_MEM, null ); try { ontModel.read(url); } catch (Exception e) { return LOAD_FAILURE_MSG.concat(e.getMessage()); } return LOAD_SUCCESS_MSG; } }
Dal codice si può notare come il servizio faccia pesantemente uso dell’OntModel di JENA per caricare l’ontologia e per navigare la gerarchia delle classi.
In particolare il metodo pubblico getCompleteTaxonomy() non fa altro che chiamare ricorsivamente il metodo generateSubTreeFromClass per creare il TaxonomyTree da ritornare poi al client che lo ha chiamato. Per fare questo sono stati usati in particolare il metdo listHierarchyRootClasses() dell’OntModel che ritorna un iteratore su tutte le classi di primo livello della tassonomia (quelle sotto owl:thing per intenderci). Per ogni OntClass viene lanciato il metodo privato ricorsivo generateSubItemTreeFromClass(OntClass) che ritorna un TaxonomyTreeItem relativo con tutti i suoi figli generati ricorsivamente.
Non c’è molto altro da dire a riguardo se non il fatto che tutti gli iteratori sulle sottoclassi sono sempre filtrati in modo da scartare le classi anonime (come per esempio le restrizioni owl):
Iterator ite = ontClass.listSubClasses() .filterDrop( new Filter() { public boolean accept( OntClass r ) { return r.isAnon(); }} );
Prima di passare alla parte client resta da aggiungere il servizio appena creato nel web.xml dell’applicazione (maledetto plugin…questo potevi farlo anche tu però!). Di seguito riporto il mio file war/WEB-INF/web.xml per chiarezza.
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd"> ontologyServlet org.example.gwt.semantic.server .OntologyServiceImpl ontologyServlet /taxonomytree/ontoservice TaxonomyTreeTest.html
Un widget semantico: il TaxonomyTree
Ora possiamo creare il nostro widget semantico riutilizzabile. Per far questo useremo il metodo classico di customizzazione dei widget secondo GWT, cioè creeremo un Composite, ossia un widget composto appunto di altri widget con una sua logica interna specifica.
Prima di entrare nel merito del codice vorrei soffermarmi su alcuni punti di interesse riguardo la creazione di widget riutilizzabili:
- è pur sempre JavaScript: il widget è un componente grafico che verrà trasformato in JavaScript e come tale non può lavorare direttamente con le librerie esterne ma solo usando librerie GWT e classi serializzabili (vedi TaxonomyDTO);
- il widget NON dialoga col server GWT: il widget non dovrebbe in linea di massima accedere direttamente ad un apposito servizio GWT per evitare di vincolare il componente e renderlo il più riutilizzabile possibile (e far scalare meglio l’intera architettura);
- massima customizzabilità: il widget dovrebbe implementare solo ed esclusivamente la logica di visualizzazione base e lasciare la totale libertà all’applicazione di gestire gli handler degli eventi, i command e gli stili.
Bene, ci siamo dati un bel codice di autoregolamentazione in materia di creazione di widget custom, ora passiamo al nostro TaxonomyTree.
L’idea che sta alla base di un widget semantico è quella di poter disporre di un componente grafico che, date delle informazioni provenienti da una ontologia, sia in grado di gestirle e visualizzarle.
Nel nostro caso vogliamo creare un widget che:
- data una tassonomia OWL [OWL] in formato TaxonomyDTO sia in grado di generare un albero navigabile delle classi;
- ogni classe nell’albero sarà visualizzata preferibilmente tramite rdf:label se esiste, altrimenti tramite rdf:id;
- al passaggio del mouse su una classe dell’albero, se disponibile, dovrà essere mostrato come tooltip lo rdf:comment della classe a titolo descrittivo;
- il widget non gestirà il click del mouse sulle classi, lasciando la possibilità all’applicazione esterna di settare l’handler del click per poter applicare la sua logica applicativa.
Di seguito riporto il codice della classe TaxonomyTree:
public class TaxonomyTree extends Composite { private TaxonomyDTO taxonomyDTO = null; private Tree taxonomyTree = new Tree(); private ClickHandler taxonomyItemClickHandler = null; private MouseOverHandler taxonomyItemMouseOverHandler = null; public ClickHandler getTaxonomyItemClickHandler() { return taxonomyItemClickHandler; } public void setTaxonomyItemClickHandler(ClickHandler taxonomyItemClickHandler) { if (taxonomyItemClickHandler!=null) this.taxonomyItemClickHandler = taxonomyItemClickHandler; } public MouseOverHandler getTaxonomyItemMouseOverHandler() { return taxonomyItemMouseOverHandler; } public void setTaxonomyItemMouseOverHandler( MouseOverHandler taxonomyItemOnMouseOverHandler) { if (taxonomyItemClickHandler!=null) this.taxonomyItemMouseOverHandler = taxonomyItemOnMouseOverHandler; } private void init() { // Creo il pannello verticale che farà da contenitore VerticalPanel panel = new VerticalPanel(); panel.add(taxonomyTree); // Chiamo init.Widget() per inizializzare il composite initWidget(panel); // Assegno uno stile css al composite setStyleName("TaxonomyTreeStyle"); } public TaxonomyTree() { init(); } public TaxonomyTree(TaxonomyDTO taxonomyDTO) { init(); this.taxonomyDTO = taxonomyDTO; } public TaxonomyDTO getTaxonomyDTO() { return taxonomyDTO; } public void setTaxonomyDTO(TaxonomyDTO taxonomyDTO) { this.taxonomyDTO = taxonomyDTO; } public void generateTree() { if (taxonomyDTO!=null) { // Resetto l'albero taxonomyTree.clear(); TaxonomyItemDTO root = taxonomyDTO.getRoot(); // Lancio la generazione ricorsiva a partire dalla root TreeItem rootItem = generateSubTree(root); // Aggiungo l'item root all'albero taxonomyTree.addItem(rootItem); } } private TreeItem generateSubTree(TaxonomyItemDTO taxonomyItemDTO) { Label itemLabel = new Label(); // Se esiste una rdf:label uso quella altrimenti uso rdf:id if (taxonomyItemDTO.getLabel()!=null && !taxonomyItemDTO.getLabel().isEmpty()) itemLabel.setText( taxonomyItemDTO.getLabel()); else itemLabel.setText(taxonomyItemDTO.getId()); // Setto gli handler di click e mouse over passati dall'esterno. if (taxonomyItemClickHandler!=null) itemLabel.addClickHandler( taxonomyItemClickHandler); if (taxonomyItemMouseOverHandler!=null) itemLabel.addMouseOverHandler( taxonomyItemMouseOverHandler); itemLabel.setTitle(taxonomyItemDTO.getComment()); TreeItem treeItem = new TreeItem(itemLabel); Iterator ite = taxonomyItemDTO.getSubItems().iterator(); while (ite.hasNext()) { TaxonomyItemDTO taxonomyChildItemDTO = (TaxonomyItemDTO) ite.next(); treeItem.addItem(generateSubTree(taxonomyChildItemDTO)); } return treeItem; } public TreeItem getSelectedTaxonomyItem() { return taxonomyTree.getSelectedItem(); } }
In pratica il TaxonomyTree non è altro che un Composite che contiene un normalissimo GWT Tree capace di generarsi a partire dal TaxonomyDTO passato tramite costruttore (o settatoin un secondo momento). La generazione vera e propria avviene quando si lancia il metodo pubblico generateTree() il quale in base al TaxonomyDTO memorizzato pulisce il vecchio albero (se necessario) e ne genera ricorsivamente un altro. Ogni TreeItem del TaxonomyTree viene generato ricorsivamente dal metodo privato generateSubTree() che aggiunge all’item oltre che i figli anche una Label che visualizza rdf:id o rdf:label della classe, setta il tooltip con rdf:comment e infine associa, se non nulli, gli handler degli eventi Click e MouseOver passati dall’esterno.
Assembliamo il tutto nell’EntryPoint: TaxonomyTreeTest
Ora abbiamo tutto quello che ci serve per creare l’EntryPoint della nostra applicazione di test.
Di seguito riporto un sunto (per questioni di spazio) delle parti più importanti del codice della classe entryPoint TaxonomyTreeTest.java (attenzione: qua il “copia incolla” non è compilabile!!!):
public class TaxonomyTreeTest implements EntryPoint { private static final String SERVER_ERROR = "An error occurred while " + "attempting to contact the server. Please check your network " + "connection and try again."; /** * Create a remote service proxy to talk to the server-side Greeting service. */ private final OntologyServiceAsync ontologyService = GWT.create(OntologyService.class); public void onModuleLoad() { final TaxonomyTree taxonomyTree = new TaxonomyTree(); final Button loadButton = new Button("Load"); final Label stateMsg = new Label("Nessuna ontologia caricata"); final DialogBox dbox = new DialogBox(true); final ListBox urlListBox = new ListBox(); final Label selectOntologyLabel = new Label("Select wich ontology to load from the server: "); selectOntologyLabel.setStyleName("selectLabel"); dbox.setText("CLASS DETAILS"); dbox.setAnimationEnabled(true); // Carico la lista degli URL locali delle ontologie urlListBox.setTitle("List of ontologies on the servers..."); urlListBox.addItem("MokaByte", "file:C:/Users/Matteo/workspace- GWT/TaxonomyTreeTest/war/ontologies/MokaByte.owl"); final HorizontalPanel loadPanel = new HorizontalPanel(); final VerticalPanel mainPanel = new VerticalPanel(); loadPanel.add(urlListBox); loadPanel.add(loadButton); // We can add style names to widgets loadButton.getElement().setId("loadButton"); mainPanel.add(selectOntologyLabel); mainPanel.add(loadPanel); mainPanel.add(stateMsg); mainPanel.add(taxonomyTree); ... RootPanel.get("mainBox").add(mainPanel); /** * Gestore eventi da passare al TaxonomyTree */ class TaxonomyTreeItemHandler implements ClickHandler, MouseOverHandler { @Override public void onClick(ClickEvent event) { Label itemLabel = (Label)taxonomyTree.getSelectedTaxonomyItem().getWidget(); VerticalPanel vpanel = new VerticalPanel(); HTML nameLabel = new HTML("Class name: "+itemLabel.getText()); HTML descriptionLabel = new HTML("Class descritpion: "+itemLabel.getTitle()); Button closeButton = new Button("Close"); closeButton.getElement().setId("closeButton"); closeButton.addClickHandler(new ClickHandler() { public void onClick(ClickEvent event) { dbox.hide(); dbox.clear(); } }); vpanel.add(nameLabel); vpanel.add(descriptionLabel); vpanel.add(closeButton); dbox.add(vpanel); dbox.center(); } @Override public void onMouseOver(MouseOverEvent event) { // TODO Auto-generated method stub } } /** * Gestore eventi per loadButton, comboBox e altro */ class MyHandler implements ClickHandler { /** * Gestione Click sul loadButton */ public void onClick(ClickEvent event) { loadOntology(); } /** * Carica l'ontologia sul server */ private void loadOntology() { ontologyService.loadOntology(urlListBox.getValue(urlListBox.getSelectedIndex()), new AsyncCallback() { public void onFailure(Throwable caught) { RootPanel.get("mainBox").add(new HTML(caught.getMessage())); } public void onSuccess(String result) { stateMsg.setText("Ontologia caricata con successo. Ora carico la tassonomia..."); loadTaxonomyTree(); } }); } /** * Carica il TaxonomyTree con la tassonomia restituita dal server */ private void loadTaxonomyTree() { ontologyService.getCompleteTaxonomy( new AsyncCallback() { public void onFailure(Throwable caught) { stateMsg.setText(caught.getMessage()); } public void onSuccess(TaxonomyDTO taxonomyDTO) { taxonomyTree.setTaxonomyDTO(taxonomyDTO); taxonomyTree.generateTree(); stateMsg.setText("Tassonomia caricata con successo"); } }); } } // Setto gli hanlder TaxonomyTreeItemHandler itemHandler = new TaxonomyTreeItemHandler(); taxonomyTree.setTaxonomyItemClickHandler(itemHandler); taxonomyTree.setTaxonomyItemMouseOverHandler(itemHandler); MyHandler handler = new MyHandler(); loadButton.addClickHandler(handler); urlListBox.addChangeHandler(handler); } }
L’entrypoint al suo interno definisce un Vertical Panel principale (mainPanel) che contiene a sua volta:
- una label per la selezione dell’ontologia;
- un Horizontal Panel per gli elementi di loading della ontologia (combobox e loadButton);
- una Label di stato per indicare i vari stati dell’applicazione (Ontologia caricata, tassonomia caricata etc.);
- un TaxonomyTree inizialmente vuoto che verrà popolato al primo caricamento di una delle ontologie previste.
Infine l’entryPoint definisce anche un DialogBox (dbox) utilizzato per l’evento di click sugli item del TaxonomyTree. Il taxonomyTree viene infatti settato dall’entryPoint con un clickHandler che mostra il dialog box contenente nome e descrizione della classe selezionata:
class TaxonomyTreeItemHandler implements ClickHandler, MouseOverHandler { @Override public void onClick(ClickEvent event) { Label itemLabel = (Label)taxonomyTree. getSelectedTaxonomyItem().getWidget(); VerticalPanel vpanel = new VerticalPanel(); HTML nameLabel = new HTML("Class name: "+itemLabel.getText()); HTML descriptionLabel = new HTML("Class descritpion: "+itemLabel.getTitle()); Button closeButton = new Button("Close"); closeButton.getElement().setId("closeButton"); closeButton.addClickHandler(new ClickHandler() { public void onClick(ClickEvent event) { dbox.hide(); dbox.clear(); } }); vpanel.add(nameLabel); vpanel.add(descriptionLabel); vpanel.add(closeButton); dbox.add(vpanel); dbox.center(); }
L’handler viene settato per mezzo dell’apposito setter definito nel TaxonomyTree:
... TaxonomyTreeItemHandler itemHandler = new TaxonomyTreeItemHandler(); taxonomyTree.setTaxonomyItemClickHandler(itemHandler); ...
Infine è stato definito un semplice CSS che per lo scopo di questo articolo non viene mostrato ma che è incluso nel progetto. Le ontologie invece per semplicità vengono caricate localmente come si può vedere dagli url predefiniti nella combobox e quindi sono state collocate nella cartella /war/ontologies/ del progetto.
Conclusione
GWT si dimostra un’ottima scelta per creare interfacce di navigazione semantiche. In particolare la possibilità di definire dei widget che implementino una logica di interfaccia specifica per il rendering di informazioni semantiche complesse e strutturate permette di creare sistemi di navigazione avanzati e intuitivi per l’utente finale, il quale, come ho già sostenuto nel primo articolo, non dovrebbe sapere se sta consultando una ontologia piuttosto che un DB o un file XML… ma dovrebbe solamente percepire i benefici e vantaggi da un tipo di navigazione delle informazioni più intelligente e coerente.
L’esempio del TaxonomyTree è un modo molto semplice per mostrare come i due aspetti, Semantic Web e interfaccie AJAX, possano al giorno d’oggi essere facilmente combinate per creare componenti grafici avanzati. Lo stesso taxonomyTree potrebbe essere ulteriormente migliorato introducendo le istanze ed eventualmente la possibilità di seguire percorsi diversi rispetto a quello gerarchico, per esempio rispetto alle proprietà. Si potrebbe inoltre arricchire il TaxonomyTree con un box di ricerca intelligente che permetta di accedere rapidamente alle informazioni contenute nell’ontologia. E tutto questo ovviamente varrebbe per qualsiasi ontologia OWL accessibile sul web. Parte di questo lavoro ho già avuto modo di sperimentarla positivamente come ricercatore presso l’ENEA sul progetto OntologyExplorer [OEXP] di cui ho già parlato nel primo articolo della serie [MOKA1].
In conclusione di questa serie vorrei far passare principalmente il concetto che tanto è stato fatto fino ad ora per standardizzare ontologie e fornire tecnologie in grado di maneggiarle ma molto resta da fare per rendere facilmente accessibili le informazioni modellate al loro interno. Gli strumenti ci sono, i tempi sono maturi… e GWT è un ottimo mezzo! Per cui… buon lavoro!
Riferimenti
[MOKA1] Matteo Busanelli, “Semantic Web. Esplorazione/visualizzazione di ontologie – I parte: Introduzione e analisi dei tool attuali”, MokaByte 142, luglio/agosto 2009 (nella stessa serie)
[MOKA2] Giovanni Puliti, “Il programmatore e le sue API – XX parte: realizzare la GUI web con GWT, MokaByte 146, dicembre 2009
[MOKA3] Ivan Diana, “Google Web Toolkit – I parte: Introduzione al framework GWT”, MokaByte 139, aprile 2009
[GECLIPSE] Google Eclipse Plugin
http://code.google.com/intl/it-IT/eclipse/
[JENA] A Semantic Web Framework for Java
[OWL] OWL Web Ontology Language Guide
http://www.w3.org/TR/owl-guide/
[OEXP] Ontology Explorer Project
http://www.cross-lab.it/cross-lab/imple/pgcl.asp?p=247&CMS=moda-ml-cms&lingua=en
Nato a Imola nel 1978, ha conseguito la laurea Specialistica in informatica nel 2005 presso l‘Università di Bologna con una tesi sull‘"Estrazione di Ontologie da Framework standardizzati EDI/XML". Per tre anni ha lavorato come ricercatore presso il centro ENEA di Bologna seguendo progetti sull‘applicazione di tecnologie semantiche a framework e standard per l‘interoperabilità come ebXML o UBL, pubblicando insieme ad altri ricercatori diversi articoli su tali argomenti.
Attualmente è consulente presso Imola Informatica S.r.l. dove si occupa di piattaforme Java EE based e progetti sul Semantic Web, e i suoi interessi principali si orientano alle piattaforme enterprise basate su middleware semantici, Ontology Engeenering e alle interfacce basate su AJAX e GWT.