Tesi in Informatica dell`Università di Trento

Transcript

Tesi in Informatica dell`Università di Trento
Facoltà di Scienze Matematiche Fisiche e Naturali
Corso di Laurea in Informatica
APPROXIMATE SEARCH:
PROBLEMATICHE DELLE
RICERCHE DI DATI NON
ESATTI IN DATABASES DI
GROSSE DIMENSIONI
Candidato:
Relatore:
Prof.
Correlatori:
Ing.
Anno Accademico 2009/2010
.
Indice
Abstract
1
Introduzione
2
1 Tecnologie Utilizzate
5
1
Ricerca Full-text . . . . . . . . . . . . . . . . . . . . . . . . .
5
2
Metodi di Ricerca . . . . . . . . . . . . . . . . . . . . . . . . .
6
2.1
Wildcard Searches . . . . . . . . . . . . . . . . . . . .
6
2.2
Fuzzy Searches . . . . . . . . . . . . . . . . . . . . . .
7
Architettura . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7
3
2 Web Service
1
2
Introduzione ai Sistemi Distribuiti . . . . . . . . . . . . . . . .
8
1.1
RPC . . . . . . . . . . . . . . . . . . . . . . . . . . . .
9
1.2
DCOM . . . . . . . . . . . . . . . . . . . . . . . . . . .
9
1.3
Java RMI . . . . . . . . . . . . . . . . . . . . . . . . .
9
1.4
CORBA . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Web Service . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.1
XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.2
SOAP . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.3
WSDL . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.4
UDDI . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.5
BPEL4WS (BPEL) . . . . . . . . . . . . . . . . . . . . 14
3 Hibernate
1
8
16
Lo Strato di Persistenza . . . . . . . . . . . . . . . . . . . . . 16
1.1
ORM – Object/Relation Mapping . . . . . . . . . . . . 18
Indice
iii
2
POJO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
3
Hibernate JPA ed EJB . . . . . . . . . . . . . . . . . . . . . . 19
4
Hibernate Entity Manager e Jpa Persistence . . . . . . . . . . 20
4.1
Ciclo di Vita di un Entity . . . . . . . . . . . . . . . . 20
4.2
Cos’è il Persistance Context e l’Entity Manager . . . . 20
L’Entity Manager . . . . . . . . . . . . . . . . . . . . . 21
Tipi di Persistance Context . . . . . . . . . . . . . . . 22
Persistence Provider . . . . . . . . . . . . . . . . . . . 22
Persistance Unit . . . . . . . . . . . . . . . . . . . . . . 22
5
Hibernate Annotations . . . . . . . . . . . . . . . . . . . . . . 22
6
Hibernate Search . . . . . . . . . . . . . . . . . . . . . . . . . 23
4 Apache Lucene
24
1
Indice di Lucene . . . . . . . . . . . . . . . . . . . . . . . . . . 24
2
Full-Text . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
3
DBMS vs Full-Text . . . . . . . . . . . . . . . . . . . . . . . . 27
4
Hibernate + Lucene . . . . . . . . . . . . . . . . . . . . . . . 28
5 Il Motore di Ricerca
30
1
it.enginsoft.search engine.ws . . . . . . . . . . . . . . . . . . . 31
2
it.enginsoft.search engine.appl . . . . . . . . . . . . . . . . . . 32
3
it.enginsoft.search engine.file prop . . . . . . . . . . . . . . . . 33
4
it.enginsoft.search engine.bean . . . . . . . . . . . . . . . . . . 34
4.1
Azienda - Citta - Contatto . . . . . . . . . . . . . . . . 36
Hibernate-Annotation . . . . . . . . . . . . . . . 36
Lucene-Annotation . . . . . . . . . . . . . . . . 37
5
4.2
Result Azienda - Result Citta - Result Contatto . . . . 37
4.3
FileProperties . . . . . . . . . . . . . . . . . . . . . . . 38
4.4
Exception . . . . . . . . . . . . . . . . . . . . . . . . . 38
it.enginsoft.search engine.jpaController . . . . . . . . . . . . . 38
5.1
Il Costruttore . . . . . . . . . . . . . . . . . . . . . . . 39
5.2
public int indexAzienda() . . . . . . . . . . . . . . . . 40
5.3
public Result Azienda searchAzienda(String searchNome,
String searchDivisione) . . . . . . . . . . . . . . . . . . 40
Indice
iv
5.4
protected void searchAziendaLucene(String searchNome,
String searchDivisione, Result Azienda ra) . . . . . . . 41
6 Utilizzo
44
1
Client . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
2
Ricerca . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
2.1
Ricerca di Dati Esatti . . . . . . . . . . . . . . . . . . 47
2.2
Ricerca di Dati Non Esatti . . . . . . . . . . . . . . . . 49
2.3
Ricerca Partendo da Dati Non Esatti . . . . . . . . . . 51
Conclusioni
54
Bibliografia
56
Sitografia
58
Abstract
Ciascun individuo esegue una data ricerca in modo diverso, in base alle proprie esigenze e all’obiettivo che intende perseguire mediante tale ricerca; da
qui l’occorrenza di creare sistemi idonei a cogliere una tale diversità di esigenze e di profili di pubblico.
É necessario garantire un accesso all’informazione secondo strade diverse
sfruttando con il concetto di multidimensionalità.
Nello sviluppo di applicazioni business, sussistono alcune funzionalità sufficientemente complesse e ben definite da poter essere “astratte” dal contesto
specifico, generalizzate in componenti e riutilizzate in più progetti. Un esempio significativo è un motore di ricerca: la necessità di effettuare una ricerca
sul contenuto di alcuni file di testo, con maggior sofisticazione rispetto alle
normali ricerche sql, è un problema comune che ben si presenta alla scrittura
di una soluzione generalizzata.
Introduzione
La Rivoluzione Informatica ha creato l’illusione che si potesse improvvisamente risolvere il problema dell’organizzazione e della gestione dell’informazione per favorirne la reperibilità.
Oggi si è giunti alla consapevolezza che la tecnologia è sicuramente un supporto per la gestione della conoscenza, ma ciò non elimina il problema dell’organizzazione e della ricerca dei suoi contenuti.
Il problema della ricerca
“Il motore di ricerca perfetto dovrebbe capire esattamente la richiesta dell’utente e
restituire esattamente ciò che egli desidera”1
Nella letteratura informatica con “problema della ricerca” si rappresentano
differenti problematiche.
La problematica più diffusa è quella riguardante la ricerca di informazioni
su Internet e sul web. In questo caso si parla di problema della ricerca per
definire la difficoltà di raggiungere tutte le informazioni presenti in Internet
dal web. Per questo motivo si è diviso il web in 2 livelli:
1. surface web
2. deep web
Il surface web è definito come quella parte delle risorse informative che generalmente viene raggiunta navigando in Internet o utilizzando i motori di
ricerca, e che contiene la parte meno consistente di dati.
Al contrario il deep web è il livello più interno dell’informazione reperibile in
1
Larry Page, cofondatore di Google
3
Internet o tramite il World Wide Web (database, banche dati, file interni dei
server, ...).
In questa tesi il “problema della ricerca” viene inteso come la difficoltà di
raggiungere tutte le informazioni presenti in un database aziendale a causa
di dati non esatti.
Il mondo del web è totalmente diverso dal mondo business: se nel primo ci
si può accontentare delle informazioni ottenute solamente dal surface web,
in quanto la mole di dati solitamente è più che sufficiente per permettere
l’eleborazione di qualsivoglia ricerca, nel secondo caso è necessario acquisire
tutti i dati per i quali si è eseguita la ricerca.
Le principali motivazioni che rendono più difficile questo processo di ricerca
sono:
• errori di standardizzazione;
• errori di scrittura.
Gli errori di standardizzazione sono normalmente più facili da correggere.
Questo tipo di problema è dovuto all’errato inserimento dei dati nei campi.
Un’esempio di facile comprensione è l’inserimento di un indirizzo aziendale.
Se il form prevede un campo per la via e un altro campo per il numero civico,
non è detto che l’utente inserisca esattamente i dati. Spesso succede che il
record contenga per due volte il numero civico (Via: via roma, 11 - Numero:
11). Fortunatamente le normali ricerche (utilizzando Wildcard Searches) riescono a gestire l’errore, andando a sostiture l’eventuale parametro via roma
con via roma%.
Gli errori di scrittura, invece, rendono molto complicata l’esecuzione delle
ricerche. In questa fase il motore di ricerca esamina tutti i record determinando la corrispondenza fra il parametro cercato e quello presente nei dati
del database. Se è presente un errore di ortografia questa corrispondenza
salta, producendo quindi un match fallito. Con le normali tecniche di ricerca
è impossibile trovare l’errore.
Il problema della ricerca: un caso specifico
Con l’aggiornamento da parte di Poste Italiane della banca dati dei Codici di
Avviamento Postale è stato necessario adeguare anche il database aziendale
4
di EnginSoft Spa, andando a sviluppare un programma che per ogni record
presente nel database, ne ricavasse l’indirizzo, e facesse matchare il medesimo
indirizzo con la banca dati dei Codici di Avviamento Postale.
Eseguendo il programma implementato esclusivamente con un metodo di
ricerca basato esclusivamente sull’esatta corrispondenza fra i termini si è notato che il numero di match falliti era eccessivo. Si è allora cercato di aiutare
il match togliendo le parti di indirizzo che potevano creare errori (denominazione urbanistica generica, eventuali numeri, segni di interpunzione,..), ed
utilizzando una ricerca di tipo wildcard. Con gli accorgimenti approntati
nel programma sono notevolmente diminuti i match falliti, ma nonostante
questo, la percentuale di match positivi era troppo basso.
Per poter ottimizzare la ricerca si è andati a studiare i principali motivi
che portavano al fallimento dei match. Analizzando a fondo la lista dei
parametri definiti errati si è notato che gran parte dei record presenti nella
lista conteneva due tipologie d’errore:
• errori ortografici;
• errori di indirizzo (es. scrivere via A. De Gasperi o via De Gasperi al
posto di via Alcide de Gasperi ).
L’obiettivo di questa tesi è far fronte a questo tipo di problematiche.
Per fare questo è stato implementato un motore di ricerca mediante il quale
si garantisce un miglior accesso alle informazioni, andando a risolvere le
problematiche inerenti alla ricerca di dati non esatti.
Capitolo 1
Tecnologie Utilizzate
Le tecnologie che sono state utilizzate per lo sviluppo di questo progetto
sono il frutto di un’attenta analisi che aveva come scopo l’introduzione nel
progetto delle seguenti caratteristiche:
• un motore di ricerca full-text;
• Wildcard Searches;
• Fuzzy Searches;
• un’applicazione non monilitica e fruibile solamente da un singolo computer, ma che si avvicinasse all’architettura client/server, cercando
però di evitare problemi di compatibilità fra architetture/sistemi operativi/linguaggi di programmazione diversi;
Inoltre sono stati definiti i sequenti requisiti:
• l’applicazione dovrà essere sviluppata utilizzando software e librerie
open-source ed utilizzando il linguaggio Java;
• il tutto dovrà essere facilmente mantenibile ed adattabile alle esigenze
dell’azienda;
• l’applicazione dovrà comunicare con un database PostgreSql tramite il
framework Hibernate/Jpa;
1
Ricerca Full-text
La ricerca full-text possiede numerosi vantaggi: per prima cosa è migliore a
livello di prestazioni (consente ricerche più veloci), inoltre consente ricerche
1.2 Metodi di Ricerca
6
molto precise ordinando anche i risultati in base al grado di attinenza con
la ricerca, in altre parole “pesa i risultati”. Per comprendere meglio l’utilità
della ricerca full-text proviamo a pensare a Google. Quando si effettua una
ricerca con Google i risultati vengono mostrati in base al grado di attinenza
con la parola cercata. Questo è proprio quello che la ricerca full-text permette
di fare.
La ricerca full-text si basa su indici, il che significa che viene svolta in due
fasi: innanzitutto viene ricercato il termine nell’indice e poi viene eseguita la
ricerca vera e propria. Per chiarire questo concetto si provi a pensare di dover
cercare in uno stradario di una qualche città “via Milano 54”. Una ricerca
condotta in modo classico inizierebbe a sfogliare tutte le carte dello stradario
analizzandole fino a trovare la via desiderata. Una ricerca full-text invece
per prima cosa va a consultare l’indice. Qui trova la via desiderata corredata
dalle informazioni per localizzarla sulle carte. Con tali informazioni estrae
poi la carta esatta. Si capisce bene che in questo modo la ricerca è molto
più veloce ed efficiente. É stato verificato che su un database di 100 MB una
ricerca di tipo full-text si dimostra circa 8/10 volte più veloce di una ricerca
libero (con “LIKE”).
2
Metodi di Ricerca
Il principale scopo del prototipo che è stato sviluppato per questa tesi è
consentire la ricerca di dati non esatti all’interno di databases. Per poter
implementare al meglio questa feature si è resa necessaria un’attenta analisi
sui metodi di ricerca da utilizzare.
2.1
Wildcard Searches
Questo tipo di ricerca, chiamato anche ricerca con caratteri jolly, permette
all’utente di inserire dei metacaratteri al posto di alcune lettere nelle parole
da ricercare.
I metacaratteri utilizzati sono:
• ?→ sostituisce un singolo carattere nel termine di ricerca;
• *→ viene utilizzato per trovare termini diversi con la stessa radice (ed
eventualmente anche stessa terminazione).
1.2.2 Fuzzy Searches
7
Per poter utilizzare i metacaratteri è necessario sostituire l’operatore = nel
where della query con l’operatore like.
2.2
Fuzzy Searches
La Distanza di Levenshtein è la distanza tra due stringhe S1 ed S2, dove per
distanza intendiamo il numero minimo di operazioni elementari che occorre
fare per trasformare la stringa S1 nella stringa S2.
Queste operazioni elementari includono:
• cancellazione di un carattere;
• sostituzione di un carattere con un altro;
• inserimento di un carattere.
L’algoritmo di Levenshtein consente di calcolare la distanza tra una stringa
di lunghezza m e una stringa di lunghezza n in tempo O(nm).
Il metodo di ricerca sfocata è basato sulla distanza di Levenshtein, permettendo quindi una ricerca di termini similari, rispetto a quelli inseriti nella
query di ricerca.
3
Architettura
Uno dei requisiti minimi della creazione del prototipo era quello di avere
un’applicazione facilmente mantenibile ed adattabile alle esigenze dell’azienda. Questo ha portato alla ricerca di tecnologie che permettessero di sviluppare il motore di ricerca senza renderlo monolitico. La volontà è stata quella
di utilizzare un’architettura del tipo clien/server, con la specifica di non dover
avere problemi interfacciamento con client architetturalmente diversi.
Per questo motivo si sono analizzati a fondo i sistemi distributi, cercando
quello più soddisfacente alle nostre esigenze. La scelta definitiva è stata il
framework dei Web Services, in quanto la definizione stessa di Web Services
come interfacce standardizzate per lo scambio di messaggi tra entità software
indipendenti collimava con i nostri requisiti.
Capitolo 2
Web Service
1
Introduzione ai Sistemi Distribuiti
Il framework dei Web Services è rivolto allo sviluppo di applicazioni orientate
ai servizi, emerso in risposta al bisogno di uno standard per le comunicazioni
tra varie entità computazionali. Per capire meglio come siano nati è necessario
capire da dove derivano: i sistemi distribuiti.
Ecco due definizioni di sistema distribuito:
• “Un sistema distribuito consiste di un insieme di calcolatori indipendenti che appaiono all’utente del sistema come un singolo calcolatore”1
• “Un sistema distribuito è formato da un insieme di entità indipendenti
che cooperano per raggiungere un fine comune e tale cooperazione si
effettua attraverso scambio di messaggi”2
Molti sono i vantaggi derivanti dall’adozione di tale modello: un sensibile
miglioramento delle prestazioni complessive, una maggiore semplicità nella
gestione delle risorse distribuite ed un incremento delle potenzialità operative.
Ad esempio si puó pensare di suddividere un processo computazionale pesante in sottoprocessi più piccoli ed eseguire tali parti di applicazioni su macchine diverse, ottenendo una drastica diminuzione del tempo di esecuzione.
Ecco alcuni esempi di infrastrutture per la comunicazione tra applicazioni
distribuite:
1
2
Tanenbaum Andrew S. - Reti di Calcolatori - Prentice Hall - 1998
Baldoni, Marchetti, Tucci - Appunti Integratici sui Sistemi Distribuiti
2.1.1 RPC
1.1
9
RPC
Remote Procedure Call è un protocollo di comunicazione che permette ad
un’applicazione di essere distribuita su macchine diverse. È un’infrastuttura client-server con le seguenti caratteristiche: interoperabilità, flessibilità
e portabilità. Questo modello è simile alla chiamata locale di procedure. Nel
caso locale, il chiamante inserisce gli argomenti della chiamata in una particolare locazione e invoca la procedura, per poi riprendere il controllo dallo stesso
punto una volta che la chiamata è terminata. Nel caso remoto, il chiamante
manda un messaggio di richiesta al server e rimane in attesa di un messaggio
di risposta. Il messaggio di chiamata contiene i parametri della procedura
mentre il messaggio di risposta contiene il risultato. Una volta ricevuta la
risposta il risultato della procedura viene estratto e il chiamante prosegue
la sua esecuzione. Lato server un processo rimane in attesa dell’arrivo del
messaggio di chiamata. Quando accade estrae i parametri di procedura, calcola il risultato, lo inserisce in un messaggio di risposta, che viene spedito al
mittente, e torna nuovamente in attesa.
1.2
DCOM
Distribuited Component Object Model è una tecnologia Microsoft per il supporto di applicazioni composte da oggetti distribuiti. DCOM è un’estensione
di COM, il quale definisce come i componenti e i loro client interagiscono fra
loro. Inoltre COM pemette, tramite opportuni componenti, la comunicazione
fra processi residenti sulla stessa macchina. Quello che fa DCOM è sostitutire
questi componenti con un protocollo in modo da permettere che questa comunicazione possa avvenire su macchine diverse in qualsiasi punto della rete.
Questo protocollo è indipendente dal livello di trasporto e anche dal linguaggio utilizzato. Viene utilizzato un linguaggio intermedio DCOM IDL tramite
il quale poi si generano le componenti per la gestione della comunicazione.
1.3
Java RMI
Remote Method Invocation è un framework che fornisce un livello di rete intermedio che permette ad oggetti Java, che risiedono su differenti macchine
2.1.4 CORBA
10
virtuali, di comunicare utilizzando le normali invocazioni dei metodi. Questa tecnologia fornisce perciò la possibilità di sviluppare applicazioni di rete
astraendo dalle problematiche di comunicazione. Si ha quindi la possibilità
di sviluppare programmi Java distribuiti con la stessa sintassi e semantica
usata per i programmi non distribuiti. Il principio su cui si basa l’architettura RMI è la possibilità di separare la definizione del comportamento dalla
sua implementazione attraverso l’uso di interfacce. RMI fa in modo che la
definizione e l’implementazione possano essere su due JVM differenti.
1.4
CORBA
La Common Object Request Broker Architecture, concepita dal consorzio
Object Managment Group, nasce come standard per lo sviluppo e l’interrogazione di oggetti distribuiti in grado di interoperare tra loro offrendo una
totale trasparenza sulla dislocazione e sul linguaggio d’implementazione degli
stessi. Il cuore dell’architettura è nell’Object Request Broker, il quale ha
il compito di trasmettere le richieste tra oggetti remoti e di restituirne le
risposte. Il client quindi non deve curarsi nè della reale locazione dell’oggetto remoto nè del linguaggio con cui è stato implementato, ma solamente del
riferimento e dell’interfaccia che esso implementa. L’Interface Definition Language è il linguaggio utilizzato dallo standard CORBA per definire l’interfaccia implementata da un oggetto remoto. Prevede le dichiarazioni di moduli,
interfacce, attributi, operazioni ed eccezioni, i quali vengono tradotti in strutture equivalenti a seconda del linguaggio utilizzato per l’implementazione.
2
Web Service
“I servizi sono elementi computazionali autonomi ed indipendenti dalla piattaforma di esecuzione che possono essere descritti, pubblicati, scoperti, orchestrati e programmati usando XML al fine di sviluppare massivamente applicazioni distribuite
interoperabili”3
3
M. Papazooglou et al. SOC: Service Oriented Computing manifesto, 2003.
URL: http://www.eu-soc.net
2.2.1 XML
11
I Web Services sono delle interfacce standardizzate per lo scambio di messaggi tra entità software indipendenti. Derivano dal continuo evolversi della rete
verso uno standard unico. Il punto di forza del modello dei Web Services è
di utilizzare un set base di protocolli disponibili ovunque, permettendo l’interoperabilità tra piattaforme diverse e mantenendo comunque la possibilità
di utilizzare i protocolli più avanzati e specializzati per effettuare compiti
specifici. Tipicamente questo set base di protocolli viene rappresentato come
una pila (W.S. Stack) di livelli che interagiscono fra loro.
Figura 2.1: Separazione logica verticale
2.1
XML
eXtensible Markup Language è un linguaggio di markup creato dal consorzio
W3C che permette di annotare dati o testo mediante tag. XML permette di
separare il significato dalla rappresentazione. Rispetto all’HTML, l’XML ha
uno scopo ben diverso: se da una parte il primo definisce una grammatica per
la descrizione e la formattazione di pagine web e, più in generale, di ipertesti,
il secondo è un metalinguaggio utilizzato per creare nuovi linguaggi, atti a
descrivere documenti strutturati. Mentre l’HTML ha un insieme ben definito
e ristretto di tag, con l’XML è invece possibile definirne di propri a seconda
2.2.2 SOAP
12
delle esigenze. In questo modo, oltre a potersi creare il proprio documento
strutturato, è possibile svolgere accurate ricerche all’interno dello stesso, ed
utilizzarlo come mezzo di interscambio di dati fra entità che adottano diversi
standard di rappresentazione dei dati.
2.2
SOAP
Simple Object Acces Protocol è un protocollo molto semplice che indica
un modo standard in cui scambiare le informazioni tra oggetti remoti. Esso
infatti definisce solamente il modo in cui i messaggi vanno codificati tramite
XML. Un messaggio SOAP è cosı̀ suddiviso:
1. Envelope per descriverne il contenuto e come deve essere elaborato
2. Header per definire la codifica dei tipi di dato
3. Body contiene le informazioni scambiate dalle richieste/risposte
SOAP può operare su differenti protocolli di rete; solitamente il più usato è
Http che, grazie alla sua vasta diffusione è adatto allo sviluppo di Web Services. Questa interoperabilità è dovuta alla semplicità, alla robustezza e specialmente alla possibilità di viaggiare sulla rete evitando problematiche legate
ai firewalls (come invece può accadere utilizzando protocolli come CORBA
o RMI).
Un messaggio SOAP è “human-readable” ossia non è in formato binario, e
quindi è comprensibile. Questa peculiarità avvantaggia lo sviluppatore che
può direttamente creare/editare/spedire messaggi SOAP ad un client.
2.3
WSDL
Web Service Description Languages è un linguaggio che descrive le interfacce dei Web Service. Un documento WSDL consiste in un file XML che
descrive i servizi di rete attraverso le loro operazioni ed i loro messaggi. Si
può paragonare all’IDL di CORBA. Sapere che i Web Services dialogano in
un linguaggio comune come SOAP non è sufficiente; ci deve essere un livello
superiore che indica come questi messaggi vanno costruiti, e questo è compito
di WSDL. Per fare questo, WSDL definisce un formato XML che permette
di rappresentare in modo astratto la descrizione di un servizio trascurando
2.2.4 UDDI
13
i dettagli concreti del servizio, il come e il dove questa funzionalità viene
gestita. Quasi sempre è collegato con SOAP, ma non è strettamente necessario utilizzare questo standard, infatti WSDL definisce una grammatica per
servizi di rete generici e non necessariamente legati allo stack dei protocolli
Web Service. Un documento WSDL definisce un servizio come una collezione
di ports, descrive i messaggi che sono delle rappresentazioni astratte dei dati
scambiati, e i port-types che sono collezioni astratte di operazioni, e devono
necessariamente essere collegate ad un servizio fisico.
2.4
UDDI
Universal Description, Discovery and Integration è una tecnologia che permette la pubblicazione e l’individuazione di un servizio nella rete, ed è frutto
di un gruppo (http://www.uddi.org) di circa 300 aziende (fra cui IBM, Microsoft e Sun). Utilizzando un registro fornisce un meccanismo per catalogare
servizi web basati su SOAP e WSDL.
Sostanzialmente sono due le categorie di utenti che accedono ad un registro
UDDI:
• le aziende che vogliono pubblicare il loro servizio
• gli utenti che cercano un particolare Web Service
Figura 2.2: Funzionamento di un registro UDDI
Il modello di funzionamento di UDDI, mostrato in figura 2.2, può essere
diviso in tre categorie:
2.2.5 BPEL4WS (BPEL)
14
• Publishing: descrizione del servizio web da parte dell’azienda fornitrice,
ad esempio un documento WSDL. Inoltre vanno fornite le caratteristiche della società e la categoria del servizio, ovvero oltre alla sintassi
WSDL bisogna fornire una descrizione del servizio inserito.
• Finding: si occupa della risposta a richieste di ricerca, individuando e
restituendo le informazioni riguardanti il servizio che soddisfa la ricerca.
• Binding: si occupa del collegamento al servizio una volta effettuata una
ricerca.
Sono stati definiti quattro elementi di base per la descrizione in un registro
UDDI:
• businessEntity: modelizzazione di informazioni di business
• businessService: descrizione di alto livello del servizio
• tModel: modellizzazione di un tipo di tecnologia o servizio
• bindingTemplate: creazione di un mapping tra businessService e tModel
Grazie a questi componenti UDDI rappresenta per il web 3 categorie di
informazioni:
• White Pages, dove vengono specificati i businessEntity che comprende
il nome, la ragione sociale, l’indirizzo, il numero di telefono e tutte
le informazioni di base che denotano l’organizzazione del fornitore del
servizio.
• Yellow Pages, dove le informazioni relative alle società e ai servizi vengono catalogate in base alla categoria di appartenenza inserendole in
oggetti chiamati businessService.
• Green Pages, riporta le informazioni tecniche sui Web Services attraverso il tModel che comprende le specifiche (nome, metodi, parametri di
invocazione) di un Web Service e il bindingTemplate che indica l’URL
dove è in opera il servizio
2.5
BPEL4WS (BPEL)
Business Process Execution Language for Web Service è un linguaggio di
flusso per la specifica formale di processi e protocolli di interazione tra Web
Service. Per fare questo, estende il modello di interazione dei Web Service
2.2.5 BPEL4WS (BPEL)
15
supportando transazioni. BPEL definisce un modello di integrazione interoperabile per facilitare l’espansione e l’integrazione di processi automatici. É un
linguaggio basato sullo standard XML che definisce le modalità di interazione
tra processi. BPEL offre la possibilità di descrivere il comportamento di processi, anche complessi, che interagiscono con un insieme di servizi, definendo
come le interazioni tra essi siano coordinate per raggiungere un determinato
obiettivo, cosı̀ come lo stato e la logica necessari per questo coordinamento.
BPEL4WS fornisce inoltre un meccanismo aggiuntivo per gestire eccezioni
ed errori. BPEL4WS utilizza molte specifiche fra cui appunto WSDL, ma
anche XML Schema, XPath; WSDL è utilizzato per rappresentare i messaggi
ai singoli servizi, XML Schema per le definizioni dei tipi di dato, XPath per
la manipolazione dei dati utilizzati nel business process.
Capitolo 3
Hibernate
Hibernate (spesso chiamato anche H8) è una piattaforma middleware open
source per lo sviluppo di applicazioni Java che gestisce la rappresentazione e
il mantenimento su database relazionale di un sistema di oggetti Java. É lo
standard de facto nel campo dei framework open-source di ORM.
1
Lo Strato di Persistenza
Fra tutte le applicazioni sviluppate al giorno d’oggi è raro trovarne di quelle
che non utilizzano alcun sistema di salvataggio dei propri dati. Ogni applicazione utilizza tecniche differenti per gestire e mantenere le proprie informazioni; dal semplice salvataggio dei dati su file, fino ad arrivare a complessi data base managment system. Durante lo sviluppo di un sistema che si
appoggia ad un database, ci si può imbattere in due diversi scenari:
1. il database esiste già, e quindi si va ad utilizzare un reverse engineering
per cercare di identificare un modello di dati a partire dalle tabelli già
esistenti.
2. è necessario progettare il database a partire da zero, andando ad identificare entità e relazioni per poi creare la struttura del database.
In entrambi i casi si parte dal modo in cui sono definiti i dati per poter
costruire sopra l’applicazione. Questa metodologia di sviluppo si rivela decisamente svantaggioso nel momento in cui si utilizzano sofistificate metodologie
3.1 Lo Strato di Persistenza
17
di sviluppo. Infatti si vincolano le entità del modello dati alla struttura relazionale tipica dei tradizionali database. Il modello relazionale è molto diverso dal modello ad oggetti usato all’interno dell’applicazione; per questo
motivo si necessità di introdurre uno strato di logica aggiuntivo che permetta
la trasformazione di un modello nell’altro, detta Object/Relation Mapping
(ORM). Naturalmente esistono diversi ORM che agevolano lo sviluppo di
applicazioni con dati persistenti, ma negli ultimi anni ha preso prepotentemente piede Hibernate.
Questo potente strumento non solo semplifica l’interfacciamento fra applicazioni ad oggetti e database relazionale, evitando di mappare manualmente
le entità, ma si occupa anche del salvataggio e del caricamento degli oggetti.
Le applicazioni Hibernate risultano più piccole, più portabili e più semplici da modificare, oltre ad avere delle migliori prestazioni. Hibernate si può
quindi definire come uno strato middleware che consente allo sviluppatore di
automatizzare le procedure per le operazioni CRUID (Create, Read, Update,
Delete) del database. Infatti, durante lo sviluppo di applicazioni web, questo
tipo di operazioni sono tra quelle che richiedono più tempo per la codifica,
il test e il debug. A renderle più complesse è sicuramente l’etereogenità tra
il linguaggio di programmazione utilizzato per la logica applicativa, come ad
esempio Java, e quello utilizzato per interrogare le basi di dati relazioneli,
quindi SQL e principalmente dialetti derivanti da esso. Con la nascita dei
JavaBean, utilizzati sopratutto negli ambienti web, è diventato necessario
avere uno strato di programmazione che si interpone fra le pagine JSP e
questi contenitori di informazioni residenti su base di dati. Hibernate nasce
con questo scopo e si evolve fino a diventare la più robusta piattaforma middleware per la persistenza attualmente presente. Ad incentivarne la diffusione
è sicuramente la licenza open source sotto la quale è registrato. L’idea alla
abse di Hibernate è quella di configurare l’interno del middleware tramite
descrittori testuali e xml, nei quali si vanno ad indicare le associazioni tra le
classi dei JavaBean e le tabelle in cui risiedono i dati all’interno del database.
Tutto il lavoro di recupero delle informazioni e di creazioni delle query avviene
in maniera quasi automatica.
3.1.1 ORM – Object/Relation Mapping
1.1
18
ORM – Object/Relation Mapping
La differenza fra modello ad oggetti e modello relazionale è chiamata “paradigm
mismatch”, ovvero “mancata corrispondenza”, e necessita di un intervento
di adattamento da parte del programmatore. In aiuto a quest’ultimo sono
stati sviluppati una serie di tools ORM che consentono di rendere persistenti
oggetti in tabelle relazionali in maniera automatica e trasparente, lasciando all’interno del codice dello sviluppatore solo l’entità del suo modello ad
oggetti. Tutto il compito di gestire le problematiche di adattamento tra i due
paradigmi è quindi svolto da questi tools ORM, mentre il programmatore
deve solamente fornire i giusti metadati all’ORM per realizzare il mapping
tra i due modelli.
Oltre a fornire tutte le funzionalità di un ORM completo ed affidabile Hibernate fornisce una serie di servizi che vanno ben oltre all’obbiettivo di base di
creare un mapping fra oggetti Java e tabelle della base dati.
ORM naturalmente non è l’unica tecnologia disponibile; per anni si sono utilizzate le funzionalità API di JDBC per l’interfacciamento con il database
relazionale andando a compiere un notevole sforzo e un impiego massiccio di
tempo nei casi più complessi. A vantaggio dei tools ORM, c’è sicuramente il
fatto di poter astrarre dal codice dell’applicazione tutte le miriadi di operazioni ripetitive tipiche dell’accesso ai dati, rendendo il codice più mantenibile
ed incrementando quindi la produttività dello sviluppo. Inoltre utilizzare una
tecnologia ORM all’interno di un sistema conferisce a quest’ultimo una reale
portabilità fra database differenti; portabilità che, con le tecniche tradizionali, è molto difficile da realizzare. Rimane però lo svantaggio che, anche se si
utilizza un framework potente come Hibernate, per ridurre le righe di codice
da dedicare all’accesso dei dati, si complica la logica dell’applicazione.
2
POJO
“We wondered why people were so against using regular objects in their systems
and concluded that it was because simple objects lacked a fancy name. So we gave
them one, and it’s caught on very nicely”1
1
MF Bliki: POJO da MartinFowler.com
3.3 Hibernate JPA ed EJB
19
Pojo è l’acronimo di Plain Old Java Object ed è una delle funzionalità aggiunte da Sun in EJB 3.0. Si tratta di un oggetto Java che non estende, ne
implementa nessuna classe specializzata richiesta dal framework EJB. Conseguentemente tutti gli oggetti normali Java si possono definire POJO. I
principali benifici ad utilizzare oggetti di tipo POJO sono:
• Disaccoppiamento: disaccoppiamento dei componenti applicativi dall’infrastruttura del contesto EJB che permette di costruire un’applicazione da componenti disaccoppiati. Non c’è più bisogno di scrivere il
codice di ricerca JNDI. É possibile progettare e implementare la logica
di business.
• Testing più facile: si può provare o eseguire la logica di business al di
fuori del server delle applicazioni in pochi secondi.
• Flessibilità: la tecnologia POJO può essere implementata con qualunque
tipo di EJB.
3
Hibernate JPA ed EJB
Con Enterprise JavaBean si vanno ad indicare i componenti della piattaforma
J2EE che implementano la logica di business lato server. Questi componenti
devono rispettare diverse proprietà definite all’interno delle specifiche tra le
quali la persistenza, il supporto alle transazioni, la gestione della concorrenza
e della sicurezza e l’integrazione con altre tecnologie come CORBA, JNDI
e JMS. EJB non è quindi un prodotto ma una specifica per lo sviluppo di
applicazioni enterprise, cioè applicazioni basate su componenti distribuiti e
transazionali, che forniscono quindi servizi via internet. Tramite queste specifiche si intende definire una metodologia standard per implementare la logica
di funzionamento delle applicazioni di tipo enterprise cercando di evitare i
numerosi problemi che nascono durante lo sviluppo delle web-application.
Con EJB si va a descrivere in maniera dettagliata come realizzare un application server che fornisca tutte le funzionalità necessarie, tra le quali si possono trovare la persistenza, il controllo della concorrenzialità, la sicurezza, la
transazionalità e molte altre. Con la versione 3.0 EJB è migliorata molto riuscendo a fornire una tecnologia matura che unisce la potenza di un framework
enterprise con la semplicità dell’approcio POJO annotation-based (Plain Old
3.4 Hibernate Entity Manager e Jpa Persistence
20
Java Object). Queste specifiche fanno parte anche del mondo Hibernate; infatti, grazie ai moduli Hibernate Annotation e Hibernate EntityManager è
stata fornita un’implementazione delle JPA (Java Persistence Api) ovvero
la parte della specifica EJB 3.0 dedicata alla persistenza. É quindi possibile
per lo sviluppatore aderire alle specifiche EJB scegliendo, come framework
di implementazione dello stato di persistenza, Hibernate.
4
4.1
Hibernate Entity Manager e Jpa Persistence
Ciclo di Vita di un Entity
Figura 3.1: Ciclo di Vita di un Entity
Durante il suo ciclo di vita, un’istanza di una entity, può essere:
• Attached (gestita da un entity manager);
• Detached (non gestita);
Nel primo caso viene tenuta traccia dei cambiamenti dell’entity, e successivamente questi cambiamenti vengono sincronizzati con il database.
Nel secondo caso, invece, non viene tenuta traccia del cambio di stato dell’entity. Un’entity di tipo detached può essere serializzata e inviata attraverso la
rete ad un client. Il client può effettuare modifiche sull’oggetto serializzato,
da remoto, e inviare il tutto indietro al server per poterle sincronizzare con
il database.
4.2
Cos’è il Persistance Context e l’Entity Manager
Un persistence context è un’insieme di istanze entity di tipo attached. Un
persistence context è gestito da un’entity manager. L’entity manager si oc-
3.4.2 Cos’è il Persistance Context e l’Entity Manager
21
cupa di tener traccia dello stato (aggiornamento,cambiamento...) delle varie
entity del persistance context, e mappare tali cambiamenti sul database. Una
volta chiuso il persistance context tutte le istanze attached all’interno, diventano detached.
In Java SE gli entity manager sono creati attraverso javax.persistence.
EntityManagerFactory.
Un persistence context può essere creato:
• attraverso il metodo
EntityManagerFactory.createEntityManagerFactory() e passando
come paramentro il nome del persistence.xml. L’istanza dell’entity manager che il metodo restituisce rappresenta un extended persistance
context;
• iniettato direttamente nell’EJB usando l’annotazione @javax.persistence.
PersistenceContext
L’Entity Manager
L’entity manager effettua le operazione di CRUD (Create, Remove, Update,
Delete) su domini di oggetti:
• ricerca delle entity (find());
• gestisce il ciclo di vita delle entity (persist(), remove(), flush(), merge(),
refresh());
• crea le query (createQuery(), createNamedQuery(), createNativeQuery());
Figura 3.2: Entity Manager
Distinguiamo 2 tipi di entity manager:
1. gestite dal container. Il ciclo di vita di queste entity manager è gestito
dal Java EE container. Il persistance context dell’istanza di un’entity
è propagato automaticamente dal container a tutte le applicazioni;
3.5 Hibernate Annotations
22
2. gestite dall’application. In questo caso, il ciclo di vita dell’entity manager, è gestito direttamente dall’applicazione: l’entity manager è creato
e distrutto dall’applicazione. Questo tipo di entity manager non hanno
nulla a che fare con il Java EE container. Ciò significa che deve essere
scritto del codice per controllare ogni aspetto del ciclo di vita dell’entity
manager.
Tipi di Persistance Context
• transaction-scoped → dura per tutta la durata della transazione, e viene
chiusa quando la transazione è finita. Solo persistence context gestite
dall’application server possono essere di questo tipo;
• extended → dura più di una transazione. Istanze entity che sono attached in un extended context rimangono attached anche dopo la fine
della transazione.
Persistence Provider
Un persistence provider è essenzialmente un ORM (Object-Relational Mapping) framework che fornisce un’implementazione del JPA. Grazie alla standardizzazione dettata dalle JPA per la piattaforma Java per gli ORM framework, possiamo utilizzare un qualsiasi ORM framework compatibile.
Persistance Unit
Un entity manager mappa un insieme fissato di classi con un database. Questo
insieme di classi e chiamato persistance unit. Un persistance unit è definito nel file persistance.xml, nel quale possono essere definiti anche più di un
persistence unit. Tale file si trova nella cartella META-INF. Normalmente ogni classe nel jar, che è annotata con @javax.persistence.Entity appartiene
al persistence unit.
5
Hibernate Annotations
Hibernate Annotations è un altro dei preziosi strumenti che estendono le
potenzialità del framework. Questo modulo infatti permette di utilizzare le
3.6 Hibernate Search
23
annotazioni, aggiunte nel linguaggio Java dalla versione 5.0 del JDK, per
effettuare il mapoping dei JavaBean con le tabelle del database. Con l’utilizzo
di Hibernate Annotations si evita di configurare i file xml, usati nella libreria
core, per l’interfacciamento con il database relazionale. Utilizzando quindi
Hibernate Annotations la configurazione dei metadati per il mapping diventa
simile a quella usata all’interno dello stato di poersistenza con gli EJB 3.0.
Hibernate Annotations offre quindi il vantaggio di poter avere all’interno dello
stesso file sia il modello ad oggetti utilizzato all’interno dell’applicazione, sia
il modello relazionale, in quanto il mapping fra i due è eseguito tramite le
annotazioni poste sopra i vari campi del JavaBean. Le annotazioni oltre ad
essere utilizzate per lo strato di persistenza vengono anche usate all’interno
degli altri moduli di Hibernate.
6
Hibernate Search
Hibernate Search, basato su Apache Lucene, è uno dei tanti moduli messi a
disposizione dal mondo Hibernate. Questo componente permette di integrare
all’interno della propria applicazione le funzionalità della ricerca full-text fornite da Apache Lucene. Le caratteristiche più interessanti sono la semplicità
e l’uniformità con cui il paradigma di ricerca full-text si integra con la gestione della persistenza dei dati: Hibernate Search si occupa di (quasi) tutta
la gestione. Tale configurazione avviene per via dichiarativa utilizzando le
annotazioni, in modo simile a quanto si è visto precedentemente con la persistenza dei dati. Inoltre l’unione del framework Hibernate con il mondo di
Apache Lucene permette a quest’ultimo di mantenere un indice completo,
aggiornandolo automaticamente dopo ogni operazione di inserimento, modifica o cancellazione. Tuttavia si tenga presente che ci sono delle irregolarità
quando si ha a che fare con un modello di dominio a oggetti sopra ad un
indice di testo (mantenere l’indice aggiornato, irregolarità tra la struttura
dell’indice ed il modello di dominio, e irregolarità di query). Ma i benefici di
velocità ed efficienza superano di gran lunga queste limitazioni.
Capitolo 4
Apache Lucene
In quasi ogni applicazione si riscontra la necessità di permettere un’interazione tra utente e risorse testuali, interazione non sempre facile e veloce.
Al fine di risolvere questo problema, l’Apache Software Foundation offre il
modulo Lucene: una libreria open-source estremamente flessibile cha mette
a disposizione delle API per poter inserire all’interno della applicazioni un
motore di ricerca full-text semplice e veloce. Grazie ad Apache Lucene è possibile realizzare soluzioni estremamente avanzate che superano e rendono più
efficente il normale utilizzo dei DBMS.
1
Indice di Lucene
Uno dei componenti di base di Lucene è l’indice, normalmente contenuto in
una cartella del filesystem oppure in RAM, avendo l’attenzione di controllare
le dimensioni e lo spazio disponbibile. All’interno di questo indice, che è il
cuore del sistema di Lucene, sono inseriti documenti (intesi come istanze di
org.apache.lucene.document.Document) a loro volta divisi in campi (fields).
L’indice rappresenta quindi una tabella nel nostro database, mentre il documento altro non è che una tupla della nostra tabella. Questa visione permette
alle API di Lucene di essere indipendenti dal formato del file: infatti qualsiasi
file in formato odf, rtf, html, doc o odt può venire indicizzato. Nella quasi
totalità dei casi Lucene, che ha il compito di fornire un servizio di ricerca, è
affiancato ad un database relazionale, il quale presenta i risultati, in modo
da sfruttare la velocità maggiore di un DBMS nella selezione di uno specifico
4.1 Indice di Lucene
25
Figura 4.1: Gerarchia di Lucene
dato (grazie ad un opportuno meccanismo di caching). La prima operazione da compiere per poter utilizzare i meccanisimi di ricerca di Lucene è la
creazione dell’indice, basato sulla scansione di tutti i documenti per poter
individuare le parole presenti. Per quest’analisi Lucene mette a disposizione
vari analizzatori, fra i quali uno standard, basato sulle lingue occidentali più
diffuse. Una volta compiuta la fase di indicizzazione, i vari documenti sono
disponibili per l’esecuzione di ricerche. L’indicizzazione di documenti è ottimizzata da Lucene in maniera tale da aumentare le prestazioni, senza però
interferire con eventuali operazioni di ricerca contemporanee. Il metodo IndexWriter.optimize() serve per l’appunto a riorganizzare l’indice, al fine di
aumentare le prestazioni delle future ricerche (e scritture). L’indice di uno
specifico insieme di dati può essere condiviso in modalità di sola lettura: non
si pone quindi, in fase di ricerca, alcun limite al numero di ricerche contemporanee. In fase di scrittura, durante la generazione dell’indice, la gestione
dell’accesso è leggermente più complicata: è possibile un solo accesso in scrit-
4.2 Full-Text
26
tura, che però può venir condiviso da vari thread. Lucene diventa quindi uno
strumento molto potente per compiere ricerche, trovare similitudini,.. grazie
sopratutto alla semplicità di utilizzo. Naturalmente esistono molti software
che permettono l’interfacciamento di Lucene a tecnologie differenti, scritte in
linguaggi diversi, come PyLucene per Python e Lucy per il mondo C.
2
Full-Text
La ricerca full-text è una tecnologia che esamina tutte le parole contenute
nei documenti salvati per trovare una corrispondenza con le specifiche parole
inserite dall’utente. Finchè il numero di documenti, all’interno dei quali si
estende la ricerca della parola chiave, è limitato, è possibile utilizzare una
semplice query; quando invece il numero di oggetti aumenta sono necessari
meccanismi di ricerca full-text che indicizzano i file nei quali ricercare e successivamente eseguono la ricerca. Nella prima fase, dedicata alla costruzione
dell’indice, si esegue uno scan di tutti i documenti per costruire una lista di
termini, detta appunto indice. Questa lista, costruita cercando di ottimizzare
la futura ricerca, è costruita tramite regole che ne semplificano la struttura:
si eliminano le parole meno importanti, come gli articoli, oppure si cerca,
tramite opportuni algoritmi, di raggruppare le parole con il medisimo significato. Come si può facilmente notare, la fase di analisi è strettamente legata alla lingua dei documenti da indicizzare. Nella fase successiva, quella di ricerca,
si andrà ad analizzare solamente l’indice alla ricerca degli opportuni risultati.
Uno dei problemi più comuni della ricerca full-text è chiamato “problema dei
falsi positivi”: esso consiste nel fatto che, all’interno dell’insieme dei documenti risultati da una specifica ricerca, potrebbero essere presenti documenti
che non sono in alcun modo correlati al significato della ricerca. Ciò è causato
da ambiguità presenti all’interno del linguaggio naturale. Per ovviare a questo
problema esistono degli algoritmi che riducono i falsi positivi suddividendo,
in fase di indicizzazione, i vari documenti in categorie tematiche. La ricerca in Lucene è una perfetta astrazione del concetto classico di query per il
recupero di informazioni da un supporto persistente; tale astrazione è fornita da un QueryParser. Il QueryParser ha l’obiettivo di acquisire la keyword
di ricerca (una o più parole, anche generiche), analizzare quest’ultima con
4.3 DBMS vs Full-Text
27
il medesimo Analyzer usato per la creazione dell’indice ed infine rendere la
stringa di ricerca una query che rispetti predeterminate regole; l’ultimo passo restituisce l’oggetto Query che sarà passato all’IndexSearcher per poter
effettuare la ricerca sull’indice ed immagazzinare i risultati di tale ricerca,
con relativi score, all’interno di un oggetto Hits. In questo modo viene restituito un oggetto con tutti i Document contenti la nostra keyword, con un
“margine di errore” dato dallo score (quanto il risultato equivale alla nostra
keyword). Per poter sfruttare meglio la ricerca in Lucene vengono utilizzate
due tecniche avanzate di analisi testuale:
• Tokenising: permette di estrarre parti significative di testo da uno
stream, eliminando quindi segni di interpunzione e spazi bianchi.
• Stemming: processo che riduce le parole alla loro radice grammaticale,
eliminando per esempio le coniugazioni verbali.
Oltre a queste due tecniche di analisi Apache Lucene offre anche altre features, fra le quali le principali sono: ricerche complesse con operatori booleani,
ricerche all’interno di una specifica frase, ricerche basate su espressioni regolari, e ricerche con wildcard.
3
DBMS vs Full-Text
Per la gestione delle risorse testuali all’interno delle applicazioni, la soluzione
più comune consiste nell’utilizzare dei database basati su modelli relazionali.
In questo caso i dati sono rappresentati tramite della tabelle sulle quali è
poi possibile eseguire le operazioni di select, update, insert e delete, semplicemente basandosi sulle colonne contenenti l’ID oppure un qualche valore
numerico o pseudo-numerico. Ad esempio, in una web-application, che visualizza le ultime news, ci sarà un database che conterrà i testi delle varie
news, e sarà poi eseguita una selezione sulla base di un qualche valore per
poter decidere quali informazioni visualizzare. Considerando quest’esempio,
si può notare come il database possa risultare ideale per quanto riguarda
il salvataggio dei dati, ma non possa essere considerato tale per la parte di
ricerca e di elaborazione delle news. Per ovviare a questo problema quasi
tutti i DBMS forniscono dei moduli o delle estensioni full-text che permettono servizi avanzati di ricerca testuale. Un’altra soluzione che permette di
4.4 Hibernate + Lucene
28
estendere la propria applicazione con un servizio avanzato di analisi testuale
è Apache Lucene. L’utilizzo di uno strumento come questo può migliorare
nettamente le prestazioni di un sistema, specialmente quando il database
contiene decine di migliaia di dati, ottenendo performance non raggiungibili
con le estensioni dei DBMS prima citate. Tramite le tecniche avanzate di
analisi testuale descritte prima (tokenising e stemming) la ricerca full-text
può portare a dei risultati migliori rispetto ai DBMS, nei quali, solitamente,
queste tecniche non sono prese in considerazione. Un altro vantaggio che va
a favore di Apache Lucene è sicuramente la licenza opern source che, oltre
ad un risparmio economico, offre una comunità di utenti, fra i quali gli stessi
sviluppatori, pronti ad aiutare.
4
Hibernate + Lucene
I vantaggi provenienti dall’utilizzo di Hibernate esteso con il modulo di ricerca
basato su Lucene sono molteplici. Primo fra tutti, la semplicità di configurazione che rende molto facile il mantenimento dell’applicazione. Tramite le
annotazioni, che possono essere usate sia per configurare la persistenza sia
per la sezione di ricerca, si riesce, in modo veloce ed intuitivo, a costruire un
sistema solido che semplifica molto la gestione dei dati. Un secondo vantaggio
deriva dal fatto di poter indicizzare più volte lo stesso testo con analyzer differenti. Nei precedenti paragrafi si è notata la stretta correlazione fra ricerca
e lingua del testo nel quale ricercare. Diventa quindi necessario conoscere la
lingua con la quale un documento è stato scritto per poterlo indicizzare in modo appropriato. Il problema, in questi casi, nasce dal fatto che non è sempre
facile ottenere quest’informazione. Usualmente infatti si ricorre a metadati,
oppure alla configurazione di sistema; tuttavia è comune scrivere un documento in una lingua differente da quel del programma/tool/browser/sistema
che si utilizza. Ancora più complesso è il caso dei dati provenienti da un
database che possono essere in lingue differenti anche se appartengono allo
stesso database. Tra le varie soluzioni a questo problema, si possono utilizzare moduli esterni che cercano di riconoscere la lingua dal testo. Per evitare
di aggiungere ulteriori strumenti alla propria applicazione, si può risolvere
il problema indicizzando più volte lo stesso domcumento, come permesso
4.4 Hibernate + Lucene
29
da Hibernate Search. Non meno importante è il problema derivante dalla
sicurezza, aspetto piuttosto complicato da gestire con le ricerche full-text.
Tutta la logica di controllo per la sicurezza è infatti, per la maggior parte
dei casi, contenuta nei metodi o nelle query, e diventa difficile durante la
fase di interrogazione degli indici di Lucene trasportare la medesima logica.
Grazie ad Hibernate Search si può risolvere il problema includendo dei dati
provenienti da un altro oggetto persistente all’interno degli indici di Lucene.
Saranno poi utilizzati i filtri di ricerca per eliminare dal set di risultati i dati
non permessi. Hibernate Search è quindi una soluzione flessibile e potente
per aggiungere alla propria applicazione la possibilità di ricerche Googlelike. Inoltre è possibile estendere l’uso di base in modo pratico e veloce per
rispondere a situazioni più complesse, senza tuttavia modificare la filosofia
di base.
Capitolo 5
Il Motore di Ricerca
Il prototipo del motore di ricerca, è stato sviluppato interamente in Java. Per
lo sviluppo la scelta è ricaduta sul server Tomcat in quanto molto stabile e
affidabile, semplice da usare, gratuito e scritto anch’esso in Java.
Nei capitoli precedenti sono stati descritti brevemente i tools e i framework
utilizzati nello sviluppo del progetto. Ora, è necessario osservare come queste
tecnologie sono state integrate all’interno dell’applicazione.
Essendo questa un’applicazione sviluppata per i Web Services la quasi totalità del tempo impiegato è stata spesa per lo studio e l’implementazione lato
server.
Come premessa per comprendere meglio la struttura dei beans è necessario soffermarsi brevemente sul database per il quale abbiamo sviluppato
il servizio. Le tabelle che abbiamo considerato sono:
• azienda → contiene i dati delle anziende partner (29256 record);
• clienti → contiene i dati dei clienti (125599 record);
• città → contiene le proprietà principali delle città italiane ed estere
nelle quali sono presenti clienti o aziende partner (76173 record);
Le ricerche possibili sono:
• ricercare un’azienda tramite i parametri nome e divisione;
• ricercare un cliente avendo come parametri nome e cognome;
• ricercare le proprietà di una città per nome.
5.1 it.enginsoft.search engine.ws
31
Vediamo ora passo per passo la struttura del progetto.
I package utilizzati sono:
• it.enginsoft.search engine.ws;
• it.enginsoft.search engine.appl ;
• it.enginsoft.search engine.file prop;
• it.enginsoft.search engine.bean;
• it.enginsoft.search engine.jpaController ;
1
it.enginsoft.search engine.ws
Figura 5.1: Class Diagramm package it.enginsoft.search engine.ws
Questo package contiene la classe Search Engine, che funziona da interfaccia
per il Web Service. Infatti la dichiarazione di classe public class Search Engine
viene preceduta dall’annotazione @WebService(), la quale indica che la classe
POJO appena creata dovrà essere deployata e configurata per rispondere
come Web Service. I metodi che vogliamo esporre per rispondere alle richieste
dovranno essere preceduti dall’annotazione @WebMethod.
Al run dell’applicazione, vengono inizializzate tutte le variabili locali. Nel
nostro caso vengono istanziati due oggetti: il primo è del tipo ReadFile, che
come vedremo in seguito viene utilizzato per leggere il file delle proprietà,
mentre il secondo è di tipo Init, che serve principalmente per istanziare il
5.2 it.enginsoft.search engine.appl
32
logger.
I metodi che sono presenti all’interno di questa classe sono esclusivamente le
richieste che il client può richiedere al server (indicizzazione e ricerca).
2
it.enginsoft.search engine.appl
Figura 5.2: Class Diagramm package it.enginsoft.search engine.appl
Il package it.enginsoft.search engine.appl contiene la classe Init che viene
utilizzata per 2 modi diversi:
1. il costruttore della classe (che ricordiamo viene chiamato al momento
in cui viene lanciata l’applicazione dalla classe it.enginsoft.search_
engine.ws.Search_Engine) configura le proprietà per la libreria log4j
che viene utilizzata per mantenere un file di log;
2. tutti gli altri metodi presenti in questa classe servono per indicizzare i
dati presenti nel database, e rendere cosı̀ possibile la ricerca full-text.
Le operazioni di indexing sono state implementate in classi diverse rispetto
agli altri metodi che vanno a lavorare sui dati, poichè questo tipo di operazioni dovrebbero essere eseguite solamente in particolari situazioni (primo
utilizzo del motore di ricerca, modifica sostanziale dei dati del database e
modifica architetturale del db), in quanto si tratta di operazioni molto lunghe
5.3 it.enginsoft.search engine.file prop
33
e laboriose. Proprio per questa ragione si era in dubbio se permettere l’esportazione di questi metodi nell’interfraccia del Web Service; alla fine la decisione di implementarli è stata presa prendendo in considerazione la scarsità
di utenti che avevano la possibilità di interagire con il prototipo, e quindi la
sostanziale sicurezza che nessuno avrebbe involontariamente richiesto queste
funzionalità.
3
it.enginsoft.search engine.file prop
Figura 5.3: Class Diagramm package it.enginsoft.search engine.file prop
Come è stato detto nei capitoli precedenti, per cercare di risolvere il problema delle ricerche di dati non esatti, abbiamo utilizzato dei metodi di ricerca
diversi da quello standard.
La sintassi del Fuzzy Searches in Lucene prevede due distinte forme:
1. specificando il grado di similarità richiesto, aggiungendo un parametro
di valore compreso fra 0 e 1 al termine fuzzy (es. milano 0.8) , dove 1
significa similarità massima, mentre 0 significa similarità nulla;
2. una forma di ricerca più generale, che viene implementata aggiungendo
solamente il carattere
al termine fuzzy (es. milano ). In questo caso
Lucene traduce questa sintassi in una ricerca del tipo milano 0.5
5.4 it.enginsoft.search engine.bean
34
Per rendere la ricerca più ottimizzata si è deciso che in questo prototipo
venisse usata la sintassi che prevede il grado di similarità richisto. Ma ovviamente questo valore non può essere deciso dal client finale (che per utilizzarlo
dovrebbe conoscere sia il metodo di ricerca che viene offerto che la sintassi
esatta per inserire il parametro), ma dall’amministratore. Inoltre si è pensato
ad un modo per poter andare a modificare questo valore, senza dover andare
a lavorare sul codice sorgente del progetto, e dover deployare l’applicazione.
Per questo si è deciso di dare la possibilità all’amministratore di creare un
file di proprietà, che potesse andare a modificare questi valori “al volo”.
La classe FileProperties si occupa di determinare l’esistenza di questo file, di
valutarne la correttezza sintattica, e di settare le proprietà.
L’esistenza/correttezza sintattica del file non è necessaria al funzionamento
del progetto, in quanto in caso di mancata esistenza del file, o in presenza di
errori all’interno del file, la classe che si occupa di mantenere i valori di proprietà (it.enginsoft.search_engine.bean.FileProperties) setterà questi
parametri con valori di default.
La modifica del file di proprietà viene controllata prima di ogni ricerca; in
questo modo è possibile cambiare il grado di similarità fra una ricerca e
l’altra.
4
it.enginsoft.search engine.bean
In questo package troviamo tutte le classi bean del progetto.
Nella spiegazione dettagliate possiamo raggruppare fra loro le classi Azienda,
Citta e Contatto poichè la logica interna è la stessa. Stessa cosa possiamo
fare per Result Azienda, Result Citta e Result Contatto in quanto, anche in
questo caso, le differenze fra le varie classi riguardano esclusivamente i tipi
di dato che sono contenuti.
5.4 it.enginsoft.search engine.bean
Figura 5.4: Class Diagramm package it.enginsoft.search engine.bean
35
5.4.1 Azienda - Citta - Contatto
4.1
36
Azienda - Citta - Contatto
In questi beans possiamo vedere come vengono utilizzate le Hibernate Annotations descritte nel paragrafo 3.5.
Facciamo un esempio con il bean Azienda.
@Entity
@Table ( name = ” a z i e n d a ” )
@NamedQueries ( {
@NamedQuery ( name = ” Azienda . f i n d A l l ” ,
query = ”SELECT a FROM Azienda a ” )
})
@Indexed ( i n d e x = ” a z i e n d a . i d x ” )
p u b l i c c l a s s Azienda implements S e r i a l i z a b l e {
@Id
@Column ( name = ” i d a z i e n d a ” )
@DocumentId
private int id azienda ;
@Column ( name = ”nome ” )
@Field ( i n d e x = Index .TOKENIZED, s t o r e = S t o r e .NO)
p r i v a t e S t r i n g nome ;
@Column ( name = ” d i v i s i o n e ” )
@Field ( i n d e x = Index .TOKENIZED, s t o r e = S t o r e .NO)
private String divisione ;
...
}
In questo caso sono presenti due tipi di annotations: Hibernate-Annotations
e Lucene-Annotations.
Hibernate-Annotation Questo tipo di annotazioni vengono utilizzate per
definire l’entità e mapparla, con tutti i suoi campi, all’interno del database.
Nel nostro esempio le Hibernate-Annotation sono:
• @Entity → definisce che il bean è un’entità;
• @Table → mappa l’entità nella tabella name del database;
5.4.2 Result Azienda - Result Citta - Result Contatto
37
• @NamedQueries → permette di creare delle query “standard” che poi
possono essere richiamate da qualsiasi parte del progetto, tramite l’attributo name;
• @Id → dichiara la chiave primaria nella tabella nella quale l’entità è
stata mappata;
• @Column → mappa ogni attributo dell’entità in una tabella del database.
Lucene-Annotation Le Lucene-Annotation servono alla libreria HibernateSearch per andare a indicizzare al meglio tutti i record dell’ entità.
Queste annotation, nel nostro esempio, sono:
• @Indexed → definisce dove saranno indicizzati i record (in questo caso
nel file azienda.idx);
• @DocumentId → va a definire la chiave primaria nell’indice;
• @Field → si vanno a definire i campi da indicizzare, e i metodi di
indicizzazione per ciascun campo.
4.2
Result Azienda - Result Citta - Result Contatto
L’introduzione del file delle proprietà all’interno del progetto, ha portato alla
luce la possibilità di avere dei risultati non corretti per via di errori dovuti
all’impostazione delle proprietà. Per questo motivo si è deciso che il client
deve essere a conoscenza dell’eventualità che la ricerca eseguita abbia avuto
un esito non esatto. É stato quindi necessario creare degli oggetti ad hoc da
utilizzare come risultato della ricerca.
Le classi Result Azienda, Result Citta e Result Contatto vengono utilizzate
proprio per questo motivo. La loro definizione prevede due campi:
1.
private List<Azienda> aziende
2.
private List<Exception> exception
Il primo campo viene utilizzato per memorizzare una lista di aziende (città
o contatti, a seconda della classe di cui ci riferiamo), che altro non è che la
lista di risultati per la ricerca che abbiamo svolto.
Il secondo campo è una lista delle eventuali eccezioni che sono occorse durante lo svolgimento della ricerca.
5.4.3 FileProperties
38
Il Web Service ritorna quindi un oggetto del tipo Result Azienda (Result Citta oppure Result Contatto a seconda del tipo di ricerca); sta poi all’applicazione client valutare come e quali dati restituire allo user finale.
4.3
FileProperties
La classe FileProperties, come già detto precedentemente, si occupa di gestire
i parametri per la ricerca.
• grado di similarità per le ricerche su azienda/contatto/città;
• massimo numero di risultati.
Se questi parametri non sono definiti nel file delle proprietà, allora vengono
settati con i valori di default
p r i v a t e s t a t i c Double rankAzienda = 0 . 7 5 ;
p r i v a t e s t a t i c Double rankContatto = 0 . 7 5 ;
p r i v a t e s t a t i c Double r a n k C i t y = 0 . 7 5 ;
p r i v a t e s t a t i c i n t maxResults = 3 0 ;
Ricercare un dato non esatto all’interno di un database, significa andare a
cercare un dato che per qualche ragione è stato inserito in forma errata (errori
di ortografia, standard di inserimento,...). Dopo vari test si è deciso di porre
il grado di similarità di default a 0.75, poichè questo valore è abbastanza
grande da obbligare ad un buon livello di vicinanza con il termine cercato,
ma allo stesso tempo permette di trovare gran parte di quei valori che, per
qualsiasi motivo, sono stati inseriti nel database in modo errato.
4.4
Exception
La classe Exception viene utilizzata per gestire il tipo di eccezione da, eventualmente, inviare al client.
Attualmente l’unico tipo di eccezioni gestite sono quelle dervianti dal file
delle proprietà.
5
it.enginsoft.search engine.jpaController
Nel package it.enginsoft.search engine.jpaController troviamo il core della
nostra applicazione.
5.5.1 Il Costruttore
39
Figura 5.5: Class Diagramm package it.enginsoft.search engine.jpaController
Le classi *JpaController permettono di gestire le entità che abbiamo mappato
nel db tramite le tecnologie descritte nel capitolo 3.4.
Vediamo nel dettaglio i metodi principali utilizzati per l’implementazione di
questo tipo di classe, utilizzando AziendaJpaController come esempio.
5.1
Il Costruttore
p u b l i c AziendaJpaController ( ReadFile r f ) {
this . rf = rf ;
emf = P e r s i s t e n c e . c r e a t e E n t i t y M a n a g e r F a c t o r y (
” WS Search Engine 0 . 1PU” ) ;
...
}
Il parametro necessario per creare un’oggetto del tipo AziendaJpaController
e di tipo ReadFile. L’oggetto ReadFile viene utilizzato dal metodo protected
void searchAziendaLucene durante la fase di ricerca per decidere il grado
di similarità voluto. Il compito principale del costrutto è quello di creare
5.5.2 public int indexAzienda()
40
un’entity manager e definire il persistence unit nel quale l’entità è stata
mappata sul database.
5.2
public int indexAzienda()
Questo metodo permette di indicizzare i record della tabella azienda.
...
FullTextEntityManager f u l l T e x t E n t i t y M a n a g e r
= S e a r c h . c r e a t e F u l l T e x t E n t i t y M a n a g e r (em ) ;
L i s t <Azienda> a z i e n d a = f i n d A z i e n d a (em , t r u e , −1, −1);
i f ( a z i e n d a != n u l l ) {
em . g e t T r a n s a c t i o n ( ) . b e g i n ( ) ;
int i = 0;
f o r ( Azienda az : a z i e n d a ) {
f u l l T e x t E n t i t y M a n a g e r . i n d e x ( az ) ;
i f ( i == 1 0 0 0 ) {
em . f l u s h ( ) ;
i ++;
}
l o g g e r . i n f o ( ” S t a r t commit a z i e n d a ” ) ;
em . g e t T r a n s a c t i o n ( ) . commit ( ) ;
l o g g e r . i n f o ( ” End commit a z i e n d a ” ) ;
}
...
Per fare questo è necessario creare un FullTextEntityManager, ovvero un entity manager in grado di lavorare con le ricerche full-text, e per ogni record
chiamare il metodo fullTextEntityManager.index(Object), che prepara l’indicizzazione dell’oggetto. L’indicizzazione viene poi completata con il metodo
em.getTransaction().commit();, che va realmente a scrivere l’indice.
5.3
public Result Azienda searchAzienda(String searchNome, String searchDivisione)
Il metodo searchAzienda viene richiamato dalla classe it.enginsoft.search_
engine.ws.Search_Engine quando l’utente vuole fare una ricerca sulla tabella azienda.
5.5.4 protected void searchAziendaLucene(String searchNome, String
searchDivisione, Result Azienda ra)
41
public Result Azienda searchAzienda
( S t r i n g searchNome , S t r i n g s e a r c h D i v i s i o n e ) {
R e s u l t A z i e n d a r a = new R e s u l t A z i e n d a ( ) ;
s e a r c h A z i e n d a L u c e n e ( searchNome , s e a r c h D i v i s i o n e , r a ) ;
return ra ;
}
Viene creato un oggetto Result Azienda che viene passato al metodo searchAziendaLucene assieme ai parametri searchNome e searchDIvisione per effettuare la ricerca.
Conclusa la ricerca viene ritornato l’oggetto Result Azienda.
5.4
protected void searchAziendaLucene(String searchNome, String searchDivisione, Result Azienda ra)
Questo è il metodo più importante di tutta l’applicazione. In queste poche
righe viene implementato tutto il core del progetto.
Inizialmente viene creato un FullTextEntityManager, e una lista di oggetti
Azienda dove inserire i risultati:
...
FullTextEntityManager f u l l T e x t E n t i t y M a n a g e r
= S e a r c h . c r e a t e F u l l T e x t E n t i t y M a n a g e r (em ) ;
L i s t <Azienda> r e s u l t s = n u l l ;
...
vengono poi caricati i valori di similarità:
...
try {
...
S t r i n g s p l P r o p [ ] = r f . getRankCont ( ) . s p l i t ( ” & ” ) ;
...
A questo punto è necessario costruire la stringa di ricerca; per prima cosa si
vanno a controllare i parametri passati alla funzione: se è presente qualche
parametro null, questo viene sosituito con una stringa vuota.
Se tutti i parametri passati sono null pongo la variabile make ad un valore
> 0, per fare in modo che la query non venga eseguita.
5.5.4 protected void searchAziendaLucene(String searchNome, String
searchDivisione, Result Azienda ra)
42
...
i f ( searchNome == n u l l ) {
searchNome = ” ” ;
}
i f ( s e a r c h D i v i s i o n e == n u l l ) {
searchDivisione = ””;
}
i f ( ! searchNome . t r i m ( ) . e q u a l s ( ” ” )
&& ! s e a r c h D i v i s i o n e . t r i m ( ) . e q u a l s ( ” ” ) ) {
searchQuery = ”+nome : ” + searchNome + ”˜” +
s p l P r o p [ 1 ] + ” +d i v i s i o n e : ” +
s e a r c h D i v i s i o n e + ”˜” + s p l P r o p [ 1 ] ;
} e l s e i f ( searchNome . t r i m ( ) . e q u a l s ( ” ” )
&& ! s e a r c h D i v i s i o n e . t r i m ( ) . e q u a l s ( ” ” ) ) {
searchQuery = ”+ d i v i s i o n e : ” + s e a r c h D i v i s i o n e +
”˜” + s p l P r o p [ 1 ] ;
} e l s e i f ( s e a r c h D i v i s i o n e . trim ( ) . equals (””)
&& ! searchNome . t r i m ( ) . e q u a l s ( ” ” ) ) {
searchQuery = ”+nome : ” + searchNome + ”˜” +
splProp [ 1 ] ;
} else {
make = 1 ;
}
...
Ora è il momento di avviare la ricerca. Se la variabile make != 0 termina
l’esecuzione del metodo (andando a chiudere il FullTextEntityManager ); in
caso contrario andiamo a definire i campi sui quali vogliamo svolgere la nostra ricerca (in questo caso nome e divisione).
Per eserguire la ricerca è necessario creare la query per lucene. Per fare questo
ci viene in aiuto l’oggetto QueryParser che, dopo averlo istanziato in modo
ottimizzato per la nostra ricerca, parsa automaticamente la searchQuery in
una query per lucene. Se per qualche motivo la fase di parsing genera un’eccezione usciamo dalla funzione andando a chiudere il FullTextEntityManager,
altrimenti eseguiamo la nostra query.
Per poter svolgere la ricerca è necessario inizializzare un oggetto del tipo FullTextQuery che prende come parametri la query e l’entità per cui effettuiamo
5.5.4 protected void searchAziendaLucene(String searchNome, String
searchDivisione, Result Azienda ra)
43
la ricerca. In questo modo come risultato avrò una lista di oggetti entità.
A questo punto, definito il numero massimo di risultati che si potranno
ottenere, inizia la ricerca.
i f ( make == 0 ) {
S t r i n g [ ] p r o d u c t F i e l d s = {”nome ” , ” d i v i s i o n e ” } ;
QueryParser parserMul
= new M u l t i F i e l d Q u e r y P a r s e r ( V e r s i o n . LUCENE 29 ,
productFields ,
new S t a n d a r d A n a l y z e r ( V e r s i o n . LUCENE 29 ) ) ;
o r g . apache . l u c e n e . s e a r c h . Query luceneQueryMul = n u l l ;
try {
luceneQueryMul = parserMul . p a r s e ( searchQuery ) ;
} catch ( ParseException e ) {
make = 1 ;
}
i f ( make == 0 ) {
FullTextQuery queryMul
= fullTextEntityManager . createFullTextQuery (
luceneQueryMul , Azienda . c l a s s ) ;
queryMul . s e t M a x R e s u l t s ( I n t e g e r . p a r s e I n t ( s p l P r o p [ 2 ] ) ) ;
em . g e t T r a n s a c t i o n ( ) . b e g i n ( ) ;
r e s u l t s = queryMul . g e t R e s u l t L i s t ( ) ;
em . g e t T r a n s a c t i o n ( ) . commit ( ) ;
}
}
} catch ( Exception e ) {
r a . addExceptionWithError ( 2 , e . getMessage ( ) ) ;
l o g g e r . e r r o r ( e . getMessage ( ) ) ;
} finally {
fullTextEntityManager . c l o s e ( ) ;
}
...
La lista di oggetti risultante dalla ricerca viene poi settata come attributo
per l’oggetto Result Azienda, che alla fine verrà restituito al client.
...
ra . setAziende ( r e s u l t s ) ;
Capitolo 6
Utilizzo
In questa sezione viene mostrata la funzionalità del progetto, andando a
sottolineare la differenza di risultati fra query sql e query svolte tramite il
prototipo sviluppato.
Poichè l’applicazione nasce dall’esigenza dell’azienda EnginSoft Spa di risolvere la problematica della ricerca di dati non esatti sui propri database, la fase
di progettazione ha permesso di utilizzare come client un’interfaccia grafica
preesitente, andando solamente ad implementare al suo interno la chiamata
web-service.
1
Client
Come già sottilineato nel capitolo 5.2 solamente pochi utenti hanno i permessi per utilizzare questo servizio di ricerca; per questo motivo il client (dopo
aver deciso su che tipo di tabella si desidera fare la ricerca), richiede un’autenticazione.
Effettuata l’autenticazione, viene proposta una GUI che richiede l’inserimento dei parametri necessari per eseguire la ricerca. Come si è visto in 5.5 per
ogni tipo di ricerca occorrono diversi tipi di parametro.
A questo punto viene effettuata la chiamata al web-service che ritorna i
risultati e li stampa a video.
6.1 Client
45
Figura 6.1: GUI client
Figura 6.2: GUI per la ricerca di un contatto
Il client offre anche la possibilità di caricare tutti i dati di un determinato
record, ed eventualmente andare a modificarne il contenuto.
6.1 Client
46
Figura 6.3: risultati per la ricerca enginsoft nella tabella aziende
Figura 6.4: scheda di visualizzazione/modifica per l’azienda EnginSoft Sede
di Trento
6.2 Ricerca
2
47
Ricerca
Vengono ora mostrate le principali differenze fra le ricerce sql fatte su un
database PostgreSQL e le stesse ricerche fatte tramite il prototipo sviluppato.
2.1
Ricerca di Dati Esatti
La query standard per cercare tutte le città passando come parametro trento
in sql è:
s e l e c t nome from c i t t a where nome=’ t r e n t o ’ ;
In questo caso risultano 0 corrispondenze.
nome
−−−−−−−−
(0 r i g h e )
É necessario considerare che in questi casi l’utente non è a conoscenza del
modo in cui il parametro cercato è scritto nel database(maiuscole e minuscole); per questo motivo è quasi d’obbligo modificare la query in modo taleda
evitare questo tipo di problemi:
s e l e c t nome from c i t t a where nome i l i k e
’ trento ’ ;
Procedendo in questo modo si riesce ad ottenere il risultato sperato, ossia:
nome
−−−−−−−−
Trento
(1 r i g a )
Per poter generalizzare ancora di più con il linguaggio sql è necessario utilizzare una Wildcard Searches (cfr. 1.2), che grazie all’opertore % permette di
trovare tutte le città il cui nome contiene il termine cercato.
La query:
s e l e c t nome from c i t t a where nome i l i k e ’% t r e n t o % ’;
restituisce come risultati:
6.2.1 Ricerca di Dati Esatti
48
nome
−−−−−−−−−−−−−−−−−−−
Trento
Albiano Di Trento
Tione Di Trento
Trentola
(4 r i g h e )
Utilizzando invece il prototipo sviluppato inserendo il termine trento (senza
nessuna differenza fra minuscolo e maiuscolo) e ponendo il grado di similarità
al valore massimo (0.9) si hanno come risultati:
nome
−−−−−−−−−−−−−−−−−−−
Trento
Albiano Di Trento
Tione Di Trento
(3 r i g h e )
Se invece si utilizza come grado di similarità il valore di default dell’applicazione (0.75), limitando il numero di risultati a 10, si ottiene:
nome
−−−−−−−−−−−−−−−−−−−
Trento
Albiano Di Trento
Tione Di Trento
Terento
Trenno
Trenta
Tretto
S a i n t −F e r r e o l −Trente−Pas
Sant ’ U l d e r i c o Di T r e t t o
Trent
(10 r i g h e )
Già da questo esempio è possibile notare come la ricerca sql sia più limitata.
Infatti, l’esecuzione di una ricerca con parametro trento innanzitutto deve
prevedere la possibilità che il termine cercato sia scritto sia in maiuscolo
che in minisculo. Inoltre se la ricerca si presuppone di trovare dei valori che
6.2.2 Ricerca di Dati Non Esatti
49
hanno un grado di similarità molto elevato (quindi con una distanza di Levenshtein molto ridotta) con il termine cercato, è necessario che fra i risultati
compaiano anche quei record formati da parole composte, una delle quali si
risultato della ricerca. Per questo motivo nelle ricerche sql sulle stringhe viene
principalmente utilizzato l’operatore ilike, supportato dal metacarattere %.
Utilizzanto invece la ricerca full-text, questo tipo di risultati viene garantito
come risultato minimo.
2.2
Ricerca di Dati Non Esatti
Il problema che si è cercato di risolvere con questa tesi prevede la ricerca
di dati non esatti. Con dati non esatti si intende non solamente la presenza
di errori ortografici dovuti all’inserimento, ma anche la possibilità che i dati
cercati siano stati inseriti in un formato diverso da quello pensato.
Per esempio, qualora si vogliano trovare tutte le aziende presenti in VIA
ALCIDE DE GASPERI ci si ritrova dinnanzi un grosso problema: come
sarà inserita questa via nel database?
In modi in cui questa via può essere inserita sono molteplici:De Gasperi,
A de Gasperi, A. De Gasperi, Alcide De Gasperi, Alcide Gasperi,..., senza
considerare l’eventualità in cui sia scritta anche la denominazione urbanistica
generica, con tutte le sue varianti ed errori (V., Via, V.le, Viale,...). Inoltre,
c’è sempre la possibilità che la numerazione civica venga inserita nello stesso
campo dell’indirizzo
Utilizzando una query sql bisogna fare una ricerca che vada a trovare ogni
record che contenga il parametro cercato;
s e l e c t i d a z i e n d a , i n d i r i z z o from a z i e n d a
where i n d i r i z z o i l i k e ’%de g a s p e r i % ’;
Nel database utilizzato per questo progetto, questa query ritorna 52 risultati.
Ricercando il parametro “de gasperi” tramite il prototipo, e utilizzando come
grado di similarità il valore di default (0.75) si hanno 388 risultati, di cui i
primi 52 corrispondo con quelli della query sql. I rimanentei 336 sono tutti
quei risultati che presentano uno dei due parametri ricercati, oppure uno dei
due parametri ha un grado di similarità >= al valore posto per la ricerca.
Volendo è possibile “obbligare” il motore di ricerca a trovare tutti i record che
contengono entrambi i parametri ricercati, utilizzando l’operatore AND(de
6.2.2 Ricerca di Dati Non Esatti
50
AND gasperi). In questo caso si ottengono gli stessi risultati della query sql.
L’operatore AND non è stato implementato di default nel progetto poichè
se un utente va a cercare alcide AND de AND gasperi avrà come risultati
esclusivamente i record che contengono tutti e tre i termini, in questo caso
solamente 7 risultati, con un evidente dimezzamento dell’accuratezza della
ricerca.
In relazione a questo esempio è possibile constatare anche che, poichè le
tabelle del database non contengono errori ortografici, il grado di similarità
è abbastanza inutile, poichè l’algoritmo di Levenshtein non trova parole con
una distanza minore rispetto a quella esatta. Abbassandolo, invece, possiamo
notare come aumenti il numero di risultati.
Se viene utilizzato il parametro di ricerca de AND gasperi con grado di similarità 0.1 si hanno 515 risultati, mentre se si utilizza come termine di ricerca
de gasperi con lo stesso grado si hanno 5045 risultati.
Si potrebbe concludere, tramite questi esempi e tralasciando la mole di risultati non esatti, che il prototipo sviluppato non faccia altro che applicare
una buona query sql, la quale vada a trovare tutti i record che contengono i
parametri cercati. Questo è vero solo in parte, soprattutto perchè le ricerche
che fino ad ora sono state definite sono ricerche su dati esatti. Non c’è stata
la necessità di sfruttare a pieno le potenzialità dell’applicazione.
Si pensi ora all’ipotesi in cui un record contenga come indirizzo via de gasprei,
quindi un dato non esatto.
Con una ricerca sql è impossibile riuscire a trovare il record cercato, avendo
come unico dato via de gasperi. Facendo la stessa ricerca con il prototipo,
utilizzando un grado di similarità pari a 0.7, si riesce senza problemi a trovare
il record cercato. Anche in questo caso se l’utente è sicuro dei parametri che
deve inserire (in questo caso de e gasperi ) è preferibile utilizzare l’operatore
AND. In questo modo si può notare che si ottengono 53 risultati: 52 con
una corrispondeza pari al 90% e il risultato voluto con una corrispondenza
del 70%. Se invece l’utente non ha la totale sicurezza dei parametri da inserire, e quindi viene utilizzato di default l’opertore OR, vengono proposti
come risultati tutti quei record che hanno nel loro campo indirizzo uno dei
parametri inseriti, o un altro termine che abbia un grado di similarità pari a
quello impostato nella ricerca. Per questo motivo la mole di risultati aumenta
6.2.3 Ricerca Partendo da Dati Non Esatti
51
progressivamente con l’aumentare di parametri di ricerca. Paradossalmente
questo non significa una ricerca meno accurata,anzi: la lista ritornata dall’applicazione è una lista ordinata che inserisce ai primi posti tutti i record
che contengono tutti i parametri richiesti; successivamente vengono aggiunti
quei record che contengono tutti i parametri di cui almeno uno ha un grado
di similarità pari a quello cercato, e poi, mano a mano che si scende nella lista
si trovano record che hanno sempre meno attinenza con la ricerca. Quindi
l’importante è limitare il numero di risultati che si vogliono ottenere. Ovvio
è che se un record è stato scritto in maniera totalmente errata, per esempio
va die gasprei, sicuramente sarà presente nella lista dei risultati, ma in una
posizione verso il basso. Questo perchè tutti e tre i termini che compongono
il parametro presentano degli errori, e di conseguenza viene dato un peso
maggiore ai parametri che magari sono composti da termini che per 2/3 sono
equivalenti al termine cercato, anche se il termine rimanente ha un grado di
similarità più basso rispetto al precedente.
2.3
Ricerca Partendo da Dati Non Esatti
Oltre al problema di avere dei record che presentano degli errori, e quindi di difficile individuazione, si è cercato di risolvere anche il problema dei
parametri di input errati. Questo tipo di problema può sembrare banale, in
quanto l’utente dopo aver ricevuto un risultato errato può controllare l’input
immesso e quindi a modificarlo. Ci sono però due particolari situazioni in cui
la risoluzione di questo tipo di problema può essere d’aiuto:
1. ricevuto un input come parametro, il motore di ricerca effettua la ricerca e controlla la lista dei risultati. Se questa lista contiene pochi record
che combaciano perfettamente con il parametro cercato, e molti record
che contengono un parametro che ha un grado di similarità elevato con
quello cercato, si presuppone che l’input possa essere stato inserito in
maniera errata. Per questo motivo si potrebbe avere un “suggeritore”
che avvisa l’utente del possibile errore, ed eventualmente propone come
parametro quello contenuto dalla maggior parte dei record;
2. gli input per la ricerca possono essere passati sia in maniera manuale che
in maniera automatica. Nel secondo caso, specialmente se il numero di
6.2.3 Ricerca Partendo da Dati Non Esatti
52
dati da immettere è elevato, l’utente può non rendersi conto dell’errore,
e questo potrebbe portare ad avere dei match falliti.
Per questi motivi è opportuno che anche in fase di input l’applicazione sfrutti
le proprie potenzialità per poter aiutare l’utente a trovare quello che cerca.
Si pensi sempre di voler cercare via alcide de gasperi, utilizzando come input
via de gaperi.
Con una query sql è impossibile trovare in questo modo il record cercato.
Utilizzando la ricerca implementata per il progetto con il parametro via de
gaperi, e utilizzando il valore di default per il grado di similarità, si ha una
lista di 13680 risultati, nella quale i primi 52 elementi sono esattamente tutti
i record della tabella che hanno come indirizzo via alcide de gasperi. Lo stesso
risultato si ottiene provando ad utilizzare come parametro gaperi, mentre se
si usa asperi come input si ottiene una lista di 53 elementi:
nome
−−−−−−−−−−−−−−−−−−−
Via A s p e s i
de G a s p e r i
Via Corso De G a s p e r i
v i a De G a s p e r i
Via de G a s p e r i
Via De G a s p e r i
Via De G a s p e r i
Via A de G a s p e r i
Via A. De Gasperi , 4/6
Via A. De G a s p e r i
...
(53 r i g h e )
Quest’ultima ricerca permette di comprendere come funziona la ricerca fuzzy.
Infatti si può notare che la lista dei risultati presenta tutti e 52 record che
contengono come indirizzo via alcide de gasperi, con l’aggiunta, come primo
elemento, di Via Aspesi. Il motivo della presenza di questo record nella lista
dei risultati, sta nel fatto che la distanza di Levenshtein fra il termine Via
Aspesi e il termine asperi è minore rispetto alla distanza fra il termine Via
Aspesi e il secondo risultato delle lista, ossia de Gasperi.
É doveroso concludere questo capitolo sottolineando che la tabella azienda
6.2.3 Ricerca Partendo da Dati Non Esatti
53
sulla quale sono stati fatti questi esempi, contiene 29256 record di aziende
sparse in prevalenza sul territorio europero.
I test riportati in questa tesi sono tutti stati eseguiti utilizzando come unico
parametro di riferimento la via. Ecco il perchè la ricerca fatta utilizzando via
de gasperi come parametro ha individuato una cosı̀ grande mole di risultati.
Realmente, se un utente vuole trovare quali aziende sono presenti in una
determinata via, definisce anche il comune in cui la via si trova, in modo tale
da limitare la ricerca esclusivamente al comune specificato.
Conclusioni
Il principale obiettivo del progetto di questa tesi era lo sviluppo di un’applicazione che garantisse un miglior accesso alle informazioni, ed una risoluzione
delle problematiche inerenti alla ricerca di dati non esatti all’interno di un
database.
Con la realizzazione del prototipo è stato pienamente raggiunto l’obbiettivo,
in quanto il sistema sviluppato è completo e rispetta ogni requisito imposto
all’inizio della creazione.
L’applicazione è stata studiata in modo da poter essere utilizzata, a seconda
delle necessità, da diversi sistemi client. Per questo motivo si è utilizzata un
architettura Web Service, per rendere possibile un’universale operabilità da
parte di sistemi architetturalmente diversi, su una stessa rete.
Lo stato di persistenza è gestito tramite il framework Hibernate arrichito
con i moduli HibernateEntityManger e Hibernate-Annotations per l’implementazione delle Java Persistence API. Inoltre, per poter implementare al
meglio i tipi di ricerca che si adattavano ai requisiti del progetto, è stato
utilizzato il modulo Hibernate-Search assieme ad Apache Lucene.
La gestione dei parametri per l’ottimizzazione della ricerca (grado di similarità e numero massimo di risultati per query), è affidata ad un file esterno.
Questa feature è stata concepita con lo scopo di poter configurare e modificare
questi parametri dall’esterno, evitando in questo modo di dover ricompilare
l’intera applicazione ad ogni modifica. Inoltre questa caratteristica permette
di poter configurare il prototipo in modo veloce ad ogni ricerca, accordando
cosı̀ una quasi totale personalizzazione della ricerca.
In caso di ricerche con parametri multipli si è deciso di lasciare all’utente la
scelta di utilizzare o meno l’operatore AND. Questa preferenza è stata concepita dopo l’esecuzione di alcuni test, che hanno messo in evidenza come, in
55
caso di utilizzo dell’operatore AND con almeno un parametro non presente
nel record richiesto, la ricerca poneva il risultato cercato ad un livello cosı̀
basso nella lista dei risultati, che sarebbe passato come errato.
Il prototipo sviluppato migliora la gestione delle ricerche da parte degli utenti.
Utilizzando la ricerca full-text si ha un incremendo di velocità nell’esecuzione
della query, mentre le funzionalità della libreria Apache Lucene permettono
di usufruire di diversi tipi di ricerca (wildcard searche e fuzzy searche) che
aiutano l’utente ad avere un miglior accesso alle informazioni. Inoltre l’applicazione aiuta il matching fra più databases, creando uno strato applicativo
che va a matchare i dati determinando il massimo grado di similarità.
Si può quindi concludere che l’obbiettivo presupposto è stato pienamente
raggiunto; nonostante l’applicazione debba essere ancora lanciata nella rete
aziendale, è stata utilizzata per aggiornare il campo CAP della tabella azienda tramite il database di Poste Italiane. Questo aggiornamento ha portato
a quasi un 92% di match positivi e corretti, a fronte di un 75% di match
positivi avuti utilizzando esclusivamente query sql.
Bibliografia
[1] Tanenbaum Andrew S., Reti di calcolatori, Prentice Hall 1998
[2] Baldoni & Marchetti & Tucci-Piergiovanni, Appunti Integrativi su Sistemi
Distribuiti, 2001/2002
http://www.dis.uniroma1.it/~marchet/docs/dispensa.pdf
[3] David A Chappell & Tyler Jewell, Java Web Services, O’Reilly Media
2002
[4] AA.VV., Java Web Service, Apogeo
[5] Giuseppe Della Penna,dispense di Web Service, 2008/2009
http://www.di.univaq.it/gdellape/students.php?crs=mwtws09
[6] Giovanni Puliti, Libro di Mokabyte Capitolo 6: Remote Method
Invocation, 2001
[7] JGuru, Fundamentals of RMI Short Course, 2010
http://java.sun.com/developer/onlineTraining/rmi/RMI.html
[8] Giampaolo Cugola, Corba & DCOM
http://home.dei.polimi.it/cugola/pub/CORBA-DCOM.PDF
[9] N.Mitra et al. (2003), SOAP Version 1.2 recommendation del W3C
http://www.w3.org/TR/soap12-part0/
[10] Emmanuel Bernard & John Griffin, Hibernate Search in Action,Manning
Publications Co. Dicembre 2008
[11] Christian Bauer & Gavin King, Java Persistence with Hibernate,
Manning Publications Co. Novembre 2007
Bibliografia
57
[12] Michael McCandless & Erik Hatcher & Otis Gospodnetic, Lucene in
Action - Second Edition, Manning Publications Co. Novembre 2010
[13] Mike Keith & Merrik Schincariol, Pro JPA 2 - Mastering the Java
Persistence API, Apress Pubblications 2009
[14] Roberto Bicchierai & Pietro Polsinelli, Hibernate Search - Ricerca,
MokaByte N. 125 Gennaio 2008
[15] Lorenzo Viscanti, Applicazioni Full-Text con Apache Lucene - Una
libreria per la ricerca testuale, MokaByte N. 100 Ottobre 2005
[16] Luca Conte, Introduzione a Hibernate - I parte: configurazione,
installazione e primi passi, MokaByte N. 93 Febbraio 2005
[17] Mario Casari, La sessione di Hibernate e la transazionalità, MokaByte
N. 110 Settembre 2006
[18] Leonardo Casini, Oggetti persistenti con Hibernate - Un approccio alla
persistenza puramente ad oggetti, MokaByte N. 101 Novembre 2005
Siti consultati
[1] Web Service wiki
http://en.wikipedia.org/wiki/Web_service
[2] Web Service w3s
http://www.w3schools.com/webservices/default.asp
[3] Web Service netbeans
http://netbeans.org/kb/trails/web.html
[4] Hibernate official site
http://www.hibernate.org
[5] Lucene official site
http://lucene.apache.org
[6] Lucene wiki
http://wiki.apache.org/jakarta-lucene
[7] Enterprise JavaBean wiki
http://en.wikipedia.org/wiki/Enterprise_JavaBean
[8] JPA wiki
http://en.wikipedia.org/wiki/Java_Persistence_API
[9] JPA oracle
http://www.oracle.com/technetwork/articles/javaee/
jpa-137156.html