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/.