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