Progettazione e Realizzazione di un Editor di Workflow per la
Transcription
Progettazione e Realizzazione di un Editor di Workflow per la
Università degli Studi di Parma Dipartimento di Matematica e Informatica Corso di Laurea in Informatica Tesi di Laurea Progettazione e Realizzazione di un Editor di Workflow per la Piattaforma WADE Candidato: Federico Bacchi Relatore: Chiar.mo Prof. Federico Bergenti Anno Accademico 2013/2014 i Indice 1 JADE, WADE e WOLF 1.1 Introduzione a JADE . . . . . . . . . . . . . . . 1.1.1 Il modello peer-to-peer . . . . . . . . . . 1.1.2 Il paradigma degli agenti . . . . . . . . . 1.1.3 I middleware . . . . . . . . . . . . . . . 1.2 JADE . . . . . . . . . . . . . . . . . . . . . . . 1.2.1 Il modello architetturale . . . . . . . . . 1.2.2 Il modello funzionale . . . . . . . . . . . 1.3 WADE e WOLF . . . . . . . . . . . . . . . . . 1.3.1 I workflow . . . . . . . . . . . . . . . . . 1.3.2 Scope di utilizzo . . . . . . . . . . . . . 1.3.3 Approccio . . . . . . . . . . . . . . . . . 1.3.4 Gestire la complessità della distribuzione . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 7 8 11 12 13 14 15 18 18 20 20 25 2 ZK e WAM 2.1 Introduzione a ZK . . . . . . . . . . . . . . . . . . . . 2.1.1 I vantaggi di ZK . . . . . . . . . . . . . . . . . 2.1.2 L’architettura ZK . . . . . . . . . . . . . . . . . 2.1.3 Model-View-Conroller e Model-View-ViewModel 2.2 WAM . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.2.1 Struttura e tipologia dei WAM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 28 28 30 31 34 35 3 WOLFWeb 3.1 Panoramica del progetto . . . . . . . . . . . . . . . 3.2 Il lato client . . . . . . . . . . . . . . . . . . . . . . 3.2.1 Diagramo Light modificato . . . . . . . . . . 3.2.2 La pagina wolfweb.zul . . . . . . . . . . . . 3.3 Il lato server . . . . . . . . . . . . . . . . . . . . . . 3.3.1 Il composer WorkflowCommunicator . . 3.3.2 Il metodo doAfterCompose . . . . . . . . . . 3.3.3 I metodi onSendFigures e onSendConnectors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 37 38 38 54 61 61 61 65 ii . . . . . . . . . . . . . . . . Ringraziamenti Innanzitutto vorrei ringraziare il prof. Federico Bergenti per avermi proposto questo progetto che si è rivelato molto interessante e soprattutto per avermi supportato/sopportato in questo lavoro di tirocinio, aiutandomi sempre quando incontravo delle difficoltà. Ringrazio anche Enrico Scagliotti e Giovanni Caire di Telecom Italia per avermi fornito il materiale necessario per lo svolgimento del mio lavoro e ringrazio anche il mio compagno di studi Simone “Tocci” Bertocchi per avermi introdotto nel mondo dei workflow. Un grazie naturalmente a tutta la mia famiglia, in particolare alla mamma Clara e al Leo che si sono goduti momenti di mio estremo nervosismo, specialmente negli ultimi mesi. Un ringraziamento speciale va anche alla Lauretta che mi ha dato un grande sostegno in questo ultimo periodo universitario. Inoltre ha contribuito in maniera fondamentale alla correzione di questa tesi di laurea rileggendola più volte, obbligata dal sottoscritto, nonostante la sua allergia all’informatica e a qualsiasi tipo di tecnologia. Ringrazio il Max, Sergio, Sammy, Abie, il Tiz e Bazooka perchè amici così (fuori di testa) è difficile trovarli. Grazie al gruppo C.A.C. per farmi ridere come un matto anche quando non ci si vede e alla Lega del Buon Riposo che mi fa attendere il primo giorno di ogni mese come se fossi un bambino che aspetta il Natale. 1 RINGRAZIAMENTI 2 Grazie a tutti i miei compagni di università, in particolare al gruppo dell’Aula Studio: Tocci, Matte, Ponz, Fonta, Fedderè, Gando, Disa, Ila, Marti e Jessica (ma in quell’aula si studiava davvero?). Grazie anche ai miei amici chimici per avermi “adottato”, in particolare ringrazio la cerchia del G.R.A.R. Bomber, Luca, Loc, Kevin, Angelone, Tanso, Ceci, Ranca, Elias e lo Zano, e gli Amici del Backgammon Ali, Giulia, Ale, Giamma e Andre. Tutti gli altri chimici non si sentano esclusi: siete troppi e la mia voglia di ringraziarvi uno ad uno è troppo poca. Un grazie a tutta la squadra degli Amatori Vigolzone, un gruppo di amici prima ancora che calciatori veramente stupefacente. Grazie ancora al Max, ad Abie e al Gardel che ogni settimana mi aiutano a staccare la spina dandomi l’opportunità di attaccare il jack all’amplificatore. Grazie a tutto il Gruppo Escursionisti Vigolzonesi, soprattutto ai giovani Cala, Je, Giammo, Simo, Ste, Michi, Silvia, Iso, Cami e Fabio che condividono con me l’amore per la montagna (oltre a quello per le grigliate). Mi sto sicuramente dimenticando di ringraziare qualcuno, essendo fortunato ad avere tante persone importanti nella mia vita ma al tempo stesso sfortunato ad avere una memoria molto difettosa. Quindi mi salvo così: grazie a tutti. RINGRAZIAMENTI 3 A papà Pinuccio. Prefazione La grande rivoluzione nel campo dello sviluppo software negli anni ’90 è stato l’avvento della programmzione ad oggetti che portava con sè la possibilità di definire classi che implementavano una certa funzionalità all’interno dell’applicazione. Il programmatore che utilizzava una certa classe, doveva semplicemente invocare i suoi metodi senza preoccuparsi di come essi venissero realizzati. Nascevano dunque concetti come riusabilità delle classi e programmazione per componenti. Oggi sta emergendo una nuova filosofia di programmazione, grazie anche alla crescente diffusione di macchine sempre più potenti e soprattutto di ambienti distribuiti quali Internet e reti locali, ovvero la programmazione ad agenti. Essa si pone l’obiettivo di superare sia il modello di programmazione tradizionale, in cui il programma gira su una sola macchina, sia il modello client-server, in cui l’applicazione è suddivisa in due o più moduli che possono risiedere in macchine diverse e dialogare tra loro, mantenendo però una gerarchia marcata e ben definita. Infatti, avendo a disposizione un ambiente di rete, sia esso locale o globale, si possono concepire applicazioni distribuite formate da un insieme di componenti, chiamati appunto agenti, che oltre alla capacità di dialogare tra loro e di prendere decisioni, possono spostarsi da una macchina all’altra portando con sè i propri dati. La programmazione ad agenti, dunque, può essere considerata come la realizzazione pratica dei 4 PREFAZIONE 5 concetti di programmazione concorrente e distribuita, rimasti troppo tempo solo una nozione meramenta teorica: essa infatti è concorrente, in quanto un agente può essere composto da più processi, ma anche distribuita, in quanto gli agenti concorrono a risolvere un problema generale. Come risultato del lavoro di molti ricercatori, nell’ambito delle università e di grandi aziende, sono già apparse diverse librerie utilizzabili per creare applicazioni multiagente. JADE (Java Agent DEvelopment Framework) [1] sviluppato da Telecom Italia e dall’Università degli Studi di Parma, è un middleware per lo sviluppo di applicazioni peer-to-peer ad agenti già molto diffuso in ambito aziendale ed universitario. Osservando bene le azioni compiute dagli agenti, si nota che queste seguono schemi prefissati ben definiti, rappresentabili come diagrammi di flusso. Per sfruttare questa similitudine, Telecom Italia ha realizzato WADE (Workflows and Agents Development Environment) [2] che aggiunge a JADE il supporto per l’esecuzione di processi definiti con la metafora dei workflow, oltre a diverse meccaniche per lo sviluppo di aplicazioni basate sul modello peer-topeer, sfruttando i diagrammi di flusso per definire il comportamento degli agenti. In aggiunta a WADE è presente anche WOLF [2], un plugin Eclipse [3] che permette ai programmatori di applicazioni WADE-based di lavorare con una view grafica per definire il flusso di esecuzioni affiancata alla classica view codice Java, mantenendo la sincronizzazione tra le due. Grazie alla potenza dell’IDE Eclipse, WOLF rende l’implementazione di applicazioni native WADE più intuitiva, semplice e veloce. PREFAZIONE 6 Lo scopo di questa tesi è progettare e realizzare un editor web di workflow, chiamato WOLFWeb, che permetta, similmente a WOLF, di creare diagrammi di flusso rappresentanti le azioni compiute da un agente. Per la creazione di WOLFWeb è stato usato Diagramo [4], un editor di workflow HTML5, e ZK [6], un framework Java per la costruzione di applicazioni Web, il quale ci ha permesso di sfruttare appieno gli strumenti per far sì che la nostra applicazione web potesse dialogare con una piattaforma WADE già esistente. Le appplicazioni Web, come è noto, offrono diversi vantaggi rispetto a quelle tradizionali: • immediatezza di utilizzo senza necessità di installazione • supporto all’accesso multiutenza • possibilità di utilizzo da qualsiasi postazione di lavoro • facilità di distribuzione ed aggiornamento • compatibilità cross-platform • richiesta minore di memoria L’applicazione WOLFWeb, dunque, avrà il compito di offrire una valida alternativa a WOLF per supportare gli sviluppatori di applicazioni WADEbased nela creazione dei workflow. Capitolo 1 JADE, WADE e WOLF 1.1 Introduzione a JADE JADE (Java Agent DEvelopment Framework) [1] è un software open source sviluppato da Telecom Italia e dall’Università degli Studi di Parma e distribuito con licenza GNU LGPL, totalmente implementato in Java, che può essere considerato un middleware conforme alle specifiche FIPA (Foundation for Intelligent Physical Agents) [5] per la realizzazione e l’esecuzione di applicazioni peer-to-peer (P2P) ad agenti, tramite strumenti appositi utilizzati nella fase di sviluppo e debugging. JADE si occupa di tutti quegli aspetti indipendenti dall’applicazione, quali il trasporto e la codifica dei messaggi o il ciclo di vita degli agenti. Per meglio capire il significato della definizione di JADE è bene sottolineare gli aspetti principali delle tecnologie utilizzate, ossia: 1. il modello di comunicazione peer-to-peer, 2. il paradigma degli agenti, 3. il concetto di middleware. 7 CAPITOLO 1. JADE, WADE E WOLF 1.1.1 8 Il modello peer-to-peer L’architettura più utilizzata oggigiorno per le applicazioni distribuite è sicuramente quello client-server, dove esiste una netta distinzione dei ruoli tra i nodi server (forniscono risorse e servizi) e i nodi client (richiedono risorse e servizi). I nodi server non possiedono capacità di iniziativa e attendono di essere chiamati dai nodi client, i quali, di contro, possono richiedere servizi (solitamente dopo una determinata azione dell’utente) ma non forniscono alcuna capability. I client, inoltre, possono comparire e scomparire continuamente e hanno indirizzi dinamici, a differenza dei server che devono fornire garanzie di stabilità e possiedono solitamente un indirizzo fisso. Altro punto fondamentale di quest’architettura riguarda la comunicazione: i nodi client comunicano solo con i nodi server ma non possono comunicare tra loro. L’esempio più lampante di applicazioni basate sul modello client-server sono le applicazioni web: i client sono i browser, il cui compito è di ottenere informazioni sui siti internet sotto specifica richiesta di un utente e di presentarle graficamente in modo opportuno, mentre i nodi server sono i siti che contengono tutte le informazioni. Figura 1.1: Modelli di rete client-server e peer-to-peer CAPITOLO 1. JADE, WADE E WOLF 9 Esistono però applicazioni distrubite che non si adattano bene a questo modello: si consideri, ad esempio, una chat dove gli utenti (i client) necessitano di comunicare tra loro. È possibile implementare tale applicazione con l’architettura client-server, ma questo rappresenterebbe una forzatura: si pensi al singolo messaggio che dovrebbe essere spedito al server dal client X, per poi essere recuperato successivamente dal client Y. Il modello peer-to-peer sarebbe molto più adatto. Infatti, nel modello peer-to-peer non vi è una distinzione tra i ruoli, ciascun nodo può sia fornire che richiedere informazioni e servizi e tutti i nodi possono comunicare tra loro. La logica dell’applicazione, dunque, non è più concentrata nel singolo server ma viene distribuita tra i peer. Un’altra differenza sostanziale è rappresentata dalle modalità di discovery dei nodi con cui interagire: nel modello client-server, i client devono necessariamente conoscere l’indirizzo del server e, di contro, non conoscono l’indirizzo degli altri nodi client. Viceversa, nel sistema peer-to-peer chi conosce l’indirizzo di chi è del tutto arbitrario, quindi vi è la necessità di scoprire quali sono gli altri nodi con cui comunicare: generalmente vi sono dei meccanismi di discovery appositi (pagine bianche o pagine gialle) che permettono ad ogni nodo di pubblicare le proprie informazioni e i propri servizi offerti in modo da renderli pubblici agli altri peer, i quali possono cercare nodi con le caratteristiche desiderate. CAPITOLO 1. JADE, WADE E WOLF 10 Figura 1.2: Modelli di rete peer-to-peer Come mostrato in Figura 1.2, ci sono due modelli di P2P: quello puro e quello ibrido. Una rete peer-to-peer pura è completamente decentralizzata e i nodi sono del tutto autonomi. I peer devono utilizzare i protocolli specifici che la rete offre loro per cercare altri partner e comunicare successivamente con essi. La ricerca avviene tramite scambio di messaggi, dove non è presente nesssun elemento di coordinazione: si tratta, dunque, di reti difficili da gestire, sia per quanto riguarda la consistenza della rete, sia per quanto concerne il traffico (il quale aumenta in maniera esponenziale al crescere del numero dei peer) e la sicurezza (chiunque può entrare nella rete). È preferibile quindi usare un sistema peer-to-peer ibrido, dove sono presenti nodi speciali (detti nodi indice) che hanno il compito di semplificare la ricerca dei peer attivi e quella di determinati servizi. Questo tipo di rete peer-to-peer è più sicuro e genera solitamente meno traffico, però presenta il difetto di dipendere dai nodi indice: se essi non sono disponibili viene meno il sistema di discovery dei peer. CAPITOLO 1. JADE, WADE E WOLF 1.1.2 11 Il paradigma degli agenti Questo paradigma nasce dall’unione di concetti provenienti dall’intelligenza artificiale con altri riguardanti la tecnologia degli oggetti distribuiti. Il paradigma ad agenti prevede la realizzazione di un software che viene considerato come collezione di componenti, detti appunto agenti, i quali devono risultare: • autonomi, in grado di svolgere lunghe operazioni senza ricevere input dall’utente ad ogni passo della computazione, • proattivi, in grado di prendere iniziativa anche senza stimoli da parte dell’utente, • comunicativi, in grado cioè di interagire tra loro per raggiungere lo scopo globale dell’intero sistema. La caratteristica di comunicatività ci suggerisce che ogni applicazione che utilizza una tecnologia ad agenti sia intrinsecamente peer-to-peer, perché ogni agente è effettivamente in grado di iniziare una comunicazione con qualsiasi altro agente presente nel sistema, ogni agente ha la propria logica e può sia offrire che utilizzare servizi e risorse ed infine, gli agenti hanno bisogno di meccanismi di discovery per identificare con quali altri agenti possono comunicare. Si può quindi dire che, in ottica P2P, ogni agente rappresenti un peer. CAPITOLO 1. JADE, WADE E WOLF 12 È evidente come la comunicazione rivesta un ruolo fondamentale ed è bene sottolineare tre aspetti: • asincronia - ogni agente ha una sua logica interna che stabilisce come e quando gestire i messaggi ricevuti • elvato grado di disaccoppiamento - gli agenti sono autonomi e sono legati debolmente tra loro • interoperabilità - un agente, una volta ricevuto un messaggio, è in grado di attribuirgli il significato corretto e intuire l’intenzione del mittente. 1.1.3 I middleware Con il termine middleware si identifica un insieme di software che semplifica lo sviluppo e la realizzazione di applicazioni. Si parla dunque di librerie, framework o toolkit che offrono servizi non tanto ad una singola applicazione, bensì ad un insieme di applicazioni che condividono determinate caratteristiche. Riprendendo il concetto di applicazione distribuita trattato in precedenza, a titolo esemplificativo, quando due nodi devono comunicare dovranno aprire una connessione di rete, trasferire i dati formattati opportunamente ed infine chiudere la connessione. Queste operazioni sono solitamente complesse e la realizzazione di esse può richiedere più tempo di quanto non necessiti la logica applicativa stessa. Si noti come tali operazioni sono indipendenti dall’applicazione, da cui l’importanza del middleware che si occupa di questi compiti e può essere riutilizzato da tutte le applicazioni che necessitano di scambio di dati. Non sarà quindi l’applicazione a doversi occupare della soluzione di comunicazione ma sarà possibile utilizzare il middleware, il quale si interpone (sta appunto nel middle) tra applicazioni e strati di basso livello. CAPITOLO 1. JADE, WADE E WOLF 13 Figura 1.3: Approccio verticale ed orizzontale La genericità e la trasversatilità del middleware suggeriscono il nome di approccio orizzontale, in contrasto con lo sviluppo verticale, in cui viene creata una soluzione ad hoc per ogni applicazione e quindi non riutilizzabile. 1.2 JADE JADE [1] è un middleware sviluppato da Telecom Italia e dall’Università degli Studi di Parma per lo sviluppo di applicazioni distribuite ad agenti basate sull’architettura di comunicazione P2P. L’ambiente può evolvere dinamicamente nel tempo con i peer (chiamati appunto agenti in JADE) che possono apparire e scomparire nel sistema in qualsiasi momento, secondo le necessità dell’applicazione. La comunicazione tra agenti è del tutto simmetrica, siano essi su una rete mobile o su di una fissa, ed è attivabile dall’iniziativa di qualsiasi peer. JADE è completamente sviluppato in Java e poggia sui seguenti principi fondamentali: • interoperabilità - JADE rispetta le specifiche dello standart FIPA, pertanto un agente JADE può comunicare ed operare anche con peer che non utilizzano il run-time di JADE. CAPITOLO 1. JADE, WADE E WOLF 14 • uniformità e portabilità - JADE fornisce un’insieme di API che non dipende nè dalla rete nè dalla versione di Java, rendendo dunque le appplicazioni utilizzabili su qualsiasi tipo di dispositivo. • semplicità - JADE nasconde la complessità del middleware, fornendo al programmatore API molto semplici da usare. • filosofia pay-as-you-go - il programmatore non è obbligato ad utilizzare tutte le funzionalità che JADE offre e queste, se non usate, non aggiungono complessità, ne overhead computazionale. 1.2.1 Il modello architetturale JADE include sia le librerie di classi Java necessarie per la creazione degli agenti, sia l’ambiente run-time che fornisce i servizi base i quali devono essere attivi per far si che un agente possa essere eseguito. Ogni istanza del run-time in JADE viene denominata container (contenitore di agenti) e un insieme di container è detto piattaforma, la quale nasconde agli agenti la complessità degli strati più bassi (hardware, sistemi operativi, tipologie di rete). Come evidenziato in Figura 1.4, JADE è compatibile con l’ambiente J2ME CLDC/MIDP1.0, ed è già stato testato su diversi dispositivi, tra cui: Nokia 3650, Motorola Accompli008, Siemens SX45, PalmVx, Compaq iPaq, Psion5MX, HP Jornada 560. La libreria JADE in ambiente MIDP pesa circa 100 KB, ma può essere ridotto fino a 50 KB utilizzando la tecnica del ROMizing, di conseguenza può essere installato su qualsiasi dispositivo mobile, purchè Java-enabled. Oltre all’integrazione con terminali mobile, o più in generale in ambienti con risorse assai limitate, JADE è stato utilizzato anche in architetture complesse come .NET o J2EE, in cui JADE diventa un servizio per eseguire applicazioni multiparty e pro-attive. CAPITOLO 1. JADE, WADE E WOLF 15 Figura 1.4: Architettura JADE 1.2.2 Il modello funzionale Dal punto di vista funzionale, JADE fornisce i servizi base necessari ai software distribuiti P2P in ambiente fisso e mobile. Esso permette ad ogni agente di scoprire dinamicamente altri agenti e di scambiare dati tra loro secondo il paradigma peer-to-peer. Una piattaforma JADE include un servizio di nomenclatura univoca degli agenti ed un servizio di pagine gialle dove ogni agente può pubblicare e modificare i propri servizi e allo stesso tempo, cercare risorse offerte da altri agenti. La comunicazione tra gli agenti si basa sullo scambio asincrono di messaggi, un modello di comunicazione largamente utilizzato per le applicazioni distribuite, dove le entità sono eterogenee e non accoppiate. Il vantaggio maggiore è che quando un agente invia un messaggio ad una destinazione, il destinatario non deve essere per forza disponibile (adirittura potrebbe non CAPITOLO 1. JADE, WADE E WOLF 16 esistere). Quindi il mittente non deve per forza conoscere il destinatario e viceversa. Tutto questo non intacca la sicurezza, perché JADE offre anche servizi di autenticazione degli agenti alle applicazioni che lo richiedono. Vi è dunque la possibilità di un agente di autorizzare la ricezione di messaggi inviati da una lista prestabilita di peer o di specificare le azioni che un agente può compiere, ad esempio potrebbe essere abilitato a ricevere ma non a trasmettere messaggi. La struttura dati del messaggio è conforme al linguaggio ACL definito da FIPA [5] e permette di rappresentare informazioni di supporto all’interazione, quali contenuto del messaggio, timeout per le risposte, variabili e, soprattutto, permette di riferire a conversazioni multiple parallele. Il supporto alla conversazione è una funzionalità molto importante di JADE, il quale fornisce alle applicazioni pattern tipici di interazione associati a task specifici, tra cui la negoziazione, le aste e la delega di task. Usando questi pattern, il compito di gestire sincronizzazioni, timeout, condizioni di eccezione e, in generale, tutti gli aspetti che non riguardano direttamente la logica applicativa, non riguarda più il programmatore. Per incrementare la scalabilità e soddisfare i vincoli che ambienti con risorse limitate presentano, JADE offre la possibilità di eseguire task in parallelo eseguendoli all’interno di uno stesso thread. Si pensi ad esempio a task elementari, come la comunicazione tra agenti e l’opportunità di combinarli tra loro per realizzare task più complessi, che vengono così strutturati come macchine a stati finiti simultanei. In ambiente J2SE e PersonalJava JADE fornisce inoltre la portabilità del codice e dello stato di esecuzione.Vi è dunque la possibilità da parte di un agente di fermare la propria esecuzione su un host, migrare su un host remoto e riprendere l’esecuzione dallo stesso punto nel quale era stata CAPITOLO 1. JADE, WADE E WOLF 17 interrotta. Questa importante caratteristica permette di distribuire il peso computazionale a run-time, muovendo agenti verso macchine con meno carico lavorativo senza per questo intaccare la corretta esecuzione dell’applicazione. Queste caratteristiche rendono JADE molto adatto per supportare lo sviluppo e l’esecuzione di applicazioni distribuite, multi-party, macchina-amacchina, intelligenti e proattive. JADE è reso disponibile a tutti sotto la licenza GPL ed una comunità open source è stata creata intorno ad esso. Grazie al contributo significativo di questa comunità, il middleware ha raggiunto l’attuale livello di eccellenza ed un numero rilevante di enti interessati al suo utilizzo e sviluppo, che vanno da gruppi accademici o di ricerca e sviluppo alle imprese e start-up che basano parte dei loro prodotti e servizi sul middleware JADE. CAPITOLO 1. JADE, WADE E WOLF 1.3 18 WADE e WOLF WADE (Workflows and Agents Development Environment) [2] è una piattaforma indipendente costruita su JADE. Come descritto in precedenza, JADE fornisce un ambiente distribuito a tempo d’esecuzione, l’astrazione degli agenti (i peer) e dei behaviour (i task), la comunicazione peer-to-peer tra gli agenti stessi, la gestione del ciclo di vita degli agenti e i meccanismi di discovery di quest’ultimi. A tutto questo, WADE aggiunge la possibilità di eseguire task tramite la metafora dei workflow (diagramma di flusso) e un numero di componenti e di meccanismi che aiutano nella gestione della complessità della distribuzione. In generale, in WADE non vi è nulla che lo sviluppatore non possa controllare. Tuttavia, considerando che uno dei vantaggi principali dell’approccio con i workflow è proprio quello di rappresentare i processi con una veste grafica user-friendly, WADE presenta un ambiente di sviluppo chiamato WOLF, il quale facilita la creazione di software basato su di esso. WOLF è un plug-in per Eclipse [3] e permette agli sviluppatori di utilizzare tutto il potenziale e i vantaggi dell’IDE Eclipse in aggiunta alle caratteristiche specifiche di WADE. 1.3.1 I workflow Un workflow è la definizione formale di un processo in termini delle attività che devono essere eseguite, relazioni tra esse, i criteri di attivazione e di terminazione e informazioni aggiuntive, quali i partecipanti, i tools software che devono essere invocati, input richiesti, output attesi e le modifiche ai dati durante l’esecuzione. L’aspetto principale della metafora del workflow è che i passi dell’esecuzione sono espliciti, così come la loro sequenza. Questo rende possibile una rappresentazione grafica di un processo definito come workflow. CAPITOLO 1. JADE, WADE E WOLF 19 Figura 1.5: WADE È lampante che una rappresentazione simile sia molto più chiara e leggibile di quanto non possa essere un frammento di codice e può essere compresa non solo dai programmatori, favorendo dunque lo sviluppo dell’intero sistema. Essendo i vari passi dell’esecuzione identificatati esplicitamente, un altro aspetto fondamentale è che il sistema che esegue i processi in automatico, chiamato nello specifico workflow engine, può tracciarli senza problemi e questo rende possibile la creazione di meccanismi automatici per il monitoraggio del sistema, solitamente implementati con procedure di controllo delle varie attività e creazione di reports sui processi stessi. In aggiunta possono essere attivati dei meccanismi semi-automatici di rollback in caso di errore inaspettato. Un ultimo aspetto importante dei workflow è che essi sono auto documentati, di conseguenza non vi è la necessità da parte del gruppo di sviluppo di mantenere la documentazione aggiornata ogni volta che vengano apportate delle modifiche. CAPITOLO 1. JADE, WADE E WOLF 1.3.2 20 Scope di utilizzo Abbiamo visto in precedenza che i workflow portano con sè numerosi vantaggi, i quali però non si verificano se si applica questa metafora ad operazioni a basso livello, come ad esempio gestione e trasformazionne di dati o operazioni matematiche complesse: scrivere codice software per tali operazioni è molto più efficiente per implementarle. Di contro, ad oggi la metafora dei workflow è largamente utilizzata in ambienti BPM (Business Process Management), dove un workflow rappresenta un processo business e dirige un numero di sistemi esistenti tipicamente accessibili tramite interfacce basate sui servizi web. L’obiettivo di WADE è quello di portare l’approccio a workflow anche ai livelli di logica interna del sistema. In definitiva, come descritto in Figura 1.6, WADE può essere utilizzato sia come orchestratore per coordinare sistemi esistenti, sia come framework di sviluppo software per creare nuove applicazioni che implicano l’esecuzione di task lunghi e complessi. 1.3.3 Approccio WADE non contiene un singolo e potente workflow engine, al contrario fornisce un tipo di agente ad hoc (WorkflowEngineAgent) che ne include uno molto leggero. Di conseguenza, oltre ai normali task (JADE behaviour), ogni WorkflowEngineAgent attivo in un’applicazione WADE-based è in grado di eseguire workflow rappresentati seguendo il formalismo di WADE. Per permettere ai programmatori di definire le logiche interne del sistema con la semplicità dei workflow e al tempo stesso di dare ad essa la stessa potenza espressiva di un linguaggio di programmazione con una efficienza di esecuzione comparabile, il formalismo di rappresentazione dei workflow CAPITOLO 1. JADE, WADE E WOLF 21 Figura 1.6: Contesti di utilizzo di WADE WADE è basato interamente sul linguaggio Java. Un workflow che può essere eseguito dai WorkflowEngineAgent di WADE è espresso come una classe Java con una struttura ben definita (i dettagli si possono trovare sulla guida per utenti di WADE). Esso può essere dunque gestito come una qualsiasi classe Java: vi è la possibilità di inserirlo in qualsiasi frammento di codice, sia esso un metodo, un campo di qualsiasi tipo, una classe interna, un riferimento ad una classe esterna e così via, al fine di implementare i dettagli voluti. Oltre a tutto ciò, come detto in precedenza, il flusso di esecuzione che i workflow rappresentano, può essere presentato e modificato in maniera grafica, molto più intuitiva. Più in dettaglio, WOLF (l’ambiente di sviluppo per le applicazioni basate su WADE) è un plugin Eclipse che permette agli sviluppatori di lavorare con una view grafica (più adatta a gestire il flusso del processo) e con una view codice (l’editor Java classico di Eclipse per definire i dettagli dell’esecuzione), le quali sono mantenute in sincronia per offrire al CAPITOLO 1. JADE, WADE E WOLF 22 programmatore la possibilità di operare sull’una o sull’altra in base alle sue esigenze. Figura 1.7: View grafica di un workflow con WOLF In Figura 1.7 è possibile visualizzare la view grafica di WOLF, all’interno di Eclipse, che permette tramite una palette di comporre i workflow per applicazioni WADE. Gli elementi si inseriscono selezionando dalla palette di destra il componente e cliccando poi all’interno dello spazio di lavoro nella posizione desiderata; una finestra nello stile Eclipse comparirà per permettere di definire le caratteristiche dell’elemento che si sta creando. CAPITOLO 1. JADE, WADE E WOLF 23 Figura 1.8: Creare Activity (tipo Code) in WOLF Sarà poi possibile spostare l’elemento inserito, aggiungere le transition (connessioni) da un elemento ad un altro e modificare le caratteristiche di un elemento tramite un click con il tasto destro del mouse. Ad ogni workflow creato con la view grafica corrisponde una classe Java modificabile tramite il classico editor per codice di Eclipse, dove è possibile cambiare il sorgente associato al workflow creato. Ogni modifica apportata al workflow in una specifica view è immediatamente riflessa nell’altra, garantendo la consistenza tra le due. Il workflow engine di WADE, incluso in ogni WorkflowEngineAgent non è un interprete di un linguaggio di descrizione di workflow, bensì è un esecutore codice Java compilato. Per questo motivo esso è estremamente efficiente, d’altra parte occorrono però le classi workflow necessarie quando esso riceve una richiesta di esecuzione di un workflow da parte di un agente. CAPITOLO 1. JADE, WADE E WOLF 24 Figura 1.9: Modificare Activity in WOLF Per questo motivo WADE utilizza class loaders Java ad hoc per permettere lo sviluppo di workflow nuovi o modificati che diventano immediatamente eseguibili senza bisogno di dover spegnere l’intero sistema. In WADE è inoltre possibile creare un workflow estendendone uno di base e specificando le differenze che intercorrono tra essi. Infine è bene notare che WADE non impone che tutte le logiche di sistema siano definite come workflow, al contrario gli sviluppatori sono liberi di utilizzare normali JADE behaviours quando lo ritengono più opportuno. CAPITOLO 1. JADE, WADE E WOLF 25 Figura 1.10: View codice associata al workflow creato 1.3.4 Gestire la complessità della distribuzione WADE eredita da JADE il sistema distribuito a runtime composto da diversi container che possono essere eseguiti su host differenti e che possono contenere un certo numero di agenti. Solitamente un container corrisponde ad una specifica JVM, anche se ciò non deve avvenire per forza. Un insieme di containers attivi è chiamato plaform, nella quale esiste un container speciale chiamato Main Container. Il Main Container deve essere il primo a partire e tutti gli altri (chiamati solitamenti container periferici) si registrano ad esso al tempo di bootstrap. Inoltre il Main Container contiene due agenti speciali: CAPITOLO 1. JADE, WADE E WOLF 26 • l’AMS (Agent Management System) che rappresenta l’autorità nella piattaforma e può eseguire azioni particolari, ad esempio è l’unico a poter creare o terminare altri agenti, terminare containers e spegnere l’intera piattaforma. Gli agenti normali, per potere svolgere azioni di questo tipo, devono richiedere i permessi direttamente all’AMS. • il DF (Directory Facilitator) che implementa il servizio di pagine gialle, dove gli agenti possono pubblicare i loro servizi e trovare altri agenti, i quali a loro volta offrono altri servizi Figura 1.11: Architettura principale CAPITOLO 1. JADE, WADE E WOLF 27 Le applicazioni distribuite sono particolarmente indicate quando si tratta di gestire grandi carichi di lavoro, in quanto possono essere ospitate su architetture hardware altamente scalabili come i server blade. È chiaro però che amministrare un’applicazione distribuita è molto più complesso rispetto alla stessa operazione eseguita su un’applicazione monolitica se non si hanno a disposizione strumenti appositi. Inoltre la probabilità che si verifichino crash degli host cresce proporzionalmente con l’aumentare del numero dei nodi sui quali l’applicazione è distribuita: sono dunque necessari appositi meccanismi di recovery per garantire la stabilità dell’applicazione. Spesso vengono usati sistemi di clustering per questi scopi, ma sono tipicamente costosi e difficili da configurare. WADE affronta questi problemi fornendo alcuni meccanismi per aiutare l’amministratore a: • configurare l’applicazione, • attivare/disattivare la distribuzione di componenti dell’applicazione (container e agenti) sugli hosts disponibili in base alle esigenze specifiche, • monitorare gli eventi a runtime e situazioni critiche come il consumo di memoria, • effettuare a runtime modifiche senza necessità di spegnere il sistema, • recuperare automaticamente da guasti ad host, container o agenti. Capitolo 2 ZK e WAM 2.1 Introduzione a ZK ZK Framework [6] è un framework AJAX open source per applicazioni Web, sviluppato da Potix interamente in Java, che permette di creare interfacce utente grafiche. Il cuore di ZK si basa sul paradigma della programmazione ad eventi, consta di oltre 100 componenti XUL e 83 XHTML e di un suo linguaggio mark-up per la creazione delle interfacce. I programmatori sviluppano le loro pagine contenenti le applicazioni grazie a componenti ZUL/XHTML feature-rich che vengono manipolati tramite eventi generati dall’attività degli utenti. 2.1.1 I vantaggi di ZK Il vantaggio più grande che ZK offre è quello di permettere allo sviluppatore di creare applicazioni RIA (Rich Internet Application) senza che egli debba conoscere JavaScript o AJAX, in quanto tali applicazioni vengono implementate puramente in Java (per l’appunto l’approccio utilizzato è denominato AJAX without JavaScript). 28 CAPITOLO 2. ZK E WAM 29 Inoltre ZK offre oltre cento componenti pensati per qualsiasi necessità dello sviluppatore: window, bottoni, componenti per visualizzare dati numerosi, componenti per l’input dell’utente e così via. Questi componenti possono essere creati dallo sviluppatore tramite un linguaggio XML-formatted chiamato ZUL. Tutti gli elementi di una pagina formano una struttura ad albero, dove ogni componente possiede un genitore e può avere a sua volta più figli. È bene sottolineare la versatilità di ZK per quanto riguarda la dichiarazione dei componenti che vanno a comporre l’interfaccia utente: lo sviluppatore può usare un approccio basato sul XML (tag ZUL) oppure utilizzare un approccio orientato totalmente a Java, come mostrato in Figura 2.1. Figura 2.1: I diversi approcci per la composizione dell’UI in ZK CAPITOLO 2. ZK E WAM 2.1.2 30 L’architettura ZK Un’applicazione ZK viene eseguita sul server e può accedere a determinate risorse, assembla l’interfaccia utente con i componenti e manipola questi ultimi per aggiornare l’interfaccia stessa. La sincronizzazione degli stati dei componenti tra il lato client e il lato server è eseguita automaticamente da ZK in modo trasparente dall’applicazione stessa. Le interfacce utente sono costruite con componenti che seguono il modello POJO (Plain Old Java Object), in quanto esso rappresenta un approccio molto produttivo per sviluppare un’applicazione Web. Con l’architettura di ZK denominata server+client fusion, l’applicazione viene eseguita ininterrottamente sul server e può aumentare la propria interattibilità aggiungendo funzioni opzionali sul lato client, come ad esempio la gestione di eventi, di effetti di visualizzazione personalizzati o anche della composizione dell’interefaccia utente senza la presenza di codice sul lato server. ZK, dunque, permette una versatilità estrema da una soluzione puramente incentrata sul server ad una basata totalmente sul lato client, potendo così raggiungere ottimi livelli di produttività e di flessibilità. Figura 2.2: Architettura ZK CAPITOLO 2. ZK E WAM 2.1.3 31 Model-View-Conroller e Model-View-ViewModel ZK prevede diverse soluzioni per la progettazione, lo sviluppo e il testing di un’applicazione, in particolare per la creazione di un sistema è consigliabile usare un design pattern che isoli il dominio dei dati, il dominio logico e l’interfaccia utente. I pattern architetturali che separano questi tre concetti, entrambi supportati da ZK, sono: • il Model-View-Controller (MVC) che viene così strutturato: la View in ZK consiste, come già detto in precedenza, ad un insieme di componenti e può essere implementata da un documeno ZUML o da codice Java. Il Controller (composer nella terminologia ZK) invece corrisponde ad una classe Java che funge da collante tra l’interfaccia (la View) e i dati (il Model), il quale estende la classe SelectorComposer o implementa la classe Composer. La classe che rappresenta il Controller andrà in seguito specificata nell’elemento che lo sviluppatore vuole gestire, all’interno del documento ZUML. Infine la parte Model viene rappresentata dai dati che l’applicazione gestisce, quindi dipende dalle specifiche dell’applicazione stessa. Figura 2.3: Pattern architetturale MVC in ZK CAPITOLO 2. ZK E WAM 32 • il Model-View-ViewModel (MVVM) è una variante del pattern MVC originariamente introdotta da Microsoft. La View e il Model hanno lo stesso ruolo che ricoprono nel modello MVC, mentre la parte ViewModel rappresenta un Controller speciale per la View, il quale è responsabile di esporre i dati presi dalla parte Model e di svolgere determinate azioni secondo le richieste dell’utente ricevute dalla View. Il ViewModel è un tipo di astrazione della View il quale contiene il suo stato e i suoi comportamenti, ma che non contiene nessun riferimento alle componenti che formano l’interfaccia e non conosce nulla riguardo agli elementi che la compongono, il che rende le due componenti perfettamente separate. Questo porta a diversi vantaggi, quali la totale di libertà di modifica della parte View senza intaccare l’efficienza del ViewModel, l’alta riusabilità del codice in quanto è possibile definire numerose interfacce differenti dato un ViewModel e, infine, un’alta testabilità del codice. Figura 2.4: Pattern architetturale MVVM in ZK CAPITOLO 2. ZK E WAM 33 ZK fornisce un supporto a questo pattern architetturale con il meccanismo ZK Bind, come mostrato in figura 2.4. È infatti necessario sincronizzare la View con il ViewModel e in ZK il binder gioca questo ruolo di orchestratore tra le due componenti ed è lo sviluppatore a decidere le relazioni di binding tra gli elementi dell’interfaccia e la parte ViewModel grazie alle ZK bind annotation. La binding source è rappresentata da un attributo di un componente, mentre il binding target e una proprietà o un comando del ViewModel e le annotazioni possono avere la sintassi di espressioni Java o di espressioni EL (Expression Language). Figura 2.5: Esempio di utilizzo delle ZK bind annotation CAPITOLO 2. ZK E WAM 2.2 34 WAM I WAM sono componenti ZK sviluppati da Telecom Italia che possiedono la capacità di connettersi con una piattaforma JADE e/o WADE. Al momento esistono diversi WAM che svolgono vari compiti, dal monitoraggio dei workflow alla gestione amministrativa della piattaforma connessa. Grazie ai WAM, dunque, è possibile operare con una piattaforma JADE e/o WADE tramite un’applicazione web. Figura 2.6: Schema di riferimento dell’utilizzo dei WAM CAPITOLO 2. ZK E WAM 2.2.1 35 Struttura e tipologia dei WAM Tutti i WAM estendono due classi base: WAMComponent e WADEOrientedWAMComponent. La scelta tra le due classi dipende dalla volontà dello sviluppatore nel creare uno specifico componente per una piattaforma WADE oppure uno più generico che possa essere compatibile anche con una piattaforma JADE. I metodi più importanti che la classe WAMComponent mette a disposizione sono bind() e unbind(), i quali permettono al generico WAM di connettersi e sconnettersi da una piattaforma. L’utilizzo più indicato del metodo bind() è quello di invocarlo nel metodo doAfterĊompose() del composer ZK del contenitore di una WAMComponent, metre il metodo unbind() viene invocato automaticamente quando la pagina contenente la WAMComponent si stacca dal desktop (ovvero un inseme di pagine ZUML). Figura 2.7: Class diagram dei WAM CAPITOLO 2. ZK E WAM 36 Quando viene chiamato il metodo bind(), viene attivato il WAMBridge, il quale rappresenta il collante tra le componenti grafiche del WAM e la piattaforma. La classe WAMBridge offre metodi per ottenere lo stato di una determinata piattaforma e di ricevere notifiche quando questo cambia o quando viene inizializzato o terminato un agente, inoltre incorpora e rende disponibile un AgentTreeModel il quale pò essere condiviso tra tutte le componenti dell’AgentTree. Come detto in precedenza, esistono diverse componenti WAM per diverse necessità: si va da quelle più semplici come la classe Versions che reperisce e visualizza la versione della piattaforma connessa alla classe LaunchWorkflow, sicuramente più complessa, la quale permette di lanciare un workflow gestendone opportunamente i parametri. I WAM possono essere poi raggruppati in macrocomponenti che racchiudono diversi WAM elementari, come ad esempio la classe WorkflowManagement che permette di gestire tutto il ciclo di vita di un workflow (tra cui selezione, lancio, browsing). Infine i WAM mettono a disposizione alcuni meccanismi per gestire la profilatura, ovvero l’attivazione/disattivazione delle azioni (WAMAction) e personalizzare la grafica. Capitolo 3 WOLFWeb 3.1 Panoramica del progetto Il progetto prevede la creazione di un editor Web di workflow che si connetta ad una piattaforma WADE attiva per ricevere in input la lista di workflow disponibili da poter utilizzare al fine di creare altri diagrammi e poterli salvare in un file XML. Per quanto riguarda l’editor di workflow è stato scelto Diagramo, un software per disegnare diagrammi di flusso, realizzato interamente con JavaScript e HTML5, il sito ufficiale è http://diagramo.com. La scelta è caduta su questo editor in quanto è open source, distribuito con licenza GPL, e quindi è totalmente personalizzabile, adatto alle nostre necessità. A questo editor JavaScript viene affiancata poi una palette che mostra la lista dei workflow disponibili sulla piattaforma WADE. Per implementarla, è stata scelto il framework ZK, principalmente per la ragione descritta più in dettaglio nel precedente capitolo: tutte le componenti WAM sono componenti ZK, quindi si adattano perfettamente ad un’applicazione Web creata con questo framework. Inoltre, nella creazione della pagina ZUL, ZK permette allo 37 CAPITOLO 3. WOLFWEB 38 sviluppatore di definire e chiamare funzioni JavaScript, nel nostro caso tutte le funzioni che vanno a costruire Diagramo nella pagina Web. Sostanzialmente, dunque, si è creata una pagina ZUL la quale contiene tutto il codice HTML della pagina light-editor.html del pacchetto Light di Diagramo con qualche modifica in un tag <html> ZK e componenti per la creazione della palette contenti i workflow disponibili sulla piattaforma WADE e per salvare in un file XML tutto il contenuto del canvas dell’editor web. Per quanto riguarda il lato server, è stata implementata una classe WorkflowCommunicator.java che si occupa di connettere l’applicazione web ad una piattaforma WADE e di reperire la lista di workflow, per poi comunicarla all’interfaccia web. Inoltre essa prevede due metodi specifici per il salvataggio del contenuto del canvas della pagina in un file XML. 3.2 Il lato client 3.2.1 Diagramo Light modificato Come detto precedentemente, è stato scelto Diagramo come editor di workflow, più precisamente la versione Light, client JavaScript e senza implementazione del lato server (realizzata in PHP nella versione LAMP di Diagramo). L’edizione Light di Diagramo, inserita nella cartella WebContent del nostro progetto, presenta la seguente struttura: • una cartella assets (risorse), la quale contiene subdirectory divise per categoria: css, images e javascript. Quest’ultima contiene tutte le librerie JavaScript utilizzate, come ad esempio jQuery [7]. CAPITOLO 3. WOLFWEB 39 • una cartella lib che contiene tutti i file JavaScript specifici dell’applicazione, una subdirectory commands che contiene tutti gli script che implementano i comandi dell’interfaccia grafica e una subdirectory sets, la quale conteneva originariamente le cartelle dei set di figure utilizzabili per i diagrammi. Dati i nostri scopi, però, è stato deciso di cancellare i sets iniziali di diagramo e crearne uno custom per realizzare workflow in stile WADE. Figura 3.1: Struttura del progetto WOLFWeb e delle cartelle contenenti Diagramo CAPITOLO 3. WOLFWEB 40 Nel pannello di sinitra della pagina è presente una select con cui selezionare il set di figure desiderato. Anche se come detto prima, nel progetto è presente un solo set di figure, è stato deciso di mantenere la select per ipotetiche implementazioni future che utilizzeranno diversi insiemi di immagini per comporre diagrammi differenti. Gli elementi del set workflow venegono caricati nello spazio sottostante. È bene notare che, sebbene la figura subworkflow.png faccia parte del set, essa non appare con le altre figure nella pagina per essere inserita direttamente nel canvas, ma viene utilizzata solo dalla lista di workflow presi dalla piattaforma WADE, quindi per l’utente è impossibile creare un subworkflow se questo non è effettivamente disponibile sulla piattaforma. Questo argomento verrà trattato con maggior dettaglio in seguito. Gli elementi del set si inseriscono nel canvas con il sistema drag-and-drop, rilasciandoli nella posizione desiderata. Il pannello di destra contiene una minimappa che visualizza in piccolo ciò che è disegnato sul canvas e, sotto di essa, l’elenco delle proprietà del canvas stesso o dell’elemento selezionato in quel momento. Tra le proprietà, interamente modificabili dall’utente, troviamo: le dimensioni e il colore di sfondo del canvas, il colore e lo stile di linea della figura selezionata e altre possibili modifiche grafiche. Tra le proprietà degli elementi, vi è anche un riquadro dove è possibile inserire codice, o un generico testo, associato ad un elemento. Nella fase di salvataggio del workflow in file XML quest’ultimo viene ignorato, ma è stato comunque deciso di aggiungere questo elemento all’interfaccia originale di Diagramo Light per implementazioni future di WOLFWeb. CAPITOLO 3. WOLFWEB 41 Figura 3.2: La pagina wolfeb.zul Nella barra in alto, sotto al logo WADE, troviamo i vari comandi di Diagramo che permettono all’utente di: • inserire uno straight connector (freccia), • inserire un jagged connector (retta spezzata), • inserire un container (contenitore logico di elementi), • mostrare la griglia sul canvas, • attivare la modalità snap to grid per allineare gli elementi alla griglia, • muovere un elemento in primo piano (nel caso in cui si sovapponga con un’altra figura), • muovere un elemento in secondo piano, • muovere un elemento di un livello maggiormente in superficie, CAPITOLO 3. WOLFWEB 42 • muovere un elemento di un livello maggiormente in profondità, • raggruppare le figure, • aggiungere un testo, • annullare l’ultima azione eseguita dall’utente. Tutti le funzioni che definiscono i comandi in elenco, sono contenute nello script main.js dove sono implementate le funzionalità più importanti dell’applicazione. Ogni elemento inserito si può selezionare, spostare, ruotare, eliminare, copiare, tagliare, incollare oppure modificarne le dimensioni. È possibile modificare il testo associato ai connector o alle figure con un doppio click e viene gestito tramite un pop-up. L’unico elemento che non può subire modifiche al testo è il subworkflow, in quanto esso rappresenta il nome del suo componente presente sulla piattaforma WADE e questa relazione deve essere preservata. In definitiva, per creare il nostro editor di workflow personalizzato, si sono apportate le seguenti modifiche: • rimozione dei set integrati con la versione Diagramo Light, eliminando le directory omonime dalla cartella lib/sets e rimuovendo i tag script corrispondenti dalla pagina HTML • rimozione del comando di inserimento di un’immagine, modificando il codice HTML e le funzioni nel file main.js • rimozione del comando per la creazione di organic connector, modificando il codice HTML e le funzioni sia del file main.js che del file connectorManager.js, responsabile di gestire tutti i connector CAPITOLO 3. WOLFWEB 43 Figura 3.3: Esempio di workflow creato con WOLFWeb • rimozione delle funzionalità che dipendevano da PHP lato server, quali esportare/importare da altre versioni di Diagramo, salvare un workflow come immagine e stampare • rimozione dell’applicazione di test per Diagramo che utilizzava QUnit • rimozione dell’uso delle finestre modali e dello strumento SimpleModal Finito di rimuovere le funzioni non utili ai nostri fini, come accennato in precedenza, si è proceduto a creare un set di figure per workflow WADE. Oltre alle quattro figure che rappresentano gli elementi principali, ovvero start, activity, branch ed end, si è inserita una quinta figura per i subworkflow. Questo è stato necessario per trattare i workflow disponibili sulla piattaforma WADE in modo diverso dagli altri elementi: per esempio essi ricevono il CAPITOLO 3. WOLFWEB 44 testo da porre all’interno della figura da disegnare nel canvas dal server, il quale invia all’elemento semplicemente il nome del componente del workflow selezionato nella palette. Il set è formato dunque da una cartella chiamata workflow all’interno della directory lib/sets, dentro alla quale sono state inserite le 5 immagini in formato PNG corrispondenti agli elementi sopracitati e lo script workflow.js, il quale si occupa di aggiornare l’oggetto JSON figureSets contenente tutti i set presenti nell’applicazione e di definire una funzione per la creazione di ogni elemento del set. I metodi specifici di ogni figura si occupano del disegno dell’elemento e dell’associazione alla figura creata nel canvas, in aggiunta ad un insieme di proprietà. In seguito viene presentato il codice della funzione definita all’interno del file workflow.js che si occupa della creazione dell’elemento activity per il set workflow WADE. Il metodo in questione ritorna la figura finalizzata ed esso viene invocato ogni volta che l’utente crea sul canvas un elemento activity. function figure_Activity ( x , y ) { var f = new Figure ( " A c t i v i t y " ) ; f . style . fillStyle = FigureDefaults . fillStyle ; f . style . strokeStyle = FigureDefaults . strokeStyle ; f . properties . push (new BuilderProperty ( ' Text ' , ' p r i m i t i v e s . 1 . s t r ' , ←BuilderProperty . TYPE_TEXT ) ) ; f . properties . push (new BuilderProperty ( ' Text S i z e ' , ←' p r i m i t i v e s . 1 . s i z e ' , BuilderProperty . TYPE_TEXT_FONT_SIZE ) ) ; f . properties . push (new BuilderProperty ( ' Font ' , ' p r i m i t i v e s . 1 . f o n t ' , ←BuilderProperty . TYPE_TEXT_FONT_FAMILY ) ) ; f . properties . push (new BuilderProperty ( ' A l i g n m e n t ' , ←' p r i m i t i v e s . 1 . a l i g n ' , BuilderProperty . TYPE_TEXT_FONT_ALIGNMENT ) ) ; f . properties . push (new BuilderProperty ( ' Text U n d e r l i n e d ' , ←' p r i m i t i v e s . 1 . u n d e r l i n e d ' , BuilderProperty . TYPE_TEXT_UNDERLINED ) ) ; f . properties . push (new BuilderProperty ( ' Text C o l o r ' , ←- 45 CAPITOLO 3. WOLFWEB ' p r i m i t i v e s . 1 . s t y l e . f i l l S t y l e ' , BuilderProperty . TYPE_COLOR ) ) ; // f . p r o p e r t i e s . push ( new B u i l d e r P r o p e r t y ( B u i l d e r P r o p e r t y .SEPARATOR) ) ; f . properties . push (new BuilderProperty ( ' S t r o k e S t y l e ' , ←' s t y l e . s t r o k e S t y l e ' , BuilderProperty . TYPE_COLOR ) ) ; f . properties . push (new BuilderProperty ( ' F i l l S t y l e ' , ←- ' s t y l e . f i l l S t y l e ' , BuilderProperty . TYPE_COLOR ) ) ; f . properties . push (new BuilderProperty ( ' L i n e Width ' , ←' s t y l e . l i n e W i d t h ' , BuilderProperty . TYPE_LINE_WIDTH ) ) ; f . properties . push (new BuilderProperty ( ' L i n e S t y l e ' , ←' s t y l e . l i n e S t y l e ' , BuilderProperty . TYPE_LINE_STYLE ) ) ; f . properties . push (new BuilderProperty ( BuilderProperty . SEPARATOR ) ) ; f . properties . push (new BuilderProperty ( ' Code ' , ' u r l ' , ←BuilderProperty . TYPE_Code ) ) ; var rectangleHeight = FigureDefaults . segmentShortSize + 5 ; var r = new Polygon ( ) ; r . addPoint (new Point ( x , y ) ) ; r . addPoint (new Point ( x + FigureDefaults . segmentSize , y ) ) ; r . addPoint (new Point ( x + FigureDefaults . segmentSize , y + ←rectangleHeight ) ) ; r . addPoint (new Point ( x , y + rectangleHeight ) ) ; f . addPrimitive ( r ) ; var t2 = new Text ( FigureDefaults . textStr , x + ←FigureDefaults . segmentSize / 2 , y + rectangleHeight / 2 , ←FigureDefaults . textFont , FigureDefaults . textSize ) ; t2 . style . fillStyle = FigureDefaults . textColor ; f . addPrimitive ( t2 ) ; var l = FigureDefaults . segmentShortSize + 5 ; // t o p CONNECTOR_MANAGER . connectionPointCreate ( f . id , new Point ( x + ←FigureDefaults . segmentSize / 2 − 1 0 , y ) , ←ConnectionPoint . TYPE_FIGURE ) ; CONNECTOR_MANAGER . connectionPointCreate ( f . id , new Point ( x + ←FigureDefaults . segmentSize / 2 , y ) , ConnectionPoint . TYPE_FIGURE ) ; CAPITOLO 3. WOLFWEB 46 CONNECTOR_MANAGER . connectionPointCreate ( f . id , new Point ( x + ←FigureDefaults . segmentSize / 2 + 1 0 , y ) , ←ConnectionPoint . TYPE_FIGURE ) ; // bottom CONNECTOR_MANAGER . connectionPointCreate ( f . id , new Point ( x + ←FigureDefaults . segmentSize / 2 − 1 0 , y + l ) , ←ConnectionPoint . TYPE_FIGURE ) ; CONNECTOR_MANAGER . connectionPointCreate ( f . id , new Point ( x + ←FigureDefaults . segmentSize / 2 , y + l ) , ←ConnectionPoint . TYPE_FIGURE ) ; CONNECTOR_MANAGER . connectionPointCreate ( f . id , new Point ( x + ←FigureDefaults . segmentSize / 2 + 1 0 , y + l ) , ←ConnectionPoint . TYPE_FIGURE ) ; // r i g h t CONNECTOR_MANAGER . connectionPointCreate ( f . id , new Point ( x + ←FigureDefaults . segmentSize , y + l / 2 − 1 0 ) , ←ConnectionPoint . TYPE_FIGURE ) ; CONNECTOR_MANAGER . connectionPointCreate ( f . id , new Point ( x + ←FigureDefaults . segmentSize , y + l / 2 ) , ←ConnectionPoint . TYPE_FIGURE ) ; CONNECTOR_MANAGER . connectionPointCreate ( f . id , new Point ( x + ←FigureDefaults . segmentSize , y + l / 2 + 1 0 ) , ←ConnectionPoint . TYPE_FIGURE ) ; // l e f t CONNECTOR_MANAGER . connectionPointCreate ( f . id , new Point ( x , y + l / 2 ←− 1 0 ) , ConnectionPoint . TYPE_FIGURE ) ; CONNECTOR_MANAGER . connectionPointCreate ( f . id , new Point ( x , y + l / ←2 ) , ConnectionPoint . TYPE_FIGURE ) ; CONNECTOR_MANAGER . connectionPointCreate ( f . id , new Point ( x , y + l / 2 ←+ 1 0 ) , ConnectionPoint . TYPE_FIGURE ) ; f . finalise ( ) ; return f ; } Tutte le funzioni che si occupano della creazione delle figure ricevono in input due interi che definiscono la posizione del canvas in cui andrà CAPITOLO 3. WOLFWEB 47 effettivamente disegnata la figura. Solo il metodo che si occupa della creazione di un subworkflow riceve in input tre parametri: oltre all’ascissa e all’ordinata, riceve anche una stringa (inviata dal server) che andrà a comporre il testo all’interno dell’elemento che lo rappresenta. Le proprietà associate alla figura devono essere create all’interno del file builder.js che è responsabile della creazione e della gestione delle modifiche delle proprietà associate agli elementi, sia quelle presenti nel pannello di destra che quelle a pop up, come il testo al centro di un generico elemento. Il frammento di codice sottostante è presente nello script builder.js e viene invocato per modificare il testo di una figura o di un connector. /∗ ∗ Generate t h e code t o e d i t t h e t e x t . ∗The t e x t g o t u p d a t e d when you l e a v e t h e i n p u t a r e a ∗ ∗@param {HTMLElement} DOMObject − t h e d i v o f t h e p r o p e r t i e s p a n e l ∗@param {Number} s h a p e I d − t h e i d o f t h e { F i g u r e } or { Connector } we ←are using ∗ ∗/ generateTextCode : function ( DOMObject , shapeId ) { var uniqueId = new Date ( ) . getTime ( ) ; var value = t h i s . getValue ( shapeId ) ; var div = document . createElement ( " d i v " ) ; div . className = " t e x t L i n e " ; var labelDiv = document . createElement ( " d i v " ) ; labelDiv . className = " l a b e l " ; labelDiv . textContent = t h i s . name ; div . appendChild ( labelDiv ) ; var text = document . createElement ( " t e x t a r e a " ) ; text . className = " t e x t " ; // r e q u i r e d f o r onkeydown text . value = value ; text . spellcheck = false ; text . style . width = "100%" ; CAPITOLO 3. WOLFWEB 48 div . appendChild ( document . createElement ( " b r " ) ) ; div . appendChild ( text ) ; // used t o change Text p r o p e r t y text . onchange = function ( shapeId , property ) { return function ( ) { // u p d a t e s h a p e b u t w i t h o u t a d d i n g {Command} t o t h e ←{ History } updateShape ( shapeId , property , t h i s . value , true ) ; }; } ( shapeId , t h i s . property ) ; // used t o c r e a t e undo {Command} text . onblur = function ( shapeId , property , previousValue ) { return function ( ) { // c r e a t e {Command} where p r e v i o u s v a l u e i s // t h e i n i t i a l i z a t i o n v a l u e o f t e x t a r e a updateShape ( shapeId , property , t h i s . value , false , ←previousValue ) ; }; } ( shapeId , t h i s . property , text . value ) ; text . onmouseout = text . onchange ; text . onkeyup = text . onchange ; DOMObject . appendChild ( div ) ; } Gli elementi di start e di end risultano più semplici rispetto agli altri perché non hanno proprietà specifiche. Ecco il codice all’interno del file workflow.js che permette di creare una figura start: function figure_Start ( x , y ) { var circleRadius = 7 ; var f = new Figure ( " S t a r t " ) ; f . style . fillStyle = " #000000 " ; f . style . strokeStyle = FigureDefaults . strokeStyle ; //CIRCLE CAPITOLO 3. WOLFWEB 49 var c = new Arc ( x , y , circleRadius , 0 , 3 6 0 , false , 0 ) ; f . addPrimitive ( c ) ; //CONNECTION POINTS CONNECTOR_MANAGER . connectionPointCreate ( f . id , new Point ( x , y ) , ←ConnectionPoint . TYPE_FIGURE ) ; f . finalise ( ) ; return f ; } È stato inserito il vincolo di poter inserire un solo elemento di start o di end sul canvas: se l’utente provasse ad inserirne due dello stesso tipo, apparirebbe un messaggio d’errore. Questa modifica è stata fatta all’interno dello script main.js, nelle funzioni che si occupano della creazione e rimozione degli elementi. È bene precisare che WOLF, in realtà, permette di creare più elementi di end all’interno di un solo workflow ma è stata fatta questa scelta implementativa per mantenere workflow più leggibili, mantenendo la stessa potenza espressiva. Come detto in precedenza, la creazione di un elemento subworkflow possiede un meccanismo diverso rispetto agli altri. Innanzitutto funzione che ne gestisce la creazione viene invocata da un metodo della classe WorkflowCommunicator presente sul lato server, quindi non è possibile per l’utente creare uno specifico subworkflow se non è presente nella lista di workflow disponibili sulla piattaforma WADE. Sono state dunque inserite righe di codice apposite per gestire questo evento: per prima cosa è stata aggiunta una funzione al file main.js che viene invocata da ZK, il quale le passa la label del workflow selezionato come stringa di input. CAPITOLO 3. WOLFWEB 50 DIAGRAMO . addSubworkflow = function ( labelZK ) { label = labelZK ; var figureThumbURL = ' l i b / s e t s / w o r k f l o w / s u b w o r k f l o w . png ' ; var figureFunction = ' f i g u r e _ S u b w o r k f l o w ' ; var figureFunctionName = ' f i g u r e _ S u b w o r k f l o w ' ; DIAGRAMO . createSubworkflowFigure ( window [ figureFunction ] , ←figureThumbURL , label ) ; } La funzione addSubworkflow chiama a sua volta la funzione createSubworkflowFigure DIAGRAMO . createSubworkflowFigure = function ( b , a , label ) { createFigureFunction = b ; selectedFigureThumb = a ; selectedFigureId = −1; selectedConnectorId = −1; selectedConnectionPointId = −1; i f ( state == STATE_TEXT_EDITING ) { currentTextEditor . destroy ( ) ; currentTextEditor = null } state = STATE_SUBWORKFLOW_CREATE ; draw ( ) } L’aggiunta dell’elemento subworkflow, dunque, può essere trattato a parte nell’invocazione dell’event listener al momento della creazione della figura. In seguito viene riportato il frammento di codice all’interno dello script main.js che gestisce questo aspetto: case STATE_SUBWORKFLOW_CREATE : i f ( ! draggingFigure ) { draggingFigure = document . createElement ( " img " ) ; draggingFigure . setAttribute ( " i d " , " draggingThumb " ) ; draggingFigure . style . position = " a b s o l u t e " ; draggingFigure . style . zIndex = 3 ; 51 CAPITOLO 3. WOLFWEB body . appendChild ( draggingFigure ) } draggingFigure . setAttribute ( " s r c " , selectedFigureThumb ) ; draggingFigure . style . width = " 100 px " ; draggingFigure . style . height = " 100 px " ; draggingFigure . style . left = ( a . pageX − 5 0 ) + " px " ; draggingFigure . style . top = ( a . pageY − 5 0 ) + " px " ; draggingFigure . style . display = " b l o c k " ; draggingFigure . addEventListener ( " mousedown " , function ( b ) { } , ←false ) ; draggingFigure . addEventListener ( " mouseup " , function ( d ) { var e = getCanvasXY ( d ) ; i f ( e == null ) { return } var b = e [ 0 ] ; var g = e [ 1 ] ; switch ( state ) { case STATE_SUBWORKFLOW_CREATE : Log . info ( " d r a g g i n g F i g u r e >onMouseUp ( ) + ←STATE_SUBWORKFLOW_CREATE" ) ; snapMonitor = [ 0 , 0 ] ; i f ( window . createFigureFunction ) { var c = new SubworkflowCreateCommand ( window . createFigureFunction , b , g , label ) ; c . executeSubworkflow ( ) ; History . addUndo ( c ) ; selectedConnectorId = −1; createFigureFunction = null ; mousePressed = false ; redraw = true ; draw ( ) ; document . getElementById ( " draggingThumb " ) . style . display = " none " } else { Log . info ( " d r a g g i n g F i g u r e >onMouseUp ( ) ←- 52 CAPITOLO 3. WOLFWEB + STATE_SUBWORKFLOW_CREATE−−> b u t ←no ' c r e a t e F i g u r e F u n c t i o n ' " ) } break ; } a . stopPropagation ( ) } , false ) ; break } } Inoltre sono state aggiunte altre funzioni all’interno del file originale main.js di Diagramo: getData, getFigures e getConnectors. getData chiama in sequenza getFigures e getConnectors i quali mandano al server, tramite il metodo zaU.send che a sua volta invoca le funzioni onSendFigures e onSendConnectors della classe Java WorkflowCommunicator, una stringa contenente tutte le figure e tutti i connectors disegnati nel canvas e le loro proprietà principali, quali il testo che essi contengono, i punti del canvas in cui vengono disegnati, il tipo di figura e il codice ad essa associata. Si noti che se la figura in questione è di tipo start o end, essa viene trattata a parte per recuperare i punti in cui è stata disegnata nel canvas. Di seguito viene riportato a titolo esemplificativo il codice della funzione getFigures DIAGRAMO . getFigures = function ( ) { var figureToSend = " " ; var circleId = " " ; var circleX = " " ; var circleY = " " ; var circleText = " " ; for ( var e = 0 ; e < STACK . figures . length ; e++) { i f ( STACK . figures [ e ] . name == " S t a r t " ) { circleText = " S t a r t " ; circleId = STACK . figures [ e ] . id ; for ( var i = 0 ; i < ←- CAPITOLO 3. WOLFWEB 53 CONNECTOR_MANAGER . connectionPoints . length ; i++) { i f ( CONNECTOR_MANAGER . connectionPoints [ i ] . id == circleId ) { circleX = CONNECTOR_MANAGER . connectionPoints [ i ] . point . x ; circleY = CONNECTOR_MANAGER . connectionPoints [ i ] . point . y ; } } figureToSend = figureToSend + " i d : " + STACK . figures [ e ] . id + ←" , t y p e : " + STACK . figures [ e ] . name + " , Text : " + ←circleText + " x : " + circleX + " y : " + circleY + " , u r l : ←" + STACK . figures [ e ] . url + "ENDSTRING" ; } else i f ( STACK . figures [ e ] . name == "End" ) { circleText = "End" ; circleId = STACK . figures [ e ] . id ; for ( var i = 0 ; i < ←CONNECTOR_MANAGER . connectionPoints . length ; i++) { i f ( CONNECTOR_MANAGER . connectionPoints [ i ] . id == circleId ) { circleX = CONNECTOR_MANAGER . connectionPoints [ i ] . point . x ; circleY = CONNECTOR_MANAGER . connectionPoints [ i ] . point . y ; } } figureToSend = figureToSend + " i d : " + STACK . figures [ e ] . id + ←" , t y p e : " + STACK . figures [ e ] . name + " , Text : " + ←circleText + " x : " + circleX + " y : " + circleY + " , u r l : ←" + STACK . figures [ e ] . url + "ENDSTRING" ; } else figureToSend = figureToSend + " i d : " + STACK . figures [ e ] . id + ←" , t y p e : " + STACK . figures [ e ] . name + " , " + ←STACK . figures [ e ] . getText ( ) + " , u r l : " + ←STACK . figures [ e ] . url + "ENDSTRING" ; }; zAu . send (new zk . Event ( zk . Widget . $ ( ' $ s a v e ' ) , ' o n S e n d F i g u r e s ' , ←figureToSend ) ) ; } CAPITOLO 3. WOLFWEB 3.2.2 54 La pagina wolfweb.zul Tutto il caricamento di Diagramo, delle sue librerie e dei suoi file CSS è affidato ad una pagina ZUL. ZK può includere codice HTML tramite il componente XUL html e mandare tutto direttamente al browser. Per far sì che ZK non interpreti il codice all’interno dei tag HTML lo si è inserito dentro ai tag <![CDATA[ e ]]>, potendo così caricare Diagramo come una normale pagina HTML. Terminato il caricamento di Diagramo, è stata inserita una window ZK denominata palette, la quale contiene la listbox ZK che si occupa di rendere disponibile per l’utente la lista di workflow presenti sulla piattaforma WADE e un bottone ZK che permette all’utente di salvare il workflow disegnato con WOLFWeb in un file XML. La listbox possiede l’attributo emptyMessage, un messaggio che la pagina mostra all’utente nel caso non vi fossero workflow disponibili sulla piattaforma WADE. Si noti che al termine del caricamento della pagina, la window viene creata sotto la select del set di figure ma che può essere spostata e ridimensionata a piacere. Ecco il codice della pagina wolfweb.zul <?page title="WOLFWEB" contentType=" t e x t / h t m l ; c h a r s e t=UTF−8" ?> <zk> <html> <! [CDATA[<head> <meta charset="UTF−8"> <title>HTML5 Workflow Editor</ title> <meta http-equiv="X−UA−Compatible" content=" I E=9" /> <script type=" t e x t / j a v a s c r i p t " ←src=" . / a s s e t s / j a v a s c r i p t /dropdownmenu . j s "></script> CAPITOLO 3. WOLFWEB 55 <link rel=" s t y l e s h e e t " media=" s c r e e n " type=" t e x t / c s s " ←href=" . / a s s e t s / c s s / s t y l e _ d i a g r a m o . c s s " /> <link rel=" s t y l e s h e e t " media=" s c r e e n " type=" t e x t / c s s " ←href=" . / a s s e t s / c s s / minimap . c s s " /> <script type=" t e x t / j a v a s c r i p t " ←src=" . / a s s e t s / j a v a s c r i p t / j s o n 2 . j s "></script> <script type=" t e x t / j a v a s c r i p t " ←src=" . / a s s e t s / j a v a s c r i p t / j q u e r y − 1 . 1 1 . 0 . min . j s "></script> <script type=" t e x t / j a v a s c r i p t " ←src=" . / a s s e t s / j a v a s c r i p t / j q u e r y − s i m p l e m o d a l − 1 . 4 . 4 . j s "></script> <script type=" t e x t / j a v a s c r i p t "> var appURL = ' . ' ; function initLight ( i d ) { init ( i d ) ; } </script> Di seguito omettiamo tutte le chiamate agli script contenuti nella cartella lib. <! -- List of figure sets included--> <script type=" t e x t / j a v a s c r i p t " ←src=" . / l i b / s e t s / w o r k f l o w / w o r k f l o w . j s "></script> <script type=" t e x t / j a v a s c r i p t " src=" . / l i b / minimap . j s "></script> Omettiamo anche le chiamate agli script contenuti nella cartella lib/commands. <script type=" t e x t / j a v a s c r i p t " ←src=" . / a s s e t s / j a v a s c r i p t / c o l o r P i c k e r _ n e w . j s "></script> <link rel=" s t y l e s h e e t " media=" s c r e e n " type=" t e x t / c s s " ←- 56 CAPITOLO 3. WOLFWEB href=" . / a s s e t s / c s s / c o l o r P i c k e r _ n e w . c s s " /> <! -- [ if IE ]> <script src=" . / a s s e t s / j a v a s c r i p t / e x c a n v a s . j s "></script> <! [ endif ] --> </head> Qua termina il contenuto del tag <head>. Di seguito viene riportato il contenuto della parte <body>. <body onload=" i n i t L i g h t ( ' ' ) ; " i d=" body "> <div i d=" h e a d e r "> <span> <img src=" . / a s s e t s / i m a g e s /Wade−Logo . png "/> </span> </div> <div i d=" a c t i o n s "> Omettiamo tutti i tag <a> che definiscono i link alle azioni che l’utente può compiere, come creare un connector o visualizzare la griglia nel canvas. </div> <div i d=" e d i t o r "> <div i d=" f i g u r e s "> <select style=" w i d t h : 160 px ; " onchange=" s e t F i g u r e S e t ( t h i s . o p t i o n s [ t h i s . s e l e c t e d I n d e x ] . v a l u e ) ; "> <script type=" t e x t / j a v a s c r i p t "> for ( var setName in figureSets ) { var set = figureSets [ setName ] ; document . write ( '<o p t i o n v a l u e =" ' + setName + ←' "> ' + set [ ' name ' ] + '</ o p t i o n> ' ) ; } </script> CAPITOLO 3. WOLFWEB 57 </select> <script> /∗∗ Builds the figure panel∗/ function buildPanel ( ) { //var first = true ; var firstPanel = true ; var i = 0 ; for ( var setName in figureSets ) { var set = figureSets [ setName ] ; //creates the div that will hold the figures var eSetDiv = document . createElement ( ' d i v ' ) ; eSetDiv . setAttribute ( ' i d ' , setName ) ; //eSetDiv . style . border = ' 1 px s o l i d g r e e n ' ; if ( firstPanel ) { firstPanel = false ; } else{ eSetDiv . style . display = ' none ' ; } document . getElementById ( ' f i g u r e s ' ) . appendChild ( eSetDiv ) ; //add figures to the div for ( var figure in set [ ' f i g u r e s ' ] ) { i++; if ( i <= 4 ) { figure = set [ ' f i g u r e s ' ] [ figure ] ; var figureFunctionName = ' f i g u r e _ ' + ←figure . figureFunction ; var figureThumbURL = ' l i b / s e t s / ' + setName + ' / ' ←+ figure . image ; var eFigure = document . createElement ( ' img ' ) ; eFigure . setAttribute ( ' s r c ' , figureThumbURL ) ; eFigure . addEventListener ( ' mousedown ' , function ←( figureFunction , figureThumbURL ) { return function ( evt ) { 58 CAPITOLO 3. WOLFWEB evt . preventDefault ( ) ; createFigure (window [ figureFunction ] /∗we ←need to search for function in window ←namespace ( as all that we have is a ←simple string ) ∗∗/ , figureThumbURL ) ; }; } ( figureFunctionName , figureThumbURL ) , false ) ; //in case use drops the figure eFigure . addEventListener ( ' mouseup ' , function ( ) { createFigureFunction = null ; selectedFigureThumb = null ; state = STATE_NONE ; } , false ) ; eFigure . style . cursor = ' p o i n t e r ' ; eFigure . style . marginRight = ' 5 px ' ; eFigure . style . marginTop = ' 2 px ' ; eSetDiv . appendChild ( eFigure ) ; } } } } buildPanel ( ) ; </script> </div> <div> </div> <div i d=" c e n t e r " style=" w i d t h : 100%"> <div i d=" c o n t a i n e r "> <canvas i d=" a " width=" 800 " height=" 600 "> 59 CAPITOLO 3. WOLFWEB </canvas> <div i d=" t e x t − e d i t o r "></div> <div i d=" t e x t − e d i t o r − t o o l s "></div> </div> </div> <div i d=" r i g h t "> <div i d=" minimap "> </div> <div style=" o v e r f l o w : s c r o l l ; " i d=" e d i t "> </div> </div> </div> <script type=" t e x t / j a v a s c r i p t "> function loadFill ( check ) { if ( check . checked === true ) { if ( $ ( '#c o l o r p i c k e r H o l d e r 3 ' ) . css ( ' d i s p l a y ' ) === ' none ' ) { $ ( '#c o l o r S e l e c t o r 3 ' ) . click ( ) ; } } else{ if ( $ ( '#c o l o r p i c k e r H o l d e r 3 ' ) . css ( ' d i s p l a y ' ) === ' b l o c k ' ) { $ ( '#c o l o r S e l e c t o r 3 ' ) . click ( ) ; } } } </script> <br/> <? //require_once dirname ( __FILE__ ) . </body> ] ]> </html> ' /common/ a n a l y t i c s . php ' ; ?> CAPITOLO 3. WOLFWEB 60 Qua termina il contenuto del tag <html> e inizia la parte finale dove viene creata la window contente la listbox che elenca i workflow disponibili sulla piattaforma WADE e il bottone che permette di salvare il contenuto del canvas su file XML. <style> . palette { float: left ; width: 200 px ; padding-left: 5px ; background-color: #F6F6F6 ; } </style> <window i d=" p a l e t t e " title=" P a l e t t e " p o s i t i o n=" l e f t , c e n t e r " ←sclass=" p a l e t t e " border=" none " s i z a b l e=" t r u e " ←apply="com . w o l f w e b . WorkflowCommunicator " mode=" o v e r l a p p e d "> <listbox i d=" w o r k f l o w s L i s t b o x " emptyMessage="No w o r k f l o w ←a v a i l a b l e . "> <listhead> <listheader label=" W o r k f l o w s " /> </ listhead> </ listbox> <separator/> <separator/> <button i d=" s a v e " label=" Save w o r k f l o w t o XML f i l e "/> </window> </zk> Figura 3.4: La finestra ZK contenente la palette dei workflow disponibili sulla piattaforma WADE e il bottone per il salvataggio del diagramma in file XML CAPITOLO 3. WOLFWEB 3.3 3.3.1 61 Il lato server Il composer WorkflowCommunicator Gli elementi ZK presenti nella pagina wolfweb.zul sono tutti gestiti dal controller WorkflowCommunicator. Questa classe presente nel package com.wolfeb estende il SelectorComposer fornito da ZK. Nel codice della pagina dell’editor si noti come i nomi della listbox e del bottone combacino con i corrispettivi oggetti all’interno della classe, il cui legame è esplicitato dall’annotazione @Wire, mentre la window palette presenta l’attributo apply a cui è associata la classe del nostro composer: così facendo il controller può controllare tutti gli elementi ZK figli della window (la listbox e il bottone, appunto). La classe presenta tre metodi: • il metodo doAfterCompose, • i metodi onSendFigures e onSendConnectors. 3.3.2 Il metodo doAfterCompose Il metodo doAfterCompose del nostro controller ridefinisce quello della classe SelectorComposer. Esso riceve in input l’elemento ZK su cui è applicato il controller, ovvero la finestra palette, e viene invocato quando tutti i suoi elementi figli sono stati creati. Per prima cosa viene chiamato il metodo doAfterCompose della classe genitore per assicurare il corretto funzionamento, secondo la documentazione fornitaci da ZK. In seguito viene verificato se il DynamicJadeGateway sia già inizializzato e in caso contrario viene connesso alla piattaforma in modo opportuno. Una volta inizializzato correttamente il DynamicJadeGateway, viene riempita 62 CAPITOLO 3. WOLFWEB la lista di workflow disponibili sulla piattaforma grazie all’EngineProxy, un potente gateway che permette di connettere un’applicazione non nativa WADE (il nostro Web editor di workflow) con una piattaforma WADE. Di seguito riportiamo il codice del metodo getWorkflows della classe EngineProxy che ritorna la lista dei workflows. La chiamata a questo metodo all’interno della classe WorkflowCommunicator passa come primi due parametri di input il valore null, perché non si vuole nessuna categoria ne alcun modulo specifici, mentre come terzo parametro si passa il valore true per ricevere solo component-workflow. /∗∗ ∗ Get the list of workflows ∗ @param category category of workflows ( can be null ) ∗ @param moduleName name of module containing the workflows ( can be ←null ) ∗ @param componentsOnly permit to select only the component−workflow ←( can be null −> false ) ∗ @return list of workflows ∗ @throws EngineProxyException ∗/ public List<WorkflowDetails> getWorkflows ( String category , String ←moduleName , Boolean componentsOnly ) throws EngineProxyException { GetWorkflowsBehaviour getWfsBehaviour = new ←GetWorkflowsBehaviour ( category , moduleName , componentsOnly ) ; execute ( getWfsBehaviour ) ; myLogger . log ( Logger . INFO , " E n g i n e P r o x y : R e q u e s t w o r k f l o w s l i s t ") ; return getWfsBehaviour . getWorkflowsDetails ( ) ; } Successivamente viene inizializzata una variabile di nome model e di tipo ListModelList istanziata sul tipo WorkflowDetails (il tipo di oggetti che compongono la lista dei workflow presa dalla piattaforma WADE), chiamando il costruttore che prende in input due parametri: la lista da rap- CAPITOLO 3. WOLFWEB 63 presentare (la workflowList) e un booleano, il quale indica se le eventuali modifiche subite dalla ListModelList si debbano verificare in tempo reale anche nella lista passata come primo parametro oppure no (nel nostro caso tale booleano viene settato con il valore true). Viene poi eseguito tutto il procedimento per la corretta visualizzazione della lista di workflow nell’editor: si chiama il metodo setItemRenderer della listbox il quale setta il renderer che verrà usato per renderizzare tutti gli elementi della listbox, passandogli come parametro un oggetto di tipo ListitemRenderer istanziato sul tipo Object. Per questo oggetto si ridefinisce il metodo render passandogli come paramentri un oggetto di tipo Listitem (il generico elemento della listbox che verrà visualizzato nell’editor), un oggetto di Object che contiene i dati del workflow corrente preso dalla piattaforma e un intero che indicizza l’ennesimo dato che sta per essere visualizzato. A questo punto si preparano tutti gli oggetti che andranno a comporre la listbox: la label che prende il nome del component dell’ennesimo workflow presente sulla piattaforma WADE viene attaccata ad un oggetto di tipo Listcell, al quale viene settato come elemento genitore l’oggetto Listitem passato come parametro al metodo render. A terminare la preparazione della listbox è l’aggiunta di un event listener che permette, con un doppio click del mouse, di aggiungere il workflow desiderato al canvas con la chiamata alla funzione JavaScript addSubworkflow descritta precedentemente. CAPITOLO 3. WOLFWEB 64 In ultimo si aggiunge un event listener al bottone della window ZK il quale chiama il metodo JavaScript getData, anch’esso trattato in precedenza, per permettere all’utente di salvare il proprio workflow creato con l’editor WOLFWeb in un file XML e viene invocato il metodo setModel per associare la workflowListbox con il model. Di seguito viene riportato il codice del metodo doAfterCompose. @SuppressWarnings ( { " u n c h e c k e d " , " r a w t y p e s " } ) @Override public void doAfterCompose ( Window win ) throws Exception { super . doAfterCompose ( win ) ; i f ( gateway == null ) { Properties props = new Properties ( ) ; props . put ( " s e r v i c e s " , ←" jade . core . messaging . TopicManagementService ; " ) ; gateway = new DynamicJadeGateway ( ) ; gateway . init ( null , props ) ; gateway . checkJADE ( ) ; } // Prendo solo i workflows component List<WorkflowDetails> workflowsList = ←EngineProxy . getEngineProxy ( gateway ) . getWorkflows ( null , null , ←true ) ; ListModelList<WorkflowDetails> model = new ←ListModelList<WorkflowDetails>( workflowsList , true ) ; workflowsListbox . setItemRenderer (new ListitemRenderer<Object>( ) { @Override public void render ( Listitem item , Object data , int index ) throws Exception { // TODO Auto−generated method stub final WorkflowDetails wfl = ( WorkflowDetails ) data ; Listcell listcell = new Listcell ( ) ; Label label = new Label ( wfl . getClassName ( ) ) ; label . setParent ( listcell ) ; CAPITOLO 3. WOLFWEB 65 listcell . setParent ( item ) ; item . addEventListener ( " o n D o u b l e C l i c k " , new EventListener ( ) { @Override public void onEvent ( Event event ) throws Exception { // Chiamo Diagramo che crea un subworkflow Clients . evalJavaScript ( "DIAGRAMO . a d d S u b w o r k f l o w ( ' " + ←label . getValue ( ) + " ' ) ; " ) ; } }) ; } }) ; save . addEventListener ( " o n C l i c k " , new EventListener ( ) { @Override public void onEvent ( Event event ) throws Exception { Clients . evalJavaScript ( "DIAGRAMO . g e t D a t a ( ) ; " ) ; } }) ; workflowsListbox . setModel ( model ) ; } 3.3.3 I metodi onSendFigures e onSendConnectors Entrambi i metodi vengono invocati dalle funzioni JavaScript getFigures e getConnectors dello script main.js già trattate in precedenza. Ricevute dal client le stringhe che contengono le informazioni riguardanti le figure e i connector disegnati sul canvas, esse vengono elaborate, inserite in una matrice le cui colonne rappresentano le varie proprietà e successivamente usate per comporre un file XML contenente appunto tutte le informazioni necessarie a comporre il workflow. Il file (chiamato a titolo d’esempio workflow.xml) viene così suddiviso in due parti: la prima racchiusa nel tag <Activities>, il quale contiene a sua volta un tag <ActivityLayout> per ogni figura creata nel canvas e tutti i tag figli che compongono le proprietà delle figure. La seconda 66 CAPITOLO 3. WOLFWEB parte, invece, è contenuta nel tag <Transitions> che racchiude tutti i tag <TransitionLayout> i quali rappresentano ogni connectors disegnato nell’editor. Di seguito viene riportato il codice del metodo getConnectors @SuppressWarnings ( " u n u s e d " ) @Listen ( " o n S e n d C o n n e c t o r s = #s a v e " ) public void onSendConnectors ( Event e ) throws IOException , ←ParserConfigurationException , TransformerException , SAXException { String textValue = " " ; String xStartValue = " " ; String yStartValue = " " ; String xEndValue = " " ; String yEndValue = " " ; String startPositionValue = " " ; String endPositionValue = " " ; Element startPosition = null ; Element endPosition = null ; Element text = null ; String connector = ( String ) e . getData ( ) ; String delims = "ENDSTRING" ; String [ ] connectors = connector . split ( delims ) ; for ( int i = 0 ; i < connectors . length ; i++){ connectors [ i ] = connectors [ i ] . replaceAll ( " [ , ] + " , "ENDPROP" ) ; connectors [ i ] = connectors [ i ] + "ENDPROP" ; } ArrayList<ArrayList<String>> matrix = new ←- ArrayList<ArrayList<String>>( ) ; try{ for ( int i = 0 ; i < connectors . length ; i++){ String delims2 = "ENDPROP" ; String [ ] properties = connectors [ i ] . split ( delims2 ) ; ArrayList<String> list = new ArrayList<String>( ) ; for ( int j = 0 ; j < properties . length ; j++){ list . add ( ( properties [ j ] . substring ( properties [ j ] . indexOf ( ' : ' ) , properties [ j ] . length ( ) ) ) . replaceAll ( " [ : ←]+" , " " ) ) ; CAPITOLO 3. WOLFWEB 67 } matrix . add ( list ) ; } } catch ( Throwable T ) {System . out . println ( T . toString ( ) ) ; } ; DocumentBuilderFactory docFactory = ←DocumentBuilderFactory . newInstance ( ) ; DocumentBuilder docBuilder = docFactory . newDocumentBuilder ( ) ; org . w3c . dom . Document doc = ←docBuilder . parse ( "C : \ \ \ \ U s e r s \\ F e d e r i c o \\ D e s k t o p \\ w o r k f l o w . xml " ) ; Element transitions = doc . createElement ( " T r a n s i t i o n s " ) ; doc . appendChild ( transitions ) ; for ( int i = 0 ; i < matrix . size ( ) ; i++) { ArrayList<String> table = matrix . get ( i ) ; textValue = " noText " ; for ( int j = 0 ; j < table . size ( ) ; j++) { i f ( j == 1 ) { xStartValue = table . get ( j ) ; } i f ( j == 2 ) { yStartValue = table . get ( j ) ; } i f ( j == 3 ) { xEndValue = table . get ( j ) ; } i f ( j == 4 ) { yEndValue = table . get ( j ) ; } i f ( j == 5 ) { i f ( table . get ( j ) != null ) textValue = table . get ( j ) ; } CAPITOLO 3. WOLFWEB 68 } Element transitionLayout = doc . createElement ( " T r a n s i t i o n L a y o u t " ) ; startPositionValue = " ( " + xStartValue + " , " + yStartValue + " ) " ; endPositionValue = " ( " + xEndValue + " , " + yEndValue + " ) " ; startPosition = doc . createElement ( " S t a r t P o s i t i o n " ) ; endPosition = doc . createElement ( " E n d P o s i t i o n " ) ; text = doc . createElement ( " L a b e l " ) ; startPosition . appendChild ( doc . createTextNode ( startPositionValue ) ) ; endPosition . appendChild ( doc . createTextNode ( endPositionValue ) ) ; text . appendChild ( doc . createTextNode ( textValue ) ) ; transitionLayout . appendChild ( text ) ; transitionLayout . appendChild ( startPosition ) ; transitionLayout . appendChild ( endPosition ) ; transitions . appendChild ( transitionLayout ) ; } TransformerFactory transformerFactory = ←TransformerFactory . newInstance ( ) ; Transformer transformer = transformerFactory . newTransformer ( ) ; DOMSource source = new DOMSource ( doc ) ; File file = new File ( "C : \ \ \ \ U s e r s \\ F e d e r i c o \\ D e s k t o p \\ w o r k f l o w . xml " ) ; StreamResult result = new StreamResult ( file ) ; StreamResult result2 = new StreamResult ( System . out ) ; transformer . transform ( source , result ) ; } CAPITOLO 3. WOLFWEB Presentiamo infine il codice sorgente completo della classe WorkflowCommunicator, omettendo però tutti gli import package com . wolfweb ; public class WorkflowCommunicator extends SelectorComposer<Window>{ private static final long serialVersionUID = 6 8 9 1 0 2 7 7 6 8 6 1 6 7 8 3 8 7 5 L ; private static DynamicJadeGateway gateway ; @Wire private Listbox workflowsListbox ; @Wire private Button save ; @Override public void doAfterCompose ( Window win ) throws Exception { super . doAfterCompose ( win ) ; i f ( gateway == null ) { Properties props = new Properties ( ) ; props . put ( " s e r v i c e s " , ←" jade . core . messaging . TopicManagementService ; " ) ; gateway = new DynamicJadeGateway ( ) ; gateway . init ( null , props ) ; gateway . checkJADE ( ) ; } // Prendo solo i workflows component List<WorkflowDetails> workflowsList = ←EngineProxy . getEngineProxy ( gateway ) . getWorkflows ( null , null , ←true ) ; ListModelList<WorkflowDetails> model = new ←ListModelList<WorkflowDetails>( workflowsList , true ) ; workflowsListbox . setItemRenderer (new ListitemRenderer<Object>( ) { @SuppressWarnings ( { " u n c h e c k e d " , " r a w t y p e s " } ) @Override public void render ( Listitem item , Object data , int index ) 69 CAPITOLO 3. WOLFWEB throws Exception { // TODO Auto−generated method stub final WorkflowDetails wfl = ( WorkflowDetails ) data ; Listcell listcell = new Listcell ( ) ; Label label = new Label ( wfl . getClassName ( ) ) ; label . setParent ( listcell ) ; listcell . setParent ( item ) ; item . addEventListener ( " o n D o u b l e C l i c k " , new EventListener ( ) { @Override public void onEvent ( Event event ) throws Exception { // Chiamo Diagramo che crea un subworkflow Clients . evalJavaScript ( "DIAGRAMO . a d d S u b w o r k f l o w ( ' " + ←label . getValue ( ) + " ' ) ; " ) ; } }) ; save . addEventListener ( " o n C l i c k " , new EventListener ( ) { @Override public void onEvent ( Event event ) throws Exception { Clients . evalJavaScript ( "DIAGRAMO . g e t D a t a ( ) ; " ) ; } }) ; } }) ; workflowsListbox . setModel ( model ) ; } @SuppressWarnings ( " u n u s e d " ) @Listen ( " o n S e n d C o n n e c t o r s = #s a v e " ) public void onSendConnectors ( Event e ) throws IOException , ←ParserConfigurationException , TransformerException , SAXException { String textValue = " " ; String xStartValue = " " ; String yStartValue = " " ; String xEndValue = " " ; String yEndValue = " " ; String startPositionValue = " " ; String endPositionValue = " " ; 70 71 CAPITOLO 3. WOLFWEB Element startPosition = null ; Element endPosition = null ; Element text = null ; String connector = ( String ) e . getData ( ) ; String delims = "ENDSTRING" ; String [ ] connectors = connector . split ( delims ) ; for ( int i = 0 ; i < connectors . length ; i++){ connectors [ i ] = connectors [ i ] . replaceAll ( " [ , ] + " , "ENDPROP" ) ; connectors [ i ] = connectors [ i ] + "ENDPROP" ; } ArrayList<ArrayList<String>> matrix = new ←- ArrayList<ArrayList<String>>( ) ; try{ for ( int i = 0 ; i < connectors . length ; i++){ String delims2 = "ENDPROP" ; String [ ] properties = connectors [ i ] . split ( delims2 ) ; ArrayList<String> list = new ArrayList<String>( ) ; for ( int j = 0 ; j < properties . length ; j++){ list . add ( ( properties [ j ] . substring ( properties [ j ] . indexOf ( ' : ' ) , ←properties [ j ] . length ( ) ) ) . replaceAll ( " [ : ]+ " , " " ) ) ; } matrix . add ( list ) ; } } catch ( Throwable T ) {System . out . println ( T . toString ( ) ) ; } ; DocumentBuilderFactory docFactory = ←DocumentBuilderFactory . newInstance ( ) ; DocumentBuilder docBuilder = docFactory . newDocumentBuilder ( ) ; org . w3c . dom . Document doc = ←docBuilder . parse ( "C : \ \ \ \ U s e r s \\ F e d e r i c o \\ D e s k t o p \\ w o r k f l o w . xml " ) ; Element transitions = doc . createElement ( " T r a n s i t i o n s " ) ; doc . appendChild ( transitions ) ; for ( int i = 0 ; i < matrix . size ( ) ; i++) { ArrayList<String> table = matrix . get ( i ) ; CAPITOLO 3. WOLFWEB 72 textValue = " noText " ; for ( int j = 0 ; j < table . size ( ) ; j++) { i f ( j == 1 ) { xStartValue = table . get ( j ) ; } i f ( j == 2 ) { yStartValue = table . get ( j ) ; } i f ( j == 3 ) { xEndValue = table . get ( j ) ; } i f ( j == 4 ) { yEndValue = table . get ( j ) ; } i f ( j == 5 ) { i f ( table . get ( j ) != null ) textValue = table . get ( j ) ; } } Element transitionLayout = doc . createElement ( " T r a n s i t i o n L a y o u t " ) ; startPositionValue = " ( " + xStartValue + " , " + yStartValue + " ) " ; endPositionValue = " ( " + xEndValue + " , " + yEndValue + " ) " ; startPosition = doc . createElement ( " S t a r t P o s i t i o n " ) ; endPosition = doc . createElement ( " E n d P o s i t i o n " ) ; text = doc . createElement ( " L a b e l " ) ; startPosition . appendChild ( doc . createTextNode ( startPositionValue ) ) ; endPosition . appendChild ( doc . createTextNode ( endPositionValue ) ) ; text . appendChild ( doc . createTextNode ( textValue ) ) ; transitionLayout . appendChild ( text ) ; transitionLayout . appendChild ( startPosition ) ; transitionLayout . appendChild ( endPosition ) ; transitions . appendChild ( transitionLayout ) ; } 73 CAPITOLO 3. WOLFWEB TransformerFactory transformerFactory = ←TransformerFactory . newInstance ( ) ; Transformer transformer = transformerFactory . newTransformer ( ) ; DOMSource source = new DOMSource ( doc ) ; File file = new File ( "C : \ \ \ \ U s e r s \\ F e d e r i c o \\ D e s k t o p \\ w o r k f l o w . xml " ) ; StreamResult result = new StreamResult ( file ) ; StreamResult result2 = new StreamResult ( System . out ) ; transformer . transform ( source , result ) ; } @SuppressWarnings ( " u n u s e d " ) @Listen ( " o n S e n d F i g u r e s = #s a v e " ) public void onSendFigures ( Event e ) throws ←ParserConfigurationException , TransformerException { String typeValue = " " ; String nameValue = " " ; String xValue = " " ; String yValue = " " ; String positionValue = " " ; Element position = null ; Element type = null ; Element name = null ; String figure = ( String ) e . getData ( ) ; String delims = "ENDSTRING" ; String [ ] figures = figure . split ( delims ) ; for ( int i = 0 ; i < figures . length ; i++){ figures [ i]=figures [ i ] . replaceAll ( " [ , ] + " , "ENDPROP" ) ; figures [ i]=figures [ i ] . replaceAll ( " x : " , "ENDPROPx : " ) ; figures [ i]=figures [ i ] . replaceAll ( " y : " , "ENDPROPy : " ) ; figures [ i]=figures [ i]+ "ENDPROP" ; } ArrayList<ArrayList<String>> matrix = new ArrayList<ArrayList<String>>( ) ; try{ ←- CAPITOLO 3. WOLFWEB 74 for ( int i = 0 ; i < figures . length ; i++){ String delims2 = "ENDPROP" ; String [ ] properties = figures [ i ] . split ( delims2 ) ; ArrayList<String> list = new ArrayList<String>( ) ; for ( int j = 0 ; j < properties . length ; j++){ list . add ( ( properties [ j ] . substring ( properties [ j ] . indexOf ( ' : ' ) , ←properties [ j ] . length ( ) ) ) . replaceAll ( " [ : ]+ " , " " ) ) ; } matrix . add ( list ) ; } } catch ( Throwable T ) {System . out . println ( T . toString ( ) ) ; } ; DocumentBuilderFactory docFactory = ←DocumentBuilderFactory . newInstance ( ) ; DocumentBuilder docBuilder = docFactory . newDocumentBuilder ( ) ; org . w3c . dom . Document doc = docBuilder . newDocument ( ) ; Element activities = doc . createElement ( " A c t i v i t i e s " ) ; doc . appendChild ( activities ) ; for ( int i = 0 ; i < matrix . size ( ) ; i++) { ArrayList<String> table = matrix . get ( i ) ; for ( int j = 0 ; j < table . size ( ) ; j++) { i f ( j == 1 ) { typeValue = table . get ( j ) ; } i f ( j == 2 ) { nameValue = table . get ( j ) ; } i f ( j == 3 ) { xValue = table . get ( j ) ; } i f ( j == 4 ) { yValue = table . get ( j ) ; } CAPITOLO 3. WOLFWEB 75 } // Costruisco i tag Element activityLayout = doc . createElement ( " A c t i v i t y L a y o u t " ) ; type = doc . createElement ( typeValue ) ; name = doc . createElement ( "Name" ) ; position = doc . createElement ( " P o s i t i o n " ) ; name . appendChild ( doc . createTextNode ( nameValue ) ) ; positionValue = " ( " + xValue + " , " + yValue + " ) " ; position . appendChild ( doc . createTextNode ( positionValue ) ) ; type . appendChild ( name ) ; type . appendChild ( position ) ; activityLayout . appendChild ( type ) ; activities . appendChild ( activityLayout ) ; } TransformerFactory transformerFactory = ←TransformerFactory . newInstance ( ) ; Transformer transformer = transformerFactory . newTransformer ( ) ; DOMSource source = new DOMSource ( doc ) ; File file = new File ( "C : \ \ \ \ U s e r s \\ F e d e r i c o \\ D e s k t o p \\ w o r k f l o w . xml " ) ; StreamResult result = new StreamResult ( file ) ; StreamResult result2 = new StreamResult ( System . out ) ; transformer . transform ( source , result ) ; return ; } } Conclusioni Dopo uno studio delle soluzioni possibili per implementare l’editor di workflow WOLFWeb, è stato deciso di realizzarlo usando il framework ZK affiancato ad una versione custom di Diagramo, editor di diagrammi open source. In sintesi, WOLFWeb: • ha un lato client composto da una pagina ZUL contenente componenti HTML e JavaScript che costruiscono Diagramo, modificato appositamente per le nostre esigenze, • permette il salvataggio dei workflow in file XML tramite controller ZK lato server. Il composer ZK analizza dunque la stringa ricevuta dall’editor, prende le informazioni necessarie a comporre un workflow specifico e le salva in un file XML. Rispetto al plugin WOLF, WOLFWeb presenta tutti i vantaggi delle applicazioni Web, possiede un’interfaccia più intuitiva e soprattutto offre la possibilità di essere esteso facilmente per aggiungere funzionalità, sia sul lato client che sul lato server. Non è difficile, infatti, pensare per esempio ad un salvataggio delle informazioni in formati diversi, magari in un file Java compatibile con la sintassi di WOLF e quindi subito utilizzabile nello sviluppo delle applicazioni WADE, o ancora alla possibilità di disegnare un workflow 76 CONCLUSIONI 77 sul canvas prendendo le informazioni che lo compongono da un file XML salvato in locale o di associare codice ai suoi elementi, sfruttando la textbox presente nell’editor, per poi eseguirlo sulla WADE platform utilizzando i metodi già presenti nella classe EngineProxy per connettere WOLFWeb alla piattaforma. Bibliografia [1] JADE - Java Agent DEvelopment Framework; http://jade.tilab.com/ [2] WADE - Workflows and Agents Development Environment; http://jade.tilab.com/wade/index.html [3] Eclipse - The Eclipse Foundation open source community Web site.; http://www.eclipse.org/home/ [4] Diagramo; http://diagramo.com/ [5] FIPA - Foundation for Intelligent Physical Agents; http://www.fipa.org [6] ZK - ZK: Leading Enterprise Java Web Framework; http://www.zkoss.org/ [7] jQuery; https://jquery.com/ 78