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