Università degli studi di Firenze
FACOLTÀ DI SCIENZE MATEMATICHE, FISICHE E NATURALI
Tesi di Laurea in Informatica
Sviluppo di un traduttore automatico
di specifiche UML4SOA in COWS
Relatore:
Candidato:
Prof. Rosario Pugliese
Francesco Cianferoni
Correlatori:
Dott. Francesco Tiezzi
Dott. Federico Banti
Anno Accademico 2008/2009
Indice
1 Introduzione
1
2 UML e UML4SOA
7
2.1
2.2
UML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8
2.1.1
Caratteristiche generali . . . . . . . . . . . . . . . . . .
8
2.1.2
Diagrammi delle attività . . . . . . . . . . . . . . . . . 11
2.1.3
Estendibilità e profili . . . . . . . . . . . . . . . . . . . 13
UML4SOA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.2.1
Il meta-modello . . . . . . . . . . . . . . . . . . . . . . 14
2.2.2
Sintassi . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.2.3
Esempio . . . . . . . . . . . . . . . . . . . . . . . . . . 21
3 COWS
25
3.1
Sintassi
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
3.2
Semantica Operazionale . . . . . . . . . . . . . . . . . . . . . 30
3.3
Esempi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
i
ii
INDICE
4 UStoC: da UML4SOA a COWS
43
4.1
Creazione di un diagramma UML4SOA . . . . . . . . . . . . . 44
4.2
Struttura del file XMI . . . . . . . . . . . . . . . . . . . . . . 46
4.2.1
4.3
Struttura di UStoC . . . . . . . . . . . . . . . . . . . . . . . . 50
4.3.1
4.4
4.5
4.6
Definizione degli elementi UML4SOA . . . . . . . . . . 47
Panoramica sulle classi . . . . . . . . . . . . . . . . . . 52
Parsing e analisi del file XMI . . . . . . . . . . . . . . . . . . . 55
4.4.1
Inserimento all’interno di una Orchestration . . . . . . 56
4.4.2
Inserimento all’interno di uno Scope . . . . . . . . . . . 58
Creazione degli oggetti . . . . . . . . . . . . . . . . . . . . . . 60
4.5.1
Scope . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
4.5.2
Initial e Final Node . . . . . . . . . . . . . . . . . . . . 63
4.5.3
Fork e Join . . . . . . . . . . . . . . . . . . . . . . . . 64
4.5.4
Decision e Merge . . . . . . . . . . . . . . . . . . . . . 65
4.5.5
Action . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
Traduzione in COWS . . . . . . . . . . . . . . . . . . . . . . . 69
4.6.1
Orchestration . . . . . . . . . . . . . . . . . . . . . . . 70
4.6.2
Scope . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
4.6.3
Initial e FinalNode . . . . . . . . . . . . . . . . . . . . 76
4.6.4
Fork e Join . . . . . . . . . . . . . . . . . . . . . . . . 77
4.6.5
Decision e Merge . . . . . . . . . . . . . . . . . . . . . 78
4.6.6
Action e Scope Graph . . . . . . . . . . . . . . . . . . 79
4.6.7
Send e Receive Action . . . . . . . . . . . . . . . . . . 80
4.6.8
Azioni per Fault e Compensation Handling . . . . . . . 82
INDICE
iii
5 Utilizzo di UStoC
85
5.1
Diagramma UML4SOA . . . . . . . . . . . . . . . . . . . . . . 85
5.2
Guida all’uso di UStoC . . . . . . . . . . . . . . . . . . . . . . 88
5.3
CMC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
5.4
Esempi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
6 Conclusioni
103
iv
INDICE
Capitolo
1
Introduzione
Negli ultimi anni il successo crescente di alcuni domini applicativi legati
al Web, quali e-commerce, e-learning, e-business, ha indirizzato il mondo
dell’Information Technology verso Architetture Orientate ai Servizi (Service
Oriented Architecture, SOA). Tali architetture forniscono metodi e tecnologie per la progettazione e lo sviluppo di applicazioni software che possono
essere eseguite su diverse infrastrutture di rete. Le SOA sono costituite da
componenti con compiti ben definiti, autonome, eterogenee nella loro realizzazione e funzionamento interno, ma capaci di interagire fra loro tramite
interfacce e protocolli comuni; tali componenti sono dette, appunto, servizi.
Uno tra i paradigmi di implementazione per SOA che ha avuto maggior
successo è certamente quello basato sui servizi Web (Web Service). Possiamo definire un servizio Web come un’entità computazionale autonoma,
accessibile sia da utenti che da altri servizi attraverso il Web, che consente
di creare soluzioni a specifici problemi di programmazione e di mettere alcune funzionalità a disposizione di altre applicazioni. Le tecnologie legate ai
1
2
Introduzione
servizi Web consentono di generare servizi complessi orchestrando servizi più
semplici. Il risultato dell’orchestrazione, dunque, può essere visto anch’esso
come un servizio vero e proprio. Per permettere l’orchestrazione di servizi
Web sono stati introdotti vari linguaggi, tra i quali il più conosciuto è ad
oggi WS-BPEL (Web Service Business Process Execution Language) [27], il
linguaggio standard OASIS [17].
Come per tutti i sistemi software potenzialmente complessi e di grandi
dimensioni, la progettazione di applicazioni orientate ai servizi richiede l’uso
di una metodologia di sviluppo rigorosa, che accompagni tutte le fasi, dalla
progettazione al rilascio. L’approccio preso in considerazione in questa tesi
consiste nel descrivere i servizi mediante un linguaggio di modellazione ad
alto livello e, successivamente, analizzare i modelli ottenuti mediante tecniche formali, per poi procedere verso l’implementazione vera e propria. Lo
sviluppo di una tale metodologia è, ad esempio, uno dei principali obiettivi
del progetto SENSORIA [23] finanziato dalla Unione Europea.
Allo stato attuale, UML [8] è il linguaggio di modellazione di sistemi
software maggiormente utilizzato. Recentemente per tenere conto delle specificità delle applicazioni orientate ai servizi, sono stati definiti appositi profili
UML quali ad esempio SoaML [18] e UML4SOA [13, 14]. Il primo è maggiormente incentrato sugli aspetti strutturali dei servizi, mentre il secondo
tratta in maniera più dettagliata gli aspetti comportamentali degli stessi.
Attualmente entrambi i profili sono privi di una semantica formale vera e
propria; devono essere considerati, pertanto, come linguaggi di modellazione
semi-formali.
Viceversa diversi calcoli di processo (ad esempio [11, 5, 9, 4]) sono stati
3
recentemente proposti per specificare ed analizzare sistemi orientati ai servizi. I calcoli di processo sono formalismi dotati di una semantica rigorosa, ma
forniscono un numero molto limitato di costrutti elementari. Ciò, unito al
fatto che essi sono linguaggi testuali e non grafici, rende il loro utilizzo come
linguaggi di specifica meno intuitivo e richiede una certa familiarità con i
linguaggi formali. Per contro, essi mettono a disposizione una vasta gamma
di tecniche di analisi, quali ad esempio equivalenze comportamentali, utili
per l’ottimizzazione del codice, sistemi di tipo, utili per verificare compatibilità tra servizi, e logiche modali, utili per esprimere e verificare proprietà
comportamentali.
Con l’obiettivo di combinare i vantaggi offerti dai linguaggi di specifica
grafici ad alto livello con i vantaggi offerti dai calcoli di processo relativamente agli strumenti di analisi formale, in [2] è stata definita una funzione
di traduzione da UML4SOA in COWS (Calculus for Orchestration of Web
Service) [11]. COWS è un calcolo di processo per specificare e orchestrare
sistemi orientati ai servizi che consente di modellarne i comportamenti dinamici. La scelta di COWS è motivata sia dal fatto che, come UML4SOA,
è anch’esso ispirato a WS-BPEL, sia dai vari strumenti di analisi che mette
a disposizione. Tra questi, vanno menzionati CMC (COWS interpreter &
Model Checker) [15], un interprete e model checker per il calcolo, ed alcuni
strumenti per l’analisi qualitativa e quantitativa (si veda [10, 3, 22]).
Lo scopo di questa tesi è quello di automatizzare, attraverso uno strumento software, il processo di traduzione da UML4SOA a COWS. Oltre agli
ovvi vantaggi di risparmio di tempo e prevenzione di errori umani rispetto a
traduzioni effettuate ‘a mano’ (si veda [1]), il lavoro effettuato ha permesso
4
Introduzione
Figura 1.1: Sintesi del progetto UStoC
di evidenziare e quindi correggere alcune imprecisioni inizialmente presenti
nella definizione originaria della funzione di traduzione.
Lo strumento software realizzato, denominato UStoC, accetta come input
un insieme di file XMI [21] che rappresentano diagrammi UML4SOA in forma testuale e li traduce in un termine COWS, scritto nella sintassi definita
da CMC. A loro volta i file XMI possono essere automaticamente generati
dall’editor UML MagicDraw [12], utilizzando il profilo UML4SOA. La traduzione ottenuta potrà quindi essere fornita in ingresso a CMC per passare alla
fase di analisi (una visione di insieme del framework è graficamente rappresentata in Figura 1.1). UStoC, realizzato in Java [16], consta di due fasi: la
prima provvede ad analizzare il file XMI e, per ogni elemento UML trovato,
a creare l’oggetto Java corrispondente; la seconda si occupa di realizzare la
traduzione di ogni oggetto Java, così da ottenere la traduzione completa della
specifica UML4SOA.
5
Il resto della tesi è strutturato come segue. Nel Capitolo 2 sono introdotte le principali caratteristiche di UML, con particolare attenzione per i
diagrammi delle attività e una descrizione, mediante un esempio, del profilo
UML4SOA e del suo meta-modello. Nel Capitolo 3 viene presentato COWS:
sintassi, semantica e alcuni esempi. Il Capitolo 4 descrive, in dettaglio, il
progetto UStoC: la struttura, le tecniche realizzative scelte e la traduzione
da UML4SOA a COWS. Il Capitolo 5 mostra, invece, una guida all’uso di
UStoC. Grazie anche ad alcuni esempi, vedremo come avviene l’installazione,
la realizzazione di un diagramma UML4SOA, la traduzione attraverso UStoC
e l’interpretazione per mezzo di CMC. Il Capitolo 6, infine, riporta alcune
osservazioni conclusive e un accenno ad eventuali sviluppi futuri.
Il CD-ROM allegato contiene il codice sorgente di UStoC, il relativo jar
eseguibile, alcuni diagrammi, realizzati con MagicDraw, che saranno presentati come esempi nel corso del Capitolo 5 ed infine una versione pdf della
tesi.
6
Introduzione
Capitolo
2
UML e UML4SOA
UML (Unified Modeling Language) è un linguaggio per la modellazione frequentemente utilizzato nell’ambito della progettazione di Sistemi Software.
Le caratteristiche che ne determinano il successo sono l’intuitività conferitagli dalla notazione grafica, l’esistenza di uno standard, la notevole capacità
espressiva e l’estendibilità. UML fornisce, infatti, un meccanismo flessibile
di estensioni per definire linguaggi di dominio specifico (DSL), detti profili
UML.
In questo capitolo, dopo aver brevemente presentato le funzionalità principali di UML [6, 7], andremo a descrivere il profilo UML4SOA [24], un’estensione di UML per architetture orientate ai servizi. Ispirato a WS-BPEL,
linguaggio standard OASIS per l’orchestrazione di servizi Web, UML4SOA
integra i diagrammi delle attività di UML con azioni per l’invocazione di
servizi e il conseguente scambio di messaggi; inoltre specializza i nodi delle
attività strutturate e gli archi per rappresentare scope, fault e compensation
handler.
7
8
UML e UML4SOA
2.1
UML
UML rappresenta una famiglia di notazioni grafiche che si basano su un
singolo meta-modello e servono a supportare la descrizione e il progetto di
sistemi software, in particolare quelli costruiti seguendo il paradigma orientato agli oggetti (Object-Oriented). Lo standard UML, gestito dall’Object
Management Group (OMG) [20], nasce dall’esigenza di avere un linguaggio
universale per la modellazione di applicazioni basate su oggetti, che potesse
essere utilizzato da ogni industria produttrice di software.
Le notazioni grafiche per la progettazione non sono una novità, il loro
valore sta nell’aiuto che forniscono alla comunicazione e alla comprensione.
Un buon diagramma spesso aiuta a tramettere le idee che sono alla base di
un progetto. Tra tutti i linguaggi grafici, l’importanza di UML deriva dalla
sua ampia diffusione e dalla standardizzazione all’interno della comunità che
si occupa dello sviluppo orientato agli oggetti, sebbene la sua popolarità si
sia diffusa anche tra chi adotta tecniche non orientate agli oggetti. Gran
parte della letteratura sulla progettazione e programmazione orientata agli
oggetti usa, infatti, UML per descrivere soluzioni analitiche e progettuali in
modo sintetico e comprensibile.
2.1.1
Caratteristiche generali
La notazione UML è semi-grafica e semi-formale; un modello UML è costituito da una collezione organizzata di diagrammi correlati, costruiti componendo elementi grafici (con significato formalmente definito), elementi testuali
formali ed elementi di testo libero.
2.1 UML
9
Il linguaggio è stato progettato con l’obiettivo esplicito di facilitare il
supporto software alla costruzione di modelli e di integrare questo supporto
con gli ambienti di sviluppo. OMG, in particolare, gestisce una famiglia
di standard correlati a UML, detta Model Driven Architecture (MDA) [19],
che ha lo scopo di fornire le fondamenta per lo sviluppo di ambienti evoluti
in cui la modellazione UML, in qualche misura, possa sostituire di fatto la
programmazione tradizionale.
UML è un linguaggio di modellazione general-purpose, che fornisce concetti e strumenti applicabili in molti contesti. Poiché particolari domini
applicativi o famiglie di applicazioni potrebbero aver bisogno di concetti e
soluzioni specifici a particolari esigenze, UML fornisce un meccanismo standard che consente di estendere il linguaggio. Una estensione di UML per un
particolare contesto viene detta profilo UML.
Aspetti di modellazione
UML consente di descrivere un sistema secondo tre aspetti principali, per
ciascuno dei quali si utilizzano un insieme di diagrammi specifici (che possono
poi essere messi in relazione fra loro):
• Il modello funzionale (functional model) rappresenta il sistema
dal punto di vista dell’utente, ovvero ne descrive il comportamento così
come esso è percepito dall’esterno.
• Il modello a oggetti (object model) rappresenta la struttura
e sottostruttura del sistema utilizzando concetti della progettazione
10
UML e UML4SOA
orientata agli oggetti, quali classe, oggetto, relazione fra classi e fra
oggetti.
• Il modello dinamico (dynamic model) rappresenta il comportamento degli oggetti del sistema, ovvero la loro evoluzione nel tempo e
le dinamiche delle loro interazioni.
Struttura di un modello UML
Un modello UML è costituito da:
• Viste che mostrano i diversi aspetti del sistema per mezzo di un insieme
di diagrammi.
• Elementi del modello cioè concetti che permettono di realizzare
diagrammi, ad esempio attori, classi, pacchetti, oggetti ...
• Diagrammi che permettono di descrivere graficamente le viste. I
diagrammi UML sono di nove tipologie:
1. Diagramma dei casi d’uso: descrive le funzioni o servizi offerti da
un sistema, così come sono percepiti e utilizzati dagli attori che
interagiscono col sistema stesso.
2. Diagramma delle classi: descrive i tipi di entità, con le loro caratteristiche e le eventuali relazioni. Gli strumenti concettuali
utilizzati sono quelli di classe del paradigma orientato agli oggetti
e altri come la generalizzazione, l’associazione e la dipendenza.
3. Diagramma degli oggetti: descrive un sistema in termini di oggetti
e relative relazioni; è molto simile al diagramma delle classi.
2.1 UML
11
4. Diagramma di stato: descrive il comportamento di entità o di
classi in termini di stato. Il diagramma mostra gli stati che sono
assunti dall’entità o dalla classe in risposta ad eventi esterni.
5. Diagramma delle attività: mostrano il flusso di lavoro di un
progetto, con il supporto di interazioni e concorrenza.
6. Diagramma di sequenza: utilizzato per descrivere uno scenario,
cioè una determinata sequenza di azioni in cui tutte le scelte sono
state già effettuate.
7. Diagramma di comunicazione:
descrive l’interazione fra più
partecipanti alla realizzazione di una certa funzionalità.
8. Diagramma dei componenti: ha lo scopo di rappresentare la struttura interna del sistema software modellato in termini dei suoi
componenti principali e delle relazioni fra di essi.
9. Diagramma di deployment: descrive un sistema in termini di
risorse hardware.
Nella prossima sezione vedremo una descrizione più approfondita dei
diagrammi delle attività, sui quali ci concentreremo nel corso di questa tesi.
2.1.2
Diagrammi delle attività
I diagrammi delle attività servono per la modellazione di workflow e descrivono il flusso di controllo tra un’attività e un’altra. L’attività, che è il
componente principale, rappresenta un processo del mondo reale (ad esempio la compilazione di un ordine) o l’esecuzione di una procedura software
12
UML e UML4SOA
Figura 2.1: Esempio di diagramma delle attività
(ad esempio un metodo di una classe); è rappresentata graficamente da un
rettangolo smussato. Il flusso è rappresentato tramite delle frecce orientate,
che indicano la sequenza con cui devono essere effettuate le diverse attività.
È previsto un simbolo per indicare l’inizio del flusso, rappresentato da un
cerchio pieno, ed un altro per indicarne la fine, rappresentato da un cerchio
con un punto all’interno. Il comportamento parallelo si ha mediante elementi (Fork) rappresentati da una linea perpendicolare rispetto al flusso. Il
comportamento condizionale si ha quando il flusso si dirama in una serie di
possibilità di decisione (Decision), rappresentati da dei rombi da cui partono
i flussi alternativi. Nell’immagine in Figura 2.1 viene presentato un esempio
di semplice Diagramma delle attività. L’esempio mostra il flusso di lavoro
di una persona che si deve recare al lavoro la mattina. Sono rappresenta-
2.2 UML4SOA
13
te nell’esempio una sequenza di azioni, di cui alcune in parallelo (bere il
caffè e mangiare) e alcuni comportamenti condizionali (ad esempio Guida
Macchina se c’è benzina, Prendi Autobus altrimenti).
2.1.3
Estendibilità e profili
UML include tre componenti che consentono l’estensione della sua sintassi
e della sua semantica da parte dell’utente: stereotipi (stereotypes), valori
etichettati (tagged values) e vincoli (constraints). Questi strumenti possono
essere usati nel contesto di un modello per esprimere concetti altrimenti non
rappresentabili in UML, o non rappresentabili in modo chiaro e sufficientemente astratto. I profili UML sono collezioni di stereotipi, valori etichettati
e vincoli che specializzano il linguaggio per particolari domini applicativi o
per l’uso di UML in congiunzione con particolari tecnologie. Fra i profili
riconosciuti ufficialmente da OMG si trovano profili per CORBA, per sistemi
distribuiti, per sistemi con vincoli di QoS e per sistemi real-time. Nel corso
della prossima sezione vedremo una descrizione di UML4SOA, il profilo UML
introdotto per modellare sistemi orientati ai servizi.
2.2
UML4SOA
Le architetture orientate ai servizi (SOAs) introducono molti nuovi concetti e
pattern architetturali per la modellazione di sistemi software. Per permttere
agli sviluppatori di usare questi nuovi concetti in tutte le loro potenzialità,
un linguaggio di modellazione come UML necessita di essere esteso con specifici elementi che possano esprimere questi nuovi concetti al giusto livello di
14
UML e UML4SOA
astrazione. Per questo è stato realizzato il profilo UML4SOA, un’estensione
UML che definisce un linguaggio di dominio specifico per i sistemi orientati
ai servizi. UML4SOA è un profilo UML il cui scopo è facilitare il lavoro di
progettazione e implementazione di software orientato ai servizi. UML4SOA,
definito a partire dal meta-modello Meta Object Facility (MOF) di UML, è
un’estensione conservativa di UML. Il beneficio di una tale estensione se comparata con il ‘puro’ UML è quello di fornire elementi per modellare concetti
frequentemente usati nella rappresentazione di architetture orientate ai servizi. Così il meta-modello e il corrispondente profilo UML sono la base per
definire un processo guidato per la trasformazione e la generazione automatica di codice. Presenteremo più avanti il meta-modello, gli elementi delle
orchestrazioni di servizi che fanno parte del profilo e la sintassi utilizzata
all’interno di questo lavoro.
2.2.1
Il meta-modello
Il meta-modello proposto in Figura 2.2 include gli elementi UML4SOA per
il supporto alla specifica degli aspetti comportamentali dei sistemi orientati
ai servizi, che comprende la descrizione dei comportamenti dei componenti e dei servizi. L’attenzione è posta sulle interazioni fra servizi, gestione
delle eccezioni e compensazioni. Il meta-modello è presentato analizzando
principalmente l’orchestrazione di servizi.
L’orchestrazione di servizi è il processo che combina insieme più servizi
esistenti per crearne uno nuovo. Per permettere la modellazione di queste
composizioni in UML, sono stati aggiunti alcuni specifici elementi ai dia-
2.2 UML4SOA
15
Figura 2.2: Meta-modello UML4SOA
grammi delle attività. Il meta-modello rappresentato in Figura 2.2 mostra
questi elementi (rappresentati su sfondo bianco), le loro relazioni con gli ele-
16
UML e UML4SOA
menti UML (rappresentati su sfondo grigio), la definizione di Orchestration
Activity e le informazioni dei messaggi inviati e ricevuti.
Di seguito presentiamo un elenco con la descrizione di ogni stereotipo
rappresentato in Figura 2.2, per ciascuno dei quali specificheremo la sintassi
nella prossima sezione.
• Orchestration: Attività che specifica l’orchestrazione di servizi che
deve essere eseguita.
• Scope:
StructureActivityNode che può avere associato event,
exception e compensation handler.
• Send: Azione che invoca metodi inviando messaggi in modo non
bloccante.
• Receive: Azione per la ricezione di messaggi. Si blocca fino a che il
messaggio non è ricevuto.
• Receive&Send: Azione che denota una sequenza ordinata di receive
e send.
• Send&Receive: Azione che denota una sequenza ordinata di send e
receive.
• Lnk:
Pin che contiene il riferimento al servizio coinvolto
nell’interazione.
• Snd: Pin che contiene il dato in output prodotto da un’azione.
• Rcv: Pin che contiene il valore in input che deve essere ricevuto.
2.2 UML4SOA
17
• ExceptionEdge: ActivityEdge usato per associare exception handler
ad azioni o scope.
• Raise: Azione che causa l’arresto del normale flusso di esecuzione e
invoca l’exception handler associato.
• CompensationEdge:
ActivityEdge che aggiunge compensation
handler ad azioni o scope.
• Compensate: Azione che attiva l’esecuzione delle compensazioni
definite per uno scope o una activity.
• CompensateAll: Azione che attiva le compensazioni di uno scope.
Può essere inserito solo in scope definiti per la compensazione.
• Event: ActivityEdge che associa event handler alle azioni o agli scope.
È importante sottolineare che per le azioni e per gli eventi non vengono
utlizzati i pin del meta-modello UML, ma sono invece definiti stereotipi particolari. Questo è dovuto al fatto che gli elementi UML non forniscono una
semantica per i flussi di dati utile, hanno un numero limitato di input e
output pin e non sempre sono supportati dai tool di UML.
2.2.2
Sintassi
Dopo aver definito il meta-modello UML4SOA, presentiamo in Tabella 2.1
la sintassi in stile BNF di UML4SOA, più adatta a definire un’eventuale
traduzione. Ogni riga della tabella rappresenta una produzione nella forma:
SIMBOLO ::= ALTER1 | . . . | ALTERn
18
UML e UML4SOA
dove il non terminale SIMBOLO è nell’angolo in alto a sinistra (in sfondo
grigio), mentre le alternative ALTER1 , . . . , ALTERn sono gli altri elementi
della riga.
Per semplificare la traduzione e la sua esposizione adotteremo alcune restrizioni sul linguaggio. Assumeremo che ogni azione o scope abbia un solo
arco entrante ed uscente, che Fork o Decision abbiano un solo arco entrante e
che Join o Merge abbiano solo un arco uscente. Queste restrizioni non compromettono l’espressività del linguaggio e sono solitamente adottatate dalla
maggior parte degli utilizzatori di UML per ragioni di chiarezza. Ometteremo
anche alcuni dei più classici costrutti UML, quali object flows, expansion regions e alcune azioni. La ragione di questa scelta sta nel fatto che UML4SOA
offre una versione specializzata di molti di questi costrutti. Riguardo gli object flows, usati per passare valori attraverso i nodi, essi non sono necessari,
poiché per la comunicazione tra servizi UML4SOA si affida a input e output pin, mentre i dati sono condivisi tra gli elementi di uno scope tramite
variabili.
Un’applicazione UML4SOA è rappresentata da un insieme finito di Orchestration ORC. Un Orchestration è un’attività UML che racchiude al suo
interno uno scope con, eventualmente, ulteriori scope annidati. Uno scope è
un’attività strutturata di UML che permette di raggruppare insieme attività
con proprie variabili associate, collegamenti ad altri servizi, event, fault e
compensation handler.
Una lista di variabili è generata dalla seguente grammatica:
VARS ::= nil | X , VARS |
wo X , VARS
2.2 UML4SOA
19
ORC
SCOPE
GRAPH
CONTROL_FLOW
ACTION
GRAPH GRAPH
SCOPE
CONTROL_FLOW
FORK
JOIN
DECISION
ACTION
Tabella 2.1: Sintassi UML4SOA
MERGE
20
UML e UML4SOA
Useremo X per definire le variabili e il simbolo wo per indicare che una
variabile è write-once (cioè non riscrivibile). Gli editor grafici per UML4SOA
(in particolare MagicDraw) permettono di dichiarare variabili all’interno di
uno scope, ma non le raffigurano nella loro rappresentazione grafica. Nella
nostra sintassi le variabili sono comunque raffigurate perché risulteranno utili
ai fini della traduzione in COWS. Per motivi simili, mostriamo i nomi degli
archi nella rappresentazione grafica. Vale la pena ricordare che, al fine di ottenere una traduzione composizionale, ogni arco è diviso in due parti: quella
uscente da un’attività e quella entrante in un’altra. Nella parte uscente è
specificata una guardia; cioè un’espressione booleana che può essere omessa
qualora il valore corrisponda a true.
Un grafo GRAPH può essere costruito connettendo archi agli initial node,
final node, flussi di controllo, azioni e scope. Event, exception e compensation handler sono attività collegate ad uno scope rispettivamente da un
event, un compensation e un exception edge. Un event handler è uno scope
attivato da un evento. Un compensation handler è uno scope la cui esecuzione compensa l’esecuzione del relativo scope principale. Un exception handler
è un’attività generata da una raise, il cui scopo è di attivare l’esecuzione delle
compensazioni installate.
Event, exception e compensation handler hanno per default la seguente
struttura:
2.2 UML4SOA
21
In modo da rendere più leggibile e chiara la rappresentazione di un Orchestration, questi handler possono essere omessi dalla raffigurazione. I CONTROL_FLOW sono quelli definiti dallo standard UML: fork, join, decision e
merge.
In conclusione UML4SOA fornisce sette azioni specializzate per lo scambio di dati, per sollevare eccezioni e per attivare compensazioni di scope.
L’azione SEND invia al servizio identificato da p, il messaggio ottenuto dalla
valutazione delle espressioni expr1 ,. . . ,exprn . In UML4SOA la sintassi delle
espressioni è deliberatamente omessa; assumeremo in UStoC che esse contengano somme, espressioni booleane, e variabili. L’azione RECEIVE consente di
ricevere un messaggio salvato in X1 ,. . . ,Xn , dal servizio identificato da p. Le
altre due azioni per lo scambio di messaggi, cioè SEND&RECEIVE e RECEIVE&SEND, rappresentano una scorciatoia rispettivamente per una sequenza
di send e receive dallo stesso partner e viceversa. Una RAISE causa la terminazione del flusso di controllo e attiva gli exception handler associati allo
scope. Una COMPENSATE attiva le compensazioni degli scope a cui è collegata. Una azione COMPENSATE_ALL, che può essere inserita solo all’interno
di un compensation o exception handler, attiva la compensazione di tutti gli
scope annidati all’interno dello scope a cui l’azione è collegata.
2.2.3
Esempio
Presentiamo in questa sezione le specifiche UML4SOA mediante un esempio
realistico ma semplificato, rappresentato in Figura 2.3, basato sul classico
scenario di una Agenzia di Viaggi.
22
UML e UML4SOA
Figura 2.3: Esempio: Agenzia di Viaggi
2.2 UML4SOA
23
Un’agenzia di viaggi offre un servizio che prenota un volo e un hotel a
seconda della richiesta del cliente. L’attività comincia ricevendo (mediante
un’azione receive) un messaggio da un client contenente la richiesta di un volo
(flightReq) e di un hotel (hotelReq). Il flusso procede, quindi, in due rami
paralleli: nella parte sinistra, mediante una Send&Receive, il volo richiesto
è inviato al servizio di ricerca voli (flightService) e la computazione attende
la risposta del messaggio che viene salvato nella variabile flightAnswer, il cui
valore sarà in seguito controllato da un decision. Appena questa azione è
eseguita, viene installato un compensation handler. Il compensation handler
consiste in una send di un messaggio di cancellazione della richiesta al servizio
di ricerca voli. La stessa cosa avviene nella parte destra, per la prenotazione
dell’hotel, mediante il servizio di ricerca hotel (hotelService).
Se entrambe le risposte sono positive, le due ramificazioni si uniscono, le
risposte sono inoltrate al cliente e l’attività termina con successo. Se almeno
una risposta è negativa viene sollevata un’eccezione tramite un’azione raise.
Un’eccezione può essere generata anche a causa di un event che consista in
un messaggio in arrivo dal client con l’ordine di cancellare la sua richiesta.
Tutte le eccezioni sollevate sono catturate dall’exception handler che grazie
all’azione Compensate_All attiva tutte le compensazioni fino a quel momento
installate, in ordine inverso rispetto al loro inserimento, e notifica al client
che la richiesta non è stata soddisfatta.
24
UML e UML4SOA
Capitolo
3
COWS
COWS (Calculus for Orchestration of Web Services) è un linguaggio di modellazione per applicazioni orientate ai servizi, che specifica e combina sistemi
orientati ai servizi, modellando i loro comportamenti dinamici. Sebbene sia
un vero e proprio calcolo di processo, COWS prende ispirazione anche da
WS-BPEL, il linguaggio standard OASIS per l’orchestrazione di servizi Web.
3.1
Sintassi
Un componente base di COWS è l’endpoint di comunicazione, che rappresenta una sorta di canale di comunicazione. Per descrivere un endpoint sono
necessari due elementi base della sintassi COWS: un nome di partner e un’operazione. Ciò fornisce un meccanismo flessibile per l’assegnazione di nomi,
in quanto fa si che lo stesso servizio possa essere identificato mediante nomi
logici differenti. Inoltre, è possibile comporre i nomi degli endpoint in modo separato come, ad esempio, nel caso di una interazione richiesta-risposta,
dove il fornitore del servizio conosce il nome dell’operazione di risposta ma
25
26
COWS
non il nome del partner verso il quale deve rispedire i risultati. Ad esempio,
il servizio Ping:
[x] p
•
oreq ?hxi . x
•
ores ! hI livei
conoscerà solo a run-time il nome del partner name per la risposta: infatti,
l’operazione ores viene invocata tramite il partner il cui valore viene assegnato
alla variabile x durante l’attività di receive iniziale. È possibile scambiare
nomi di partner e operazioni durante la comunicazione e questo permette alle
istanze dei servizi di interagire secondo differenti pattern di comunicazione.
Per motivi implementativi, però, i nomi ricevuti dinamicamente non possono
essere utilizzati per definire nuovi endpoint di ricezione.
Le entità computazionali di COWS sono chiamate servizi. Tipicamente,
quando riceve una richiesta, un servizio genera una specifica istanza per
occuparsene. I servizi possono essere in grado di ricevere messaggi multipli
in un ordine imprevedibile a livello statico, in modo tale che il primo che
arriva genera un’istanza del servizio verso la quale vengono instradati tutti i
messaggi che arrivano successivamente.
La metodologia utilizzata per la correlazione dei messaggi che formano
una stessa interazione si basa sul meccanismo di pattern-matching. Questo
meccanismo permette di individuare quali dati risultano importanti per l’identificazione delle istanze dei servizi e di conseguenza instradare in modo
corretto i messaggi. Il meccanismo permette ad un singolo messaggio di partecipare a molteplici sessioni di interazione, ognuna delle quali è identificata
da un differente valore di correlazione.
3.1 Sintassi
s
27
::=
|
|
|
|
|
|
g
kill(k)
u • u0 !¯
g
s | s0
{|s|}
[e] s
∗s
(services)
(kill)
(invoke)
(receive-guarded choice)
(parallel composition)
(protection)
(delimitation)
(replication)
0
p • o?w̄ . s
g+g
(receive-guarded choice)
(nil)
(receive)
(choice)
::=
|
|
Tabella 3.1: Sintassi COWS
Il range in cui le sostituzioni generate dalla comunicazione sono applicate
è regolato dall’operatore di delimitazione, che è l’unico operatore legante
definito nel calcolo. Inoltre, questo operatore permette di generare nuovi
nomi, e delimitare il campo di azione delle attività di kill, che possono essere
usate per forzare la terminazione di istanze di servizio. Codice sensibile,
situato nel raggio di azione di una kill, può comunque essere protetto da
terminazioni forzate mediante l’uso dell’operatore di protezione.
Introduciamo in questa sezione la sintassi di COWS specificata in Tabella 3.1. Essa è parametrizzata da tre insiemi numerabili e a due a due
disgiunti:
• l’insieme delle killer labels, indicate con k, k 0 , . . .
• l’insieme dei valori, indicati con v, v 0 , . . .
• l’insieme delle variabili, indicate con x, y 0 , . . .
28
COWS
Il linguaggio è parametrizzato inoltre rispetto all’insieme delle espressioni,
indicate con , la cui sintassi è omessa per incrementare la genericità di impiego del calcolo. Si assume però che contengano solamente valori e variabili,
e non killer label (che dunque non sono possono essere scambiate all’interno
di una comunicazione). Non è specificato l’insieme dei valori; tuttavia, si
assume che esso includa l’insieme dei nomi, indicati con n, m . . . , utilizzati principalmente per specificare il nome dei partner e delle operazioni. La
notazione
•
è usata per rappresentare tuple (sequenze ordinate) di elementi
dello stesso tipo, dove ad esempio x è la notazione compatta di hx1 , . . . , xn i.
Si assume che le variabili nelle tuple siano a due a due differenti.
Le attività principali del linguaggio sono nove:
Nil Indica il servizio vuoto ed è rappresentato mediante il simbolo 0.
Kill La sintassi è kill(k), dove k rappresenta lo scopo dell’attività Kill.
Una Kill forza la terminazione di tutte le attività parallele non protette,
all’interno del corpo di una delimitazione [k] (che confina gli effetti della
Kill). Le Kill hanno la precedenza su tutte le altre attività.
Protection L’attività è utilizzata per proteggere un servizio dagli effetti
di una terminazione forzata (Kill). La sintassi è {|s|} dove s è il servizio che
intendiamo preservare.
Delimitation Attività utilizzata per specificare lo scopo di un nome, una
variabile o una killer label. Attraverso la delimitazione è possibile generare
nuovi nomi (se la delimitazione ha come argomento un nome), regolare il
3.1 Sintassi
29
raggio di applicazione delle sostituzioni generate da una comunicazione (se
la delimitazione ha come argomento una variabile) e delimitare il campo di
azione dell’attivita Kill (se la delimitazione ha come argomento una killer
label). La Delimitation, inoltre, è l’unico operatore legante di COWS. La
sintassi è [e] s dove e è la variabile, il nome o la killer label delimitata e s
rappresenta lo scopo di e. In pratica, la Delimitation [e] s vincola e all’interno
del termine s.
Invoke La sintassi di una invoke è u • u0 !¯ , dove u
•
u0 rappresenta l’end-
point e ¯ costituisce la tupla che deve esssere valutata. Una invoke permette
di invocare un’operazione fornita da un partner e quindi di trasmettere i dati
risultanti dalla valutazione delle espressioni che compongono la tupla. L’attività di Invoke può essere eseguita solo se tutte le sue espressioni possono
essere valutate in modo corretto.
Receive La sintassi di una Receive è p • o?w̄ . s , dove p e o sono rispettivamente il nome del partner e dell’operazione che compongono l’endpoint,
w̄ costituisce i parametri per la ricezione del messaggio e s è il servizio che
verrà eseguito non appena l’attivita di ricezione sarà conclusa. Una Receive permette al servizio di offrire un’operazione che può essere invocata da
un altro partner. La comunicazione con un’attività di Invoke che utilizza
lo stesso endpoint può avvenire qualora i parametri della Receive soddisfino una relazione di matching con le tuple di valori inviati dall’Invoke. Se
questo avviene le variabili dell’argomento della Receive sono sostituite con i
rispettivi valori inviati dalla Invoke all’interno degli scope individuati dalle
30
COWS
corrispondenti delimitazioni.
Replication La Replication consente di creare un illimitato numero di copie di un servizio. In questo modo, la Replication può essere utilizzata per
implementare servizi persistenti o ricorsivi. La sua sintassi è ∗ s dove s è il
servizio che verrà replicato.
Guarded Choice La sintassi di una Guarded Choice è g + g dove g è
un servizio che può essere esclusivamente una Nil, una Receive o una stessa
Guarded Choice. Una scelta guardata si comporta esattamente come uno dei
suoi distinti rami, a seconda di quale tra di essi esegue una Receive.
Parallel Definisce l’esecuzione in parallelo di più servizi. La sua sintassi è
s | s0 dove s e s0 sono una qualsiasi delle attività COWS sopra definite.
3.2
Semantica Operazionale
La semantica operazionale di COWS è definita per servizi chiusi, cioè servizi
senza variabili o killer label libere (variabili e killer label libere sono considerate come errori del programmatore, come avviene per i compilatori reali),
ma ovviamente le regole riguardano anche servizi non chiusi. Formalmente
la semantica è data in termini di una congruenza strutturale e di una relazione di transizione etichettata. La prima identifica servizi differenti a livello
sintattico che possiedono però intuitivamente la stessa struttura, mentre la
seconda definisce le computazioni. In Tabella 3.2 mostriamo le leggi della
congruenza strutturale per le attività di replicazione, protezione e delimita-
3.2 Semantica Operazionale
∗0 ≡ 0
{| {|s|} |} ≡ {|s|}
[e1 ] [e2 ] s ≡ [e2 ] [e1 ] s
31
∗ s ≡ s |∗ s
{|[e] s|} ≡ [e] {|s|}
{|0|} ≡ 0
[e] 0 ≡ 0
s1 | [e] s2 ≡ [e] (s1 | s2 ) if e ∈
/ fe(s1 )∪fk(s2 )
Tabella 3.2: Alcune leggi di congruenza strutturale
zione, mentre sono omesse le leggi per gli altri operatori. Si tenga inoltre
presente che la composizione parallela e la scelta guardata sono associative,
commutative ed hanno 0 come elemento neutro; mentre la scelta guardata
aggiunge alle proprietà della composizione parallela quella di idempotenza.
Un’occorrenza di una variabile, di un nome o di una killer label è libera
se non è nella portata di una Delimitation. Indichiamo con f e(s) l’insieme
di occorrenze di variabili, nomi e killer label libere in un termine s, e con
f k(s) l’insieme di occorrenze libere delle killer label nel termine s. L’ultima
legge di congruenza strutturale può estendere lo scopo dell’argomento della
delimitazione, in modo da consentire la comunicazione, eccetto quando e è
una killer label di s2 (questo evita di coinvolgere s1 nell’effetto della kill
presente all’interno di s2 ).
Per descrivere la relazione di transizione etichettata, è necessario definire
alcune funzioni ausiliarie:
• la funzione [[_]] per la valutazione di espressioni chiuse;
• la funzione halt(_) per determinare gli effetti dell’esecuzione di una
terminazione forzata;
• la funzione M(_, _) di pattern-matching.
32
COWS
halt(kill(k)) = halt(u1 • u2 !e) = halt(g) = 0
halt({|s|}) = {|s|}
halt(s1 | s2 ) = halt(s1 ) | halt(s2 )
halt(∗s) = ∗halt(s)
halt([d]s) = [d]halt(s)
Tabella 3.3: Funzione halt(_)
M(x, v) = {x 7→ v}
M(v, v) = ∅
M(w1 , v1 ) = ρ1
M(hi, hi) = ∅
M(w̄2 , v̄2 ) = ρ2
M((w1 , w̄2 ), (v1 , v̄2 )) = ρ1 ] ρ2
Tabella 3.4: Regole della funzione di pattern matching
Definiamo per prima la funzione [[_]]: essa prende in input un’espressione
chiusa (cioè senza variabili) e restituisce un valore. Tale funzione non può,
ovviamente, essere definita in modo esplicito essendo già stata omessa in
precedenza la sintassi delle espressioni.
Descriviamo dunque la funzione halt(_) la quale, riceve un servizio s come
argomento e restituisce il servizio preservando esclusivamente le attività di
s presenti all’interno dell’operatore di protezione. La definizione dettagliata
di halt(_) è presentata in Tabella 3.3. Qualora la funzione sia applicata a
una composizione parallela, a una delimitazione o a una replicazione essa
si comporta come un omomorfismo, come dimostrano le regole descritte in
tabella. Il caso più significativo è certamente halt({|s|}) = {|s|}, negli altri
casi halt(_) restituisce 0.
La funzione parziale M(_, _), definita dalle regole in Tabella 3.4, descrive
il pattern-matching su dati semi-strutturati e consente di determinare se può
avvenire la sincronizzazione di una Receive e una Invoke sullo stesso endpoint.
3.2 Semantica Operazionale
33
Le regole stabiliscono che il matching è soddisfatto quando due tuple hanno lo stesso numero di campi e se i valori nei campi corrispondenti soddisfano
anch’essi il matching. Le variabili soddisfano il matching con qualsiasi valore,
mentre due valori soddisfano il matching solo se sono identici. Se due tuple
w e v soddisfano il matching, la funzione M(w, v) restituisce una sostituzione per la variabili in w, altrimenti la funzione non è definita. Chiamiamo
sostituzione una funzione (indicata con σ) che associa variabili a valori. Una
sostituzione è rappresentata da un insieme di associazioni della forma x 7→ v,
che indicano che v è assegnato a x. L’applicazione di una sostituzione ad
un termine s, indicata con s • σ, ha l’effetto di sostituire tutte le occorrenze
libere di x con v, per ogni x 7→ v appartenente a σ. Il numero di associazioni variabile-valore contenute in σ è indicato con |σ|, mentre con σ1 ] σ2
definiamo l’unione tra le due sostituzioni σ1 e σ2 nel caso in cui i loro domini
risultino disgiunti.
In conclusione, definiamo in Tabella 3.5 due predicati: noKill(s, e) ha
valore true se e non è una killer label oppure se e = k e s non può immediatamente eseguire una kill(k); noConf(s, n, v̄, `), con ` numeri naturali, vale
true se s non produce conflitti nella comunicazione; ad esempio s non può
eseguire una receive che soddisfi il matching con v̄ lungo l’endpoint ` che
generi una sostituzione con meno coppie di `.
Possiamo specificare adesso la definizione di relazione di transizione etichettata che definisce la computazione dei termini COWS. La relazione di
α
transizione etichettata → è definita come la più piccola relazione sui servizi indotta dalle regole in Tabella 3.6, dove l’etichetta α è generata dalla
seguente grammatica:
34
COWS
α ::= n C v̄ | n B w̄ | n ρ ` v̄ | k | †
Descriviamo dapprima il significato delle varie etichette delle transizioni:
• n C v̄ indica l’esecuzione di una Invoke sull’endpoint n.
• n B w̄ indica l’esecuzione di una Receive sull’endpoint n.
• n ρ ` v̄ (se ρ 6= ∅) indica l’esecuzione di una comunicazione lungo n, con
i valori scambiati v̄, che genera una sostituzione formata da ` coppie,
di cui la sostituzione ρ deve essere ancora applicata.
• k indica l’esecuzione della richiesta di terminare un termine all’interno
della delimitazione [k] .
• † indica l’effettuazione di una terminazione forzata.
In particolare † e n ∅ ` v̄ indicano i passi computazionali, che quindi corrispondono alla terminazione forzata e alla comunicazione (senza sostituzioni
pendenti). É possibile definire una computazione a partire da un servizio
chiuso s0 come una sequenza di transizioni della forma:
α
α
α
1
2
3
s0 −→
s1 −→
s2 −→
s3 . . . .
dove per ogni i, αi è † oppure n ∅ ` v̄.
Commentiamo di seguito le regole della semantica operazionale. L’attività kill(k) forza la terminazione di tutti le attività non protette che si
trovano all’interno della delimitazione [k] (regole (kill) e (parkill )), che blocca l’effetto dell’azione di kill trasformando l’etichetta della transizione da †k
in † (regola (delkill )). L’esistenza di questa delimitazione è assicurata dall’assunzione che la semantica è definita esclusivamente per servizi chiusi. Il
3.2 Semantica Operazionale
noKill(s, e) = true
35
noKill(s | s0 , k) = noKill(s, k) ∧ noKill(s0 , k)
if fk(e) = ∅
noKill(kill(k), k) = false
noKill(kill(k’), k) = true
noKill([e] s, k) = noKill(s, k)
if k 6= k0
noKill(u!¯
, k) = noKill(g, k) = true
if e 6= k
noKill([k] s, k) = true
noKill({|s|}, k) = noKill(∗ s, k) = noKill(s, k)
noConf(kill(k), n, v̄, `) = noConf(u!¯
, n, v̄, `) = noConf(0, n, v̄, `) = true
noConf(n0 ?w̄.s, n, v̄, `) =
false
true
if n0 = n ∧ | M(w̄, v̄) |< `
otherwise
noConf(g + g 0 , n, v̄, `) = noConf(g, n, v̄, `) ∧ noConf(g 0 , n, v̄, `)
noConf(s | s0 , n, v̄, `) = noConf(s, n, v̄, `) ∧ noConf(s0 , n, v̄, `)
noConf([e] s, n, v̄, `) =
noConf(s, n, v̄, `)
true
if e ∈
/n
otherwise
noConf({|s|}, n, v̄, `) = noConf(∗ s, n, v̄, `) = noConf(s, n, v̄, `)
Tabella 3.5: Non ci sono kill(k) attive / Non ci sono receive in conflitto lungo n
codice sensibile può essere protetto dalla terminazione forzata inserendolo
all’interno dell’operatore {|_|}, in questo modo {|s|} continua a comportarsi
come s ma rimane protetto (regola (prot)). In modo simile, la delimitazione [e]s si comporta come s (regola (del)), eccetto quando l’etichetta della
transizione a contiene e, nel qual caso a deve corrispondere sia a una comunicazione che assegna un valore ad e (regola (delcom )) sia ad una kill per e
(regola (delkill1 ), o quando un’attività kill per e è attiva in s, in quel caso
solo azioni kill possono essere eseguite (regole (delkill2 ) e (delkill3 )). L’invocazione di un servizio può procedere solo se tutte le espressioni contenute
nel proprio argomento possono essere valutate (regola (inv)). Una attività
di Receive offre la possibilità di invocare un’operazione attraverso un nome
di partner dato (regola (rec)); inoltre l’esecuzione di una Receive permette
di effettuare una scelta fra differenti comportamenti (regola (choice)). La
36
COWS
k
n B w̄
(kill)
kill(k) −−→ 0
n?w̄.s −−−−−→ s
a
[[¯
]] = v̄
g −−→ s
(inv)
n C v̄
a
n ρ]{x/v} ` v̄
k
s −−→ s0
s −−−−−−−−−−−→ s0
n ρ ` v̄
[x] s −−−−−→ s0 {x 7→ v}
k
k
a
(delcom )
†
[k] s −−→ [k] s0
†
(delkill2 )
e∈
/ e(a)
a 6= k, †
†
[e] s −−→ [e] s0
a
(del)
a
n B w̄
(delkill3 )
s −−→ s0
noKill(s, e)
(prot)
a
[e] s −−→ [e] s0
s1−−−−−→s01
(delkill1 )
s −−→ s0
k 6= e
[e] s −−→ [e] s0
s −−→ s0
(choice)
g + g 0 −−→ s
n!¯
−−−−−→ 0
s −−→ s0
(rec)
{|s|} −−→ {|s0 |}
n C v̄
s2−−−−−→s02
M(w̄, v̄) = ρ
noConf(s1 | s2 , n, v̄, | ρ |)
(com)
n ρ |ρ| v̄
s1 | s2 −−−−−−−→ s01 | s02
n ρ ` v̄
s1 −−−−−→ s01
n ρ ` v̄
s1 | s2 −−−−−→ s01 | s2
a
s1 −−→ s01
a 6= k, n ρ ` v̄
a
k
s1 −−→ s01
noConf(s2 , n, v̄, `)
(par)
s1 | s2 −−→ s01 | s2
(parcom )
k
s1 | s2 −−→ s01 | halt(s2 )
s ≡ s1
a
s1 −−→ s2
a
(parkill )
s2 ≡ s0
(cong)
s −−→ s0
Tabella 3.6: Semantica di COWS
comunicazione può essere realizzata qualora una Receive e una Invoke soddisfino il predicato di matching (regola (com)). La comunicazione genera
una sostituzione che è memorizzata nell’etichetta della transizione (per permettere applicazioni successive). Se più di un’attività di ricezione soddisfa il
pattern-matching con una data invocazione, solo la più definita (cioè quella
per cui è necessario un minor numero di sostituzioni) potrà concludere la
transizione (regole (com) e (parcom )). Tale meccanismo permette di correlare differenti comunicazioni tra servizi e può essere sfruttato per modellare
la priorità di un’istanza di un servizio rispetto alla corrispondente specifi-
3.3 Esempi
37
ca, quando entrambe possono processare la stessa richiesta. Quando poi si
incontra la delimitazione per una variabile x, argomento di una receive, la
delimitazione viene rimossa e viene applicata la sostituzione per x a tutto
il termine delimitato (regola (delcom )). La variabile x scompare dal termine, e non può più ricevere alcun valore (le variabili di COWS sono infatti
denominate write-once, cioè non riscrivibili). L’esecuzione di servizi paralleli corrisponde alla singola esecuzione di ogni servizio, tranne quando sono
presenti attività kill o comunicazioni. Infatti le kill attivano la terminazione
di tutti i servizi paralleli (regola (parkill )), mentre in caso di comunicazione
è necessario accertarsi solamente che l’esecuzione delle attività di Receive
con maggiore priorità proceda (regole (com) e (parcom )). L’ultima regola
(cong) afferma che servizi strutturalmente congruenti possiedono le stesse
transizioni.
3.3
Esempi
Concludiamo la descrizione del linguaggio COWS con la presentazione di
alcuni esempi, in modo da rendere più chiari alcuni aspetti importanti del
calcolo.
Introduciamo in questa sezione, inoltre, la sintassi usata dal CMC a cui
faremo riferimento nei due esempi finali e nei capitoli successivi durante la
presentazione di alcuni esempi d’uso di UStoC.
s ::=
|
|
nil
kill ( k )
u .u ’! < args >
( services )
( empty activity )
( kill )
( invoke )
38
|
|
|
|
|
|
|
|
|
|
COWS
p . o ? < params >. s
s1 + ... + sn
s | s’
{s}
[ n #] s
[k] s
[X] s
* s
A ( fparams )
let A ( fparams ) = s in s ’ end
( receive )
( receive - guarded choice )
( parallel composition )
( protection )
( name delimitation )
( kill delimitation )
( variable delimitation )
( replication )
( call )
( let construct )
dove:
• Killer labels: k,k’,... cominciano con la lettera minuscola e possono
essere usate solo all’interno di una kill.
• Variabili:
X,Y,... cominciano con la lettera maiuscola.
• Nomi: n,m,... cominciano con la lettera minuscola.
• Valori: v,v’,... ::= interi | boolean | nomi
• Identificatori: u,u’,... ::= variabili | nomi
• espressioni: e,e’,... ::=
– variabili
– valori
– e + e’ (se sono interi ritorna la somma, altrimenti il nome
corrispondente alla concatenazione)
– e = e’ (se sono interi ritorna il boolean del confronto, altrimenti
ritorna false)
• Invoke arguments: args ::= e | args,args
3.3 Esempi
39
• Receive parameters:
params ::= variabili | valori |
params,params
• Formal
parameters:
fparams
::=
variabili
|
nomi
|
KillerLabels | fparams,fparams
• Actual parameters:
aparams ::= variabili | valori |
KillerLabels | aparams,aparams
La semantica di CMC rispecchia quella di COWS; sottolineamo in aggiunta,
le clausole:
• let A(fparams)=s in s’ end : il costrutto let permette di assegnare
un nome ad un servizio s per facilitare l’uso ripetuto di s all’interno di
s’.
• A(fparams/aparams) : una chiamata di un servizio nel corpo di s’
al costrutto let si comporta come il servizio ottenuto da s sostituendo
i parametri formali fparams con i corrispondenti actual parameters
aparams.
Attività Kill
L’esempio mostra gli effetti di un’attività kill all’interno di un blocco protetto.
†
[k] ({| s1 | {| s2 |} | kill(k) |} | s3 ) | s4 → [k] {|{| s2 |}|} | s4
dove assumiamo che halt(s1 ) = halt(s3 ) = 0. Possiamo notare che kill(k)
termina tutti i servizi in parallelo presenti all’interno dello scopo della delimitazione [k] (cioè i due servizi s1 e s3 ), ad eccezione dei servizi che sono protetti
40
COWS
allo stesso livello di annidamento dell’attività di terminazione (nell’esempio
il servizio s2 ).
Nomi privati
L’interazione tra due attività Receive e Invoke avviene esclusivamente
se entrambe sono all’interno dello scopo delle delimitazioni di variabili
contenute all’interno dell’argomento della Receive. Per abilitare la comunicazione di nomi privati dobbiamo quindi estendere lo scopo di tali nomi ed
eventualmente anche quello delle variabili usate nella Receive.
[x](p • o? hxi .s | s0 ) | [n]p • o! hni
≡
[n]([x](p • o? hxi .s | s0 ) | p • o! hni)
≡
[n][x](p • o? hxi .s | s0 | p • o! hni)
(n fresh)
p • o b∅c hxi hni
−−−−−−−−−−→
[n](s | s0 ) {x 7→ n}
Si noti che la sostituzione viene applicata a tutti i termini delimitati
da [x] e non solo alla continuazione s dell’attività di Receive, a differenza di
quanto accade per la maggior parte dei calcoli di processo.
Variabili riscrivibili
In questo esempio mostriamo, con la sintassi di CMC definita sopra, come
le variabili riscrivibili (che possono, cioè, essere assegnate ripetutamente)
vengono modellate in COWS. Faremo ricorso a tale modellazione nel corso
della presentazione di UStoC.
Variabili riscrivibili sono rappresentate come servizi che forniscono le
3.3 Esempi
41
operazioni read e write. Quando il servizio della variabile è inizializzato (cioè
la prima volta che è invocata l’operazione write), viene creata un’istanza
che è in grado di fornire il valore correntemente salvato. Quando il valore
deve essere aggiornato, l’istanza corrente viene terminata e ne viene creata
una nuova nella quale è salvato il nuovo valore. Questa è la specifica del
servizio che rappresenta una variabile riscrivibile:
var ( v )
=
[ XV ][ XA ]
v . write ? < XV , XA >.[ n #]
(( n . op ! < XV , XA > | *[ X ][ y #]
n . op ? <X ,y >. ( y . op ! < > | [ k ]
( * [ y1 #] v . read ? < y1 >.{ y1 . op ! <X >}
| [ X1 ][ y1 #] v . write ? < X1 , y1 >.( kill ( k ) { n . op ! < X1 , y1 >} ) )
)))
Il servizio var(v) fornisce, come detto, due operazioni:
• read per ottenere il valore corrente;
• write per sostituire il valore con uno nuovo.
Per accedere al servizio, un utente deve invocare una tra queste due operazioni fornendo un endpoint per la risposta e in caso di una write, il valore
che deve essere scritto. L’inizializzazione di una variabile avviene invocando,
la prima volta, l’operazione di write tramite il nome di partner v. Il servizio
var(v) utilizza l’endpoint privato n per memorizzare il valore della variabile.
Quest’ultima caratteristica è sfruttata per implementare ulteriori operazioni
write in termini di terminazioni forzate e re-installazioni. La delimitazione
[k] è usata per confinare gli effetti dell’attività kill alla istanza corrente,
mentre la protezione {_} evita terminazioni forzate di risposte in sospeso e
l’invocazione che attiverebbe nuove istanze.
42
COWS
Ping Server
L’esempio finale mostra un semplice scambio di messaggi, tra un PingServer
ed un Client.
[ Xy ](
pingserver . ping ! < client , alive > |
client . reply ? < pingserver , Xy > )
|
[ Xx ](
pingserver . ping ? < client , Xx > |
client . reply ! < pingserver , Xx > )
Il primo servizio rappresenta il servizio Client che effettua il ping ad un server,
tramite l’attività di Invoke. Il Client si pone quindi in attesa di una risposta
da parte del server. Il servizio PingServer, posto in parallelo, durante la sua
computazione svolge il seguente processo: inizialmente si pone in attesa di
una comunicazione lungo l’endpoint pingserver.ping, se la comunicazione
avviene e soddisfa il pattern-matching il Server può inviare la risposta al
client mediante l’operazione: client.reply!<pingserver,Xx>.
L’esempio mostra, dunque, due evoluzioni: l’operazione di ping da parte
del Client, che assegna alla variabile Xx del server il valore alive e la risposta del Server nel quale il valore alive contenuto in Xx viene passato alla
variabile Xy del client. Il Client controllando il contenuto della variabile Xy
può conoscere, quindi, lo stato del Server.
Capitolo
4
UStoC: da UML4SOA a COWS
In questo capitolo analizziamo la parte centrale del lavoro svolto, ovvero la
realizzazione di UStoC, strumento per la traduzione automatica di diagrammi
UML4SOA in un termine COWS. La scelta del software per la realizzazione
dei diagrammi UML4SOA è ricaduta su MagicDraw UML, uno strumento
grafico per la realizzazione di modelli UML. In effetti, UStoC effettua la traduzione mediante l’analisi di un file XMI che rappresenta, in forma testuale,
il diagramma.
La scelta di MagicDraw deriva dal fatto che esistono diversi plugin per
MagicDraw tra i quali quello per l’estensione a diagrammi UML4SOA. Utilizzando MagicDraw la realizzazione del diagramma è semplice e intuitiva;
una volta creato il diagramma, inoltre, l’applicazione permette di esportarlo
in formato UML2 EMF XMI 2.1.
43
44
UStoC: da UML4SOA a COWS
4.1
Creazione di un diagramma UML4SOA
Il primo passo per l’utilizzo di UStoC è la creazione del diagramma
UML4SOA che siamo interessati a tradurre. Come già detto, la creazione di
un diagramma risulta un’operazione piuttosto semplice utilizzando MagicDraw: il programma dispone, infatti, di un’interfaccia grafica decisamente
intuitiva.
MagicDraw non supporta direttamente la creazione di Orchestration
UML4SOA, per questo la prima cosa da fare è installare il plugin per il
profilo UML4SOA (vedi Capitolo 5, Sezione 5.1). Al momento della creazione del diagramma è importante aver presenti alcune informazioni e vincoli
che derivano dall’utilizzo di MagicDraw come strumento per la creazione di
diagrammi UML4SOA.
Le specifiche dei diagrammi UML4SOA accettati da UStoC devono rispettare la sintassi UML4SOA presentata nel Capitolo 2, in particolare: ogni
diagramma ha una sola Orchestration, ogni Orchestration prevede uno e un
solo scope principale, all’interno del quale è possibile inserire ed orchestrare
ogni singolo elemento, compresi ulteriori scope collegati fra loro. In UStoC
non sono ammesse azioni o altri elementi all’interno dell’Orchestration che
non siano contenute nello scope principale. Ogni elemento deve essere completo; in particolare i costrutti per il flusso di controllo, azioni e scope interni
devono sempre essere collegati fra loro mediante degli archi.
Vi sono due casi in cui UStoC permette un po’ di elasticità. Il primo riguarda gli exception, compensation ed event handler: la traduzione in COWS
prevede che siano sempre presenti; in caso di eventuali assenze, UStoC ese-
4.1 Creazione di un diagramma UML4SOA
45
gue in modo comunque corretto la traduzione, considerandoli presenti come
handler di default. Il secondo caso riguarda i nomi degli elementi. La maggior parte degli elementi UML4SOA, infatti, necessita obbligatoriamente di
un nome per far sì che la sua traduzione sia corretta. Per garantire ad utenti
‘pigri’ la corretta esecuzione della traduzione, UStoC si occupa di assegnare
nomi di default ad ogni elemento del diagramma che ne è sprovvisto.
Prima di procedere alla creazione del file XMI è necessario puntualizzare
un piccolo bug di MagicDraw che può rivelarsi molto fastidioso in alcuni
casi. Talvolta può verificarsi che operando solo sul grafico, alcuni elementi
cancellati e non più visibili all’interno dell’Orchestration risultino, tuttavia,
ancora presenti nel diagramma e quindi pure nel file XMI generato. Questo
può causare molti errori a livello di traduzione, soprattutto nel caso in cui
il diagramma sia complesso e siano state effettuate numerose modifiche. Per
questo è importante sempre controllare nel riquadro Containment posto a
sinistra del grafico, che al momento della creazione del file XMI, siano presenti
solo gli elementi che sono realmente attivi nel grafico e non anche elementi
già precedentemente cancellati.
Dopo aver creato un’Orchestration corretta, seguendo queste basilari regole, è possibile procedere alla creazione del file XMI; ciò avviene utilizzando
il comando Export–>EMF UML2(v2.x) XMI del menù File. Tale procedura
genera un file .uml conforme allo standard XMI.
46
UStoC: da UML4SOA a COWS
4.2
Struttura del file XMI
Come detto la traduzione del diagramma UML4SOA in COWS avviene
mediante l’analisi del file XMI generato dall’apposito comando di MagicDraw.
XMI (XML Metadata Interchange) è uno standard per definire, scambiare, manipolare ed integrare oggetti elaborando dati in formato XML [26].
Un file XMI ha la struttura di un file XML e consente di rappresentare i
diagrammi UML in formato machine-understandable.
La struttura del file è ad albero, la cui radice è l’Orchestration. Il suo
primo nodo figlio è lo scope principale, il quale, a sua volta, conterrà tanti
figli quanti sono gli elementi da esso contenuti nel diagramma. La parte
rilevante del file XMI è quella centrale, racchiusa all’interno dell’elemento
< packagedElement xmi : type =" uml : Activity "
.
.
</ packagedElement >
xmi : id ="..." ... >
che rappresenta l’Orchestration. Le parti iniziale e finale del documento XMI,
invece, non sono utili al fine della traduzione, ad eccezione della sezione finale
in cui sono elencate tutte le specifiche relative agli elementi caratteristici del
plugin UML4SOA, come ad esempio gli elementi seguenti:
< L M U U M L 4 S O A O r c h e s t r a t i o n : scope xmi : id ="..." ... / >
< L M U U M L 4 S O A O r c h e s t r a t i o n : send xmi : id ="..." ... / >
< L M U U M L 4 S O A O r c h e s t r a t i o n : receive xmi : id ="..." ... / >
< L M U U M L 4 S O A O r c h e s t r a t i o n : link xmi : id ="..." ... / >
É importante sottolineare che ogni elemento UML, nel file XMI, ha un
attributo id che lo identifica in modo univoco. Questo attributo è, in particolare, molto importante nel caso in cui ci troviamo ad analizzare elementi
specifici del profilo UML4SOA, i quali sono definiti dettagliatamente soltanto
4.2 Struttura del file XMI
47
nella lista posta a conclusione del file. Così attraverso il campo id possiamo identificare nella lista l’elemento che stiamo analizzando e ricavarne le
specifiche.
4.2.1
Definizione degli elementi UML4SOA
In questa sezione andiamo a vedere come ogni singolo elemento UML4SOA
è rappresentato all’interno del file XMI.
Orchestration
Una Orchestration è rappresentata da un nodo così definito:
< packagedElement xmi : type =" uml : Activity " xmi : id ="..." name ="..." >
.
.
</ packagedElement >
L’attributo type con valore Activity permette di riconoscere il nodo che
rappresenta l’Orchestration, mentre il nome è rappresentato mediante l’attributo name. All’interno di questo nodo vi sono i nodi figli che rappresentano
tutti gli elementi contenuti nell’Orchestration.
Scope
Uno scope è rappresentato da un nodo così definito:
< node xmi : type =" uml : S t r u c t u r e d A c t i v i t y N o d e " xmi : id ="..." name =" scope " ... >
.
.
</ node >
Uno scope è riconoscibile dal valore StructuredActivityNode dell’attributo
type, mentre il nome dall’attributo name. Nel caso di uno scope interno, esso
48
UStoC: da UML4SOA a COWS
avrà anche gli attributi incoming e outgoing aventi come valori gli identificatori degli archi entranti e uscenti dallo scope. All’interno del nodo scope
vi sono tanti nodi figli quanti sono gli elementi contenuti in esso.
Edge
I primi elementi figli di uno scope sono gli Edge, identificati mediante un
nodo così definito:
< edge xmi : type =" uml : ControlFlow " xmi : id ="..." name ="..." source ="..."
.
target ="..." >
.
</ edge >
Un edge è rappresentato dall’attributo type con valore ControlFlow. Un
nodo edge include nella sua definizione gli attributi source e target che
contengono gli identificatori dell’origine e della destinazione dell’arco. Nel
caso in cui l’edge contenga una guardia essa è definita all’interno del nodo
mediante un nodo figlio così rappresentato:
< guard xmi : type =" uml : Op aque E x p r e s s i o n " xmi : id ="..." name ="..." >
< language > English </ language >
< body >... </ body >
</ guard >
dove il corpo della guardia è contenuto nel figlio body.
Variable
Nella definizione di una variabile l’unico attributo essenziale è il nome:
< variable xmi : id ="..." name ="..." visibility =" public "/ >
Initial e Final Node
La rappresentazione di initial e final node è simile:
4.2 Struttura del file XMI
49
< node xmi : type =" uml : InitialNode " xmi : id ="..." outgoing ="..."/ >
< node xmi : type =" uml : Ac t iv it y Fi n al No d e " xmi : id ="..." incoming ="..."/ >
un Initial Node è identificato dall’attributo type con valore InitialNode,
mentre in un Final Node type ha come valore ActivityFinalNode. Il primo
avrà poi l’attributo obbligatorio outgoing, il secondo invece incoming.
Action
Le azioni di comunicazione (Send, Receive, SendReceive e ReceiveSend)
sono rappresentate da elementi node con attributo type avente valore
CallBehaviorAction.
< node xmi : type =" uml : C a l l B e h a vi o r A c t i o n "
.
.
</ node >
xmi : id ="123456" name =" delete " ... >
Per distinguere il tipo di azione è necessario far riferimento alla lista
posta a conclusione del file XMI, dove è presente un elemento con lo stesso
identificatore dell’azione in cui è specificata la tipologia.
< L M U U M L 4 S O A O r c h e s t r a t i o n : send xmi : id ="..." base_Action ="123456"/ >
Come si nota nell’esempio il valore dell’attributo id dell’azione delete è lo
stesso dell’attributo base_Action dell’elemento send posto nella lista con gli
elementi UML4SOA: ciò assicura che l’azione delete è una Send.
Ogni azione ha ovviamente gli attributi name, incoming e outgoing. Gli
argomenti in input e output (cioè i pin di input e output connessi all’azione)
sono rappresentati come figli rispettivamente identificati da nodi con nome
argument e result. Anche il link pin è compreso tra gli argument; per
50
UStoC: da UML4SOA a COWS
poter distinguere, dunque, tra un normale argument e un link è necessario
controllare quale id corrisponde al link nella lista contenente gli elementi
UML4SOA, come visto precedentemente per distinguere il tipo di azione.
Le azioni Raise sono identificate invece mediante l’attributo type avente
valore RaiseExceptionAction.
Compensate e Compensate_All sono rappresentate entrambe con dal valore OpaqueAction dell’attributo type; per effettuare la distinzione tra le
due è necessario controllare l’identificatore nella lista in fondo al file XMI.
Control Flow
La rappresentazione dei costrutti per il controllo del flusso avviene in modo
analogo tra Join/Fork e Decision/Merge.
< node xmi : type =" uml : ForkNode " xmi : id ="..." outgoing ="..." incoming ="..."/ >
< node xmi : type =" uml : DecisionNode " xmi : id ="..." outgoing ="..."
incoming ="..."/ >
I primi sono identificati entrambi dall’attributo type con valore ForkNode;
per effettuare una distinzione tra i due è quindi necessario effettuare un
controllo sul numero di archi in entrata e uscita.
Lo stesso procedimento vale per i Decision e Merge che sono identificati
mediante il valore DecisionNode dell’attributo type.
4.3
Struttura di UStoC
Dopo aver definito le modalità di realizzazione del diagramma e le caratteristiche del file XMI corrispondente, passiamo a vedere in che modo UStoC
4.3 Struttura di UStoC
51
produce la traduzione COWS.
UStoC, realizzato in Java, ha un’interfaccia grafica di base, di semplice
utilizzo, che permette di caricare alcuni file XMI esportati da MagicDraw e
di effettuarne la traduzione, la quale può essere salvata in un file COWS.
Tale file conterrà il codice che potrà essere utilizzato all’interno di CMC.
Il processo di lavoro di UStoC si compone di due fasi principali:
• Il parsing del file XMI
• La traduzione in COWS dell’Orchestration
Il concetto alla base di UStoC è piuttosto semplice: per ogni elemento UML
possibile vi è una classe associata. Ad esempio la seguente è la classe associata
all’Initial Node.
public class InitialNode implements Graph {
private String ID ;
private Edge outgoingEdge ;
public InitialNode ( String id , Edge e ) {
outgoingEdge = e ;
ID = id ;
}
public Edge getOutgoingEdge () { ... }
// restituisce una stringa con la traduzione del nodo iniziale in COWS
public String toCOWS () { ... }
.
.
}
Tali classi contengono metodi specifici caratteristici degli elementi associati
e ognuna ha un costruttore che, ricevendo i parametri corretti, è in grado di
costruire un elemento completo. Inoltre ogni oggetto che può essere tradotto
52
UStoC: da UML4SOA a COWS
in COWS presenta nella definizione della sua classe un metodo denominato toCOWS() che restituisce una stringa contenente il codice ottenuto dalla
traduzione in COWS dell’elemento.
É facile capire, dunque, come si struttura il processo di lavoro di UStoC.
L’idea di base è quella di analizzare il file XMI e costruire, per ogni elemento
UML4SOA trovato, il relativo oggetto Java in modo corretto. Successivamente ogni oggetto verrà ‘inserito all’interno’ del suo oggetto padre (Scope
o Orchestration). In termini Java questo significa che il riferimento (Java
reference) all’oggetto in questione viene assegnato all’attributo opportuno
dell’oggetto padre.
Una volta costruita l’Orchestration e inseriti all’interno di essa tutti gli
elementi, si passa alla fase di traduzione.
La traduzione è un processo relativamente semplice: è sufficiente invocare
il metodo toCOWS() dell’oggetto Orchestration e questo produrrà il codice
COWS, traducendo il suo scope e ricorsivamente tutti gli elementi in esso
contenuti. I dettagli relativi alla traduzione di un Orchestration e di ogni
singolo elemento verranno presentati nelle prossime sezioni.
4.3.1
Panoramica sulle classi
Introduciamo in questa parte la struttura delle classi che compongono UStoC.
Le classi principali, che fanno riferimento agli elementi UML, sono:
Initial e Final Node, Action (Send, Receive, Raise e Compensate),
ControlFlow
(Decision,
Merge,
Join,
Fork),
Edge,
Scope,
ActionGraph, ScopeGraph, Orchestration e Variable. In particolare
4.3 Struttura di UStoC
53
è importante sottolineare le classi ActionGraph e ScopeGraph: infatti all’interno di un oggetto Scope non saranno inserite le singole azioni (risp.
Scope), bensì gli oggetti ActionGraph (risp. ScopeGraph) che rappresentano
un’azione (risp. Scope) con i relativi archi entranti ed uscenti.
In Figura 4.1 è descritta la struttura delle classi, in cui sono omesse per
una questione di leggibilità le relazioni d’uso fra le classi e vengono mostrate
solo le relazioni di tipo implementa ed estende. Lo schema delle classi prevede
tre interfacce:
• UMLElement
• Graph
• Action
UMLElement è l’interfaccia implementata da tutte le classi che rappresentano
elementi UML4SOA; essa definisce il metodo toCOWS() poiché tutti gli oggetti che implementano questa interfaccia possono essere tradotti in COWS.
Tale interfaccia è fondamentale, come vedremo, nella fase di analisi dove ogni
elemento UML4SOA trovato viene aggiunto in una tabella con il proprio id.
La tabella è un oggetto della classe HashTable<String,UMLElement> in cui
String corrisponde all’id e UMLElement corrisponde all’elemento. Poiché gli
oggetti creati implementano l’interfaccia UMLElement, possono essere tutti
aggiunti alla tabella.
UMLElement è estesa da un’altra interfaccia, Graph, che rappresenta gli
elementi che sono contenuti all’interno del grafo di uno scope. Per questo
la classe Scope contiene la definizione di un Vector<Graph> che rappresenta
proprio il suo grafo e conterrà quindi tutti i suoi elementi.
54
UStoC: da UML4SOA a COWS
Figura 4.1: Schema delle classi
4.4 Parsing e analisi del file XMI
55
L’ultima interfaccia è Action. Essa definisce gli elementi che rappresentano le azioni e prevede alcuni metodi quali: SetVar(), per impostare le variabili associate; getIncoming() e getOutgoing() che restituiscono
l’identificatore degli archi in ingresso ed in uscita dall’azione.
L’importanza dell’interfaccia Action sta nella definizione della classe
ActionGraph, la quale ha come attributi i riferimenti a tre oggetti: due
Edge (incoming e outgoing) e un oggetto di tipo Action. L’ultimo oggetto
ha come tipo l’interfaccia Action; questo fa sì che sia possibile costruire un
ActionGraph con qualsiasi tipo di azione.
4.4
Parsing e analisi del file XMI
Il primo approccio all’analisi del file XMI avviene tramite il Parser DOM della
classe DocumentBuilder. DOM (Document Object Model) [25] è un’interfaccia di programmazione, implementata in una moltitudine di linguaggi, per
la manipolazione di file XML. DOM costruisce, partendo dal file XML, un
albero in cui ogni nodo corrisponde ad un elemento del file XML.
Il parser DOM restituisce un oggetto di tipo Document (vedi API della
classe org.w3c.dom.Document) che rappresenta l’albero del file XML. Una
volta effettuato il parsing e ottenuto il Document si procede con l’analisi dei
nodi dell’albero in esso rappresentati. La classe XMIAnalyzer provvede ad
esaminare il Document: per ogni elemento incontrato crea l’oggetto corrispondente in modo corretto. Ogni oggetto creato è inserito, poi, all’interno
del proprio padre.
L’idea su cui si basa l’analisi del Document ricevuto dal parser è molto
56
UStoC: da UML4SOA a COWS
semplice: si effettua una ricerca dell’elemento che rappresenta l’Orchestration, ci si assicura che vi sia e che sia correttamente rappresentata. L’Orchestration avrà, a livello XMI, tanti nodi figli quanti sono i suoi elementi nel
diagramma.
Si procede, quindi, con l’analisi dei figli dell’Orchestration, che avviene
in maniera ricorsiva. Per ogni elemento individuato, viene creato l’oggetto
corrispondente; si scorrono, quindi, tutti i figli dell’elemento trovato, che
saranno inseriti al suo interno, e per ognuno viene ripetuto il procedimento
precedente: viene creato l’oggetto corrispondente e nel caso abbia nodi figli
essi vengono creati e aggiunti ad esso.
Una volta che per un oggetto sono stati creati ed inseriti tutti i propri figli,
questo può essere a sua volta inserito all’interno del padre. E’ importante
sottolineare che non molti elementi nel file XMI contengono figli, tutto ciò
vale principalmente per gli scope e le Orchestration.
L’inserimento di un oggetto all’interno del padre può presentare alcune
varianti a seconda del tipo di elemento creato e del fatto che il padre sia una
Orchestration o uno scope.
4.4.1
Inserimento all’interno di una Orchestration
Per come è stata definita la struttura di UStoC, all’interno di una Orchestration è possibile incontrare esclusivamente elementi quali edge e scope. Gli
altri elementi (quelli cioè appartenti all’interfaccia Graph) devono trovarsi
necessariamente all’interno di uno scope.
4.4 Parsing e analisi del file XMI
57
Scope
Nel caso che l’elemento da inserire sia uno scope si possono presentare due
possibili casi:
• Scope Principale: nel caso in cui lo scope non abbia nessun tipo di arco
entrante questi è sicuramente lo scope principale relativo all’Orchestration; pertanto deve essere semplicemente aggiunto mediante il metodo
setMainScope(Scope s).
• Handler Scope: se lo scope rappresenta un event, exception o compensation handler, inizialmente la procedura controlla se è già stato creato
l’oggetto relativo allo scope a cui l’handler è connesso. In caso affermativo esso viene settato come event, exception o compensation handler
allo scope a cui fa riferimento, altrimenti sarà aggiunto non appena
viene trovato lo scope a cui deve essere connesso.
Edge
All’interno di un Orchestration è possibile incontrare esclusivamente Edge
che collegano handler, non è possibile che vi sia un Edge per la gestione del
flusso. Per ogni elemento della classe Edge si controlla, quindi, che vi siano i
due scope da esso collegati. Se sono già stati creati entrambi, viene impostato
l’handler come event, exception o compensation scope dello scope a cui fa
riferimento. Altrimenti l’handler sarà installato non appena viene creato lo
scope a cui fa riferimento.
58
UStoC: da UML4SOA a COWS
4.4.2
Inserimento all’interno di uno Scope
Se l’oggetto all’interno del quale stiamo inserendo gli elementi è uno scope,
ciò significa che ci possiamo trovare ad inserire uno di questi elementi al suo
interno:
• Scope
• Action
• Graph
• Edge
• Variable
Scope
Vi sono due tipi di scope che è possibile incontrare:
• ScopeGraph, cioè uno scope interno con arco entrante e uscente;
• scope che rappresenta un event, exception o compensation handler.
Nel primo caso l’inserimento avviene all’interno del grafo dello scope,
immediatamente dopo aver costruito il relativo ScopeGraph con i due archi.
Nel secondo caso si parla di handler e non di un vero e proprio scope.
Occorre controllare se è già stato creato l’oggetto scope a cui l’handler è connesso: in caso affermativo l’handler gli viene aggiunto come event, exception
o compensation scope. Altrimenti sarà installato non appena viene creato lo
scope a cui l’handler in questione fa riferimento.
4.4 Parsing e analisi del file XMI
59
Action
Se l’elemento trovato appartiene all’interfaccia Action, la modalità di inserimento nel suo scope padre risulta essere molto semplice. Alla azioni vengono
impostate le variabili dello scope padre e viene creato l’ActionGraph mediante gli archi entranti e uscenti, il quale poi viene aggiunto al grafo dello scope
padre.
Graph
Se l’elemento che vogliamo inserire all’interno dello scope appartiene all’interfaccia Graph, potrebbe essere un oggetto come: Initial Node, Final
Node, Merge, Decision, Join, Fork. In questo caso l’elemento deve essere
semplicemente inserito all’interno del grafo dello scope padre.
Edge
Se l’elemento è un Edge la procedura di inserimento consiste semplicemente nell’inserire l’arco nel vettore contente tutti gli Edge dello scope padre
mediante il metodo addEdge(Edge e) definito nella classe scope. Questo
vettore sarà poi utilizzato al momento della traduzione dell’Orchestration
per delimitare tutti gli archi contenuti negli scope.
Variable
Nel caso in cui l’elemento da inserire sia una Variable essa viene soltanto
inserita all’interno della struttura dati che contiene tutte le variabili dello
scope padre, mediante il metodo addVars(Variable v) della classe scope.
60
UStoC: da UML4SOA a COWS
4.5
Creazione degli oggetti
Come detto precedentemente, riguardo l’analisi del documento XMI, UStoC
procede esaminando ogni elemento riscontrato all’interno dell’Orchestration
e per ognuno di essi richiama il metodo che crea l’oggetto corrispondente. A
seconda del tipo di elemento trovato, i campi da ricercare e le operazioni da
svolgere possono essere differenti. Ultimata la procedura di creazione, l’oggetto viene inserito all’interno dello scope o dell’Orchestration che lo contiene.
Vediamo pertanto, in dettaglio, come avviene la creazione di ogni singolo
elemento.
4.5.1
Scope
Al momento in cui UStoC riconosce all’interno del file XMI la dichiarazione
di un elemento scope, si procede per prima cosa alla creazione di un oggetto
di tipo Scope, passando al costruttore i parametri name e id.
La procedura prevede un controllo sul campo name. Se nel documento
XMI il nome non fosse stato specificato UStoC assegna un nome di default
mediante l’utilizzo di un contatore definito nella classe Util. Il nome di
default è del tipo ScopeN, dove N è il valore del contatore (inizializzato ad
1).
La seconda parte della procedura di creazione di uno scope riguarda il
controllo degli attributi incoming e outgoing. Come già specificato, gli
sviluppatori di MagicDraw che hanno realizzato la parte di esportazione in
XMI hanno definito questi campi come attributi dell’elemento e non come
nodi figli. La seconda opzione sarebbe stata più conveniente perché avrebbe
4.5 Creazione degli oggetti
61
permesso l’inserimento di più campi outgoing o incoming. Essendo stati
definiti come attributi è possibile definire un solo campo outgoing e incoming.
Per questo, nel caso in cui un elemento contenga più outgoing o incoming
Edge, questi sono specificati all’interno del corrispondente attributo separati
da uno spazio vuoto, come nel seguente esempio:
outgoing =" _16 _0_1 1586 9_84 9 "
outgoing =" _16_0_65098_710 _ 16 _ 0 _ 5 4 9 9 2 2 3 8 _ 7 1 5 "
Come si vede dall’esempio il primo campo presenta un solo arco uscente. Il
secondo descrive un elemento che ha due archi uscenti, i quali sono separati,
come si può notare, dallo spazio vuoto. Considerando che sono molti gli
oggetti che presentano più archi uscenti (o entranti), è facile intuire che
l’utilizzo di questa soluzione necessita di uno sforzo maggiore per identificare
il numero e l’identificatore degli archi in questione.
Analizzando il numero e il tipo degli archi connessi ad uno scope possiamo
distinguere alcuni casi:
• Scope senza archi connessi.
• Scope con soli archi uscenti.
• Scope con soli archi entranti.
• Scope con archi entranti ed uscenti.
Uno scope senza archi connessi non può essere altro che lo scope principale.
Uno scope con soli archi uscenti rappresenta uno scope principale al quale
sono connessi alcuni handler. Per questo è necessario controllare se gli eventuali handler siano già stati creati, nel qual caso devono essere collegati allo
62
UStoC: da UML4SOA a COWS
scope in questione. Uno scope con soli archi entranti può rappresentare un
event, exception o compensation handler di un altro scope. Per questo è necessario controllare quale sia lo scope a cui l’handler è connesso. Se lo scope
corrispondente è già stato creato, si conclude collegando l’handler allo scope.
Uno scope che ha sia archi in ingresso che in uscita rappresenta invece uno
scope interno, per il quale sarà necessario creare l’oppportuno ScopeGraph
da inserire all’interno dello scope padre. Effettuati questi controlli l’oggetto
scope può essere restituito.
Variabili
Nel file XMI i primi nodi figli di uno scope sono le variabili, se presenti.
La creazione di un oggetto della classe Variable avviene in modo molto
semplice.
E’ necessario solo un controllo per verificare se la variabile sia di tipo
write-once. UStoC è definito in modo tale che una variabile è write-once
se il suo nome nel diagramma comincia con i caratteri <<wo>> . Effettuato
questo banale controllo l’oggetto può essere creato passando al costruttore i
parametri id, name e isWriteOnce.
Edge
La dichiarazione di tutti gli Edge contenuti nello scope segue immediatamente
la definizione delle variabili. La creazione di un oggetto di tipo Edge avviene
acquisendo il nome e l’identificatore presenti negli attributi del nodo, ed
eseguendo un controllo sulla lunghezza del nome. Se il campo è vuoto, verrà
4.5 Creazione degli oggetti
63
creato di default come edgeN, dove N è il valore del contatore al momento
della creazione dell’oggetto.
Per procedere alla creazione di un oggetto Edge è necessario stabilire
se esso contiene una guardia. Come descritto in Sezione 4.2, la guardia di
un Edge è rappresentata mediante un nodo figlio dell’elemento Edge. Si
analizzano, quindi, i figli dell’elemento in cerca di una guardia. Se essa è
presente viene passata al costruttore dell’oggetto, altrimenti viene inserita
con il valore di default true.
4.5.2
Initial e Final Node
Illustriamo adesso la creazione di elementi quali Initial e Final Node, che
avviene in modo quasi equivalente.
Initial Node
Si esegue un controllo sul valore del campo outgoing (che deve essere non
vuoto). In caso di errore la traduzione non si avvia e viene notificato un messaggio di errore nel log. Il campo outgoing contiene l’identificatore dell’arco
uscente ed essendo la dichiarazione degli archi precedente, esso deve essere
già stato creato. Pertanto se il documento XMI è corretto, l’oggetto Edge in
questione sarà già stato creato e può essere passato al costruttore insieme a
name e id.
64
UStoC: da UML4SOA a COWS
Final Node
La procedura di creazione di un Final Node segue quanto visto per l’Initial
Node. L’unica differenza sta nel fatto che un Initial Node include un arco
uscente, mentre nel caso di un Final Node questo deve essere sostituito con
un arco entrante. Ricavato l’arco entrante esso può essere inviato insieme a
name e id per la creazione dell’oggetto.
4.5.3
Fork e Join
Per ciò che riguarda la creazione di elementi quali Fork e Join, è essenziale
inizialmente controllare i valori degli attributi outgoing e incoming. In caso
di errori la traduzione non sarà neppure avviata e un messaggio di errore
verrà segnalato nel log.
Come detto riguardo la creazione degli scope, nel caso in cui l’elemento contenga più archi in entrata (o in uscita), questi sono tutti specificati
all’interno dello stesso attributo incoming (o outgoing). Risulta essenziale pertanto la presenza di una routine che scandisca la stringa inserendo
all’interno di un vettore tutti gli archi che vengono riscontrati.
Come visto durante la descrizione del documento XMI, gli sviluppatori
di MagicDraw non hanno previsto alcuna distinzione nel nome del nodo tra
gli elementi Join e Fork, ambedue infatti sono rappresentati da elementi con
nome ForkNode. Pertanto l’unica indicazione riguardo la separazione da
effettuare è data dal numero di Edge in ingresso e in uscita. Si individuano
tre differenti casi:
1. archi uscenti >1 e archi entranti =1
4.5 Creazione degli oggetti
65
2. archi uscenti =1 e archi entranti >1
3. archi uscenti =1 e archi entranti =1
Non è stato trattato il caso in cui entrambe le tipologie hanno un numero di
archi maggiore di 1, in quanto questa possibilità non conduce ad un elemento
previsto dalla sintassi UML4SOA accetta da UStoC. Analogamente, il caso
in cui non siano presenti archi uscenti o entranti non viene considerato.
Il caso 1) delinea un oggetto di tipo Fork, vi è infatti un numero di archi
uscenti maggiore di 1. La creazione dell’oggetto Fork avviene inserendo gli
archi uscenti in un vettore che verrà passato al costruttore insieme con l’unico
arco entrante e con il valore del campo id.
Gli altri due casi descrivono, invece, un elemento di tipo Join. Una volta
costruito in modo appropriato il vettore degli archi entranti, è possibile procedere alla creazione dell’oggetto in modo analogo a quanto visto per la Fork,
passando al costruttore il vettore degli archi entranti invece di quello degli
archi uscenti. In verità l’ultimo caso rappresenta un’eventualità particolare
che un attento sviluppatore di specifiche UML4SOA non dovrebbe generare,
in quanto privo di senso. UStoC, comunque, prevede la gestione anche di
casi di questo tipo, segnalando un avviso nel log a traduzione completata.
4.5.4
Decision e Merge
La procedura di creazione di oggetti delle classi Decision e Merge è del tutto
simile a quanto visto in precedenza per Fork e Join.
Anche in questa circostanza non è prevista alcuna distinzione tra Decision
e Merge all’interno del XMI, come già descritto in precedenza, entrambi
66
UStoC: da UML4SOA a COWS
gli elementi sono identificati da nodi con attributo DecisionNode. L’unica
particolarità che ne permette la differenziazione è la presenza di un numero
differente di archi in ingresso e uscita.
Dopo aver controllato che i campi outgoing e incoming non siano vuoti,
si procede con il popolamento del vettore nel caso uno dei due attributi
contenga riferimenti a più Edge. Come visto in precedenza si procede a
distinguere alcune eventualità, a seconda del numero degli archi:
1. archi uscenti >1 e archi entranti =1
2. archi uscenti =1 e archi entranti >1
3. archi uscenti =1 e archi entranti =1
Il caso 1) descrive un oggetto di tipo Decision, vi è infatti un numero di archi
uscenti maggiore di 1. La creazione dell’oggetto Decision avviene inserendo
gli archi uscenti in un vettore che verrà passato al costruttore insieme con
l’unico arco entrante e con il valore del campo id.
Gli altri due casi rappresentano, invece, un elemento di tipo Merge. Una
volta costruito in modo appropriato il vettore degli archi entranti, è possibile
procedere alla creazione dell’oggetto passando al costruttore il vettore degli
archi entranti, l’arco uscente, il nome e l’identificatore. Il caso 3) rappresenta,
in realtà, un’eccezione. Un DecisionNode con un solo arco entrante e uscente
è privo di significato. UStoC, comunque, prevede la gestione anche di casi di
questo tipo, segnalando, però, un avviso nel log a traduzione completata.
4.5 Creazione degli oggetti
4.5.5
67
Action
Azioni di comunicazione
Le azioni di questo tipo sono rappresentate nel documento XMI mediante
elementi con lo stesso nome CallBehaviourAction. Inizialmente, quindi,
non è possibile distinguere il tipo preciso di azione che l’elemento rappresenta.
Come descritto nei paragrafi precedenti è necessario controllare nella lista
degli elementi UML4SOA posta in fondo al documento XMI per ricavare la
stringa che specifica il tipo di azione.
Le azioni di Send e Receive prevedono nella loro specifica la presenza
obbligatoria del pin link. Come già visto in precenza essi sono trattati come
normalissimi valori in input all’azione, pertanto è necessario controllare per
ogni valore in input se questo è un link controllando se è presente nella lista
posta in fondo al file XMI.
Ricavato il link, possiamo procedere con la descrizione delle procedure
comuni alle azioni Send e Receive. Una volta stabilito il tipo di azione, viene
effettuato un controllo sugli attributi incoming e outgoing, controllando che
essi non siano valori nulli. La seconda parte della procedura scorre tutti i
figli del nodo creando due tuple: la prima contenente tutti gli argomenti in
input (rappresentati da nodi argument), la seconda con gli elementi in output
(rappresentata da nodi result). La Send ha solo argomenti in output, a
differenza della Receive che ha solo argomenti in input. Vediamo, quindi,
come avviene la creazione di ogni singola azione.
68
UStoC: da UML4SOA a COWS
Send
Si crea l’oggetto send passando al costruttore identificatore, nome (che indica l’operazione da invocare), link pin, tupla degli output e nome dell’Orchestration. Si settano poi gli identificatori per gli archi entranti ed uscenti
che permetteranno, al momento dell’inserimento nello scope, la creazione
dell’oggetto ActionGraph da inserire all’interno del grafo dello scope.
Receive
Per la creazione dell’oggetto Receive le modalità sono le stesse, con l’unica differenza che la tupla da passare al costruttore in questo caso è quella
contenente gli argomenti in input.
SendReceive e ReceiveSend
La realizzazione di un oggetto della classe SendReceive o ReceiveSend prevede che i parametri da passare al costruttore siano tutti quelli già visti nelle
precedenti azioni, in questo caso sia la tupla degli output che quella degli
input è necessaria.
Azioni per Fault e Compensation Handling
Fanno parte di questo insieme azioni come Raise, Compensate e
Compensate_All. Le Raise sono facilmente reperibili all’interno del documento XMI, essendo identificate mediante il nome RaiseExceptionAction.
Le azioni della famiglia Compensate non sono rappresentate attraverso un nome univoco, ambedue le azioni sono identificate, infatti, dal nome
4.6 Traduzione in COWS
69
OpaqueAction. Per effettuare una distinzione tra Compensate e Compensate_All è necessario controllare il riferimento nella lista posta in fondo al file
XMI.
Raise
La creazione di oggetti Raise avviene in maniera piuttosto semplice. Controllato che gli archi entranti e uscenti non siano nulli, si procede alla creazione
dell’oggetto con gli attributi name e id. La procedura termina impostando
l’identificatore degli archi entranti ed uscenti che saranno necessari al fine di
creare l’oggetto ActionGraph.
Compensate e Compensate_All
Per le azioni Compensate e Compensate_All oltre al controllo sull’esistenza dei campi outgoing e incoming, necessari per la creazione dell’oggetto
ActionGraph, è necessario accertarsi che sia specificato il tipo di Compensate. Per creare un oggetto Compensate_All sono essenziali semplicemente
gli attributi name e id; nel caso di un oggetto Compensate occorre specificare anche il nome dello scope da compensare, necessario al momento della
traduzione COWS.
4.6
Traduzione in COWS
Una volta creati tutti gli oggetti e inseriti all’interno della Orchestration si
può procedere alla sua traduzione.
70
UStoC: da UML4SOA a COWS
Vediamo come questa avviene andando a descrivere le specifiche della
traduzione di ogni singolo elemento.
4.6.1
Orchestration
La traduzione dell’Orchestration avviene in due parti:
• La dichiarazione di processi ausiliari (costrutto Let ...
in, vedi
Sezione 3.3 )
• La traduzione dell’Orchestration vera e propria.
Il termine COWS corrispondente ad un’Orchestration è definito tramite il
costrutto Let ...
in della forma seguente:
let
var ( v )
=
[ XV ][ XA ]
v . write ? < XV , XA >.[ n #]
(( n . op ! < XV , XA > | * [ X ][ y #] n . op ? <X ,y >.
( y . op ! < > | [ k ]( *[ y1 #] v . read ? < y1 >.{ y1 . op ! <X >}
| [ X1 ][ y1 #] v . write ? < X1 , y1 >.
( kill ( k ) | { n . op ! < X1 , y1 >}) ) ) )
)
Lifo ( q ) =
[ m #][ h #]
( * [ YV ][ YR ][ YE ]
( q . push ? < YV >.[ Z ] m . op ? <z >.
[ C ]( h . op ! < YV ,Z ,C >| m . op ! <C >)
+ q . pop ? < YR , YE >.[ Z ]
( m . op ? <Z >.[ YV ][ YT ]
h . op ? < YV , YT ,Z >.( m . op ! < YT >| yr . op ! < YV >)
+ m . op ? < empty >.( m . op ! < empty >| ye . op ! < >) ) )
| m . op ! < empty >
)
Stack ( stack , c )
=
[ q #]( Lifo ( q )
4.6 Traduzione in COWS
71
| * [ X ] stack . push ? <X >. q . push ! <X >
| * [ X ] stack . compAll ? <X >.[ loop #]( loop . op ! < >
| * loop . op ? < >.[ r #][ e #]
( q . pop ! <r ,e > | [ y #]
( r . op ? <y >.( c . y ! <y >| stack . end ? <y >. loop . op
! < >)
+ e . op ? < >. x . op ! < >) ) )
)
in
Come è possibile notare, all’interno del preambolo (rappresentato dal costrutto Let ...
in) sono presenti alcune definizioni di serivizi che saranno
richiamati dal corpo della traduzione.
UStoC produce la definizione di 3 servizi in particolare: var(v), Lifo(q)
e Stack(stack). In realtà Lifo(q) appartiene appunto alla definizione del
servizio Stack, come si può evincere dal codice.
Il processo var(v) è necessario nel caso in cui, nel corso della traduzione,
si abbia a che fare con variabili riscrivibili. Il servizio mette a disposizione
due operazioni: read, che restituisce l’attuale valore della variabile; write,
per sostituire l’attuale valore con uno nuovo. Per accedere al servizio, un
utente deve chiamare una delle due operazioni fornendo l’endpoint per la
risposta e nel caso di una write il valore da scrivere.
Variabili riscrivibili possono presentarsi in espressioni utilizzate da attività di invoke o receive. Per ottenere un termine in COWS ‘puro’, nel caso
di invoke o receive con espressioni contenenti variabili riscrivibili è quindi
necessario procedere come segue:
• se una invoke u.u’!e conetiene le variabili riscrivibili x1,. . . ,xm, viene
sostituita dal termine seguente:
72
UStoC: da UML4SOA a COWS
[ m #][ n1 #]...[ nm #]
( x1 . read ! < n1 > | ... | xm . read ! < nm >
| [ X1 ]...[ Xm ] n1 . op ? < X1 >. ... . nm . op ? < Xm >. m . op ! e ’
| [ y1 ]...[ ym ] m . op ? < y1 ,... , ym >. u .u ’! < y1 ,... , ym >)
dove e’ rappresenta la sostituzione di tutti gli xi con gli Xi e l’endpoint m.op
invia il risultato della valutazione di e’.
• se una receive p.o?w.s contiene le variabili riscrivibili x1,. . . ,xm, viene
sostituita dal termine seguente:
[ X1 ]...[ Xm ]
p . o ?w ’.[ n1 #]...[ nm #]
( x1 . write ! < X1 , n1 > | ... | xm . write ! < Xm , nm >
| n1 . op ? < >. ... . nm . op ? < >. s ’)
dove w’ rappresenta la sostituzione di tutti gli xi con gli Xi e s’ rappresenta
la traduzione COWS del termine s.
Il servizio Stack, associato a uno scope, mette a disposizione tre operazioni: end, per catturare la terminazione dello scope specificato come argomento dell’operazione, push per inserire nello stack lo ScopeName specificato
come argomento dell’operazione nello stack associato e compAll che innesca
la ‘compensazione’ di tutti gli scope il cui nome è nello Stack.
Lifo è una coda interna che fornisce le due operazioni push e pop. Lo
stack può inserire e estrarre uno ScopeName all’interno (o all’esterno) di
Lifo. Per inserire, Stack invia il valore che deve essere aggiunto, mentre per
estrarre sono necessari due endpoint: se la coda non è vuota, l’ultimo valore
inserito deve essere rimosso e restituito lungo il primo endpoint, altrimenti
viene inviato un segnale attraverso il secondo endpoint.
La traduzione dell’Orchestration si completa con la restrizione di ogni arco contenuto nei suoi scope. Questo avviene mediante il metodo getEdges()
4.6 Traduzione in COWS
73
della classe Scope che restituisce una stringa contenente tutti gli archi
contenuti nello scope e in tutti i suoi scope interni.
La traduzione dell’Orchestration si conclude con la traduzione del suo
scope principale, richiamata mediante il metodo toCOWS(). Ecco come è
possibile sintetizzare la traduzione di una Orchestration ORC:
[[ORC]] = LetIn()[kr][c#][edges(SCOPE)][[SCOPE]] end
dove:
• LetIn() rappresenta la traduzione del preambolo come visto nel listato;
• [[SCOPE]] rappresenta la traduzione COWS dello scope principale;
• edges(SCOPE) restituisce la restrizione di tutti gli archi contenuti nello
scope.
4.6.2
Scope
Uno scope è tradotto come l’esecuzione parallela, con le necessarie delimitazioni, di tutti i suoi componenti. Ecco come è possibile sintetizzare la
traduzione di un generico SCOPE:
74
UStoC: da UML4SOA a COWS
[[SCOPE]] =
[e#][stack#][t#][r#][i#]vars(VARS)
( [r#]( [kr][kt]( [[GRAPH]] | {|Stack(stack,c)|}
| [t#][kt][[GRAPHev 1 ]] | . . . | [t#][kt][[GRAPHev n ]] )
| r.op?hi. e.op!hi )
| [[VARS]] | e.op?hi. [t#][kt][[GRAPHe ]]
| i.op?hi. {| c.scopeName?hscopeNamei. [t#]( [kt][[GRAPHc ]]
| t.op?hi. stack.end!hscopeNamei )
| ∗ [X] c.scopeName?hXi. stack.end!hscopeNamei |} )
La funzione vars(VARS), data una lista di variabili UML4SOA, restituisce
una lista di variabili e nomi COWS. Un nome COWS x corrisponde alla variabile x in VARS, mentre una variabile write-once in COWS è denominata
Xx. Per fare un esempio, supponendo che l’insieme VARS contenga le variabili var e woVar(write-once), la funzione vars(VARS) restituisce la seguente
restrizione:
[var#][XwoVar]
La traduzione COWS di GRAPH (cioè [[GRAPH]]) corrisponde alla traduzione COWS del grafo dello scope. La sua realizzazione è banale: essa
4.6 Traduzione in COWS
75
consiste nella traduzione in parallelo di tutti gli elementi appartenenti al
vettore graph dell’oggetto Scope.
La chiamata Stack(stack,c) richiama la procedura Stack descritta
precedentemente.
[[GRAPHev i ]] rappresenta la traduzione del grafo dell’i-esimo event scope
connesso allo scope, se presente. Nel caso in cui non siano presenti event
scope, la procedura non restituisce alcun valore.
[[GRAPHe ]] e [[GRAPHc ]] restituiscono invece la traduzione dei graph degli
exception e compensation scope, connessi allo scope oggetto della traduzione. Nel caso essi non siano stati inseriti nel diagramma, UStoC fornisce,
in entrambi i casi, una traduzione di default. Infatti, è sempre obbligatorio
inserire per ogni scope i suoi exception e compensation handler.
La traduzione di VARS (rappresentata da [[VARS]]) mostra la traduzione
delle variabili non write-once (cioè riscrivibili). La traduzione avviene così:
per ogni variabile x non write-once viene richiamato il servizio definito nel
preambolo dell’Orchestration, mediante l’invocazione var(x).
Il partner privato r permette di catturare i segnali generati dalle RAISE e
attivare i corrispondenti handler. kr e kt sono killer label usate per delimitare
i campi di azione delle attività kill generate dalla traduzione di azioni RAISE
o Final Node.
Quando uno scope è completato con successo, i suoi compensation handler sono installati mediante un segnale sull’endpoint i.op. I compensation
handler installati sono protetti per garantire che possano essere eseguiti a
dispetto di ogni eccezione. In seguito la compensation può essere attivata mediante il partner name c. Va ricordato che un compensation hand-
76
UStoC: da UML4SOA a COWS
ler può essere eseguito esclusivamente una volta. Dopo questo il termine ∗ [X] c.scopeName?hXi. stack.end!hscopeNamei permette di ignorare future
compensazioni richieste.
Vediamo adesso come avviene la traduzione di ogni elemento appartente
al grafo di uno scope.
4.6.3
Initial e FinalNode
Initial Node
Un Initial Node viene così tradotto in COWS da UStoC:
[[INITIAL_NODE]] = e.op!hguard i
Se guard contiene variabili riscrivibili la invoke deve essere modificata, come
descritto in Sezione 4.6.1. Ciò vale per tutte le invoke e le receive che contengono espressioni che incontreremo nel corso della descrizione della traduzione
di ogni elemento UML.
FinalNode
[[FINAL_NODE]] = e.op?htruei. ( kill(kt) | {|t.op!hi|} )
4.6 Traduzione in COWS
77
Il Final Node, quando raggiunto, abilita un’attività kill dove la killer
label kt è delimitata a livello di scope, che istantaneamente termina tutti i
processi non protetti nella traduzione dello scope che la racchiude, ma senza
effetto sugli altri scope. Simultaneamente il termine protetto t.op!<> invia
un segnale di terminazione per avviare l’esecuzione delle (eventuali) attività
successive.
4.6.4
Fork e Join
Fork
[[FORK]] = ∗ e.op?htruei. ( e1 .op!hguard1 i | . . . | en .op!hguardn i )
La traduzione di una FORK è un servizio COWS che può essere istanziato
sincronizzando l’attività di Receive corrispondente all’arco entrante. Effettuata la sincronizzazione, un’attività di Invoke è attivata simultaneamente
per ogni arco uscente.
Join
[[JOIN]] = ∗ e1 .op?htruei. . . . . en .op?htruei. e.op!hguard i
78
UStoC: da UML4SOA a COWS
La traduzione di una JOIN è un servizio COWS che prevede una serie di
attività di Receive, una per ogni arco entrante, e un’attività di Invoke per
l’arco uscente. L’ordine delle attività di Receive non è importante, infatti al
momento della Invoke tutte le Receive dovranno essere state eseguite.
4.6.5
Decision e Merge
Decision
[[DECISION]] = ∗ e.op?htruei. [n1 #] . . . [nn #] ( n1 .op!hguard1 i
| . . . | nn .op!hguardn i | n1 .op?htruei. e1 .op!htruei
+ . . . + nn .op?htruei. en .op!htruei )
Nella traduzione di una DECISION, i partner name n1 . . . nn (uno per ogni
arco uscente) sono localmente delimitati e usati per implementare una scelta guardata non deterministica che seleziona uno degli endpoint attraverso
il quale la guardia valutata (che deve essere true) attiva l’invocazione del
corrispondente arco uscente.
Merge
4.6 Traduzione in COWS
79
[[MERGE]] = ∗ ( e1 .op?htruei. e.op!hguard i
+ . . . + en .op?htruei. e.op!hguard i )
Una MERGE è tradotta come una scelta guardata da tutti i suoi archi
entranti. Ogni guardia è seguita da un’attività di Invoke dell’arco uscente.
4.6.6
Action e Scope Graph
Action Graph
[[
ACTION
]] = ∗ e1 .op?htruei. [t#] ( [[ACTION]]
| t.op?hi. e2 .op!hguard i )
Un’azione con i proprio archi entranti ed uscenti è tradotta come un servizio a sè stante, denominato in UStoC come ActionGraph. La traduzione
consiste in una Receive sull’arco entrante seguita dalla traduzione dell’azione e, in parallelo, da un processo che attende un segnale di terminazione
inviato dalla traduzione dell’azione lungo l’endpoint interno t.op e quindi
un invoke sull’arco uscente. Ovviamente, t è delimitata per non consentire
sincronizzazioni indesiderate con altri processi.
Scope Graph
[[
SCOPE
]] = ∗ e1 .op?htruei. [t#] [i#] ( [[SCOPE]] |
t.op?hi. ( i.op!hi | stack .push!hscopeNamei | e2 .op!hguard i ) )
La traduzione di uno scope interno è del tutto simile alla precedente, con la
presenza di due aggiunte. Quando uno scope termina, la traduzione del suo
nodo invia un segnale i.op!hi installando così il suo compensation handler.
80
UStoC: da UML4SOA a COWS
Inoltre invia il suo nome al processo locale Stack per consentire la compensazione in ordine inverso rispetto a quello di completamento degli scope figli
del suo scope padre. Ovviamente, per evitare interferenze con altri processi
in parallelo, oltre alla t deve essere delimitata anche la i.
4.6.7
Send e Receive Action
Le azioni di Send e Receive sono tradotte basandosi rispettivamente sulle invoke e receive di COWS. Va ricordato che bisogna prestare attenzione
particolare al fatto che un messaggio è ricevuto solo tramite l’endpoint individuato dal link pin e dal nome della receive. Così send e receive possono
scambiare messaggi solo se essi condividono lo stesso nome. In particolare il partner name lungo il quale avviene la comunicazione è il nome orc
dell’Orchestration che contiene la Receive.
Send
[[SEND]] = {| [[p]].name!horc, expr1 , . . . , exprn i |} | t.op!hi
Una azione SEND è una chiamata asincrona: il messaggio, rappresentato da
expr1 , . . . , exprn , è inviato al patner p e il processo procede senza aspettare
una risposta. Tutto questo è realizzato mediante un’attività di Invoke che
invia la tupla horc, expr1 , . . . , exprn i, dove orc rappresenta l’Orchestration che
invia il messaggio e sarà usato dal ricevente per provvedere a una possibile
risposta. Il partner name p su cui è attivata l’attività di Invoke è tradotto
4.6 Traduzione in COWS
81
come link p, se p è una costante, o come una variabile Xp nel caso p sia
una variabile write-once. Infine vi è, in parallelo, l’invio di un segnale di
terminazione mediante l’endpoint t.op, per permettere alla computazione di
proseguire.
Receive
[[RECEIVE]] = orc.name?h[[p]], [[X1 ]], . . . , [[Xn ]]i. t.op!hi
A differenza della send, la Receive è un’attività bloccante, in modo tale da
evitare che il flusso di esecuzione vada avanti fino a che il messaggio non
è ricevuto. La traduzione è realizzata mediante un’attività di receive di
COWS lungo l’endpoint orc.name, avente come input pattern una tupla
dove il primo elemento è la traduzione del link p, e gli altri elementi sono
variabili COWS Xvar se var è write-once, altrimenti var. In questo modo un
messaggio può essere ricevuto solo se c’è correlazione tra i suoi dati presenti
nel pattern di input e in quel caso, gli altri dati sono salvati sui valori delle
corrispondenti variabili.
Send&Receive
82
UStoC: da UML4SOA a COWS
[[SEND&RECEIVE]] = {| [[p]].name!horc, expr1 , . . . , exprn i |} |
orc.name?h[[p]], [[X1 ]], . . . , [[Xn ]]i. t.op!hi
La traduzione di una Send&Receive non è altro che la composizione della
traduzione di un’azione Send con una Receive. Come vediamo in questo
esempio.
Receive&Send
[[RECEIVE&SEND]] = orc.name?h[[p]], [[X1 ]], . . . , [[Xn ]]i.
{| [[p]].name!horc, expr1 , . . . , exprn i |} | t.op!hi
La traduzione di una Receive&Send è invece la composizione della traduzione di una Receive con quella di una Send, come vediamo in questo
esempio.
4.6.8
Azioni per Fault e Compensation Handling
Raise
[[RAISE]] = kill(kr) | {|r.op!hi|}
4.6 Traduzione in COWS
83
Una azione RAISE termina tutte le attività nel suo scope (dove la killer label
kr è delimitata) e attiva il relativo exception handler mediante il segnale inviato dalla invoke r.op!<>. Una eccezione può essere propagata da un altro
exception handler che esegue un’altra azione RAISE. Va notato che, siccome
gli excpetion handler di default eseguono semplicemente una RAISE e poi terminano, non specificare gli exception handler causa la propagazione dell’eccezione verso lo scope di livello superiore, eventualmente fino a raggiungere
il livello più alto e così portare alla terminazione dell’Orchestration.
Compensate
[[COMPENSATE]] = c.scopeName!hscopeNamei | t.op!hi
L’azione COMPENSATE è tradotta come l’invocazione di un compensation
handler installato per lo scope targetScope.
Compensate_All
[[COMPENSATE_ALL]] = [n#] ( stack.compAll!hni | n.op?hi.t.op!hi )
L’azione COMPENSATE_ALL è tradotta come un’invocazione del processo
locale Stack a cui è richiesto di eseguire tutti i compensation handler installati fino a quel momento in quello scope. Al termine di tale operazione, il
84
UStoC: da UML4SOA a COWS
processo Stack invierà un segnale lungo l’endpoint privato n.op per consentire alla traduzione della COMPENSATE_ALL di notificare la terminazione
alle attività successive.
Capitolo
5
Utilizzo di UStoC
In questo capitolo viene presentata una guida all’uso di UStoC: vedremo
come configurare i software necessari al suo utilizzo e alcuni esempi che ne
illustreranno il funzionamento. Tratteremo, in dettaglio, la creazione di un
diagramma UML4SOA utilizzando MagicDraw, la sua traduzione in COWS mediante UStoC e, dopo averne brevemente descritto le caratteristiche
principali, useremo CMC per testare il codice ottenuto.
UStoC, sviluppato in Java (JRE e JDK 1.6), necessita esclusivamente della
Java Virtual Machine (JVM) per essere eseguito correttamente. La JVM è
disponibile all’indirizzo http://www.java.com/it/download/manual.jsp.
5.1
Diagramma UML4SOA
UStoC è testato su diagrammi realizzati con il programma MagicDraw (ver.
16). Inizialmente, dunque, è necessario installare il software, disponibile
in download all’indirizzo http://www.magicdraw.com/ e successivamente
configurare il profilo UML4SOA per MagicDraw.
85
86
Utilizzo di UStoC
Figura 5.1: Pagina web con i profili UML4SOA
Una breve descrizione sull’utilizzo e configurazione del profilo e i file necessari all’installazione sono disponibili all’indirizzo http://www.mdd4soa.eu,
selezionando Installation and use of the UML profile.
Nella parte superiore della pagina (vedi Figura 5.1), all’interno della
sezione Installation sono disponibili due file:
• de.lmu.ifi.pst.uml4soa.profile.mdzip
• de.lmu.ifi.pst.uml4soa.profile.xml
i quali devono essere copiati nella cartella MagicDraw/profiles. Per installare correttamente il profilo, è necessario eseguire MagicDraw, selezionare
5.1 Diagramma UML4SOA
87
Figura 5.2: MagicDraw: Import dei Profili
Diagrams > Customize > Import (vedi Figura 5.2) e scegliere il file .xml
(precedentemente scaricato) all’interno della directory dei profili.
Siamo adesso in grado di usare il profilo all’interno di MagicDraw e di
creare quindi un’Orchestration UML4SOA semplicemente selezionando Diagrams > LMU UML4SOA Diagram > UML4SOA Orchestration Diagrams.
Nella palette a sinistra (vedi Figura 5.3) sono presenti tutti gli elementi che
possono essere inseriti, tra i quali quelli specifici del profilo UML4SOA sotto
la voce Orchestration. Una volta creato correttamente il diagramma, affinchè
possa essere utilizzato e tradotto da UStoC, è necessario esportarlo in XMI,
selezionando File > Export > EMF UML2 (v2.x) XMI : tale operazione genera alcuni file, uno dei quali (quello con estensione .uml ) è quello da fornire
in input a UStoC.
88
Utilizzo di UStoC
Figura 5.3: MagicDraw: Finestra Principale
5.2
Guida all’uso di UStoC
Per poter utilizzare UStoC, dopo essersi accertati che la Java Virtual Machine sia installata nella propria macchina, occorre semplicemente eseguire il
file UStoC.jar mediante un doppio click sull’icona oppure da console con il
comando java -jar UStoC.jar.
Come si nota in Figura 5.4, UStoC presenta un’interfaccia grafica semplice ed intuitiva, così da agevolarne l’utilizzo. Per iniziare un progetto di
traduzione è necessario selezionare dal file system i diagrammi che intendiamo tradurre, selezionando dal menù File > Open UML file .... Ogni qualvolta
un diagramma viene caricato, esso va ad aggiungersi alla lista di Selected Files, posta sulla parte destra della finestra principale di UStoC. In questa lista
sono presenti tutti i nomi dei file caricati per i quali è possibile effettuare al-
5.2 Guida all’uso di UStoC
89
Figura 5.4: UStoC: interfaccia grafica
cune operazioni mediante i pulsanti a destra. É possibile modificare l’ordine
in cui i file verranno tradotti (anche se tale operazione è priva di senso ai
fini della traduzione, dal momento che tutti i file sono tradotti in parallelo)
e rimuovere i file che non desideriamo più tradurre. Mediante il pulsante in
basso Start Encoding ... o tramite il menù Run > COWS Encoding ... è
possibile eseguire la traduzione dei diagrammi selezionati. Se la traduzione
ha avuto buon esito viene mostrato un messaggio di avvenuta traduzione
(vedi Figura 5.5) e il codice COWS risultante viene presentato direttamente
sulla textArea posta al centro. La sintassi generica, nel caso di un singolo
diagramma, sarà del tipo:
diagr ::= let . . . in body end
Nel caso, invece, si stia effettuando la traduzione di più file, UStoC esegue
sequenzialmente la traduzione dei diagrammi nell’ordine prestabilito. Per
ogni file tradotto correttamente, UStoC mostra un messaggio di avvenuta
traduzione e aggiunge alla textArea il codice prodotto. La sintassi generale
di una traduzione multi-file sarà del tipo:
90
Utilizzo di UStoC
Figura 5.5: UStoC: traduzione eseguita
diagr1 | diagr2 | . . . | diagrn
dove la definizione di diagri , per i da 1 a n, corrisponde a quanto definito
precedentemente. A causa di errori dovuti a diagrammi UML4SOA non
correttamente specificati, possono verificarsi due casi:
1. traduzione eseguita, ma sono presenti errori non critici (warning).
2. traduzione non eseguita, per la presenza di errori critici.
Nel primo caso, terminata la traduzione del file, appare un messaggio contente le informazioni riguardo tali segnalazioni, quali: elementi privi di nomi,
decision, fork, join o merge con un solo arco entrante ed uscente, ecc. La textArea mostrerà, comunque, il risultato della traduzione dei file. Nel secondo
caso, invece, data la presenza di errori critici che non rendono possibile la
traduzione del diagramma, compare un messaggio contente i dettagli degli
errori riscontrati: la text area risulterà, dunque, vuota.
5.3 CMC
91
Se, invece, la traduzione è stata eseguita correttamente, è possibile copiare
il codice selezionandolo con il tasto destro del mouse, o salvarlo in un file
COWS, mediante il comando File > Save COWS file
Una volta terminata la traduzione dei diagrammi, è possibile iniziare un
nuovo lavoro con il comando New Encoding, il quale riporta UStoC allo stato
di partenza; se qualche diagramma risulta, invece, ancora utile è possibile
eliminare singolarmente i file non necessari, cancellare la textArea con il tasto
Clear Code Area e inserire nuovi diagrammi dal menù File > Open UML file
....
5.3
CMC
Figura 5.6: CMC: interfaccia Web
92
Utilizzo di UStoC
CMC (COWS interpreter & Model Checker) è un tool che svolge la fun-
zione di interprete del linguaggio e di analisi di un modello COWS. CMC
mostra tutte le evoluzioni e configurazioni possibili di un modello che presenti una sintassi corretta. Una versione online di CMC, la cui interfaccia
grafica è mostrata in Figura 5.6, e tutte le informazioni sul tool sono disponibili all’indirizzo http://fmt.isti.cnr.it/cmc/; è possibile, inoltre,
scaricare una versione che può essere usata in offline mode.
La sintassi di CMC che riprende quella di COWS è già stata definita in
Sezione 3.3.
5.4
Esempi
In questo sezione riportiamo alcuni esempi per rendere più chiare le modalità
di utilizzo di UStoC.
Esempio PingServer
L’esempio mostra una semplice interazione tra un server ed un client che
esegue un’operazione di ping.
Inizialmente occorre realizzare con MagicDraw i due diagrammi.
L’Orchestration del client presenta due operazioni : una send e una receive.
La send è invocata sul link pingserver e invia il messaggio alive; la receive
si pone in attesa della risposta del server pingserver che verrà salvata nella
variabile write-once y.
L’Orchestration del server presenta, invece, le due operazioni invertite:
una receive e una send. Per prima cosa il servizio si pone in attesa del
5.4 Esempi
Figura 5.7: Client
93
Figura 5.8: PingServer
messaggio da parte del client e, una volta ricevuto e memorizzato nella
variabile write-once x, invia la risposta al client stesso.
Utilizzando UStoC possiamo adesso effettuare la traduzione in cows delle
due Orchestrazioni: esportiamo i diagrammi in file .uml, eseguiamo l’applicazione, carichiamo i due file ed eseguiamo la traduzione COWS. Otterremo,
così, il codice seguente:
let
var ( v ) = [ XV ][ XA ] v . write ? < XV , XA >.
[ n #](( n . op ! < XV , XA >
|*[ X ][ y #] n . op ? <X ,y >.( y . op ! < >|[ k ](*[ y1 #] v . read ? < y1 >.{ y1 . op ! <X >}
|[ X1 ][ y1 #] v . write ? < X1 , y1 >.
( kill ( k ) | { n . op ! < X1 , y1 >}) ) ) )
)
Lifo ( q ) = [ m #][ h #](*[ YV ][ YR ][ YE ]( q . push ? < YV >.[ Z ] m . op ? <z >.[ C ]( h . op ! < YV ,Z ,C >|
m . op ! <C >)
+ q . pop ? < YR , YE >.[ Z ]( m . op ? <Z >.[ YV ][ YT ] h . op ? < YV , YT ,Z >.( m . op ! < YT >| yr . op ! < YV >)
+ m . op ? < empty >.( m . op ! < empty >| ye . op ! < >) ) )
| m . op ! < empty >
)
Stack ( stack , c ) =
94
Utilizzo di UStoC
[ q #]( Lifo ( q ) |*[ X ] stack . push ? <X >. q . push ! <X >
| *[ X ] stack . compAll ? <X >.[ loop #]( loop . op ! < > | * loop . op ? < >.[ r #][ e #]
( q . pop ! <r ,e >|[ y #]( r . op ? <y >.( c . y ! <y >| stack . end ? <y >. loop . op ! < >) + e . op ? < >. x . op
! < >) ) )
)
in
[ kr ][ c #][ e1 #][ e2 #][ e3 #][ e #][ stack #][ t #][ r #][ i #][ Xy ]
([ r #]([ kr ][ kt ]( e1 . op ! < true >
| * e1 . op ? < true >.[ t #]({ pingserver . ping ! < client , alive >}| t . op ! < >| t . op ? < >. e2 . op
! < true >)
| e3 . op ? < true >.( kill ( kt ) |{ t . op ! < >})
| * e2 . op ? < true >.[ t #]( client . reply ? < pingserver , Xy >. t . op ! < >| t . op ? < >. e3 . op ! <
true >) | { Stack ( stack , c ) })
| r . op ? < >. e . op ! < > )
| e . op ? < >.[ t #][ kt ]([ ea #][ eb #]
( ea . op ! < true >
| * ea . op ? < true >.[ t #]( kill ( kr ) |{ r . op ! < >}| t . op ? < >. eb . op ! < true >)
| eb . op ? < true >.( kill ( kt ) |{ t . op ! < >}) ) )
| i . op ? < >.{ c . client ? < client >.[ t #]([ kt ]([ ea #][ eb #]
( ea . op ! < true >
| * ea . op ? < true >.[ t #]([ n #]( stack . compAll ! <n >| n . op ? < >. t . op ! < >) | t . op ? < >. eb . op ! <
true >)
| eb . op ? < true >.( kill ( kt ) |{ t . op ! < >}) ) )
| t . op ? < >. stack . end ! < client >)
| *[ X ] c . client ? <X >. stack . end ! < client >})
end
|
let
var ( v ) = [ XV ][ XA ] v . write ? < XV , XA >.
[ n #](( n . op ! < XV , XA >
|*[ X ][ y #] n . op ? <X ,y >.( y . op ! < >|[ k ](*[ y1 #] v . read ? < y1 >.{ y1 . op ! <X >}
|[ X1 ][ y1 #] v . write ? < X1 , y1 >.
( kill ( k ) | { n . op ! < X1 , y1 >}) ) ) )
)
Lifo ( q ) = [ m #][ h #](*[ YV ][ YR ][ YE ]( q . push ? < YV >.[ Z ] m . op ? <z >.[ C ]( h . op ! < YV ,Z ,C >|
m . op ! <C >)
+ q . pop ? < YR , YE >.[ Z ]( m . op ? <Z >.[ YV ][ YT ] h . op ? < YV , YT ,Z >.( m . op ! < YT >| yr . op ! < YV >)
+ m . op ? < empty >.( m . op ! < empty >| ye . op ! < >) ) )
| m . op ! < empty >
)
Stack ( stack , c ) =
[ q #]( Lifo ( q ) |*[ X ] stack . push ? <X >. q . push ! <X >
| *[ X ] stack . compAll ? <X >.[ loop #]( loop . op ! < > | * loop . op ? < >.[ r #][ e #]
( q . pop ! <r ,e >|[ y #]( r . op ? <y >.( c . y ! <y >| stack . end ? <y >. loop . op ! < >) + e . op ? < >. x . op
5.4 Esempi
95
! < >) ) )
)
in
[ kr ][ c #][ e1 #][ e2 #][ e3 #][ e #][ stack #][ t #][ r #][ i #][ Xx ]
([ r #]([ kr ][ kt ]( e1 . op ! < true >
| e3 . op ? < true >.( kill ( kt ) |{ t . op ! < >})
| * e1 . op ? < true >.[ t #]( pingserver . ping ? < client , Xx >. t . op ! < >| t . op ? < >. e2 . op ! < true
>)
| * e2 . op ? < true >.[ t #]({ client . reply ! < pingserver , Xx >}| t . op ! < >| t . op ? < >. e3 . op ! <
true >) | { Stack ( stack , c ) })
| r . op ? < >. e . op ! < > )
| e . op ? < >.[ t #][ kt ]([ ea #][ eb #]
( ea . op ! < true >
| * ea . op ? < true >.[ t #]( kill ( kr ) |{ r . op ! < >}| t . op ? < >. eb . op ! < true >)
| eb . op ? < true >.( kill ( kt ) |{ t . op ! < >}) ) )
| i . op ? < >.{ c . pingserver ? < pingserver >.[ t #]([ kt ]([ ea #][ eb #]
( ea . op ! < true >
| * ea . op ? < true >.[ t #]([ n #]( stack . compAll ! <n >| n . op ? < >. t . op ! < >) | t . op ? < >. eb . op ! <
true >)
| eb . op ? < true >.( kill ( kt ) |{ t . op ! < >}) ) )
| t . op ? < >. stack . end ! < pingserver >)
| *[ X ] c . pingserver ? <X >. stack . end ! < pingserver >})
end
Tale codice può essere fornito come input a CMC, mediante la funzione ‘copia
col tasto destro del mouse’ o dopo averlo salvato in un file COWS. Una volta
che CMC ha preso in input il codice, mostrerà le evoluzioni descritte in
Figura 5.9 e 5.10.
La prima rappresenta l’operazione di ping da parte del client, mentre la
seconda mostra la risposta da parte del server tramite l’operazione reply.
Tali transizioni sono precedute e seguite da sincronizzazioni interne che si
occupano del controllo del flusso d’esecuzione.
96
Utilizzo di UStoC
Figura 5.9: CMC: Ping del Client
Figura 5.10: CMC: Risposta del Server
5.4 Esempi
97
Esempio Bank
L’esempio descrive le interazioni di un servizio che rappresenta una banca che
offre la possibilità ad un cliente di effettuare un versamento sul proprio conto.
Un cliente può effettuare un versamento specificando il numero del conto,
l’identificatore personale e la somma da versare. La banca, una volta ricevuti
questi dati (mediante una receive) richiama il servizio checkService che
controlla se i dati inviati dal cliente sono corretti e si pone quindi in attesa
della risposta.
Il servizio checkService, dopo aver controllato i dati, può rispondere con
un checkOK che indica che i dati sono corretti e il versamento può procedere;
altrimenti notifica, tramite la send checkFail, che i dati non sono corretti
e l’operazione di versamento non deve procedere. La banca, a seconda del
tipo di risposta ricevuta dal servizio checkService, invia al cliente un
messaggio tramite la send chargeOK se il versamento è andato in porto, altrimenti tramite la send chargeFail comunica il fallimento della transazione.
Il servizio client include un’azione send con cui invia i dati iniziali e
successivamente due receive con cui attende la risposta inerente il risultato del versamento. Le orchestrazioni bank, checkService e client sono
rappresentate in Figura 5.11.
98
Utilizzo di UStoC
(a) Orchestration client.
(b) Orchestration checkService.
(c) Orchestration bank.
Figura 5.11: Orchestrazioni dell’esempio Bank
5.4 Esempi
99
Dopo aver esportato i 3 diagrammi come file XMI, mediante MagicDraw,
possiamo eseguire la traduzione con UStoC. Il codice che risulta è il seguente:
let
var ( v ) = [ XV ][ XA ] v . write ? < XV , XA >.
[ n #](( n . op ! < XV , XA >
|*[ X ][ y #] n . op ? <X ,y >.( y . op ! < >|[ k ](*[ y1 #] v . read ? < y1 >.{ y1 . op ! <X >}
|[ X1 ][ y1 #] v . write ? < X1 , y1 >.
( kill ( k ) | { n . op ! < X1 , y1 >}) ) ) )
)
Lifo ( q ) = [ m #][ h #](*[ YV ][ YR ][ YE ]( q . push ? < YV >.[ Z ] m . op ? <z >.[ C ]( h . op ! < YV ,Z ,C >|
m . op ! <C >)
+ q . pop ? < YR , YE >.[ Z ]( m . op ? <Z >.[ YV ][ YT ] h . op ? < YV , YT ,Z >.( m . op ! < YT >| yr . op ! < YV >)
+ m . op ? < empty >.( m . op ! < empty >| ye . op ! < >) ) )
| m . op ! < empty >
)
Stack ( stack , c ) =
[ q #]( Lifo ( q ) |*[ X ] stack . push ? <X >. q . push ! <X >
| *[ X ] stack . compAll ? <X >.[ loop #]( loop . op ! < > | * loop . op ? < >.[ r #][ e #]
( q . pop ! <r ,e >|[ y #]( r . op ? <y >.( c . y ! <y >| stack . end ? <y >. loop . op ! < >) + e . op ? < >. x . op
! < >) ) )
)
in
[ kr ][ c #][ e1 #][ e2 #][ e3 #][ e4 #][ e8 #][ e5 #][ e6 #][ e7 #][ e9 #][ e #][ stack #][ t #][ r #][ i
#][ Xcust ][ Xid ][ Xcc ][ Xamount ]
([ r #]([ kr ][ kt ]( e1 . op ! < true >
| e7 . op ? < true >.( kill ( kt ) |{ t . op ! < >})
| * e1 . op ? < true >.[ t #]( bank . charge ? < Xcust , Xcc , Xamount , Xid >. t . op ! < >| t . op ? < >. e2 .
op ! < true >)
| * e2 . op ? < true >.[ t #]({ checkService . check ! < bank , Xid , Xcc , Xamount >}| t . op ! < >| t .
op ? < >. e3 . op ! < true >)
| * e3 . op ? < true >.( e4 . op ! < true >| e5 . op ! < true >)
| * e4 . op ? < true >.[ t #]( bank . checkOK ? < checkService , Xid >. t . op ! < >| t . op ? < >. e8 . op ! <
true >)
| * e8 . op ? < true >.[ t #]({ Xcust . chargeOK ! < bank , Xid >}| t . op ! < >| t . op ? < >. e9 . op ! < true
>)
| * e5 . op ? < true >.[ t #]( bank . checkFail ? < checkService , Xid >. t . op ! < >| t . op ? < >. e6 . op
! < true >)
| * e6 . op ? < true >.[ t #]({ Xcust . chargeFail ! < bank , Xid >}| t . op ! < >| t . op ? < >. e7 . op ! <
true >)
| e9 . op ? < true >.( kill ( kt ) |{ t . op ! < >}) | { Stack ( stack , c ) })
| r . op ? < >. e . op ! < > ) | e . op ? < >.[ t #][ kt ]([ ea #][ eb #]
( ea . op ! < true > | * ea . op ? < true >.[ t #]( kill ( kr ) |{ r . op ! < >}| t . op ? < >. eb . op ! < true >)
| eb . op ? < true >.( kill ( kt ) |{ t . op ! < >}) ) ) | i . op ? < >.{ c . bank ? < bank >.[ t #]([ kt ]
([ ea #][ eb #] ( ea . op ! < true > | * ea . op ? < true >.[ t #]([ n #]
100
Utilizzo di UStoC
( stack . compAll ! <n >| n . op ? < >. t . op ! < >) | t . op ? < >. eb . op ! < true >)
| eb . op ? < true >.( kill ( kt ) |{ t . op ! < >}) ) ) | t . op ? < >. stack . end ! < bank >)
| *[ X ] c . bank ? <X >. stack . end ! < bank >})
|
[ kr ][ c #][ e1 #][ e2 #][ e4 #][ e3 #][ e5 #][ e6 #][ e #][ stack #][ t #][ r #][ i #][ Xcustid ][ Xcc
][ Xamount ]
([ r #]([ kr ][ kt ]( e1 . op ! < true >
| e5 . op ? < true >.( kill ( kt ) |{ t . op ! < >})
| * e1 . op ? < true >.[ t #]( checkService . check ? < bank , Xcustid , Xcc , Xamount >. t . op ! < >| t
. op ? < >. e2 . op ! < true >)
| * e2 . op ? < true >.[ n3 #][ n4 #]( n3 . op ! < true >| n4 . op ! < true >| n3 . op ? < true >. e3 . op ! <
true > + n4 . op ? < true >. e4 . op ! < true >)
| * e4 . op ? < true >.[ t #]({ bank . checkOK ! < checkService , Xcustid >}| t . op ! < >| t . op ? < >.
e6 . op ! < true >)
| * e3 . op ? < true >.[ t #]({ bank . checkFail ! < checkService , Xcustid >}| t . op ! < >| t . op
? < >. e5 . op ! < true >)
| e6 . op ? < true >.( kill ( kt ) |{ t . op ! < >}) | { Stack ( stack , c ) })
| r . op ? < >. e . op ! < > ) | e . op ? < >.[ t #][ kt ]([ ea #][ eb #]
( ea . op ! < true > | * ea . op ? < true >.[ t #]( kill ( kr ) |{ r . op ! < >}| t . op ? < >. eb . op ! < true >)
| eb . op ? < true >.( kill ( kt ) |{ t . op ! < >}) ) ) | i . op ? < >.{ c . checkservice ? <
checkservice >.[ t #]
([ kt ]([ ea #][ eb #] ( ea . op ! < true >
| * ea . op ? < true >.[ t #]([ n #]( stack . compAll ! <n >| n . op ? < >. t . op ! < >) | t . op ? < >. eb . op ! <
true >)
| eb . op ? < true >.( kill ( kt ) |{ t . op ! < >}) ) ) | t . op ? < >. stack . end ! < checkservice >)
| *[ X ] c . checkservice ? <X >. stack . end ! < checkservice >})
|
[ kr ][ c #][ e1 #][ e2 #][ e3 #][ e4 #][ e5 #][ e6 #][ e #][ stack #][ t #][ r #][ i #]
([ r #]([ kr ][ kt ]( e1 . op ! < true >
| e5 . op ? < true >.( kill ( kt ) |{ t . op ! < >})
| * e1 . op ? < true >.[ t #]({ bank . charge ! < client ,123 ,100 , id1 >}| t . op ! < >| t . op ? < >. e2 .
op ! < true >)
| * e2 . op ? < true >.( e3 . op ! < true >| e4 . op ! < true >)
| * e3 . op ? < true >.[ t #]( client . chargeOK ? < bank , id1 >. t . op ! < >| t . op ? < >. e5 . op ! < true
>)
| * e4 . op ? < true >.[ t #]( client . chargeFail ? < bank , id1 >. t . op ! < >| t . op ? < >. e6 . op ! <
true >)
| e6 . op ? < true >.( kill ( kt ) |{ t . op ! < >}) | { Stack ( stack , c ) })
| r . op ? < >. e . op ! < > ) | e . op ? < >.[ t #][ kt ]([ ea #][ eb #] ( ea . op ! < true >
| * ea . op ? < true >.[ t #]( kill ( kr ) |{ r . op ! < >}| t . op ? < >. eb . op ! < true >)
| eb . op ? < true >.( kill ( kt ) |{ t . op ! < >}) ) ) | i . op ? < >.{ c . client ? < client >.[ t #]
([ kt ]([ ea #][ eb #] ( ea . op ! < true > | * ea . op ? < true >.[ t #]([ n #]
( stack . compAll ! <n >| n . op ? < >. t . op ! < >) | t . op ? < >. eb . op ! < true >)
| eb . op ? < true >.( kill ( kt ) |{ t . op ! < >}) ) ) | t . op ? < >. stack . end ! < client >)
| *[ X ] c . client ? <X >. stack . end ! < client >})
5.4 Esempi
101
end
dove sono omessi (per una questione di lunghezza) i costrutti let ...
in
delle ultime due orchestrazioni.
Tale codice può essere quindi inserito all’interno di CMC, il quale può
generare due possibili scenari: il primo in cui il servizio checkService risponde alla banca indicando che il controllo ha avuto successo, in cui si ha
la notifica da parte della banca al cliente che la transazione è avvenuta; il
secondo che presenta la risposta negativa del checkService e la notifica della
banca al cliente riguardo il fallimento dell’operazione, come rappresentato in
Figura 5.12.
Figura 5.12: CMC: Sincronizzazioni Bank
L’elenco delle configurazioni analizzate da CMC mostra che è avvenuta
l’operazione di charge inviata dal cliente (configurazione C5), l’operazione
di check da parte della banca (conf. C16), la risposta checkFail da parte
102
Utilizzo di UStoC
di checkService (conf. C41) e la successiva comunicazione da parte della
banca al cliente mediante l’operazione chargeFail (conf. C74). Si può
notare che, a causa della composizionalità della traduzione, il numero di
sincronizzazioni (e quindi di stati) è molto alto, nonostante l’esempio non sia
molto complesso.
Capitolo
6
Conclusioni
Questa tesi descrive il progetto e l’implementazione di UStoC, uno strumento per la traduzione automatica di diagrammi delle attività UML4SOA in
termini COWS. In dettaglio, UStoC accetta in ingresso un insieme di file
XMI contenenti una specifica UML4SOA e traduce la descrizione XMI dei
diagrammi in un termine COWS, scritto con la sintassi di CMC. La realizzazione dello strumento ha permesso di migliorare alcuni aspetti della semantica
per traduzione da UML4SOA a COWS proposta in [2], eliminando alcune
imprecisioni e ottimizzandola per un uso automatico.
La traduzione proposta è composizionale: infatti la traduzione dei diagrammi delle attività di UML4SOA è indicativamente il termine COWS ottenuto dalla composizione parallela della traduzione dei suoi componenti. Tale
caratteristica rende UStoC uno strumento facilmente estendibile a traduzioni
da UML4SOA verso altri linguaggi. Sarà sufficiente, infatti, aggiungere ad
ogni classe che rappresenta un elemento UML4SOA un metodo, analogo a
toCOWS(), che implementi la traduzione nel linguaggio considerato.
103
104
Conclusioni
Un primo possibile sviluppo di UStoC consiste nel raffinamento della traduzione, in modo da ridurre l’elevato numero di sincronizzazioni (e di stati)
aggiuntive dovute alla composizionalità della traduzione, che rendono meno efficiente l’analisi della specifica tramite model checking, o che, in alcuni
casi, la precludono del tutto. Per facilitare il model checking di specifiche UML4SOA tramite l’uso di UStoC sarebbe inoltre auspicabile una sua
integrazione più stretta con CMC. Un tale framework dovrebbe consentire
all’utilizzatore di specificare anche le proprietà comportamentali che si desidera verificare sulla specifica UML4SOA. Inoltre, dovrebbe permettere di
passare automaticamente alla fase di analisi, una volta completata la traduzione della specifica UML4SOA. In realtà, una completa integrazione tra
UStoC e CMC richiede alcuni sforzi anche nella direzione opposta. Il model
checker fornito da CMC, infatti, nel caso un termine COWS non soddisfi una
certa proprietà, è in grado di riportare una descrizione del perché ciò accade.
Tali descrizioni, però, risulteranno difficilmente comprensibili da utenti privi
di una certa familiarità con COWS e con le tecnicalità dei calcoli di processo. Sarebbe allora interessante rendere il framework capace di riportare
tali informazioni all’utilizzatore in termini della specifica UML4SOA e delle
relative proprietà comportamentali.
Bibliografia
[1] F. Banti, A. Lapadula, R. Pugliese, and F. Tiezzi. Specification and
analysis of SOC systems using COWS: A finance case study. In WWV,
volume 235 of Electr. Notes Theor. Comput. Sci., pages 71–105. Elsevier,
2009. Disponibile all’indirizzo: http://rap.dsi.unifi.it/cows.
[2] F. Banti, R. Pugliese, and F. Tiezzi. Towards a framework for the verification of uml models of services. In Proc. of 4th International Workshop
on Automated Specification and Verification of Web Systems (WWV’09),
2009. http://rap.dsi.unifi.it/cows/papers/cows_wwv09.pdf.
[3] J. Bauer, F. Nielson, H.R. Nielson, and H. Pilegaard. Relational Analysis
of Correlation. In SAS, volume 5079 of LNCS, pages 32–46. Springer,
2008.
[4] M. Boreale, R. Bruni, R. De Nicola, and M. Loreti. Sessions and Pipelines for Structured Service Programming. In FMOODS, volume 5051
of LNCS, pages 19–38. Springer, 2008.
105
106
BIBLIOGRAFIA
[5] N. Busi, R. Gorrieri, C. Guidi, R. Lucchi, and G. Zavattaro. Choreography and orchestration conformance for system design.
In
COORDINATION, volume 4038 of LNCS, pages 63–81. Springer, 2006.
[6] Martin Fowler. Uml Distilled: Applying the Standard Object Modeling
Language. Addison-Wesley, 2003.
[7] Ivar Jacobson Grady Booch, James Rumbaugh.
Unified Modeling
Language User Guide. Addison-Wesley, 1999.
[8] Object Management Group. Unified Modeling Language (UML), version
2.1.2. Sito Web: http://www.uml.org/.
[9] C. Guidi, R. Lucchi, R. Gorrieri, N. Busi, and G. Zavattaro. SOCK: A
Calculus for Service Oriented Computing. In ICSOC, volume 4294 of
LNCS, pages 327–338. Springer, 2006.
[10] A. Lapadula, R. Pugliese, and F. Tiezzi. Regulating data exchange in
service oriented applications. In FSEN, volume 4767 of LNCS, pages
223–239. Springer, 2007.
[11] A. Lapadula, R. Pugliese, and F. Tiezzi. A calculus for orchestration of web services.
Technical report, DSI, Università di Firenze,
2008. http://rap.dsi.unifi.it/cows/papers/cows-esop07-full.
pdf. An extended abstract appeared in ESOP, LNCS 4421, pages 33-47,
Springer.
[12] No Magic. MagicDraw UML. Sito Web: http://www.magicdraw.com/.
BIBLIOGRAFIA
107
[13] Philip Mayer, Andreas Schroeder, and Nora Koch. Mdd4soa: Modeldriven service orchestration. In 12th International IEEE Enterprise Distributed Object Computing Conference, ECOC 2008, 15-19 September
2008, Munich, Germany, pages 203–212. IEEE Computer Society, 2008.
[14] Philip Mayer, Andreas Schroeder, and Nora Koch. A model-driven approach to service orchestration. In 2008 IEEE International Conference
on Services Computing (SCC 2008), 8-11 July 2008, Honolulu, Hawaii,
USA, pages 533–536. IEEE Computer Society, 2008.
[15] F. Mazzanti. COWS Model Checker (CMC V0.4). http://fmt.isti.
cnr.it/cows/V0.4/cows.html.
[16] Sun Microsystem. Java. Sito Web ufficiale: http://java.sun.com/.
[17] OASIS. Organization for the Advancement of Structured Information
Standards. Sito Web: http://www.oasis-open.org/.
[18] Object Management Group.
Sevice oriented architecture Modeling
Language (SoaML) - Specification for the UML Profile and Metamodel for Services (UPMS). Technical report, OMG, 2008. Disponibile
all’indirizzo: http://www.omg.org/docs/ad/08-08-04.pdf.
[19] OMG. Model driven architecture. Sito Web: http://www.omg.org/
mda/.
[20] OMG. Object management group. Sito Web: http://www.omg.org/.
[21] OMG. XML Metadata Interchange. Sito Web: http://www.omg.org/
technology/documents/formal/xmi.htm.
108
BIBLIOGRAFIA
[22] D. Prandi and P. Quaglia. Stochastic COWS. In ICSOC, volume 4749
of LNCS, pages 245–256. Springer, 2007.
[23] Sensoria. Software engineering for service-oriented overlay computers.
IST project funded by the EU as Integrated Project in the 6th Framework Programme as part of the Global Computing Initiative. Sito
Wev: http://www.sensoria-ist.eu/.
[24] UML4SOA. Uml for service oriented architecture. Profilo MagicDraw
disponibile a http://www.mdd4soa.eu/web/.
[25] W3C. Document Object Model (DOM). Sito Web: http://www.w3.
org/DOM/.
[26] W3C. Extensible Markup Language (XML). Sito Web: http://www.
w3.org/XML/.
[27] WS-BPEL. Web Services Business Process Execution Language Version
2.0. Technical report, OASIS, April 2007. Sito Web: http://www.
oasis-open.org/committees/wsbpel/.
Scarica

Sviluppo di un traduttore automatico di specifiche UML4SOA in COWS