Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
FACOLTÀ DI INGEGNERIA
CORSO DI LAUREA IN INGEGNERIA INFORMATICA
DIPARTIMENTO DI ELETTROTECNICA ED ELETTRONICA
TESI DI LAUREA
IN
SISTEMI INFORMATIVI
Relatore:
Chiar.mo Prof. Ing. E. Di Sciascio
Correlatore:
Ing. S. Colucci
Laureando:
Andrea Martelli
1 Introduzione................................................................1
4 L'interfaccia grafica..................................................51
7 Conclusioni.............................................................107
Appendice.......................................................................111
1 Introduzione
risorse umane a una scienza esatta. Le competenze della forza lavoro infatti, sono da
Alcuni studi mostrano come la spesa per il miglioramento e l’arricchimento dei sistemi
gestione delle competenze (Skill Management Systems, SMS), usati dalle compagnie,
1
questo è un compito la cui complessità cresce al crescere delle dimensioni
e-learning.
Pertanto potrebbe in futuro essere esteso o integrato in un sistema più ampio in modo da
ad aziende che a personale qualificato in cerca di lavoro, con lo scopo finale di trovare il
in due fasi: da una scrematura iniziale coi metodi tradizionali dei database, si passa a
La prima fase del lavoro, quindi, è stata incentrata sullo studio della teoria che
sta alla base delle Logiche Descrittive e del linguaggio OWL, necessari alla
formulazione della descrizione dei profili individuali e dei task, richiesta dall'inferenza.
In particolare si sono studiati gli algoritmi di inferenza non standard implementati dal
2
Successivamente è stato approfondito lo “Staffing Exchange Protocol” − e nel
dettaglio la parte denominata “Position Matching Type” − uno standard messo a punto
umane. Il Position Matching Type può essere visto quindi come un formato comune di
l’uso delle librerie del GWT per realizzare l’interfaccia grafica in AJAX (Asynchronous
JavaScript and XML). Questa, oltre a comprendere i campi previsti dallo standard HR-
XML, è stata dotata di una sezione aggiuntiva per la creazione della descrizione
sistema di Servlets Java per lo scambio di dati tra client e server. Con questo approccio
relazionale.
dell’algoritmo di match uno-a-uno, che restituisce infine i dati del miglior candidato
3
Nel caso di studio oggetto di questa tesi, il linguaggio OWL verrà utilizzato per
OWL [8] è infatti un linguaggio tramite il quale è possibile scrivere delle ontologie che
del Web semantico, che è un’estensione in continua evoluzione del world wide web,
nella quale i contenuti possono essere espressi non solo in linguaggio naturale, ma
anche in una forma che può essere capita, interpretata ed elaborata dai calcolatori,
un sistema di conoscenza distribuito. Questa visione è in gran parte dovuta alle idee
diffuse dal direttore del W3C (World Wide Web Consortium), Tim Berners-Lee, il quale
in base a parole chiave pure e semplici, ma si cerca di fornire una “spiegazione” dei
Per lo sviluppo del progetto ho utilizzato diversi strumenti software, tutti con
licenza GPL (Gnu Public License) eccetto un plugin commerciale per l’IDE, e tutti
(JDK) e al Java Runtime Environment (JRE) della Sun, e utilizzando come tool di
sviluppo l’IDE Eclipse, open source e ideato dalla Eclipse Foundation, un consorzio di
4
grandi società del settore. In accoppiata ad Eclipse, ho utilizzato il plugin GWT
grafica.
5
2 Description Logics & OWL
problemi di inferenza standard e non standard. La relazione tra DL e OWL verrà inoltre
introdotta.
questo i ricercatori hanno fatto riferimento a teorie già in uso nella scienza cognitiva.
Dal momento che la conoscenza è usata per ottenere dei comportamenti intelligenti,
premesse. Alcune questioni centrali in questo ambito, dal punto di vista dell' I.A., sono:
6
Uno schema di rappresentazione deve ridursi a un particolare dominio o
Possiamo dire che la risoluzione di un problema dipenderà strettamente dalla scelta che
Tra i vari approcci che nel tempo sono stati proposti, quello delle “reti
precedenza, del W3C. Una rete semantica consiste in un grafo in cui ogni nodo
rappresenta un concetto, mentre gli archi sono usati per definire relazioni tra concetti.
Nonostante questo tipo di approccio sia ormai superato, è bene ricordare che alcune
delle sue astrazioni hanno costituito una spinta allo sviluppo delle logiche descrittive e
Le logiche descrittive [11] sono una famiglia di formalismi utilizzati per rappresentare
definiti i concetti rilevanti per quel dominio e, di seguito, utilizzando questi concetti si
specificano le proprietà degli oggetti e degli individui appartenenti al dominio. Gli studi
modo sempre più specifico il dominio di interesse tale da essere utilizzati per costruire
7
un sistema di trovare delle conseguenze implicite rispetto a quelle esplicite
rappresentate dalla conoscenza. Sistemi così caratterizzati sono definiti sistemi basati
Gli studi sulla rappresentazione della conoscenza iniziarono negli anni 70' e
terminologici per poi spostarsi sui costrutti ammessi dai linguaggi, e più recentemente i
risultati ottenuti sono stati utilizzati per lo sviluppo di linguaggi per la rappresentazione
descrittive, queste sono caratterizzate da una particolare capacità espressiva che può
Andando più nello specifico, gli elementi di base della logica descrittiva sono
concetti e ruoli. Per concetti si intendono degli insiemi di oggetti, per esempio
8
possono essere per esempio laureatoIn, esperienza, livelloDiConoscenza,
ingegneria.
carattere tecnico.
ProgrammazioneAdOggetti e ProgrammazioneStrutturata :
9
Il linguaggio che useremo è di tipo ALN (Attributive Language with
8 R.C restrizione universale. Gli oggetti che partecipano alla relazione R il cui
“soddisfacibilità”, che è legata alla coerenza interna della descrizione – il che equivale
(subsumption) che tiene conto delle relazioni di maggiore o minore specificità tra
[1]:
10
Definizione 1 Sussunzione. Data una DL , siano P e T due concetti in L, e la
sussume P in relazione a T .
Come esempio ipotizziamo che T sia l'ontologia del dominio delle skill, e T e P siano
lavoro in questione e in questo caso si parla di “full match”, che è il miglior tipo di
match.
11
in T . Ovvero, la richiesta (il curriculum) è contraddittoria. Allo stesso modo, se C=P u
incompatibilità tra P e T.
Dal momento che OWL ed RDF nascono grazie ad XML [5] e ne costituiscono
• Devono essere comprensibili in funzione dello scopo del tag stesso, anche per
12
• devono rispettare delle regole: non possono iniziare con caratteri speciali o
minuscole. Queste regole sono simili a quelle di assegnazione delle variabili nei
Ogni elemento viene chiamato nodo e ogni tag può essere accompagnato da degli
attributi. La struttura che questi nodi formano può essere vista concettualmente come un
database o come un albero, a seconda degli scopi. Ogni nodo può contenere altri
elementi, siano essi valori numerici, testuali, o nodi a loro volta. I tag che non hanno
l'omonimo tag di chiusura vanno chiusi con uno slash (/) finale. Per poter essere
correttamente interpretati dai browser, i documenti XML devono essere “ben formati”,
• un unico elemento radice, ovvero il nodo principale che contiene tutti gli altri;
• tutti i tag devono esser chiusi. Ad esempio <html> va chiuso con <html/>.
documenti xml.
13
2.4 Panoramica sul linguaggio OWL
definizione delle loro proprietà e delle loro instanze, ed è progettato per essere usato in
presentare le informazioni stesse agli esseri umani. Inoltre mira a permettere una
maggiore interpretabilità del contenuto web rispetto a quella supportata da formati dati
come XML, RDF e RDF Schema, per mezzo principalmente di un vocabolario più
OWL è visto come una delle tecnologie principali per l'implementazione futura
applicazioni, con un ambito di applicazione sempre più largo, e sta spingendo lo studio
contenuto semantico delle informazioni presenti nel web e per aumentare la facilità
possono essere facilmente scambiate tra tipi diversi di computer utilizzando diversi
sistemi operativi e applicazioni scritte in linguaggi anche molto diversi fra loro. Dato
14
che è mirato all'elaborazione automatica, spesso viene considerato non facilmente
2.4.1 Sottolinguaggi
• OWL Lite: utile agli utenti che hanno bisogno di utilizzare classificazioni
facile produrre strumenti software per Owl lite che per i suoi parenti più
espressivi.
essere usati entro certi limiti come ad esempio la separazione dei tipo (una classe
non può essere anche un individuo o una proprietà, e una proprietà non può
anche essere un individuo o una classe). E' chiamato così per il suo legame con
• OWL Full: è dotato delle massime espressività e libertà sintattica possibili con
RDF, ma non offre garanzie computazionali. Ad esempio, una classe può essere
15
trattata contemporaneamente sia come una collezione di individui, che come un
Full.
Ognuno di questi linguaggi è un'estensione del suo predecessore più semplice, sia per
quanto riguarda quello che può essere legalmente espresso, sia per le conclusioni valide
2.4.2 Sintassi
AllDifferent, distinctMembers;
16
• Caratteristiche delle proprietà: ObjectProperty, DatatypeProperty, inverseOf,
someValuesFrom
Tuttavia, quelle che per i nostri scopi assumono maggior rilievo sono quelle che
possono essere messe in relazione con la sintassi di Description Logic. Nella tabella
17
< owl: maxCardinality /> ·
< owl: minCardinality /> ¸
<owl: cardinality /> =
Questo sottoinsieme dei tag di OWL consente di esprimersi in logica ALN . Vediamo, a
<rdf:Description rdf:about="http://sisinflab.poliba.it/unnamed.owl#jack">
<rdf:type>
<owl:Class>
<owl:intersectionOf rdf:parseType="Collection">
<owl:Class rdf:about="http://sisinflab.poliba.it/unnamed.owl#AssetManager"/>
<owl:Restriction>
<owl:onProperty rdf:resource="http://sisinflab.poliba.it/unnamed.owl#advanced_knowledge"/>
<owl:allValuesFrom>
<owl:Class>
<owl:intersectionOf rdf:parseType="Collection">
<owl:Class rdf:about="http://sisinflab.poliba.it/unnamed.owl#Programming"/>
<owl:Class rdf:about="http://sisinflab.poliba.it/unnamed.owl#Computer_Graphics"/>
</owl:intersectionOf>
</owl:Class>
</owl:allValuesFrom>
</owl:Restriction>
<owl:Restriction>
<owl:onProperty rdf:resource="http://sisinflab.poliba.it/unnamed.owl#advanced_knowledge"/>
<owl:minCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#int"
>1</owl:minCardinality>
</owl:Restriction>
<owl:Restriction>
<owl:onProperty rdf:resource="http://sisinflab.poliba.it/unnamed.owl#has_industry"/>
<owl:allValuesFrom rdf:resource="http://sisinflab.poliba.it/unnamed.owl#Computer_Software"/>
</owl:Restriction>
</owl:intersectionOf>
</owl:Class>
</rdf:type>
</rdf:Description>
18
Per quanto a prima vista possa sembrare ostico, leggendolo si evince che Jack è un
assett manager con conoscenza avanzata nei campi della programmazione e della
sia la soddisfacibilità che la sussunzione forniscono risposte booleane, nel senso che
restituiscono una risposta di tipo vero/falso. Per i nostri scopi, invece, è necessario
prevedere sia una spiegazione che una possibilità di rivedere i requisiti (belief revision),
per poter gestire in maniera ottimale anche casi in cui non c'è perfetta corrispondenza
richieste iniziali sulla base di nuove informazioni che ci pervengono. Così, se P uT non
potremmo ritrattare alcuni requisiti G (Give Up) in T, per ottenere una nuova richiesta
revision [2].
19
di Concept Contraction (CCP), identificato con hL, T, P, T i, consiste nel trovare
una coppia di concetti hG, K i 2 L £ L tali che T j=T ´ G uK, e che K uP sia
hG, K i = hT; >i, che corrisponde a rinunciare interamente a T. Nel nostro sistema di
gestione delle competenze, questo rispecchia la situazione nella quale, a fronte di alcuni
corrisponde a non rinunciare a niente. E' scontato che in una situazione reale si cercherà
ottimale tra domanda e offerta (full match), può essere interessante cercare una
spiegazione del perché ciò non avvenga. In altre parole, si può voler sapere quali sono le
compito richiesto, ovvero le competenze che dovrebbe acquisire per diventare idoneo.
20
• Definizione 4 Concept Abduction Siano S e D due concetti espressi in una
che dovrebbe essere aggiunto a P per renderlo più specifico di T, rendendo valida la
primo, sarebbe interessante sapere quale parte dei requisiti non è coperta dal CV.
curriculum P non viene interpretata come la sua negazione. Viene adottata cioè
specificare, o che non ha ritenuto importante precisare. Proprio per questa ragione, con
compatibilità, risolvendo un CAP, può essere fornita una possibile spiegazione H sul
21
perché il match non si verifichi; in caso di incompatibilità, invece, possiamo risolvere
è fornita inoltre una richiesta contratta K compatibile con P. In entrambi i casi siamo in
ricaviamo la spiegazione H' del perchè non abbiamo un match completo. In definitiva G
e H' sono le spiegazioni in caso di incompatibilità. Nel nostro sistema, per scegliere il
candidato che si discosta meno dai requisiti possiamo avvalerci di una funzione, detta
“match degree function (or Utility)” [2], che effettua una valutazione numerica delle
U : hG; H; T i ¡! R
Questa funzione può essere usata sia in caso di incompatibilità che di compatibilità
(dove nel secondo caso si avrà G = >), ed è basata sulla funzione rankPotential [3],
utilizzata per classificare i match potenziali, che vediamo qui nella sua struttura di base:
Algorithm rankPotential(C, D)
input concepts C, D, in forma normale, tali che C uD è soddisfacibile
output rank n ¸ 0 di C rispetto a D, dove 0 significa C v D (best
ranking)
begin algorithm
let n := 0 in
22
/* aggiunge ad n il numero dei nomi di concetti in D
che non sono tra i nomi di concetti in C */
1. n := n + jDnames+ ¡ Cnames+ j;
/* aggiunge ad n le restrizioni numeriche di D
che non sono implicate da quelle di C */
2. for each concept (¸ x R) 2 D#
such that there is no concept (¸ yR) 2 C# with y ¸ x
n := n + 1;
3. for each concept (· xR) 2 D#
such that there is no concept (· yR) 2 C# with y · x
n := n + 1;
/* per ogni restrizione universale in D
aggiunge il risultato di una chiamata ricorsiva */
4. for each concept 8R:E 2 Dall
if there does not exist 8R:F 2 Call
then n := n + rankP otential(>; E);
else n := n + rankP otential(F; E);
return n;
end algorithm
h = rankP otential(P; K; T )
23
• g: valutazione di G soluzione di un CCP tra Pi e T –
g = rankP otential(K; T; T )
misura numerica delle rinunce che si sono dovute accettare e delle ipotesi che sono state
Il processo che assegna un lavoratore a un incarico richiede l'uso sia dell'abduction che
della contraction, e la scelta del candidato migliore è fatta sfruttando la funzione Utilità
1: Algorithm Assign(T; P; T )
2: input P, T ´ K u G concepts in L such that both T j= P 6´ ? and T j= T 6´ ?
3: output hPassigned ; H; Gi
4: begin algorithm
5: Passigned ´ >;
6: Umin = 1;
7: N = rankP otential(>; T; T );
8: for each Pi 2 P
9: if T j= T u Pi ´ ?
10: then
24
11: hGi ; Ki i = contract(Pi ; T; T );
12: else
13: Ki = T ;
14: end if
15: Hi = abduce(Pi ; Ki ; T );
16: ki = rankP otential(>; Ki; T );
17: hi = rankP otential(Pi ; Ki ; T );
18: gi = rankP otential(Ti; Ki ; T );
19: Ui = u(N; ki ; hi ; gi );
20: if Ui < Umin
21: then
22: Umin = Ui ;
23: Passigned = Pi ;
24: H = Hi ;
25: G = Gi ;
26: end if
27: end for each
28: return hPassigned ; H; Gi;
29: end algorithm
profili disponibili Pi, insieme a una spiegazione H delle competenze che dovrebbe
possedere per ricoprire completamente i requisiti T, e al concetto G nel caso in cui sia
stata necessaria una contrazione preliminare dei requisiti. Il profilo che minimizza U
25
viene scelto. Come vedremo nel capitolo 5, l'algoritmo può essere preceduto da un
query a un database.
Contraction, Concept Abduction e RankPotential. E' sviluppato dal gruppo di ricerca del
MaMaS non sono implementati da nessun altro ragionatore, e attraverso essi sarà
possibile implementare l'algoritmo di match uno a uno visto nel precedente paragrafo.
26
quattro tipi di richiesta: identificazione, gestione della knowledge base, tell and ask. che
corpo dei pacchetti di risposta è codificato in XML secondo la sintassi tipica dello
standard DIG.
stata estesa per fornire il supporto ai servizi di inferenza tipici del MaMaS. Una delle
caratteristiche principali del sistema è la monotonicità delle richieste TELL: una volta
che delle informazioni sono state aggiunte alla KB, non possono più essere modificate,
almeno fino a quando non si rilascia la KB e se ne crea un'altra. Ogni messaggio, infatti,
è identificato mediante l'URI della knowledge base, che è un codice alfanumerico che
ALN Dal momento che le descrizioni semantiche generate dal sistema sono in
27
1.2: corrispondenza tra DL ALN , OWL-DL e DIG
28
3 HR-XML e Staffing Exchange Protocol
intelligente di supporto alle decisioni nell'ambito della gestione delle risorse umane. Vi
sono due requisiti fondamentali per il funzionamento del sistema: la persistenza dei dati
lavoratori abbiano tutti la medesima struttura (altrimenti non potrebbero essere inseriti
nella stessa tabella del DB). Nella progettazione della struttura da dare ai dati, e di
Questa scelta è dovuta sia al successo che l'iniziativa del consorzio sta ottenendo, e sia
al fatto che, dal momento che l'applicazione oggetto di questa tesi si inserisce nel grande
progetto del Web semantico, è stato ritenuto necessario seguire uno standard
29
3.1 Il consorzio HR-XML
allo sviluppo e alla promozione di uno standard di specifiche XML di supporto all'e-
aperti basati su XML, il consorzio fornisce ad ogni compagnia il mezzo per trattare con
importanti compagnie del campo dell' Information Technology, che fungono anche da
sponsor e finanziatori.
esistenti solo pochi anni fa. Ogni parte del processo è stata isolata, migliorata e, in molti
casi, è stata resa orientata al web per realizzare una sempre maggiore efficienza.
30
E' possibile quindi che una gran varietà di tecnologie sia coinvolta in un singolo
Lo Staffing Exchange Protocol è uno dei protocolli creati dal consorzio HR-
XML, e supporta molti tipi di transazioni inerenti allo scambio di risorse umane,
• Curriculum (Resume)
• Candidato
automatizzata tra domanda e offerta di lavoro. E' pensato in modo da contenere tutte le
e mette in risalto alcune informazioni a discapito di altre. Inoltre, può contenere dati su
31
Un'altra importante differenza è che mentre la formattazione e la presentazione
sono aspetti importanti per un curriculum, che può essere redatto con le tecniche più
disparate (HTML, Flash, PDF...), sono invece marginali per il profilo del candidato, che
dev'essere flessibile.
Profilo del candidato e curriculum si differenziano anche per il modo in cui sono
memorizzato in un database, accessibile via web mediante nome utente e password. Nel
più applicazioni possibili. Due dei modelli più comunemente usati nella realizzazione
lavorativa, e molti candidati vengono analizzati per trovare quelli che meglio si
adattano alle specifiche. E' un metodo incentrato sul datore di lavoro e usato
molte offerte lavorative vengono filtrate per trovare quelle più idonee per il
32
candidato stesso. E' un metodo incentrato sul lavoratore e usato in contesti nei
aggiuntivo.
33
Il datore di lavoro rende disponibile una posizione lavorativa, le cui specifiche
sono spesso basate su una descrizione preesistente del compito da assegnare. Una volta
34
3.2.2.2 Modello a imbuto rovesciato
infine viene prodotta una lista di posizioni lavorative alle quali il candidato potrebbe
elencati i posti disponibili; se invece è interessato alla carriera, viene fatta un'analisi
lavorativi.
35
3.3: Schema grafico del processo a imbuto rovesciato
In figura vediamo lo schema di un possibile processo di assunzione per un
36
Il modello utilizzato nella progettazione del sistema oggetto della tesi è quello a
imbuto standard. Passiamo ora ad analizzare la struttura che i dati devono avere secondo
il protocollo.
candidato potrebbe rispondere, i dati dell'agente nel caso in cui il candidato sia
rappresentato da una terza persona o linee guida per regolare la diffusione dei dati
37
5.3: schema di CandidateType
Come si può notare anche dalla figura, lo schema comprende un gran numero di
soltanto ad alcuni sotto-elementi dello standard SEP. In particolare, per quanto riguarda
nell'albero che definisce il protocollo nella posizione illustrata dalla figura seguente:
sulle sue preferenze e sulle sue competenze. Inoltre costituisce la parte che più di ogni
lavoro. Infatti quasi tutti gli elementi qui presenti compaiono anche in Position
Opening. Quando questi dati sono presenti in Candidate, rappresentano quello che una
38
persona richiede o desidera, mentre quando sono presenti in Position Opening,
39
Ognuno dei sotto-elementi di PreferredPosition è identificato dal nome e dal
tipo di dato, che può essere semplice – come nel caso di String o Integer – o composto,
e in questo caso bisognerà far riferimento allo schema corrispondente, che specificherà
dimensioni dell'azienda, collocazione fisica del posto di lavoro, tipo di lavoro e sua
lavorativo, possibilità di trasferimento e viaggio. Questi moduli saranno visti più nel
dettaglio in seguito.
quello del candidato, con le informazioni sulla transazione ai livelli più alti dello
schema.
40
8.3: Schema di PositionOpeningType
questione. Dal momento che ogni sotto-elemento dello schema è molto esteso, anche in
questo caso ci limiteremo all'analisi del modulo denominato “Position Detail”, duale
41
Lo schema del modulo è identico a quello già visto per “Preferred Position”,
con al sola differenza che al posto dell'elemento “Commute”, ora è presente il seguente:
Per stabilire una corrispondenza tra domanda e offerta nel processo di selezione
dei candidati, è necessaria una struttura che funga da collegamento tra i moduli
42
Il modulo comprende molte più informazioni di quelle reperibili comunemente
in un annuncio di lavoro. Sono inclusi infatti altri dati che possono essere raccolti in
informazioni meno strutturate facenti parte del modulo sono: informazioni sull'azienda
(nome, ID, dimensione), informazioni generali sul tipo di lavoro, sul suo stile e sulle
43
12.3: Schema principale di “Position Matching Type”
44
Questo modulo contiene tutte le informazioni utili all'individuazione geografica
di un posto di lavoro, che può anche essere diverso da quello del datore di lavoro. Nello
schema del candidato, specifica dove il candidato preferirebbe lavorare. Sono supportati
diversi tipi di dati: dalle coordinate geografiche, all'indirizzo esatto, alla definizione
45
Questo modulo mira a fornire una descrizione dell'indirizzo postale abbastanza
ogni Paese.
46
Il dato più comune riguardante gli individui che è possibile trovare pressochè in
ogni processo lavorativo è il nome. I nomi hanno diverse componenti, e queste possono
variare da uno Stato all'altro o da un'etnia all'altra. Dal momento che l'uso dei nomi può
variare anche in base allo scopo per il quale si utilizzano, bisogna prevedere sia la
possibilità di trattarli come stringhe monolitiche che come dati compositi. Il modulo
47
Contiene tutte le informazioni riguardanti il salario, i pagamenti extra e i servizi
3.2.5.5 Shift
48
Contiene informazioni dettagliate sui giorni e le ore associati a un particolare
più importante e definisce l'intervallo di tempo a cui gli altri dati fanno riferimento.
3.2.5.6 Competency
competenze dell'individuo, che nella logica del protocollo SEP sono ritenute attributi
49
18.3: Schema del modulo “Competency”
50
I moduli appena visti saranno il riferimento per la costruzione dell'interfaccia
51
4 L'interfaccia grafica
Nel capitolo precedente sono state esaminate le componenti alla base della
candidato che vuole inserire il proprio curriculum nel sistema, sia all'azienda che
lavoratore.
sia ugualmente raggiungibile via web, è stata scelta la tecnologia Ajax, della quale
4.1 AJAX
web per creare applicazioni web interattive. L'intento di tale tecnica è quello di ottenere
pagine web che rispondono in maniera più rapida, grazie allo scambio in background di
piccoli pacchetti di dati con il server, così che l'intera pagina web non debba essere
ricaricata ogni volta che l'utente effettua una modifica. Questa tecnica riesce, quindi, a
52
• HTML (o XHTML) e CSS per markup e stile
con le informazioni.
dell'utente e il server.
1.4: confronto tra l'interazione client-server di un'applicazione web classica e di una basata su Ajax
53
Le applicazioni web tradizionali consentono agli utenti di compilare moduli e,
quando questi moduli vengono inviati, viene inviata una richiesta al web-server. Il web
server agisce in base a ciò che è stato trasmesso dal modulo e risponde mostrando una
nuova pagina. Dato che molto codice HTML della prima pagina è identico a quello della
dipende dal tempo di reazione del web server, e questo comporta che l'interfaccia utente
diventa molto più lenta di quanto dovrebbe essere. Se però lo scambio dei dati è
reattiva.
grazie alla traduzione del codice Java in codice Javascript. Per questa ragione è
possibile utilizzare solo un sottoinsieme delle librerie standard del Java (java.lang
filesystem.
54
Come scelta progettuale, quindi, si demanda gran parte dell'elaborazione
pagine web già pronti per l'uso, denominati “Widgets” che possono poi essere estesi
55
4.3 L'interfaccia grafica
adottate.
56
3.4: Pannello di accesso:l'utente sceglie una delle due opzioni in base al ruolo che ricopre.
Questa è la prima schermata che l'utente si trova di fronte. Come si vede, vi sono
• come lavoratore: i dati che in seguito l'utente inserirà saranno utilizzati per la
• come azienda: i dati inseriti verranno utilizzati per formulare una query da
VerticalPanel e FlexTable. L'interattività è ottenuta grazie alla gestione degli eventi (si
dice appunto che l'interfaccia è “event driven”), che permette di associare delle azioni a
determinati elementi. In questo caso, per esempio, ai due pulsanti sono stati associati dei
57
4.4: ClickListener associato al pulsante “Add your profile”
58
5.4: Pannello del candidato
corrispondenti moduli dello Staffing Exchange Protocol. Inoltre è stata aggiunta la parte
Dal momento che, come già detto nel capitolo sul protocollo SEP, i dati inseriti
all'azienda, fatte salve soltanto alcune componenti della scheda “Other Informations”.
La prima scheda dello stack panel, visibile nella figura precedente, contiene,
oltre a una breve spiegazione degli scopi del form, i campi fondamentali per
“Add” che permette di aumentare il numero di istanze degli stessi elementi (fino a un
59
massimo definito nel codice), visto che secondo il modello del SEP, questi attributi
nell'inserire una nuova copia degli elementi costituenti il Composito in una nuova riga
Questo pannello non rispecchia nessun modulo del protocollo SEP, a differenza
degli altri, ma è stato costruito riunendo insieme alcuni elementi basilari nella
60
semplificata dei dati anagrafici e dell'indirizzo, oltre ad alcune informazioni sulle
preferenze lavorative e a un campo per l'invio del curriculum in formato testo al server.
previste dallo standard ISO 3166-2. Il campo Upload curriculum permette di inviare
61
8.4: Upload widget dopo il corretto completamento di un upload
Vediamo qui la parte cruciale del codice: l'impostazione dei parametri del form e
9.4: impostazione parametri del form e gestione dell'evento onSubmitComplete, il cui nome è
autoesplicativo
sia inizializzato con l'URL relativo sul quale è mappata, nei file di configurazione del
server, la servlet. Una volta invocato il metodo submit(), i valori contenuti negli
62
elementi figli del form dotati dell'attributo name, vengono inviati usando il metodo
HTTP prescelto. E' da notare che i metodi HTTP standard non prevedono alcuna
che vedremo in seguito, costituisce il nucleo funzionale del sistema, dal momento che
gli altri non sono stati utilizzati per l'invio di dati. Questo però si potrebbe ottenere
63
10.4: Pannello Locations
Contiene informazioni sull'area geografica nella quale risiede il lavoratore o
maggiore di uno, è prevista l'aggiunta di altre istanze dello stesso pannello in tab
64
instanziando un nuovo elemento di questo ad ogni pressione sul tasto Add, o eliminando
65
12.4: Pannello JobPosition
Questo pannello contiene informazioni sul tipo e sul nome della posizione
dall'azienda o offerti dal lavoratore). Sia JobCategory che Shift possono avere
molteplicità maggiore di uno, e per entrambi è stato usata una visualizzazione a schede
66
4.3.7 Competencies (Competency.java)
della competenza, e le seconde indicano le sedi nelle quali la competenza stessa è stata
67
13.4: Pannello Competencies
Come già visto nel capitolo sullo Staffing Exchange Protocol, questo pannello
contiene informazioni sulla paga base, sulle eventuali paghe aggiuntive, e sulle
agevolazioni che l'azienda può fornire ai suoi dipendenti (o che l'aspirante dipendente
richiede).
68
14.4: Pannello Remuneration Package
69
dalla descrizione del 'livello' del posto di lavoro, sempre facendo riferimento a
70
4.3.10 Composizione della descrizione semantica
Description”, che è uguale sia nel 'lato azienda' che nel 'lato lavoratore'. E' il nocciolo
del progetto, dal momento che costituisce l'elemento di innovazione rispetto ai sistemi
basati sul semplice confronto 'testuale' delle informazioni, il più delle volte contenute in
persona fisica. Infatti se il profilo viene costruito da un lavoratore che sta immettendo i
propri dati nel sistema, esso rappresenta la descrizione della sua formazione
professionale, delle conoscenze possedute e del grado di esperienza acquisito nei vari
ambiti; se invece il profilo viene creato da un'azienda, esso rappresenta i requisiti che i
candidati devono possedere per essere ritenuti idonei. Tuttavia, dal punto di vista dei
nell'ontologia.
71
17.4: Pannello per la descrizione semantica. A scopo dimostrativo, nell'immagine sono
contemporaneamente visibili tutti e tre gli alberi corrispondenti alle tre ontologie impiegate
componenti da utilizzare nella descrizione, sono state utilizzate tre ontologie distinte,
identificate dai nomi Degree (per le lauree e i titoli di studio), Level (per il livello di
certificazione ottenuto, es.: master, Phd) e Skill (per competenze, titoli di lavoro,
esperienza). Grazie a questa suddivisione, l'interfaccia guida l'utente attraverso tre step
72
Schematicamente, i passi da seguire possono essere descritti così:
• Step 1: selezione di un elemento dal riquadro Degree e click sul pulsante “+”.
• Step 3: selezione dall'apposita lista di una proprietà. Se per questa non è definito
proprietà.
Esempio: has_job_title ha come range Job_Title. Quindi si può scegliere tra una
Inoltre, facendo doppio click su un nodo, l'utente può eliminarlo insieme a tutti i
73
18.4: Schema di funzionamento del pannello semantico
Vedendo il pannello in questione come una black box, si può dire che questa
mediante l'interazione con l'utente, fornisca in uscita un documento XML che riproduce
altre disattivate per mezzo della funzione Steps(), che viene chiamata ad ogni
studiando. E' abbastanza facile, infatti, convertire alberi XML in widget di tipo Tree e
viceversa.
Dal momento che dal lato client non si ha accesso al file system e si può
ontologie, e quindi i rispettivi file .owl, siano memorizzati sull' hard disk del server.
Bisogna quindi elaborare le ontologie sul server e inviarle al client in un formato che
74
possa manipolare con le poche librerie a disposizione. Si è optato quindi per una
<Degree>
<Bachelor/>
<Computer_Science/>
<Design>
<Architecture/>
<Industrial_Design/>
<Interior_Design/>
</Design>
<Engineering>
<Bio_Engineering/>
<Chemical_Engineering/>
<Civil_Structural_Engineering/>
<Computer_Science_Engineering/>
<Electrical_Engineering/>
<Electronics_Engineering/>
<Industrial_Manufacturing_Engineering/>
<Managerial_Engineering/>
<Mechanical_Engineering/>
<Systems_Process_Engineering/>
</Engineering>
<Master_Degree/>
<Mathematics/>
<Physics/>
<Secondary_School/>
</Degree>
75
20.4: Funzione XML2Tree: converte alberi XML in strutture grafiche di tipo TreeItem
E' da notare che mentre le ontologie Degree e Level vengono caricate una sola
volta, l'ontologia Skill viene “interrogata” ogni volta che si seleziona una proprietà.
<Properties>
<advanced_knowledge/>
<has_experience range="Skill"/>
<years/>
<has_job_title range="Job_Title"/>
<tools_knowledge range="Skill"/>
<has_industry range="Industry"/>
<has_job_type range="Job_Type"/>
</Properties>
ovvero una struttura che associa chiavi a valori. La chiave sarà il nome della proprietà, e
76
La funzione BuildSummaryTree(), in base allo step nel quale ci si trova e agli
elementi definiti nelle varie sezioni del pannello, costruisce l'albero descrivente il
profilo dell'individuo. Nel fare ciò verifica che non vi siano elementi duplicati e
consente inoltre di aggiungere sotto-nodi a nodi già presenti nell'albero quando questi
XMLProfile().
<profile>
<class name="Computer_Science"/>
<class name="Phd"/>
<restriction name="advanced_knowledge"
sign="gt" value="1"/>
<property name="has_job_title">
<class name="Programmer"/>
<restriction name="years" sign=">" value="5"/>
</property>
</profile>
necessario compiere delle elaborazioni che sul client non sarebbero possibili, vista la
77
limitatezza del linguaggio Javascript; è necessario inoltre utilizzare un database
posizionandolo sul server; infine è necessario accedere a risorse memorizzate su files (le
ontologie), e l'interfaccia Web non consente ovviamente niente del genere. Queste
semplicemente come un terminale per interfacciarsi al sistema. Inoltre, dato che non si
Similmente a quanto fatto per il pannello semantico nel lato client, si può dare
78
1.5: Schema di principio dei servizi offerti dal server
quattro servlet java. Le servlet sono oggetti (in senso informatico) che operano
funzionalità.
La parola servlet deriva da una precedente, applet, che si riferisce a piccoli programmi
79
delle richieste HTTP, e sono associati a un determinato URL. Il mapping dei servlet
<module>
<inherits name="com.google.gwt.user.User"/>
<entry-point class="com.mycompany.project.client.ImageViewer"/>
<servlet path="/onto" class="com.mycompany.project.server.OntologyServletImpl"/>
<servlet path="/send" class="com.mycompany.project.server.SendDataServletImpl"/>
<servlet path="/fileupload" class="com.mycompany.project.server.UploadServlet"/>
<servlet path="/filedownload" class="com.mycompany.project.server.DownloadServlet"/>
<inherits name="com.google.gwt.xml.XML"/>
</module>
Upload e Download sono servlet java “classiche”, ovvero estensioni della classe
state realizzate in questo modo perchè devono svolgere compiti relativamente semplici.
richieste del client. Sono state realizzate sfruttando i vantaggi dell'infrastruttura per le
80
La differenza fondamentale tra le applicazioni GWT (e in generale Ajax) e le
normali applicazioni web HTML, sta nel fatto che le prime non hanno bisogno di
prelevare nuove pagine HTML durante la loro esecuzione, che è simile a quella dei
possono aver bisogno di prelevare dati dal server. Il meccanismo di interazione con il
server attraverso la rete si chiama Remote Procedure Call (RPC). In GWT, tramite
questo strumento, è possibile passare e ottenere oggetti Java dal server sfruttando la
connessione HTTP. Inoltre il sistema messo a punto dal team di Google permette di
sfruttare facilmente i vantaggi delle chiamate asincrone tipiche del metodo AJAX, senza
bisogno di addentrarsi troppo nel mondo dei metodi HTTP, Javascript e della
rinominata in isSerializable, e implementata dalla gran parte degli oggetti delle librerie
GWT. Secondo la terminologia usata dagli sviluppatori, le chiamate al server sono dette
anche servizi.
servizio. Ogni servizio è dotato di alcune classi e interfacce “aiutanti”, in parte definite
81
3.5: Schema dell'impianto RPC del GWT
(sincrona e asincrona), che si differenziano solo per il tipo di dati restituiti dai metodi e
per i parametri di questi ultimi. Nell'interfaccia *Async, infatti, ogni metodo ha come
gestisce gli eventi onSuccess e onFailure. In seguito si vedranno i dettagli delle servlet
implementate.
82
5.3 Upload Servlet
volta creato il form e assegnati i nomi ai campi, si imposta l'azione, ovvero l'URL
uploadForm.setAction("/fileupload");
uploadForm.setEncoding(FormPanel.ENCODING_MULTIPART);
uploadForm.setMethod(FormPanel.METHOD_POST);
una richiesta, verifica che sia codificata come MULTIPART, dopo di che scorre tutti gli
elementi ricevuti finchè non ne trova uno di tipo File. Avvalendosi di alcune classi delle
file (inizialmente è identificato col percorso completo che esso aveva sul client) e se ne
verifica la sua esistenza nella directory di default destinata all'upload dei files, che è
appunto “uploads/”. Se il file esiste già, viene rinominato aggiungendo al suo nome un
83
numero compreso tra 0 e 1000 generato casualmente, finché non si trova un nome non
utilizzato.
Nel caso in cui il file è dotato di estensione, si aggiunge il numero al nome preservando
l'estensione.
Una volta terminata la scrittura del file, utilizzando l'output stream fornito da
path del file appena scritto. Il client memorizza in una variabile il path e lo invia al
file del suo curriculum. Nel caso in cui la transazione non vada a buon fine il client,
conoscendo il nome del file appena scritto sul server, può invocare nuovamente il
questo modo si evita di occupare spazio sull'hard disk per file relativi a transazioni
fallite.
84
5.4 Download Servlet
restituisce infine al client, attraverso una struttura di tipo Map, i dati del candidato
prescelto. Tra questi c'è anche il nome del file del curriculum associato al candidato
stesso, nome che viene usato per creare un apposito link tramite il quale scaricare il file.
Se però al candidato non è associato nessun file, il relativo link non viene mostrato.
altri casi, in quanto viene aperta una nuova finestra inizializzata all'url del servlet, con il
parametro da passare al server codificato nell'url stesso, come nella figura seguente:
6.5: Chiamata del servlet per il download del file con parametri passati attraverso l'URL
In realtà più che un semplice link HTML, la chiamata del servlet è stata fatta
all'interno di un EventListener.
85
richiesta, estrae il parametro “filename” dall'header Http, imposta i parametri della
impostata uguale alla lunghezza del file e si scrive un appropriato header nel pacchetto
8.5: Scrittura del file sull'output stream della risposta. bbuf è un array di 4 Kbyte
quella che il client delega ad accedere alle ontologie. Costituisce in effetti un'interfaccia
86
Come detto in precedenza, si dispone di tre ontologie sul server, che dovranno
essere utilizzate dal client per costruire una descrizione semantica coerente
dell'individuo. E' necessario quindi che il servlet, quando invocato, legga il contenuto
delle ontologie, ovvero dei file .owl, crei un modello e recuperi le informazioni
comprensibile. Gran parte di queste operazioni vengono svolte grazie alle librerie Jena
web semantico e in grado di elaborare dati nei linguaggi RDF, RDFS e OWL.
caricarne il contenuto in tre variabili globali di tipo OntModel create secondo il profilo
Subito dopo la creazione in memoria dei modelli, vengono chiamate altre tre
procedure remote con il compito di ottenere una rappresentazione del contenuto dei
modelli tale da poter essere sfruttata dal client per costruire l'interfaccia grafica.
87
5.5.1 Invio dell'albero delle lauree
createTreeNodes, alla quale è passato come parametro un iteratore tra le classi di primo
livello dell'ontologia, viene creata una lista di elementi XML, utilizzando le librerie
JDOM. Dal momento che un elemento xml può contenere altri elementi figli, questa
10.5: Funzione loadDegTree(). Restituisce al client un albero XML che riproduce l'ontologia
88
<?xml version="1.0" encoding="UTF-8"?>
<Degree>
<Bachelor />
<Computer_Science />
<Design>
<Architecture />
<Industrial_Design />
<Interior_Design />
</Design>
<Engineering>
<Bio_Engineering />
<Chemical_Engineering />
<Civil_Structural_Engineering />
<Computer_Science_Engineering />
<Electrical_Engineering />
<Electronics_Engineering />
<Industrial_Manufacturing_Engineering />
<Managerial_Engineering />
<Mechanical_Engineering />
<Systems_Process_Engineering />
</Engineering>
<Master_Degree />
<Mathematics />
<Physics />
<Secondary_School />
</Degree>
Questa funzione è esattamente identica alla precedente nella struttura, con la sola
89
5.5.3 Invio delle proprietà
non restituisce un albero, dato che non c'è nessun vincolo gerarchico tra le proprietà
13.5: Risposta XML contenente le proprietà dell'ontologia Skill.owl col rispettivo range
Mentre le funzioni appena viste vengono eseguite una sola volta nell'arco di vita
dell'interfaccia grafica, questa può essere invocata un numero arbitrario di volte. Infatti,
quando sul client l'utente seleziona una proprietà dotata di range, il nome della classe
range viene utilizzato come parametro della funzione loadSkTree, la quale segue una
procedura analoga a quella delle altre funzioni, con la differenza che l'albero restituito è
90
il sotto-albero della classe avente il nome ricevuto. Sul client quindi l'utente potrà
selezionare solo elementi facenti effettivamente parte del range della proprietà prescelta.
14.5: Codice che evidenzia l'estrazione del sotto-albero della classe indicata da resourceName
91
In figura si vede il frammento di codice del client che invoca le quattro
procedure remote utili alla creazione del pannello per la descrizione semantica.
Nonostante l'ordine delle chiamate sia sequenziale, bisogna osservare che queste
avvengono secondo il metodo AJAX, e sono quindi asincrone. Questo vuol dire che la
seconda potrebbe iniziare prima della conclusione della prima, e così per le altre. Se ciò
modello dell'ontologia sia caricato in memoria, e restituirebbe null. Per evitare questo
procedure remote.
Sono stati creati tre flags, degreeOk, levelOk e mainOk, inizializzati come false,
che vengono posti uguali a true quando la funzione loadOwls termina di caricare i
rispettivi modelli ontologici. E' stato imposto quindi alle altre funzioni di non iniziare
Questo ciclo impone al thread corrente di cessare la sua esecuzione per un tempo
92
5.6 Send servlet
Questa servlet costituisce la parte più complessa e importante del lato server
seconda dei casi, le procedure di inserimento nel database o il processo di ricerca di cui
la descrizione semantica codificata in XML, già vista nel precedente capitolo. La prima
operazione che quindi sarà compiuta in ogni caso, è la conversione di questa struttura
compito è svolto da una funzione iterativa che naviga attraverso l'albero xml e definisce
una lista di classi, proprietà e restrizioni. L'individuo non sarà altro che un'istanza della
Il DBMS usato è MySQL, noto oltre che per la sua facilità di utilizzo, anche per il fatto
93
Il database creato prende il nome di HumanResources e contiene una sola
tabella:
Nel caso in cui l'utente accede come 'candidato' al sistema, esso inserisce i suoi
dati nel form e crea la descrizione semantica della sua formazione professionale
94
nell'apposito pannello. Infine, premendo “Submit”, i dati vengono inviati come stringhe
al server il quale, dopo aver convertito la stringa Xml in Owl, e dopo averla formattata
(con la funzione escapeChar) in modo da poter essere scritta nel DB, chiama la
Se l'utente accede al sistema come 'azienda', gli verrà chiesto di inserire alcuni
dati testuali più la descrizione semantica del profilo richiesto per un dato compito
lavorativo. Questo insieme di dati verrà utilizzato per ricercare il candidato ideale
95
I dati quindi sono inviati alla funzione sendCompany, che converte la
descrizione semantica XML ricevuta dal client prima in OWL e poi in DIG. DIG [4] è
20.5: Procedura per la ricerca del candidato. La chiamata dell'algoritmo di match avviene all'interno della
funzione Query
96
21.5: Algoritmo purgeConcepts: elimina dalla descrizione DIG le definizioni di concetti e proprietà
Al termine di queste operazioni viene chiamata la funzione Query, che dopo aver
stabilito la connessione al database, formula una query SQL in base ai dati specificati
query non conterrà country=””, ma non conterrà affatto l'indicazione del Paese di
candidati con country=””, che cioè non hanno specificato il Paese di residenza. Questa
è un' estensione dell'assunzione di “mondo aperto”, nel quale cioè la non definizione di
97
22.5: Creazione dinamica della query al database dei profili
A questo punto, il set di risultati ottenuti è inviato alla funzione OneToOne, che
implementa l'algoritmo di match uno a uno descritto nel capitolo su Description Logics.
int i;
MAMASReasoner mamas = new MAMASReasoner();
// Connessione e caricamento Knowledge Base
URL rURL = new URL("http","dee227.poliba.it",8080,"/MAMAS-tng/DIG");
mamas.setReasonerURL(rURL);
System.out.println("Connesso a MAMAS. Identifier:\n " +
mamas.identify() );
DIGDocument skillAxioms = OWL2DIG(model);
mamas.setLogFileName("MaMaS_Log.txt");
mamas.newKB();
System.out.println("KB URI: " + mamas.getKBURI());
mamas.send(skillAxioms); //invio l'ontologia
System.out.println("Invio: SkillAxioms....OK");
mamas.send(demandProfile); //invio il profilo richiesto
System.out.println("Invio: Demand....OK");
profiles.last();
int dim = profiles.getRow();
System.out.println(dim + " profili da inviare");
for(i=1; i<=dim; i++) { //invio i candidati estratti dal DB
profiles.absolute(i);
DIGDocument profile =
purgeConcepts(OWL2DIG(profiles.getString("owlprofile")));
mamas.send(profile);
System.out.println("Invio: Supply ["+ i +"]....OK");
}
// Processo di match
int choosen = 0;
float score = 0;
Concept best = null;
Individual T = new Individual("requested_profile");
float Umin = Float.POSITIVE_INFINITY;
float N = mamas.rankPotential(T, new Top());
Concept Hfin = new Top();
Concept Gfin = new Top();
for(i=0; i<dim; i++) {
Concept Hi = new Top();
Concept Gi = new Top();
profiles.absolute(i+1);
String name =
profiles.getString("name")+"_"+profiles.getString("surname");
Individual Pi = new Individual(name);
Concept Ki = T.createCopy();
Concept[] GK = new Concept[2];
try {
mamas.coverVerify(T, Pi);
} catch (CoverVerifyException e1) {
GK = mamas.contract(T, Pi);
Gi = GK[0];
98
Ki = GK[1];
}
try {
Hi = mamas.abduce(Ki, Pi);
} catch (SubsumptionException e) {
Hi = new Top();
}
float k = mamas.rankPotential(Ki, new Top());
float h = mamas.rankPotential(Pi, Ki);
float g = mamas.rankPotential(T, Ki);
float U = Math.abs(1 - N/(N-g) * (1-h/k));
System.out.println(U);
if(U < Umin) {
Umin = U;
Hfin = Hi;
Gfin = Gi;
int bias = mamas.rankPotential(Ki, Pi);
score = 100*(1f - (float)bias/N);
choosen = i;
}
profiles.absolute(choosen +1);
best =
mamas.getIndividualDescription(profiles.getString("name")+"_"+profiles.getString("surnam
e"));
}
Map risultato = packResult(......);
mamas.releaseKB();
return risultato;
}
I dati relativi al candidato scelto vengono inseriti in un oggetto Map che verrà
quindi disponibili al client in formato testo e DIG/XML, come nei casi del profilo del
candidato, del concetto G (rinuncia, concetti in conflitto che sono stati rimossi) e del
un pannello riassuntivo delle caratteristiche del candidato, nel quale saranno risportati
99
23.5: Pannello di presentazione dei risultati – esempio
parte fondamentale: il match uno a uno. Per farlo si analizzeranno alcuni esempi pratici,
in ognuno dei quali saranno dati un certo numero di candidati e un unico task, e si vedrà
se i risultati forniti rispecchiano quelli attesi. E' evidente che per un numero molto
ristretto di profili esaminati, come nel nostro caso, il risultato è deducibile in poco
infatti, è operare in situazioni nelle quali la scelta dev'essere fatta tra un numero molto
Esempio 1
realizzazione di un dato compito sulla base dei seguenti requisiti: Ingegneri, con più di
100
due anni di esperienza, capacità di leadership, coordinamento di gruppi di lavoro,
per il web, ERP Systems, pianificazione e controllo dei processi. Questi requisiti
advanced_knowledge.(Process_Performance_MonitoringuProcess_PlanninguInternet
_technologiesuERPSystems)u(¸2 has_experience).
Julia: Ingegnere dei processi, con conoscenza avanzata dei processi di business e
appena laureata.
I profili dei candidati sopra elencati può essere espresso in DL come segue:
101
P1 (Julia) = Process_Engineeringu8 advanced_knowledge.
Business_Operations_Optimization u 8 basic_knowledge.(TeamCoordinatoru
basic_knowledge.(Java u C++)
basic_knowledge.(Lead_role u Team_Coordinator)u(¸3
has_experience)
U
P1 (Julia) 0,5000
P2 (Tom) 0,7500
P3 (Richard) 0,3750
P4 (Alison) 0,8125
Tabella 1.6: Corrispondenza tra profili e valori della funzione utilità per il task T
102
Inoltre il processo di match restituisce la spiegazione delle competenze mancanti
GP3 = >
HP3 = 8 advanced_knowledge.(Internet_Technologies u
Process_Performance_Monitoring u Process_Planning) u 8
basic_knowledge.(Negotiation_skills u Communication_skills)
103
1.6: Pannello di presentazione dei risultati al client nel caso trattato nell'esempio n° 1
Esempio 2
T = 8basicKnowledge:(InternetU se u M arkupLanguage)
uM asterDegree u (· 2hasExperience)
U
P1 (Mario) 0,0000
P2 (John) 1,8000
P3 (Frank) 1,6667
104
Tabella 2.6: corrispondenza tra profili e valori della funzione utilità per il task T
2.6: Pannello di presentazione dei risultati al client nel caso trattato nell'esempio n° 2
Esempio 3
105
Mario IT
John US
Frank US
u8toolsKnowledge:InternetDeveloping
preliminare sui candidati contenuti nel DB, scartando quelli che non hanno nazionalità
americana e con nazionalità non specificata. In questo caso quindi l'algoritmo verrà
eseguito solo sui candidati John e Frank, che riporteranno i seguenti punteggi:
U
John 0,125
Frank 0,500
106
Tabella 4.6: punteggi ottenuti dai candidati selezionati. A fronte dei 3 candidati totali, quelli analizzati dal
come
107
Esempio 4
In alcuni casi è possibile che parte dei requisiti descritti nel task siano in
Computer_Science_Engineeringu8 advanced_knowledge.Object_Oriented_Programmi
ngu (·4 has_experience) e una richiesta T definita da: Engineering u Phd u (¸5
has_experience).
G = ¸5 has_experience
H = Phd
108
4.6: Presentazione dei risultati. Un'esempio di Concept Contraction
7 Conclusioni
109
In aggiunta ai dati strutturati e alla descrizione semantica, è stata prevista la
possibilità per ogni candidato di inserire nel sistema anche un file contenente il suo
E' stata implementata, inoltre, una procedura di ricerca del candidato che
processo si articola quindi in una prima fase nella quale, basandosi sui dati strutturati
contenuti nei profili, si ottiene un sottoinsieme dei candidati registrati nel database, e in
una seconda fase nella quale le descrizioni semantiche estratte dai profili così ottenuti
soddisfare i requisiti.
match, con demand e supply appropriati, e i risultati ottenuti sono risultati concordi alle
previsioni.
Infine , si può affermare che tutti gli obiettivi prefissati in fase di definizione di
110
In futuro il sistema potrebbe essere ampliato e migliorato in diversi modi, o
integrato in un sistema più ampio. Gli obiettivi più ambiziosi potrebbero essere costituiti
candidato che copra in maniera soddisfacente i requisiti, la cosa più logica sembra
questo modo il candidato non avrebbe più l'onere di costruire manualmente la propria
111
Bibliografia
organizations.
112
[4] SisInfLab - Extended MaMaS DIG Description Logic Interface 1.1
[6] R. Hanson, A. Tacy – GWT in Action, easy AJAX with Google Web Toolkit
Appendice
Si riporta ora il codice sorgente di alcune classi, corrispondenti alle parti salienti
Candidate_Side.java
113
package com.mycompany.project.client;
import com.google.gwt.core.client.GWT;
public Candidate_Side() {
initWidget(table);
table.setWidget(0, 0, stackPanel);
stackPanel.setSize("809px", "450px");
stackPanel.setStyleName("gwt-StackPanel .gwt-StackPanelItem");
//SLIDE #0
stackPanel.add(scrollCompany, "<img src=\"company.png\"> Company
Infos", true);
scrollCompany.setSize("800px", "400px");
scrollCompany.add(company);
company.setSize("100%", "100%");
//SLIDE #2
stackPanel.add(scrollLocation, "<img src=\"location.png\"> Locations",
true);
scrollLocation.add(tabLocation);
tabLocation.setSize("100%","100%");
scrollLocation.setSize("100%", "400px");
//SLIDE #3
stackPanel.add(scrollJob, "<img src=\"job.png\"> Job detail & Shifts",
true);
114
scrollJob.add(job);
job.setSize("100%", "100%");
scrollJob.setSize("100%", "400px");
//SLIDE #4
stackPanel.add(scrollCompetency, "<img src=\"competencies.png\">
Competencies", true);
scrollCompetency.add(tabCompetency);
tabCompetency.setSize("100%", "100%");
scrollCompetency.setSize("100%", "400px");
//SLIDE #5
stackPanel.add(scrollRemuneration, "<img src=\"remuneration.png\">
Remuneration Package", true);
scrollRemuneration.add(remunerationPackage);
remunerationPackage.setSize("100%", "100%");
scrollRemuneration.setSize("100%", "400px");
//SLIDE #6
stackPanel.add(scrollOthers, "<img src=\"others.png\"> Other
informations", true);
scrollOthers.add(others);
others.setSize("100%","100%");
scrollOthers.setSize("100%", "400px");
table.setWidget(1, 0, buttonsPanel);
buttonsPanel.add(submit);
submit.addClickListener(new ClickListener() {
public void onClick(Widget sender) {
if(validate()) {
Loading load = new Loading();
final PopupPanel pop = new PopupPanel();
final SendDataServletAsync sendservice =
(SendDataServletAsync)GWT.create(SendDataServlet.class);
ServiceDefTarget endpoint = (ServiceDefTarget)
sendservice;
endpoint.setServiceEntryPoint(GWT.getModuleBaseU
RL()+"send");
AsyncCallback callback = new AsyncCallback() {
public void onFailure(Throwable caught)
{
pop.hide();
// in caso di fallimento, anche
il file caricato dev'essere cancellato
personal.fancyFileUpload.uploadF
orm.setEncoding(FormPanel.ENCODING_URLENCODED);
personal.fancyFileUpload.deleteF
iles();
Window.alert("Error sending
data");
}
public void onSuccess(Object result) {
pop.hide();
Window.alert("Data successfully
sent");
}
};
String fileName =
personal.fancyFileUpload.fileName;
if(fileName.startsWith("<pre>"))
fileName = fileName.substring(5,
fileName.lastIndexOf('<'));
sendservice.sendCandidate(personal.name.getText(
), personal.surname.getText(),
personal.gender.getItemText(personal.gender.getSelectedIndex()),
115
personal.age.getText(),
personal.phone.getText(), personal.email.getText(),
personal.nationality.getItemText(personal.nationality.getSelectedIndex()),
personal.street.getText(),
personal.number.getText(), personal.city.getText(), personal.postalCode.getText(),
personal.country.getItemText(per
sonal.country.getSelectedIndex()), Boolean.toString(personal.minimumPay.isChecked()),
Boolean.toString(personal.reloca
tion.isChecked()), Boolean.toString(personal.travel.isChecked()),
Boolean.toString(personal.milita
ry.isChecked()), personal.contract.getItemText(personal.contract.getSelectedIndex()),
personal.position.getItemText(pe
rsonal.position.getSelectedIndex()), fileName, semantic.xmlprofile, callback);
pop.setPopupPosition(Window.getClientWidth()/2,
Window.getClientHeight()/2);
pop.setWidget(load);
pop.show();
}
}
});
}
116
Company_Side.java
package com.mycompany.project.client;
import java.util.Map;
public Company_Side() {
initWidget(table);
table.setWidget(0, 0, stackPanel);
stackPanel.setSize("809px", "450px");
stackPanel.setStyleName("gwt-StackPanel .gwt-StackPanelItem");
//SLIDE #0
stackPanel.add(scrollCompany, "<img src=\"company.png\"> Company
Infos", true);
scrollCompany.setSize("800px", "400px");
scrollCompany.add(company);
company.setSize("100%", "100%");
//SLIDE #2
stackPanel.add(scrollLocation, "<img src=\"location.png\"> Locations",
true);
scrollLocation.add(tabLocation);
tabLocation.setSize("100%","100%");
scrollLocation.setSize("100%", "400px");
//SLIDE #3
117
stackPanel.add(scrollJob, "<img src=\"job.png\"> Job detail & Shifts",
true);
scrollJob.add(job);
job.setSize("100%", "100%");
scrollJob.setSize("100%", "400px");
//SLIDE #4
stackPanel.add(scrollCompetency, "<img src=\"competencies.png\">
Competencies", true);
scrollCompetency.add(tabCompetency);
tabCompetency.setSize("100%", "100%");
scrollCompetency.setSize("100%", "400px");
//SLIDE #5
stackPanel.add(scrollRemuneration, "<img src=\"remuneration.png\">
Remuneration Package", true);
scrollRemuneration.add(remunerationPackage);
remunerationPackage.setSize("100%", "100%");
scrollRemuneration.setSize("100%", "400px");
//SLIDE #6
stackPanel.add(scrollOthers, "<img src=\"others.png\"> Other
informations", true);
scrollOthers.add(others);
others.setSize("100%","100%");
scrollOthers.setSize("100%", "400px");
table.setWidget(1, 0, buttonsPanel);
buttonsPanel.add(submit);
submit.addClickListener(new ClickListener() {
public void onClick(Widget sender) {
if(validate()) {
Loading load = new Loading();
final PopupPanel pop = new PopupPanel();
final SendDataServletAsync sendservice =
(SendDataServletAsync)GWT.create(SendDataServlet.class);
ServiceDefTarget endpoint = (ServiceDefTarget)
sendservice;
endpoint.setServiceEntryPoint(GWT.getModuleBaseU
RL()+"send");
AsyncCallback callback = new AsyncCallback() {
public void onFailure(Throwable caught)
{
Window.alert("Error sending
data");
}
public void onSuccess(Object result) {
Map candidate = (Map)result;
if(candidate.containsKey("error"
)) {
pop.hide();
Window.alert((String)can
didate.get("error"));
}
else {
pop.hide();
DrawResultPanel(candidat
e);
}
}
};
sendservice.sendCompany(personal.gender.getItemT
ext(personal.gender.getSelectedIndex()),
personal.age.getText(),
personal.nationality.getItemText(personal.nationality.getSelectedIndex()),
118
Boolean.toString(personal.minimu
mPay.isChecked()), Boolean.toString(personal.relocation.isChecked()),
Boolean.toString(personal.travel
.isChecked()), Boolean.toString(personal.military.isChecked()),
personal.contract.getItemText(pe
rsonal.contract.getSelectedIndex()),
personal.position.getItemText(personal.position.getSelectedIndex()),
semantic.xmlprofile, callback);
pop.setPopupPosition(Window.getClientWidth()/2,
Window.getClientHeight()/2);
pop.setWidget(load);
pop.show();
}
}
});
}
119
Document doc = XMLParser.parse(profile);
Element top = doc.getDocumentElement();
TreeItem root = new TreeItem("Thing");
if(top.getTagName().equals("catom"))
root.addItem(top.getAttribute("name"));
if(top.getTagName().equals("all")) {
TreeItem prop = new TreeItem();
NodeList list = top.getChildNodes();
for(int j=0; j<list.getLength(); j++) {
if(list.item(j).getNodeType()==Node.ELEMENT_NODE) {
Element subel = (Element)list.item(j);
if(subel.getTagName().equals("ratom"))
prop.setText(subel.getAttribute("name"))
;
if(subel.getTagName().equals("catom"))
prop.addItem(subel.getAttribute("name"))
;
if(subel.getTagName().equals("and")) {
NodeList list2 = subel.getChildNodes();
for(int k=0; k<list2.getLength(); k++) {
if(list2.item(k).getNodeType()==
Node.ELEMENT_NODE) {
Element elem2 =
(Element)list2.item(k);
if(elem2.getTagName().eq
uals("catom"))
prop.addItem(ele
m2.getAttribute("name"));
if(elem2.getTagName().eq
uals("atleast") || elem2.getTagName().equals("atmost")) {
String value =
elem2.getAttribute("num");
String sign =
new String();
if(elem2.getTagN
ame().equals("atmost")) sign = "<";
else sign = ">";
String name =
new String();
NodeList nlist2
= elem2.getChildNodes();
for(int m=0;
m<nlist2.getLength(); m++)
if(nlist
2.item(m).getNodeType()==Node.ELEMENT_NODE) {
Element e2 = (Element)nlist2.item(m);
if(e2.getTagName().equals("ratom"))
name = e2.getAttribute("name");
}
prop.addItem(sig
n + value + " " + name);
}
}
}
}
}
}
root.addItem(prop);
}
if(top.getTagName().equals("and")) {
NodeList children = top.getChildNodes();
if(children != null) {
children = top.getChildNodes();
120
for(int i=0; i<children.getLength(); i++) {
if(children.item(i).getNodeType()==Node.ELEMENT_
NODE) {
Element elem =
(Element)children.item(i);
if(elem.getTagName().equals("catom"))
root.addItem(elem.getAttribute("
name"));
if(elem.getTagName().equals("atleast")
|| elem.getTagName().equals("atmost")) {
String value =
elem.getAttribute("num");
String sign = new String();
if(elem.getTagName().equals("atm
ost")) sign = "<";
else sign = ">";
String name = new String();
NodeList nlist =
elem.getChildNodes();
for(int l=0;
l<nlist.getLength(); l++)
if(nlist.item(l).getNode
Type()==Node.ELEMENT_NODE) {
Element e =
(Element)nlist.item(l);
if(e.getTagName(
).equals("ratom"))
name =
e.getAttribute("name");
}
root.addItem(sign + value + " "
+ name);
}
if(elem.getTagName().equals("all")) {
TreeItem prop = new TreeItem();
NodeList list =
elem.getChildNodes();
for(int j=0; j<list.getLength();
j++) {
if(list.item(j).getNodeT
ype()==Node.ELEMENT_NODE) {
Element subel =
(Element)list.item(j);
if(subel.getTagN
ame().equals("ratom"))
prop.set
Text(subel.getAttribute("name"));
if(subel.getTagN
ame().equals("catom"))
prop.add
Item(subel.getAttribute("name"));
if(subel.getTagN
ame().equals("and")) {
NodeList
list2 = subel.getChildNodes();
for(int
k=0; k<list2.getLength(); k++) {
if(list2.item(k).getNodeType()==Node.ELEMENT_NODE) {
if(elem2.getTagName().equals("catom"))
prop.addItem(elem2.getAttribute("name"));
121
if(elem2.getTagName().equals("atleast") || elem2.getTagName().equals("atmost")) {
if(nlist2.item(m).getNodeType()==Node.ELEMENT_NODE) {
Element e2 = (Element)nlist2.item(m);
if(e2.getTagName().equals("ratom"))
name = e2.getAttribute("name");
}
}
}
}
}
root.addItem(prop);
}
}
}
}
}
return root;
}
122
}
Semantic.java
package com.mycompany.project.client;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.ServiceDefTarget;
import com.google.gwt.user.client.ui.AbsolutePanel;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.FocusListener;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Tree;
import com.google.gwt.user.client.ui.TreeItem;
import com.google.gwt.user.client.ui.TreeListener;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.xml.client.Document;
import com.google.gwt.xml.client.Element;
import com.google.gwt.xml.client.Node;
import com.google.gwt.xml.client.NodeList;
import com.google.gwt.xml.client.ProcessingInstruction;
import com.google.gwt.xml.client.XMLParser;
123
private Tree degreeTree = new Tree();
private Tree levelTree = new Tree();
private Tree skillTree = new Tree();
private MyTree summaryTree = new MyTree();
private boolean fromSkill=false;
private boolean fromValue=false;
private boolean fromProp=false;
private Label summaryLabel = new Label();
private FlexTable valueTable = new FlexTable();
private ListBox leastMost = new ListBox();
private TextBox valueTxt = new TextBox();
private Label insertValueLabel = new Label("Insert value");
private ListBox listProp = new ListBox();
private ListBox listProp2 = new ListBox();
private AbsolutePanel horizontalPanel = new AbsolutePanel();
private Button prevButton = new Button();
private Button nextButton = new Button();
private Button addButton = new Button();
private Map propAndRange = new HashMap();
private String description = new String();
public String xmlprofile = new String();
private boolean first = true;
public Semantic() {
initGUI();
loadOwls(service);
loadDegreeTree(service);
loadLevelTree(service);
loadProperties(service);
steps();
}
initWidget(grid);
grid.setSize("820px", "420px");
degreeTree.setImageBase("tree/");
levelTree.setImageBase("tree/");
skillTree.setImageBase("tree/");
summaryTree.setImageBase("tree/");
skillTree.addTreeListener(new TreeListener() {
public void onTreeItemSelected(TreeItem item) {
fromSkill = true;
valueTable.setStyleName("border2");
leastMost.setEnabled(true);
valueTxt.setEnabled(true);
listProp2.setVisible(true);
}
public void onTreeItemStateChanged(TreeItem item) {
}
});
124
grid.setWidget(0, 0, html);
grid.getCellFormatter().setHeight(0, 0, "10px");
grid.getCellFormatter().setWidth(0, 0, "200px");
html.setStyleName("gwt-HTML2");
grid.setWidget(0, 1, html_1);
grid.getCellFormatter().setWidth(0, 1, "190px");
html_1.setStyleName("gwt-HTML2");
grid.setWidget(0, 2, skillTitleLabel);
grid.getCellFormatter().setWidth(0, 2, "200px");
skillTitleLabel.setStyleName("gwt-HTML2");
grid.setWidget(0, 3, html_3);
grid.getCellFormatter().setWidth(0, 3, "200px");
html_3.setStyleName("gwt-HTML2");
grid.setWidget(2, 0, html_4);
grid.getCellFormatter().setHeight(2, 0, "10px");
html_4.setStyleName("gwt-HTML2");
grid.setWidget(2, 1, html_5);
html_5.setStyleName("gwt-HTML2");
grid.setWidget(1, 0, scrollDegree);
scrollDegree.setSize("200px", "200px");
grid.getCellFormatter().setWordWrap(1, 0, false);
scrollDegree.setStyleName("border");
grid.setWidget(3, 0, scrollLevel);
scrollLevel.setStyleName("border");
scrollLevel.setHeight("200px");
grid.setWidget(1,2,scrollSkills);
scrollSkills.setSize("200px", "100%");
scrollSkills.setStyleName("border");
grid.getFlexCellFormatter().setRowSpan(1, 2, 3);
grid.setWidget(1,3,scrollSummary);
scrollSummary.setSize("200px", "100%");
scrollSummary.setStyleName("border");
grid.getFlexCellFormatter().setRowSpan(1, 3, 3);
//scrollSummary.setWidget(summaryLabel);
grid.setWidget(3, 1, valueTable);
valueTable.setSize("100%", "100%");
grid.getCellFormatter().setHeight(3, 1, "100%");
valueTable.setStyleName("border");
valueTable.setWidget(1, 0, leastMost);
leastMost.addItem("=");
leastMost.addItem("<");
leastMost.addItem(">");
valueTable.setWidget(1, 1, valueTxt);
valueTxt.addFocusListener(new FocusListener() {
public void onFocus(Widget sender) {
TextBox source = (TextBox)sender;
source.selectAll();
}
public void onLostFocus(Widget sender) {
TextBox source = (TextBox)sender;
if(source.getText().length()>0) {
try {
Integer numero = new Integer(source.getText());
125
fromValue = true;
} catch (Exception e) {
Window.alert("Not an integer");
source.setText("");
}
}
}
});
valueTxt.setWidth("140px");
valueTable.setWidget(0, 1, insertValueLabel);
insertValueLabel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER);
valueTable.setWidget(2, 1, listProp2);
valueTable.getFlexCellFormatter().setColSpan(2, 1, 2);
description = "";
listProp2.setWidth("140px");
listProp2.setVisibleItemCount(4);
grid.setWidget(4, 0, horizontalPanel);
horizontalPanel.setSize("100%", "28px");
horizontalPanel.setStyleName("coloured");
grid.getCellFormatter().setHorizontalAlignment(4, 0,
HasHorizontalAlignment.ALIGN_RIGHT);
grid.getFlexCellFormatter().setColSpan(4, 0, 4);
grid.getCellFormatter().setHorizontalAlignment(4, 3,
HasHorizontalAlignment.ALIGN_RIGHT);
horizontalPanel.add(prevButton);
prevButton.setEnabled(false);
prevButton.setHTML("<img src=\"back-off.png\"/>");
prevButton.addClickListener(new ClickListener() {
public void onClick(Widget sender) {
if(actualStep!=0) {
actualStep--;
steps();
}
}
});
horizontalPanel.add(nextButton);
nextButton.setHTML("<img src=\"forward.png\"/>");
nextButton.addClickListener(new ClickListener() {
public void onClick(Widget sender) {
if(actualStep!=2) {
actualStep++;
steps();
}
else {
actualStep = SELECT_DEGREE;
first = true;
XMLProfile();
steps();
}
}
});
126
grid.setWidget(1, 1, listProp);
listProp.addClickListener(new ClickListener() {
public void onClick(Widget sender) {
if(!fromValue)
fromSkill=false;
ListBox list = (ListBox)sender;
String selected = list.getItemText(list.getSelectedIndex());
String resource = (String)propAndRange.get(selected);
if(resource!=null) {
loadSkillTree(resource, service);
scrollSkills.setVisible(true);
skillTree.setVisible(true);
scrollSkills.setStyleName("border2");
leastMost.setEnabled(false);
leastMost.setVisible(true);
valueTxt.setEnabled(false);
valueTxt.setVisible(true);
insertValueLabel.setVisible(true);
listProp2.setVisible(false);
TreeItem node = existInTree(selected); //se la proprietà è giÃ
presente nel profilo creato
if(node!=null) { //viene riabilitata la
possibilità di aggiungere delle
valueTable.setStyleName("border2"); //restrizioni
numeriche alla proprietÃ
leastMost.setVisible(true);
valueTxt.setVisible(true);
leastMost.setEnabled(true);
valueTxt.setEnabled(true);
listProp2.setVisible(true);
valueTxt.setText("");
}
}
else {
scrollSkills.clear();
skillTree.setVisible(false);
skillTitleLabel.setHTML("Select a property");
valueTable.setStyleName("border2");
insertValueLabel.setVisible(true);
leastMost.setVisible(true);
valueTxt.setVisible(true);
leastMost.setEnabled(true);
valueTxt.setEnabled(true);
listProp2.setVisible(false);
valueTxt.setText("");
}
}
});
listProp.setSize("200px", "100%");
listProp.setVisibleItemCount(11);
}
127
private void loadDegreeTree(OntologyServletAsync service) {
AsyncCallback callback = new AsyncCallback() {
public void onSuccess(Object result) {
Document xmlTree = XMLParser.parse((String)result);
degreeTree.addItem(XML2Tree(xmlTree.getDocumentElement()));
degreeTree.getItem(0).setState(true);
scrollDegree.add(degreeTree);
}
public void onFailure(Throwable caught) {
Window.alert("ERROR: degrees");
scrollDegree.setVisible(false);
}
};
service.loadDegTree(callback);
}
128
}
}
//ordinamento alfabetico
for(i=0; i<listProp.getItemCount()-1; i++) {
imin = i;
for(j=i+1; j<listProp.getItemCount(); j++)
if(listProp.getItemText(j).compareTo(listProp.getItemText(i))<1)
imin = j;
String temp = listProp.getItemText(i);
listProp.setItemText(i, listProp.getItemText(imin));
listProp.setItemText(imin, temp);
}
for(i=0; i<listProp.getItemCount(); i++)
listProp2.addItem(listProp.getItemText(i));
}
public void onFailure(Throwable caught) {
Window.alert("Errore: properties");
listProp.setVisible(false);
listProp2.setVisible(false);
}
};
service.loadAProperties(callback);
}
129
Window.alert("Please specify a value");
}
else { //se la proprietà ha un range
TreeItem node = existInTree(selected);
if(node==null) { //se non è già presente nel profilo
if(skillTree.getSelectedItem()==null)
Window.alert("Select a "+skillTitleLabel.getText());
else {
TreeItem prop = new
TreeItem(listProp.getItemText(listProp.getSelectedIndex()));
TreeItem range = new
TreeItem(skillTree.getSelectedItem().getText());
prop.addItem(range);
if(valueTxt.getText().length()>0) { //se inoltre
specifico un peso numerico
if(listProp2.getSelectedIndex()==-1) //ma non è
selezionata nessuna proprietÃ
Window.alert("Select a property");
else { //quando invece è tutto
ok
String s =
leastMost.getItemText(leastMost.getSelectedIndex())+valueTxt.getText()+"
"+listProp2.getItemText(listProp2.getSelectedIndex());
TreeItem num = new TreeItem(s);
prop.addItem(num);
}
}
summaryTree.getItem(0).addItem(prop);
}
}
else { //se la proprietà è stata già specificata
boolean done = false;
if(skillTree.getSelectedItem()!=null) { //se seleziono una
classe da aggiungere
TreeItem addedRange = new
TreeItem(skillTree.getSelectedItem().getText());
if(!existInNode(node, addedRange.getText()))
node.addItem(addedRange);
done = true;
}
if(valueTxt.getText().length()>0) { //se seleziono anche
un "peso" numerico
if(listProp2.getSelectedIndex()==-1) //ma non è selezionata
nessuna proprietÃ
Window.alert("Select a property");
else { //se è tutto ok
String s =
leastMost.getItemText(leastMost.getSelectedIndex())+valueTxt.getText()+"
"+listProp2.getItemText(listProp2.getSelectedIndex());
TreeItem addedNum = new TreeItem(s);
if(!existInNode(node, s))
node.addItem(addedNum);
done = true;
}
}
if(!done)
Window.alert("Nothing to do");
}
}
}
}
130
scrollDegree.setStyleName("border2");
levelTree.setVisible(false);
scrollLevel.setStyleName("border");
listProp.setEnabled(false);
valueTable.setStyleName("border");
skillTree.setVisible(false);
scrollSkills.setStyleName("border");
leastMost.setEnabled(false);
valueTxt.setEnabled(false);
listProp2.setVisible(false);
prevButton.setHTML("<img src=\"back-off.png\"/>");
nextButton.setHTML("<img src=\"forward.png\"/>");
}
if (actualStep==SELECT_LEVEL) {
degreeTree.setVisible(false);
scrollDegree.setStyleName("border");
scrollLevel.setStyleName("border2");
levelTree.setVisible(true);
listProp.setEnabled(false);
valueTable.setStyleName("border");
skillTree.setVisible(false);
scrollSkills.setStyleName("border");
leastMost.setEnabled(false);
valueTxt.setEnabled(false);
listProp2.setVisible(false);
prevButton.setHTML("<img src=\"back.png\"/>");
prevButton.setEnabled(true);
nextButton.setHTML("<img src=\"forward.png\"/>");
}
if (actualStep==SELECT_PROP) {
listProp.setEnabled(true);
degreeTree.setVisible(false);
scrollDegree.setStyleName("border");
levelTree.setVisible(false);
scrollLevel.setStyleName("border");
scrollSkills.setStyleName("border");
listProp.setSelectedIndex(0);
valueTable.setStyleName("border");
String selected = listProp.getItemText(listProp.getSelectedIndex());
String resource = (String)propAndRange.get(selected);
if(resource!=null) {
loadSkillTree(resource, service);
scrollSkills.setStyleName("border2");
skillTree.setVisible(true);
Iterator it = valueTable.iterator();
valueTxt.setEnabled(false);
leastMost.setEnabled(false);
}
else {
scrollSkills.clear();
skillTree.setVisible(false);
skillTitleLabel.setHTML("Select a property");
scrollSkills.setStyleName("border");
valueTable.setStyleName("border");
leastMost.setEnabled(true);
valueTxt.setEnabled(true);
listProp2.setVisible(false);
valueTxt.setText("");
}
nextButton.setHTML("<img src=\"ok.png\"/>");
}
}
131
TreeItem found = null;
for(int i=0; i<root.getChildCount(); i++)
if(root.getChild(i).getText().equals(s))
found = root.getChild(i);
return found;
}
132
item.setAttribute("value", value);
}
else { //classe
item = description.createElement("class");
item.setAttribute("name", text);
}
}
profile.appendChild(item);
}
xmlprofile = description.toString();
Window.alert("Profile successfully updated");
}
}
DownloadServlet.java
package com.mycompany.project.server;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.FileNameMap;
import java.net.URLConnection;
import javax.servlet.ServletContext;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
133
}
in.close();
op.flush();
op.close();
}
UploadServlet.java
package com.mycompany.project.server;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FilenameUtils;
if (isMultipart) {
// We are uploading a file (deletes are performed by on
multipart requests)
FileItemFactory factory = new DiskFileItemFactory();
134
ServletFileUpload upload = new ServletFileUpload(factory);
// Parse the request
try {
List items = upload.parseRequest(request);
// Process the uploaded items
Iterator iter = items.iterator();
while (iter.hasNext()) {
FileItem item = (FileItem) iter.next();
if (item.isFormField()) {
}
else {
if (writeToFile) {
suffix = "";
String fileName =
item.getName();
if (fileName != null && !
fileName.equals("")) {
fileName =
FilenameUtils.getName(fileName);
File uploadedFile = new
File(rootDirectory + fileName);
//se il file esiste già
cambia il nome aggiungendo un suffisso casuale
//se ha un estensione,
modifico solo il nome e concateno l'estensione
while(uploadedFile.exist
s()) {
suffix =
Integer.toString((int)Math.round(Math.random()*10000));
if(fileName.last
IndexOf('.')!=-1) {
String
name = fileName.substring(0,fileName.lastIndexOf('.'));
String
ext = fileName.substring(fileName.lastIndexOf('.')+1, fileName.length());
fileName
= name + suffix + "." + ext;
}
else
fileName
= fileName + suffix;
uploadedFile =
new File(rootDirectory + fileName);
}
try {
item.write(uploa
dedFile);
//out.print(retu
rnOKMessage);
String
requestUrl = request.getRequestURL().toString();
requestUrl =
requestUrl.substring(0, requestUrl.lastIndexOf('/')+1);
//restituisce al
client il percorso relativo del file
out.print(rootDi
rectory + fileName);
}
catch (Exception e) {
e.printStackTrac
e();
}
}
}
else {}
135
}
}
} catch (FileUploadException e) {
e.printStackTrace();
}
}
else {
//Process a request to delete a file
String file = request.getParameter("filePass"); //il nome del
file appena salvato (ed eventualmente modificato)
String fileName = FilenameUtils.getName(file);
File deleteFile = new File(rootDirectory+fileName);
if(deleteFile.delete()){
out.print(returnOKMessage);
}
}
}
}
OntologyServletImpl.java
package com.mycompany.project.server;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.jdom.*;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.hp.hpl.jena.ontology.OntClass;
import com.hp.hpl.jena.ontology.OntModel;
import com.hp.hpl.jena.ontology.OntModelSpec;
import com.hp.hpl.jena.ontology.OntProperty;
import com.hp.hpl.jena.ontology.OntResource;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.util.iterator.ExtendedIterator;
import com.hp.hpl.jena.util.iterator.Filter;
import com.mycompany.project.client.OntologyServlet;
136
private boolean degreeOk = false;
private boolean levelOk = false;
private boolean mainOk = false;
137
Iterator itRootCls = modelLevel.listHierarchyRootClasses().filterDrop(
new Filter(){
public boolean accept( Object o ) {
return ((Resource) o).isAnon();// ||
((Resource)o).getLocalName().equals("Nothing");
}
});
List rootNodes = createTreeNodes(itRootCls); //lista di Element
Element root = new Element("Level");
Iterator it2 = rootNodes.iterator();
while (it2.hasNext())
//aggiungo il contenuto alla radice
root.addContent((Content)it2.next());
Document level = new Document(root);
XMLOutputter out = new XMLOutputter();
out.setFormat(Format.getPrettyFormat());
String XMLlevelTree = out.outputString(level);
return XMLlevelTree;
}
138
public List createTreeNodes(Iterator RootCls) {
Map map = new TreeMap();
while (RootCls.hasNext()) {
OntClass o = (OntClass) RootCls.next();
map.put(o.getLocalName(), o);
}
List rootList = new ArrayList(); //lista di Element (elementi xml)
Iterator a = map.values().iterator();
while (a.hasNext()){
OntClass classe = (OntClass)a.next();
String localName = classe.getLocalName();
Element next = new Element(localName);
rootList.add(next);
}
Iterator it = map.values().iterator();
for (int i = 0; i < map.values().size(); i++) {
OntClass o = (OntClass) it.next();
List sub;
if (o.hasSubClass()) {
sub =
createTreeNodes(o.listSubClasses(true).filterDrop(new Filter() { //funzione ricorsiva
public boolean accept(Object o) {
return ((Resource)o).isAnon() ||
((Resource)o).getLocalName().equals("Nothing");
}
}));
Element temp = (Element)rootList.get(i);
Iterator subit = sub.iterator();
while (subit.hasNext()){
Content nodoNuovo = (Content)subit.next();
temp.addContent(nodoNuovo);
}
}
}
return rootList;
}
}
SendDataServletImpl.java
package com.mycompany.project.server;
import it.poliba.sisinflab.dig.Concept;
import it.poliba.sisinflab.dig.DIGDocument;
import it.poliba.sisinflab.dig.DIGElement;
import it.poliba.sisinflab.dig.Individual;
import it.poliba.sisinflab.dig.Top;
import it.poliba.sisinflab.dig.reasoner.ReasonerErrorException;
import it.poliba.sisinflab.dig.reasoner.mamas.CoverVerifyException;
import it.poliba.sisinflab.dig.reasoner.mamas.MAMASReasoner;
import it.poliba.sisinflab.dig.reasoner.mamas.SubsumptionException;
import it.poliba.sisinflab.util.OWLDIGTranslator;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
139
import java.io.Writer;
import java.net.*;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.dom4j.DocumentException;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import org.jdom.*;
import org.jdom.input.SAXBuilder;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.hp.hpl.jena.ontology.AllValuesFromRestriction;
import com.hp.hpl.jena.ontology.IntersectionClass;
import com.hp.hpl.jena.ontology.ObjectProperty;
import com.hp.hpl.jena.ontology.OntClass;
import com.hp.hpl.jena.ontology.OntModel;
import com.hp.hpl.jena.ontology.OntModelSpec;
import com.hp.hpl.jena.ontology.Restriction;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.rdf.model.RDFList;
import com.mycompany.project.client.SendDataServlet;
initModel();
String owlProfile = escapeChar(XML2OWL(name.replace(' ', '_'),
surname.replace(' ', '_'), xmldescription));
initModel();
140
Map risultato = Query(demand, gender, age, nationality, minPay,
relocation, travel, military, contract, position);
return risultato;
}
private Map Query(DIGDocument demand, String gender, String age, String nation,
String minPay, String relocation, String travel,
String military, String contract, String position) {
try {
Class.forName("com.mysql.jdbc.Driver").newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
String andGender = "";
String andYears = "";
String andNation = "";
String andContract = "";
String andPosition = "";
if(gender.length()>0)
andGender = " AND (gender=\'"+gender+"\' OR gender=\'\')";
if(!age.equals("0") && age.length()>0)
andYears = " AND (age<"+age+")";
if(nation.length()>0)
andNation = " AND (nationality=\'"+nation+"\' OR
nationality=\'\')";
if(contract.length()>0)
andContract = " AND (contractType=\'"+contract+"\' OR
contractType=\'\')";
if(position.length()>0)
andPosition = " AND (positionStatus=\'"+position+"\' OR
positionStatus=\'\')";
Connection conn;
String query = "SELECT * FROM People WHERE (minPay="+"\'"+minPay+"\')
AND (relocation=\'"
+relocation+"\') AND (travel=\'"+travel+"\') AND
(military=\'"+military+"\')" + andGender + andYears
+ andNation + andContract + andPosition;
ResultSet rs = null;
try {
conn =
DriverManager.getConnection("jdbc:mysql://localhost/HumanResources?user=gwtapp&password=
password");
Statement stmt = conn.createStatement();
rs = stmt.executeQuery(query);
rs.last();
if(rs.getRow()==0) {
Map m = new HashMap();
m.put("error", "No profile found");
return m;
}
//CHIAMO L'ALGORITMO DI MATCH
//invio la richiesta e i profili selezionati all'algoritmo di
match che deve RESTITUIRE 1 lavoratore
Map risultato = OneToOne(demand, rs);
conn.close();
return risultato;
} catch (SQLException e) {
System.out.println("SQLException: "+e.getMessage());
System.out.println("SQLState: "+e.getSQLState());
System.out.println("VendorError: "+e.getErrorCode());
141
e.printStackTrace();
Map m = new HashMap();
m.put("error", "Error connecting database");
return m;
}
}
int i;
MAMASReasoner mamas = new MAMASReasoner();
try {
// CONNESSIONE E CARICAMENTO KB
URL rURL = new URL("http","dee227.poliba.it",8080,"/MAMAS-
tng/DIG");
mamas.setReasonerURL(rURL);
System.out.println("Connesso a MAMAS. Identifier:\n " +
mamas.identify() );
DIGDocument skillAxioms = OWL2DIG(model);
mamas.setLogFileName("MaMaS_Log.txt");
mamas.newKB();
System.out.println("KB URI: " + mamas.getKBURI());
mamas.send(skillAxioms);
//invio l'ontologia
System.out.println("Invio: SkillAxioms....OK");
mamas.send(demandProfile);
//invio il profilo richiesto
System.out.println("Invio: Demand....OK");
profiles.last();
int dim = profiles.getRow();
System.out.println(dim + " profili da inviare");
for(i=1; i<=dim; i++) {
//invio i candidati estratti dal DB
profiles.absolute(i);
DIGDocument profile =
purgeConcepts(OWL2DIG(profiles.getString("owlprofile")));
mamas.send(profile);
System.out.println("Invio: Supply ["+ i +"]....OK");
}
// PROCESSO DI MATCH
int choosen = 0;
float score = 0;
Concept best = null;
Individual T = new Individual("requested_profile");
float Umin = Float.POSITIVE_INFINITY;
float N = mamas.rankPotential(T, new Top());
Concept Hfin = new Top();
Concept Gfin = new Top();
for(i=0; i<dim; i++) {
Concept Hi = new Top();
Concept Gi = new Top();
profiles.absolute(i+1);
String name =
profiles.getString("name")+"_"+profiles.getString("surname");
Individual Pi = new Individual(name);
Concept Ki = null;
Concept[] GK = new Concept[2];
if(!mamas.coverVerify(T, Pi)) {
GK = mamas.contract(Pi, T);
Gi = GK[0];
Ki = GK[1];
}
else
Ki = T; // T = G AND K, se
(G,K)=(TOP,T) non rinuncio a niente
try {
142
Hi = mamas.abduce(Ki, Pi);
} catch (SubsumptionException e) { // se i concetti
si sussumono (domanda meno specifica dell'offerta)
Hi = new Top();
// l'abduction non è possibile e la supply copre completamente la demand
}
float k = mamas.rankPotential(Ki, new Top());
float h = mamas.rankPotential(Ki, Pi);
float g = mamas.rankPotential(Ki, T);
float U = Math.abs(1 - N/(N-g) * (1-h/k));
System.out.println(U);
if(U < Umin) {
Umin = U;
Hfin = Hi;
Gfin = Gi;
int bias = mamas.rankPotential(Ki, Pi);
score = 100*(1f - (float)bias/N);
choosen = i;
}
profiles.absolute(choosen +1);
best =
mamas.getIndividualDescription(profiles.getString("name")+"_"+profiles.getString("surnam
e"));
}
Map risultato = packResult(profiles.getString("name"),
profiles.getString("surname"), profiles.getString("gender"),
profiles.getInt("age"),
profiles.getString("phone"), profiles.getString("mail"),
profiles.getString("nationality"),
profiles.getString("street"),
profiles.getString("number"), profiles.getString("city"),
profiles.getString("postalCode"),
profiles.getString("country"), profiles.getString("minPay"),
profiles.getString("relocation"),
profiles.getString("travel"), profiles.getString("military"),
profiles.getString("contractType"),
profiles.getString("positionStatus"),
profiles.getString("filename"), score, best,
Gfin, Hfin);
mamas.releaseKB();
return risultato;
} catch (SQLException e) {
e.printStackTrace();
} catch (MalformedURLException e1) {
e1.printStackTrace();
} catch (ReasonerErrorException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (CoverVerifyException e) {
e.printStackTrace();
}
return null;
}
private Map packResult(String name, String surname, String gender, int age,
String phone, String email,
String nationality, String street, String number, String city,
String postalCode, String country,
String minPay, String relocation, String travel, String
military, String contractType, String positionStatus,
String filename, float score, Concept best, Concept G, Concept
H) {
143
m.put("surname", surname);
m.put("gender", gender);
m.put("age", Integer.toString(age));
m.put("phone", phone);
m.put("email", email);
m.put("nationality", nationality);
m.put("street", street);
m.put("number", number);
m.put("city", city);
m.put("postalCode", postalCode);
m.put("country", country);
m.put("minPay", minPay);
m.put("relocation", relocation);
m.put("travel", travel);
m.put("military", military);
m.put("contractType", contractType);
m.put("positionStatus", positionStatus);
m.put("filename", filename);
m.put("score", Float.toString(score)+" %");
m.put("candidate", Concepts2XML(best));
m.put("G", Concepts2XML(G));
m.put("H", Concepts2XML(H));
return m;
}
144
translator.translateDomain(false);
translator.translateRange(false);
translator.translateNS(false);
return translator.translateOWL(model, null);
}
//elimina dai profili DIG le dichiarazioni di classi e proprietà, per non andare
in conflitto con le ontologie
private DIGDocument purgeConcepts(DIGDocument doc) {
DIGElement tells = doc.getRootElement();
Iterator elit = tells.elementIterator();
while(elit.hasNext()) {
org.dom4j.Element e = (org.dom4j.Element)elit.next();
if(e.getName().equals("defconcept") ||
e.getName().equals("defrole"))
tells.remove(e);
}
return doc;
}
OntModel m = ModelFactory.createOntologyModel(OntModelSpec.OWL_DL_MEM);
m.setNsPrefixes(prefixes); //uso gli stessi NS dell'ontologia principale
//parsing del documento xml
145
StringReader sr = new StringReader(xmldesc);
Document documento = null;
SAXBuilder saxBuilder = new SAXBuilder();
try {
documento = saxBuilder.build(sr);
Element profileRoot = documento.getRootElement(); //elemento
radice dell'albero xml
List list = profileRoot.getChildren();
Iterator it = list.iterator();
RDFList elements = m.createList(); //scorro
la lista degli elementi xml
while(it.hasNext()) {
//e li aggiungo alla lista di RDFnodes
Element e = (Element)it.next();
if(e.getName().equals("class")) {
String className = e.getAttributeValue("name");
OntClass c = m.getOntClass(Ns + className);
if(c==null)
c = m.createClass(Ns + className);
elements = elements.cons(c);
}
if(e.getName().equals("restriction")) {
String restrName = e.getAttributeValue("name");
String restrValue =
e.getAttributeValue("value");
String restrType = e.getAttributeValue("sign");
ObjectProperty p = m.getObjectProperty(Ns +
restrName);
if(p==null)
p = m.createObjectProperty(Ns +
restrName);
Restriction anonR = m.createRestriction(p);
if(restrType.equals("gt"))
anonR =
m.createMinCardinalityRestriction(null, p, Integer.parseInt(restrValue));
if(restrType.equals("lt"))
anonR =
m.createMaxCardinalityRestriction(null, p, Integer.parseInt(restrValue));
if(restrType.equals("="))
anonR =
m.createCardinalityRestriction(null, p, Integer.parseInt(restrValue));
elements = elements.cons(anonR);
}
if(e.getName().equals("property")) {
String propName = e.getAttributeValue("name");
ObjectProperty p = m.getObjectProperty(Ns +
propName); //restriction: on property p
if(p==null)
p = m.createObjectProperty(Ns +
propName);
RDFList subelements = m.createList();
//creo una lista dei sottoelementi
List sublist = e.getChildren();
//classi o restrizioni numeriche
Iterator it2 = sublist.iterator();
while(it2.hasNext()) {
Element e2 = (Element)it2.next();
if(e2.getName().equals("class")) {
String className =
e2.getAttributeValue("name");
OntClass c = m.getOntClass(Ns +
className);
if(c==null)
c = m.createClass(Ns +
className);
146
subelements =
subelements.cons(c);
}
if(e2.getName().equals("restriction")) {
String restrName =
e2.getAttributeValue("name");
String restrValue =
e2.getAttributeValue("value");
String restrType =
e2.getAttributeValue("sign");
ObjectProperty p2 =
m.getObjectProperty(Ns + restrName);
if(p2==null)
p2 =
m.createObjectProperty(Ns + restrName);
Restriction anonR =
m.createRestriction(p);
if(restrType.equals(">"))
anonR =
m.createMinCardinalityRestriction(null, p2, Integer.parseInt(restrValue));
if(restrType.equals("<"))
anonR =
m.createMaxCardinalityRestriction(null, p2, Integer.parseInt(restrValue));
if(restrType.equals("="))
anonR =
m.createCardinalityRestriction(null, p2, Integer.parseInt(restrValue));
subelements =
subelements.cons(anonR);
}
}
IntersectionClass inters =
m.createIntersectionClass(null, subelements); //e creo la classe intersezione di essi
AllValuesFromRestriction restr =
m.createAllValuesFromRestriction(null, p, inters);
elements = elements.cons(restr);
}
}
IntersectionClass profile = m.createIntersectionClass(null,
elements); //classe finale, intersezione di tutte le classi, restrizioni numeriche e
proprietà
m.createIndividual(Ns + name+"_"+surname, profile);
//output del modello su stringa
String stringa = new String();
Writer sw= new StringWriter();
sw.write(stringa);
m.write(sw, "RDF/XML-ABBREV");
String owlString = new String();
owlString = sw.toString();
sw.close();
return owlString;
} catch (JDOMException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
//per memorizzare la stringa nel DB, non deve contenere caratteri come ", che
converto in tag html
private String escapeChar(String string) {
final StringBuffer result = new StringBuffer();
final StringCharacterIterator iterator = new
StringCharacterIterator(string);
char c = iterator.current();
while(c != CharacterIterator.DONE) {
147
if (c == '\"')
result.append("\\\"");
else
result.append(c);
c = iterator.next();
}
return result.toString();
}
148
“Mai mandare un essere umano a fare il lavoro di una macchina” (Agente Smith)
149