Tesi Todone
Upcoming SlideShare
Loading in...5
×
 

Tesi Todone

on

  • 2,472 views

Tesi di laurea di Giancarlo Todone dal titolo

Tesi di laurea di Giancarlo Todone dal titolo
"Progetto e realizzazione di una infrastruttura modulare per acquisizione ed archiviazione remota di documenti"

Statistics

Views

Total Views
2,472
Views on SlideShare
2,471
Embed Views
1

Actions

Likes
0
Downloads
33
Comments
0

1 Embed 1

http://health.medicbd.com 1

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

Tesi Todone Tesi Todone Document Transcript

  • Università Degli Studi di Trieste Facoltà di Ingegneria Corso di Laurea Specialistica in Ingegneria Informatica Tesi di laurea in reti di calcolatori Progetto e realizzazione di una infrastruttura modulare per acquisizione ed archiviazione remota di documenti LAUREANDO RELATORE Giancarlo Todone Prof. Alberto Bartoli CORRELATORE Ing. Giorgio Davanzo Anno Accademico 2008/2009
  • Talk is cheap. Show me the code! (Linus Torvalds)
  • Indice 1 Introduzione 1 2 Il caso di studio 3 2.1 Situazione iniziale e requisiti . . . . . . . . . . . . . . . . . 3 2.2 Architettura generale e definizioni . . . . . . . . . . . . . . 5 2.2.1 Flusso di un tipico caso di utilizzo . . . . . . . . . 9 2.3 Cenni di implementazione e tecnologie utilizzate . . . . . . 11 3 SyncUtils 13 3.1 Requisiti e scopi di SyncUtils . . . . . . . . . . . . . . . . 13 3.2 Architettura . . . . . . . . . . . . . . . . . . . . . . . . . . 14 3.3 Dettagli implementativi e tecnologie . . . . . . . . . . . . 20 3.3.1 Perché il file system? . . . . . . . . . . . . . . . . . 20 3.3.2 Meccanismo di sincronizzazione . . . . . . . . . . . 21 3.3.3 Identificativi di richieste e risposte . . . . . . . . . 23 3.3.4 Protocollo . . . . . . . . . . . . . . . . . . . . . . . 24 3.3.5 Problematiche delle notifiche . . . . . . . . . . . . 27 3.3.6 Formati dei file di comunicazione . . . . . . . . . . 28 3.3.7 Serializzazione XML . . . . . . . . . . . . . . . . . 32 3.3.8 COM . . . . . . . . . . . . . . . . . . . . . . . . . 35 3.4 Installazione e preparazione dell’ambiente di esecuzione . . 40 i
  • ii INDICE 4 GEC_Scan 43 4.1 Requisiti e progetto dell’interfaccia utente . . . . . . . . . 43 4.1.1 Interfaccia grafica e usabilità . . . . . . . . . . . . 44 4.2 Composizione di GEC_Scan . . . . . . . . . . . . . . . . . 49 4.2.1 TWAIN . . . . . . . . . . . . . . . . . . . . . . . . 50 4.2.2 VisualSlideShow e design delle classi coinvolte . . . 55 4.3 Tecnologie e tecniche implementative . . . . . . . . . . . . 58 4.3.1 dotNetTwain . . . . . . . . . . . . . . . . . . . . . 59 4.3.2 Grafica . . . . . . . . . . . . . . . . . . . . . . . . 68 4.4 Installazione e preparazione dell’ambiente di esecuzione . . 70 5 Sottosistema di archiviazione e plug in 73 5.1 Caratteristiche del sottosistema di archiviazione . . . . . . 73 5.1.1 Funzionalità di manipolazione file . . . . . . . . . . 75 5.2 Design del sottosistema di archiviazione . . . . . . . . . . 76 5.3 Specifiche e tecnologie . . . . . . . . . . . . . . . . . . . . 80 5.3.1 Archiviazione locale e debug . . . . . . . . . . . . . 84 5.3.2 Funzionalità estese ed archiviazione Remota . . . . 85 5.3.2.1 Sharepoint . . . . . . . . . . . . . . . . . 86 5.3.2.2 Google Documents . . . . . . . . . . . . . 92 5.4 Setup dei sistemi di archiviazione . . . . . . . . . . . . . . 97 6 Conclusioni 99 Bibliografia 103
  • Capitolo 1 Introduzione La gestione di un documento cartaceo per via informatica inizia con le operazioni di acquisizione ed archiviazione. Per “acquisizione” si intende l’operazione di traduzione e codifica di un documento cartaceo in una forma comprensibile e fruibile da un elaboratore. Per “archiviazione” si può distinguere tra la mera operazione di immagazzinamento dati e la “conservazione sostitutiva”—con la quale si intende tutto il complesso di azioni che permettono di conservare elettronicamente dati di valore legale o tributario senza per questo conservare anche i documenti cartacei originali. A seguito e come proseguimento del tirocinio svolto presso la soft- ware house MIDA4, lo scrivente ha realizzato un pacchetto software per consentire di aggiungere le funzionalità di archiviazione al software di contabilità preesistente GEC. Tale pacchetto fornisce una infrastruttura per interfacciare GEC con altri sistemi di archiviazione (già esistenti o futuri) grazie ad un sistema estendibile con plug in, e si inserisce in un più ampio contesto di gestione documentale in sviluppo nel “Laborato- rio Reti” del DEEI, Università Degli Studi di Trieste. Un tema trattato con particolare riguardo è quello dell’archiviazione remota, cioè l’inte- grazione con servizi di terze parti—come Google Documents o Microsoft Sharepoint—per la memorizzazione dei dati su un server connesso ad Internet. 1
  • 2 1. Introduzione Dall’osservazione della situazione di partenza, dopo lo studio del pro- blema nella sua accezione più generale, sono stati definiti dei requisiti che hanno portato alla suddivisione del lavoro nei tre moduli software responsabili di comunicazione, acquisizione e archiviazione; per ognuno dei tre moduli sono stati definiti ulteriori requisiti in base ai quali sono state poi concepite l’architettura e le specifiche tecnologiche di ogni sin- gola parte. Ogni classe o componente sviluppato è stato verificato con metodologie di unit-test ed in alcuni casi anche con test automatici di carico. L’intero progetto ha subito anche diversi test funzionali condotti da operatori umani. I requisiti tecnologici del committente hanno portato alla scelta del linguaggio C# e del framework dotNet all’interno dell’ambiente di svi- luppo integrato Visual Studio 2008, su un sistema operativo Windows XP/Vista. L’interfacciamento con le periferiche di acquisizione è gestito con lo standard TWAIN mentre l’interfacciamento con il software GEC è gestito con le tecnologie COM e XML. Nel capitolo 2 si affrontano tematiche di carattere generale inerenti l’insieme del software sviluppato. Nei tre capitoli successivi ( 3 nella pa- gina 13, 4 nella pagina 43, 5 nella pagina 73) si affrontano separatamente i tre moduli software, esponendo di ognuno: • situazione pregressa e/o requisiti; • architettura e definizioni; • tecniche implementative e tecnologie utilizzate; aggiungendo—ove pertinente—un breve riassunto delle operazioni necessarie all’installazione e alla manutenzione del modulo.
  • Capitolo 2 Il caso di studio 2.1 Situazione iniziale e requisiti La software house MIDA4 ha prodotto e mantiene il software di con- tabilità chiamato GEC. La necessità dei clienti di MIDA4 di archiviare documenti per via informatica contestualizzandoli con le loro situazioni amministrative e altri dati, ha spinto la software house a ricercare un modo per ampliare in tal senso le capacità del suo prodotto. La richiesta era quella di aggiungere le funzionalità desiderate in un modo che permettesse di iniziare a servirsi di tecnologie più recenti e promettenti senza per questo dover riscrivere da capo un programma già rodato e funzionante come GEC. GEC viene attualmente fornito in due versioni: installabile e ASP (2.2): • la versione installabile è un normale programma che risiede ed ope- ra sul personal computer dell’utente finale, ed eventualmente co- munica con un server centralizzato per recuperare informazioni di licenza ed aggiornamenti; • la versione ASP consiste nella versione installabile montata però su un server accessibile da molti fruitori contemporaneamente tramite Internet ed un software di remote desktop. 3
  • 4 2. Il caso di studio Il software necessario ad aggiungere le funzionalità di archiviazione de- siderate, deve necessariamente interagire con GEC—qualsiasi sia la sua ubicazione—ma deve anche interagire con l’hardware dell’utente fina- le (es: scanner e fotocamere) e deve essere in grado di comunicare autonomamente con eventuali servizi di archiviazione di terze parti. Il modo in cui il nuovo software si integra con GEC deve 1. richiedere il minimo sforzo in termini di modifiche al codice sorgente di GEC; 2. essere facilmente utilizzabile dagli sviluppatori di GEC; 3. permettere l’integrazione futura con nuovi servizi di terze parti senza riscrivere il codice sorgente; per poter permettere rispettivamente: 1. di rendere il più possibile indipendenti lo sviluppo e il manteni- mento di GEC da quelli del nuovo software di archiviazione; 2. di consentire una rapida integrazione al momento del rilascio; 3. di poter essere rapidamente espandibile con nuove funzionalità. Dal punto di vista dell’utente, il software sviluppato dev’essere: • facilmente integrabile con il proprio sistema di produzione; • di utilizzo semplice ed immediato. In quest’ottica, un sistema di astrazione delle periferiche di acquisi- zione immagini dell’utente è stato posto come requisito; fondamentale è anche la trasparenza di tale meccanismo, necessaria al fine di ottenere un’interfaccia grafica consistente per tutti gli utenti, a prescindere da marca e modello dei loro hardware. Infine, la scelta dell’ambiente di sviluppo ha dovuto tenere conto di varie richieste: • GEC opera in ambiente Microsoft Windows, e quindi anche il nuovo software di archiviazione deve funzionare almeno in tale sistema;
  • Architettura generale e definizioni 5 • esso deve consentire di proporre all’utente finale un’interfaccia piacevole, immediata e consistente; • metodologie, linguaggi, librerie, framework utilizzati (a parte quelli finora citati che hanno diverse motivazioni) devono essere allo stato dell’arte, o quantomeno ancora vivamente supportati da produttori e da una nutrita schiera di sviluppatori; essi devono però permet- tere allo sviluppatore di interfacciarsi anche con moduli software utilizzanti tecnologie meno recenti, come TWAIN o Visual Basic 6 (di cui—come vedremo—fanno uso alcune parti del sistema). Da ciò si evince come la capacità di interoperabilità tra diverse tecnolo- gie sia essa stessa un requisito; viste queste necessità, in fase di analisi preliminare è stato posto l’utilizzo del dotNet framework di Microsoft come requisito. 2.2 Architettura generale e definizioni Per motivi di natura tecnica e per questioni di manutenibilità del codice che vedremo in seguito, il modulo che dovrà svolgere le operazioni di acquisizione e archiviazione dev’essere indipendente, cioè non fare parte di GEC, ma essere compilato in un eseguibile autonomo: questo significa che GEC e il nuovo software di acquisizione sono due entità distinte che necessitano di comunicare. Si considerino quindi i seguenti attori: GEC è il programma di contabilità già esistente, che dovrà subire delle modifiche per poter utilizzare SyncUtils e comunicare con il soft- ware di nuova creazione dedicato all’acquisizione e archiviazione; in seguito, se non esplicitamente specificato, col nome di GEC ci si riferirà indistintamente al programma in versione tradizionale o ASP; GEC_Scan è il software di acquisizione e archiviazione: esso è un soft- ware a se stante, ma può dialogare con GEC—tramite un mec- canismo descritto in 3.3 nella pagina 20—e con servizi di terze parti—mediante dei moduli aggiuntivi chiamati plug in ( 5.3 nella pagina 80).
  • 6 2. Il caso di studio ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ASP Remote desktop Internet (A) User 1 GEC_Scan ASP Remote desktop GEC-2 (B) User 2 ASP GEC-1 GEC_Scan GEC-3 (C) User 3 GEC_Scan Figura 2.1: Tre casi di utilizzo del sistema di archiviazione: nel caso (A) l’utente 1 utilizza solo l’istanza 1 di GEC tramite ASP e il suo sistema di remote desktop; nel caso (C) l’utente 3 utilizza solo GEC-3 in esecuzione sul suo computer locale; nel caso (B), l’utente 2 utilizza sia la locale istanza 2 di GEC, sia GEC-1 tramite sistema ASP. Queste due entità devono poter comunicare tra loro in vario modo. Nel caso più semplice GEC e GEC_Scan vengono eseguiti sullo stesso calcolatore e comunicano senza interferenze esterne; in figura 2.1 si os- servano anche i casi in cui GEC sia remoto e quello più complesso in cui
  • Architettura generale e definizioni 7 un solo utente utilizzi più istanze di GEC, eventualmente miste tra locali e remote. plug in SyncUtils GEC GEC_Scan plug in plug in                         acquisizione archiviazione                                             comunicazioni funzionalità                                                                 software software preesistente nuovo Figura 2.2: Descrizione dei software coinvolti; GEC è preesistente e viene modificato solo per poter usare SyncUtils; il resto del software è sviluppato ex novo; di questo è visibile la suddivisione in comunicazioni, acquisizione ed archiviazione: solo le ultime due implementano funzionalità richieste, mentre la prima serve a rendere interoperabili nuovo e vecchio software. Come illustrato in figura 2.2, tutte le funzionalità aggiunte risiedono in GEC_Scan e nei suoi plug in, mentre la libreria SyncUtils consente a GEC di comunicare con GEC_Scan e di utilizzare tali funzionalità. Si consideri inoltre la seguente nomenclatura: Utente è l’utente finale di GEC e di GEC_Scan, colui che opera i pro- grammi per archiviare documenti; si faccia attenzione alla differen- za tra “utente finale” e l’utente di una libreria (sviluppatore) o di un servizio (potrebbe essere un modulo software);
  • 8 2. Il caso di studio Documento un insieme di più immagini corredate da meta dati; ASP acronimo di Application Service Provider, è il sistema che permet- te tramite un remote desktop di utilizzare dal PC dell’utente finale un’istanza di GEC in esecuzione su un server remoto; da non con- fondersi con Active Server Pages di Microsoft (di cui non si parla mai in questo testo); Plug in è un modulo software che consente di aggiungere funzionalità a GEC_Scan senza la necessità di modificare o ricompilare il suo codice sorgente; Servizio esterno un qualsiasi servizio di archiviazione di documenti estraneo sia a GEC che a GEC_Scan, probabilmente pubblicato tramite un servizio web. Sessione di acquisizione l’insieme di operazioni iniziate dall’utente che portano all’acquisizione, riorganizzazione in documenti e archi- viazione di un insieme di immagini con i loro meta dati associati (punti da 2 a 9 in 2.2.1 nella pagina 10 e figura 2.3; si noti che tra il punto 3 e il punto 4 il sistema rimane indefinitamente in attesa di istruzioni dall’utente; quindi può essere un operazione che richiede molto tempo). In aggiunta—se non per esigenze particolari—si eviterà di seguito l’uti- lizzo di termini come “server” e “client” per riferirsi a GEC o GEC_Scan, poiché potrebbero essere causa di fraintendimenti; infatti nella sua ver- sione ASP, il software GEC viene eseguito su una macchina che interpreta il ruolo di server, ma per correttezza dovrebbe essere considerato client del software di acquisizione che gli offre per l’appunto un servizio.
  • Architettura generale e definizioni 9 2.2.1 Flusso di un tipico caso di utilizzo 1.richiede inizio sessione di acquisizione Utente 2.Request 3.mostra form principale ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ Acquisizione (tempo ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ ￿ indefinito) 4.accetta o annulla (C): GEC_SCAN (B): SyncUtils (A): GEC tempo 5.esegue il plug in adeguato 6.sottomette (E): Servizio esterno documenti (D): Plug in 7.fine del job 8.fine del job 9.Response Figura 2.3: Flusso di esecuzione di una richiesta e relativa risposta; vede- re 2.2.1 nella pagina successiva per una descrizione puntuale dei passi nume- rati; la progettazione e creazione delle entità marcate come (B), (C) e (D) costituiscono il caso di studio; le linee ondulate indicano il punto in cui l’utente può causare un’attesa a tempo indeterminato del sistema; (B) risolve i proble- mi di comunicazione tra (A) e (C) siano essi sulla stessa macchina oppure su macchine diverse comunicanti tramite Internet.
  • 10 2. Il caso di studio Un tipico caso di utilizzo del sistema—nel quale una singola istanza di GEC comunica con una singola istanza di GEC_Scan—si svolge co- me di seguito (i punti numerati si riferiscono a figura 2.3 nella pagina precedente): • il sistema dell’utente si avvia; parte automaticamente anche il soft- ware di acquisizione e carica dalla cartella plugins le estensioni di cui è stato dotato; • l’utente carica e opera GEC (versione installabile o ASP), mentre il programma di acquisizione (eseguito allo start up sul sistema dell’utente) resta in attesa nascosto; 1. l’utente esegue un’operazione su GEC per la quale è prevista l’acquisizione di un certo numero di documenti; 2. GEC—tramite una chiamata all’apposita libreria di comunicazione SyncUtils—effettua una richiesta a GEC_Scan; ad esso (sia que- sto in esecuzione sulla medesima macchina o su di una diversa) viene segnalato di mostrare la sua finestra principale, inviandogli nel contempo i meta dati da associare ai documenti che si andrà ad acquisire ed una lista di azioni desiderate da intraprendere su di essi; se la finestra era già aperta a seguito di una precedente richiesta non ancora soddisfatta, il lavoro viene messo in coda; 3. GEC_Scan accetta la richiesta e la accoda ad una lista o la scarta rispondendo con una lista di errori (ad esempio quando non è in grado di gestire la richiesta); 4. l’utente usa le funzionalità del programma di acquisizione per scan- sionare1 ed ordinare un certo numero di pagine inerenti la richiesta accettata; l’utente può inserire dei separatori tra gruppi di immagi- ni per organizzare il materiale acquisito in documenti: ad ogni do- cumento vengono associati tutti i meta dati ricevuti; alla fine della riorganizzazione e separazione dei documenti, l’utente sottomette i dati o annulla la sessione con appositi comandi; 1 Sulla scelta tra gli equivalenti italiani dell’inglese “to scan” l’Accademia della Crusca si è espressa nelle “Risposte ai quesiti” concludendo che sono accettabili tutte le soluzioni (scandire, scannare, scannerare, scannerizzare, scansionare) [3].
  • Cenni di implementazione e tecnologie utilizzate 11 5. GEC_Scan cerca tra i plug in caricati quelli in grado di soddisfare le richieste effettuate da GEC tramite la libreria di comunicazione ed esegue ogni operazione ritenuta pertinente su ogni documento; 6. questo può comportare il dialogo con un ulteriore software di ar- chiviazione, anche residente su una macchina diversa da quella su cui è in esecuzione GEC e da quella su cui esegue il programma di acquisizione (esempio: inviare tutti i documenti ad un servizio web); 7. il software di acquisizione completa ogni elaborazione e compila un rapporto con il dettaglio delle operazioni effettuate e un’eventuale lista di errori; 8. il software di acquisizione invia la risposta così costruita a GEC sempre tramite libreria di comunicazione; 9. GEC riceve la notifica che i suoi lavori richiesti sono stati completati (o annullati); • nel caso la coda di lavori del software di acquisizione non sia vuo- ta, l’utente viene invitato a continuare il suo lavoro con un’altra sessione di sottomissione documenti. 2.3 Cenni di implementazione e tecnologie utilizzate Il programma preesistente GEC è stato sviluppato in Visual Basic 6, kit di sviluppo capace di generare file di codice eseguibile nativo; esso permette anche una buona interazione con componenti COM. Nei seguenti tre capitoli, viene trattato il software sviluppato ex novo per aggiungere a GEC le funzionalità di acquisizione e archiviazione: libreria di comunicazione (progetto di nome “SyncUtils”) che per- mette ad un’istanza di GEC di effettuare richieste ad un’istanza di GEC_Scan e riceverne risposta, in 3 nella pagina 13;
  • 12 2. Il caso di studio software GEC_Scan che tramite interfaccia grafica permette l’acqui- sizione di documenti, in 4 nella pagina 43; plug in che permettono a GEC_Scan di accedere a servizi di archiviazione di terze parti, in 5 nella pagina 73. Tutti questi moduli software sono stati sviluppati nel linguaggio C# e utilizzano quindi il dotNet framework (versione 2.0 o successive). Con riguardo alla libreria di comunicazione, in 3.3.8 nella pagina 35 si parla della necessaria metodologia di interazione tra codice mana- ged e codice nativo: l’utilizzo di oggetti dotNet come componenti COM tramite software wrapper. Quest’ultimo sistema è stato adottato per permettere a GEC di chia- mare delle funzioni sviluppate con tecnologia dotNet; in questo modo la libreria SyncUtils è richiamabile sia da codice nativo (come avviene per GEC) sia da codice dotNet. Le funzioni di comunicazione utilizzano un meccanismo di sincronia e comunicazione basato sulla condivisione di un file system ( 3.2 nella pa- gina 14) per scambiarsi messaggi XML (formato descritto in 3.3.6 nella pagina 28) secondo il protocollo descritto in 3.3.4 nella pagina 24. La ge- nerazione del codice XML avviene tramite meccanismo semi automatico per serializzazione di oggetti ( 3.3.7 nella pagina 32). Per l’interfacciamento di GEC_Scan con le periferiche di acquisizio- ne immagini è stato usato TWAIN ( 4.2.1 nella pagina 50)—secondo le specifiche versione 1.9 [5]—tramite l’apposito sviluppo della libreria dotNetTwain. TWAIN si presenta come una DLL contenente codice nativo a 32 bit; per l’utilizzo da parte di dotNet, è quindi necessa- rio adottare ulteriori tecniche di interoperabilità con l’aiuto del package System.Runtime.InteropServices ( 4.3.1 nella pagina 59). Ogni plug in è stato sviluppato come una classe dotNet residente in un assembly dotNet2 . Per permettere il rapido sviluppo di plug in sono state sfruttate alcune caratteristiche del framework dotNet come reflection e caricamento dinamico di classi. 2 Un assembly dotNet è una DLL contenente bytecode dotNet nella forma di una o più classi appartenenti anche a diversi namespace, con i rispettivi meta dati di contorno.
  • Capitolo 3 SyncUtils 3.1 Requisiti e scopi di SyncUtils La parte del progetto che deve interagire più intimamente con GEC è la libreria di comunicazione chiamata SyncUtils. Mentre le funzionalità di archiviazione aggiuntive risiedono principalmente in GEC_Scan e nel- l’insieme dei suoi plug in, esse non potrebbero essere utilizzate da GEC senza l’ausilio di questa libreria: il suo scopo è quello di astrarre il meto- do di comunicazione dalle modalità tecniche con le quali le comunicazioni effettivamente avvengono. L’ambiente nel quale il sistema progettato deve operare prevede: • molti software di scansione (GEC_Scan) che hanno a disposizione varie sorgenti di dati e vengono manovrati da operatori umani; • uno o più software di controllo (GEC) che possano richiedere ad uno dei software di scansione di cominciare una sessione di acquisizione. GEC_Scan deve essere in grado di accettare una richiesta di inizio proce- dura di acquisizione, gestirla autonomamente e inviare alla fine la notifica dell’avvenuto lavoro senza tenere GEC in sospeso, permettendogli così di gestire nel frattempo eventuali altre comunicazioni. 13
  • 14 3. SyncUtils Siamo perciò interessati ad un tipo di comunicazione nella quale—nel caso peggiore (facendo riferimento alle figure 2.1 nella pagina 6 e 3.1): • come attori, sono coinvolte m istanze di GEC in versione installa- bile (al massimo una per macchina), n istanze remote di GEC in versione ASP, i istanze di GEC_Scan (al massimo una per macchi- na), con i, m ed n numeri imprecisati non necessariamente uguali, maggiori di 1; • ogni attore può prendere l’iniziativa di parola; • ogni GEC in versione installata può parlare col GEC_Scan presente sulla sua stessa macchina (se presente); • ogni GEC in versione ASP può parlare con qualsiasi GEC_Scan; • ogni GEC_Scan può parlare con GEC installato sulla sua stessa macchina e con qualsiasi altro GEC in versione ASP. Cioè: ogni utente col suo calcolatore deve poter usare la sua unica copia di GEC_Scan facendola interagire con un GEC installato sulla sua stessa macchina e/o con un certo numero di altri GEC remoti. 3.2 Architettura Si assuma che ogni copia di GEC_Scan abbia accesso in lettura e scrit- tura ad una porzione privata del file system dell’elaboratore sul quale è installato; chiunque voglia dialogare con esso (ad es. GEC installato o GEC versione ASP) dovrà poter avere accesso in lettura e scrittura alla stessa porzione del file system (per brevità di seguito “file system” o “FS”). La comunicazione è dunque a mezzo condiviso, nel senso che gli attori che dialogano tra loro di volta in volta devono condividere un file system cui hanno accesso; per analizzare il caso più generale continueremo a dire che ogni GEC condivide con l’i-esimo GEC_Scan il file system i (o FS-i). Le richieste vengono effettuate da un GEC scrivendo nel FS-i dei file con estensione .IN contenenti i dettagli delle richieste in formato
  • Architettura 15 XML ( 3.3.6 nella pagina 28); le risposte al termine di una sessione di acquisizione vengono fornite da GEC_Scan scrivendo nel FS-i dei file con estensione .OUT e nome file uguale alla corrispondente richiesta. GEC ASP ASP GEC GEC FS-i GEC_Scan-i Figura 3.1: Schema dell’utilizzo concorrente da più istanze di GEC del file system i, associato alla i-esima istanza di GEC_Scan. Naturalmente, più processi potrebbero accedere contemporaneamen- te ad uno stesso file system; è tuttavia stato approntato un meccanismo di mutua esclusione (descritto in 3.3.2 nella pagina 21): • per fare in modo che ogni richiesta abbia un nome univoco sul file system i; • per garantire che più GEC concorrenti nell’effettuare una richiesta allo stesso GEC_Scan non si intralcino a vicenda; • per fare in modo che le richieste siano ordinabili—per istante di arrivo—in una coda.
  • 16 3. SyncUtils Un attore che voglia prendere possesso del mezzo comunicativo costitui- to dal FS-i, per prima cosa utilizzerà detto meccanismo impedendo a qualunque altro attore di impadronirsi a sua volta del file system. Ovviamente non sempre un attore riesce a prenotare l’uso esclusivo del file system, ad esempio quando questo è già in uso da un altro attore; in seguito ( 3.3.2 nella pagina 21) si darà spiegazione dei dettagli del meccanismo: si dica “tentativo di acquisizione del lock ” il tentativo di impadronirsi del mezzo di comunicazione, e “acquisizione del lock ” il provare reiteratamente ad acquisire il lock finché non si abbia successo. La generazione di un nome file univoco nel FS-i, necessaria per la scrittura dei file .IN dipende da una stringa originata a partire dalla da- ta/ora della sua creazione: questa stringa e l’entità che essa rappresenta assumono il nome di JobId. Definiamo quindi un JobId come l’iden- tificativo univoco all’interno di un file system, utilizzato per identificare esattamente richieste e relative risposte. Ogni file di nome {JobId}.IN contiene delle informazioni organiz- zate in un’entità chiamata Request; ogni file di nome {JobId}.OUT contiene dati relativi ad un’entità di tipo Response. Come si può osservare in figura 3.2 nella pagina successiva, ogni Request contiene: • una o più ActionRequest; • uno e un solo AdditionalData; • un Timestamp. In ottica di programmazione ad oggetti, ActionRequest e AdditionalData sono solo delle classi base astratte, atte a definire l’archetipo dei campi contenuti in ogni richiesta: una Request ben for- mata non conterrà mai direttamente una ActionRequest, bensì una classe derivata da essa (similmente per AdditionalData ed altre clas- si simili); d’ora in avanti per “una ActionRequest” ad esempio, si intenderà una qualsiasi entità che derivi da ActionRequest e non un oggetto di tipo ActionRequest, se non diversamente specificato.
  • Architettura 17 Response Request Document Page ActionRequest ActionResponse ResponsePage Page ActionRequest ResponsePage Page ActionResponse ResponsePage Page AdditionalData ResponsePage Document ActionResponse Page ResponsePage ActionResponse ResponsePage Figura 3.2: Esposizione grafica della struttura di Request e Response; le frecce indicano che l’oggetto d’origine mantiene internamente un riferimento all’oggetto puntato. Le azioni intraprese da GEC_Scan alla ricezione di una Request dipendono dal tipo specializzato di ogni ActionRequest contenuta; AdditionalData contiene dei meta dati che devono essere interpreta- ti da GEC_Scan per soddisfare correttamente ogni richiesta e possono essere associati ai documenti acquisiti.
  • 18 3. SyncUtils Per quanto riguarda Response essa contiene sempre: • la sezione Documents; • la sezione Errors, relativa agli errori generici di comunicazione. Si ponga attenzione sul fatto che un documento è stato definito come un’insieme di una o più immagini: finora non si è parlato di alcun limite al numero di immagini acquisibili dall’utente, o di vincoli sul modo di raggrupparle in documenti; quindi nè il numero di documenti che ver- ranno elaborati nè il numero di pagine di ognuno di questi è predicibile salvo casi particolari1 . Per questi motivi, in una Response • Documents contiene uno o più oggetti Document, entità rappresentante un gruppo di immagini; • ogni Document contiene una lista di oggetti Page, entità rappresentanti le singole immagini scansionate dall’utente; • ogni Document contiene una o più ActionResponse (con considerazioni simili al caso di ActionRequest); • ogni ActionResponse rappresenta il rapporto su una sola azione intrapresa per il Document che la contiene in seguito all’elaborazione di una ActionRequest; • ogni ActionResponse contiene una lista di ResponsePage, enumerazione delle azioni intraprese sulle singole Page del Document. Anche in questo caso l’esatto contenuto di una ActionResponse dipen- de dalla classe specializzata che la estende. Si pensi al seguente esempio pratico: si ha la necessità di archiviare una fattura del signor Mario Ros- si presso un servizio di archiviazione remota chiamato “MyArchive”; a tale scopo saranno stati precedentemente definiti alcuni tipi specializzati come 1 In realtà GEC può richiedere un numerno minimo ed uno massimo di gruppi di immagini (vedi 4.1.1 nella pagina 47).
  • Architettura 19 • SendToMyArchiveActionRequest; • SendToMyArchiveActionResponse; • SendToMyArchiveActionPerformer; • SendToMyArchiveResponsePage; derivanti rispettivamente da • ActionRequest; • ActionResponse; • ActionPerformer; • ResponsePage. In più, per veicolare correttamente i dati relativi ad una fat- tura, InvoiceAdditionalData è stato derivato—con l’ag- giunta di appositi campi—da AdditionalData. A que- sto punto è possibile costruire una Request contenente ad esempio una sola SendToMyArchiveActionRequest ed un InvoiceAdditionalData. SendToMyArchiveActionRequest conterrà informazioni relative l’indirizzo al quale raggiungere il servizio MyArchive ed impostazioni simili; InvoiceAdditionalData potrebbe contenere l’anagrafica del signor Mario Rossi e ogni altro dato utile ai fini della compilazione dei meta dati associati ai documenti veri e propri. Se l’utente ha acquisito due documenti da tre pagine ciascuno, con tale ri- chiesta è lecito aspettarsi una risposta per cui vi saranno due Document contenenti tre Page e una SendToMyArchiveActionResponse cia- scuno; ogni SendToMyArchiveActionResponse conterrà a sua volta tre SendToMyArchiveResponsePage, ognuna delle quali—facendo riferimento ad una singola Page—fornirà ad esempio informazioni sul successo o meno dell’archiviazione, un link per recuperare l’immagine corrispondente ecc. . .
  • 20 3. SyncUtils Tutti i tipi specializzati, finalizzati a specifiche richieste, sono defi- niti nei moduli aggiuntivi chiamati plug in, caricati in modo dinamico all’avvio di GEC_Scan. 3.3 Dettagli implementativi e tecnologie Posto che GEC_Scan è un modulo software a se stante, anche il solo aggiungere a GEC le funzionalità di comunicazione con quest’ultimo ri- chiede necessariamente la modifica del programma e l’aggiunta di molto codice. Integrare direttamente in GEC queste capacità sviluppandole in Vi- sual Basic 6—lo stesso linguaggio in cui è stato scritto—sarebbe potuta sembrare una buona idea: al contrario, aggiungere a del codice già ma- turo una ulteriore complessità per quanto lieve, potrebbe portare a dei problemi di manutenibilità. Se si aggiunge il fatto che il kit di sviluppo di Visual Basic 6 è alla fine del suo ciclo di vita, si capisce come sia stato preferibile comporre il sistema di comunicazione con una tecnologia più moderna: si è scelto di sviluppare con dotNet e la tecnologia COM del codice che—proprio dal punto di vista del ciclo di vita—è autonomo e interfacciabile con GEC con un ridotto numero di modifiche a quest’ultimo. La libreria SyncUtils è utilizzabile da codice dotNet come un qualsiasi altro assembly e (previa registrazione di alcuni tipi, 3.4 nella pagina 40) come componente COM. 3.3.1 Perché il file system? La comunicazione tra sistemi informatici è sempre un ambito insidioso e ricco di difficoltà, spesso nemmeno troppo palesi; anche per questo motivo, nell’affrontarlo si tenta quasi sempre di evitare di reinventare la ruota: come in molte altre situazioni ci si affida più il possibile a soluzioni già note ed affermate, almeno per i problemi più comuni. Nel caso in esame, ci si trova nella situazione abbastanza tipica di un software (GEC_Scan) che pur eseguendo sull’elaboratore di un utente finale (ed essendo perciò molto probabilmente isolato dalla rete da un
  • Dettagli implementativi e tecnologie 21 firewall e da un sistema NAT/NAPT) deve comportarsi come server, attendendo richieste e non iniziandole. Le possibili soluzioni note che usino protocolli consueti come TCP/IP, sarebbero comunque state tutte piuttosto complesse da mettere in pratica: fortunatamente il sistema ASP—come già accennato—mette a disposizione un file system condiviso tra i suoi utilizzatori. L’utilizzo di un file system condiviso per la sincronia tra processi, la regolamentazione di accesso esclusivo a risorse e lo scambio di informazio- ni, è una pratica da tempo largamente diffusa e di dimostrata efficacia2 : lo studio di alcune specifiche Microsoft [8] ed alcuni esperimenti condotti, hanno consentito di giudicare tali metodologie idonee anche all’utilizzo su un file system condiviso con una macchina remota tramite il sistema ASP. Ciò permette di delegare al gestore del file system alcune problemati- che descritte in seguito come l’atomicità della creazione di un oggetto di sincronia, la sicurezza del canale trasmissivo, la capacità di comunicare in determinate condizioni. 3.3.2 Meccanismo di sincronizzazione L’acquisizione del lock per il file system i, si effettua tramite la scrittura in esso di un file—vuoto—di nome concordato che non possa collidere con gli eventuali file .in e .out già presenti (attualmente esso si chiama lockfile.lck). Si dice che tale lock viene rilasciato quando il file viene cancellato. Un attore che volesse acquisire il lock dovrebbe per prima cosa veri- ficare che tale file non esista già e solo in tal caso provvedere a crearlo; una volta creato, altri attori desiderosi di acquisire il lock rileverebbero la presenza del file sopra citato e desisterebbero (o resterebbero in attesa che il file venga rimosso, a seconda dei casi). È di fondamentale importanza capire come l’operazione di verifica e creazione del file di lock debba necessariamente essere atomica per gestire le eventuali situazioni di concorrenza: non è accettabile alcuna situazio- 2 Basti pensare ai files di lock utilizzati da alcuni software di database o a quelli usati del popolare Microsoft Word per regolare l’accesso ai suoi documenti.
  • 22 3. SyncUtils ne di inconsistenza come quella riportata di seguito a titolo d’esempio: nell’ordine 1. l’attore A vuole acquisire il diritto di parola, pertanto tenta di segnalarlo, provando a creare il file di lock ; 2. in un istante di tempo ravvicinato ad (1) l’attore B vuole acquisire anch’esso il diritto di parola, pertanto tenta di creare a sua volta il file di lock ; 3. A verifica che il file di lock non esiste; 4. B verifica che il file di lock non esiste; 5. A crea fisicamente il file di lock ; 6. B—visto il punto 4—crede che il file di lock non esista e lo sovrascrive. Se il sistema di sincronia fosse prono ad un tale tipo di errore, due attori potrebbero ritenere di essere ognuno l’unico autorizzato ad accedere al file system in un dato istante di tempo, creando JobId non certamente univoci e sovrascrivendo potenzialmente l’uno i file dell’altro, portando così ad evoluzioni impredicibili nella comunicazione. Il problema della atomicità delle operazioni di verifica e creazione del file di lock viene risolto considerando che all’atto della creazione di un nuovo file, in ambiente Microsoft Windows si può indicare al sistema cosa fare nel caso in cui non sia possibile completare l’operazione (es: perché il file esiste già): sovrascrivere il file, accodare al contenuto o segnalare un’errore. Quando un attore desidera acquisire il lock, può semplicemente provare a creare il file, istruendo il sistema operativo a sollevare un’errore in caso di fallimento: se non si riceve alcun errore il lock è acquisito, altrimenti no. Al sistema operativo si delega la responsabilità della consistenza del suo file system; in caso di file system remoti, il sistema operativo che fornisce l’accesso remoto ad un file system da lui mantenuto si occupa di garantire la consistenza quando più processi tentino di creare lo stesso file [8].
  • Dettagli implementativi e tecnologie 23 Per impedire che altri processi come un antivirus o lo stesso uten- te possano inavvertitamente interferire col meccanismo di sincronia, il file viene mantenuto aperto in modalità esclusiva per tutto il tempo di detenzione del lock : in questo modo è impossibile per chiunque altro cancellarlo, rinominarlo o modificarlo. Nel caso il processo detentore del lock venga terminato, il file resta scritto sul file system, ma la modalità esclusiva viene a mancare: ogni eventuale altro processo in attesa di rilascio del lock non avrà speranze di acquisirlo fino al prossimo riavvio di GEC_Scan che causa la pulizia dei file residui di vecchie esecuzioni. 3.3.3 Identificativi di richieste e risposte Richieste e risposte vengono scambiate tramite dei file di estensione .IN e .OUT, diversi dal file di lock ma residenti nel suo stesso file system. Questi file hanno sempre il nome costituito da dodici cifre ed un trattino orizzontale, che rappresenta il JobId del lavoro in questione. Un JobId è stato definito come l’identificativo univoco all’interno di un file system, utilizzato per identificare univocamente richieste e ri- sposte. Esso viene generato a partire dalla data-ora della sua creazione; nell’implementazione C# è in realtà un oggetto che conserva come stato interno un timestamp, e permette di ricavare da questo una rappresenta- zione stringa nel formato yyyyMMdd-hhmmss dove “y” sono le quattro cifre corrispondenti all’anno, “M” quelle corrispondenti al mese, “d” al giorno, “h” all’ora, “m” ai minuti e “s” ai secondi. Per creare un JobId valido (avendo certezza della sua univocità per un determinato file system) ed associarlo ad una richiesta si effettua la seguente procedura: • si acquisisce il lock ; • si crea a partire da data e ora correnti la stringa yyyyMMdd-hhmmss; • si usa la stringa come nome file, considerando che l’estensione per una richiesta dev’essere .IN;
  • 24 3. SyncUtils • se tale nome file esiste già, si incrementa di uno il numero indi- viduato graficamente da yyyyMMddhhmmss e si torna al punto precedente (senza preoccuparsi del fatto che il numero corrisponda ancora ad una data-ora valida; es: 800419170261 non corrisponde ad una data-ora valida, ma è comunque valido come base per creare un JobId); • si crea il file; • si scrive una richiesta valida nel file; • si rilascia il lock. Una risposta ad una richiesta ha il suo stesso nome file ma con estensione .OUT; i file di richiesta e di risposta, contengono dati in formato che vedremo in seguito (vedi 3.3.6 nella pagina 28). Spesso una risposta fornisce anche un certo quantitativo di dati— nel nostro caso, ad esempio le immagini catturate: formato e ubicazione di questi dati sono indipendenti dal sistema di comunicazione, e deter- minati dai singoli plug in che gestiscono l’archiviazione (vedi 5.2 nella pagina 76), ma vengono comunque specificati in apposite sezioni nel file .OUT. 3.3.4 Protocollo Di seguito vengono riportate in pseudo codice alcune delle procedure che costituiscono il protocollo di comunicazione3 . Le procedure con prefisso GEC sono eseguite su iniziativa di GEC sulla macchina ove questo è in esecuzione, mentre quelle con prefisso SCAN si riferiscono al software di scansione GEC_Scan. FS sta per file system. L’indice i riferisce il prefisso al file system i-esimo. “Lavoro” o “Job” è il termine generico per indicare una richiesta, solitamente coincidente con la richiesta di inizio sessione di acquisizione. 3 Protocollo studiato dal prof. Alberto Bartoli con l’aiuto della documentazione prodotta dall’ing. Giorgio Davanzo; implementazione pratica in C#, sostituzione dell’active poll con sistemi di notifica e cura di dettagli tecnici pratici a cura di Giancarlo Todone.
  • Dettagli implementativi e tecnologie 25 Algorithm 3.1 Lock-CleanUp: Da eseguire all’avvio di GEC_Scan. 1: if EXISTS(lockfile-i) then 2: TIMEOUT = 10sec 3: BeginWait = now() 4: while now() < BeginWait + TIMEOUT do 5: if NOT EXISTS(lockfile-i) then 6: break; 7: end if 8: end while 9: DELETE lockfile-i 10: end if Algorithm 3.2 GEC-RequestJob: Da eseguire per richiedere un lavoro. 1: GET lock-i 2: GET NEW JobId-i 3: RELEASE lock-i Algorithm 3.3 GEC-Abort: Da eseguire per annullare una richiesta di lavoro pendente su iniziativa di GEC, dato il suo JobId. 1: GET lock-i 2: DELETE request-file (filename = JobId.IN) in FS-i 3: RELEASE lock-i Algorithm 3.4 SCAN-CleanUp: Da eseguire all’avvio del programma di scansione; FS-i viene riportato nello stato iniziale. Per semplicità, non si cerca di recuperare eventuali lavori effettuati nella incarnazione precedente ma non completati. 1: EXEC Lock-CleanUp 2: GET lock-i 3: DELETE ALL response, data, request files (in quest’ordine) 4: RELEASE lock-i
  • 26 3. SyncUtils Algorithm 3.5 SCAN-Abort: Da eseguire per annullare (su iniziativa del software di acquisizione) una richiesta di lavoro pendente, dato il suo JobId. 1: GET lock-i 2: GENERATE EMPTY response-file (name = JobId.OUT) in FS-i 3: RELEASE lock-i Algorithm 3.6 SCAN-CompleteJob Da usarsi per segnalare il completamento (andato a buon fine o meno) di una richiesta di lavoro. 1: GET lock-i 2: if EXISTS(Input) then 3: CREATE data-files in FS-i 4: CREATE Output-file in FS-i 5: else 6: DISMISS data 7: end if 8: RELEASE lock-i In pratica: • Per effettuare una richiesta, il GEC deve: ￿ acquisire il lock; ￿ calcolare un JobId valido; ￿ scrivere un file {JobId}.IN contenente la richiesta nel corretto formato; ￿ rilasciare il lock; ￿ restare in attesa che venga scritto nella stessa posizione un file con lo stesso nome ma estensione .OUT. • Per recepire la richiesta ed effettuare una risposta, il GEC_Scan deve: ￿ restare in attesa finché sul file system predefinito non viene creato un file con nome corrispondente ad un JobId valido ed estensione .IN;
  • Dettagli implementativi e tecnologie 27 ￿ acquisire il lock; ￿ leggere il contenuto del file di richiesta; ￿ rilasciare il lock ; ￿ elaborare la richiesta; ￿ acquisire il lock ; ￿ scrivere il file .OUT; ￿ rilasciare il lock. Come si può notare dall’ultima esemplificazione, l’elaborazione viene fatta da GEC_Scan mentre il lock non è mantenuto, per permette- re che altre comunicazioni avvengano mentre è in atto un processo di archiviazione molto lungo. Inoltre è visibile che se GEC_Scan notasse la presenza del file di richiesta e tentasse di leggerlo prima che il GEC abbia finito di scriverlo, non vi riuscirebbe semplicemente perché prima dovrebbe acquisire il lock ancora detenuto dalla controparte. Un analogo ragionamento si può fare per il file di risposta. Se questo tipo di meccanismo da un lato permette una corretta evolu- zione delle dinamiche di comunicazione, dall’altro introduce anche delle situazioni in cui i processi comunicanti si intralciano a vicenda, spre- cando delle risorse; per questo motivo nell’implementazione reale degli algoritmi di comunicazione si sono previsti dei piccoli ritardi program- mabili, impostati poi a valori dedotti in modo sperimentale. Questi non alterano in alcun modo la logica della comunicazione, ma minimizzano i tempi di attesa attiva di ciascuna parte. 3.3.5 Problematiche delle notifiche Inizialmente si erano concepite ulteriori due procedure: SCAN-CheckForRequests con la quale il software di acquisizione controllava l’eventuale arrivo di nuove richieste; GEC-CheckJobCompleted che si occupava di controllare—dal lato di GEC—se una richiesta fatta in precedenza non fosse stata soddisfatta.
  • 28 3. SyncUtils Le due funzioni utilizzavano la tecnica dell’active poll, interrogando di continuo il file system sulla presenza o meno di determinati file. Consi- derato che questo caricava notevolmente i file system (e in minor parte anche la rete, in caso di sistema acceduto da remoto) si è provveduto a modificare il meccanismo in modo da utilizzare strumenti chiamati FileSystemWatcher [9]. Questi consistono fondamentalmente in hook 4 di sistema alle funzioni di base di accesso al disco, che sollevano un evento quando si presentano determinate condizioni, come il tentativo da parte di un programma di scrivere un nuovo file in una cartella. Purtroppo, come rilevato anche da una nutrita schiera di utenti5 , il codice che implementa questa tecnica a volte ha dei comportamenti inattesi quando eseguito in ambiente Microsoft Windows 2003 Server. All’epoca della prima stesura del programma non erano disponibili dei work around efficaci o soluzioni pubbliche: si è stati costretti a svi- luppare una soluzione alternativa poi segnalata sulla pagina ufficiale di bug report6 . La soluzione consiste nel sopperire ai comportamenti inattesi con de- gli snapshot della cartella ove avvengono gli scambi dei file: se tutto funziona come da specifiche Microsoft, l’esecuzione procede normalmen- te e le operazioni sul file system generano le notifiche attese; se invece qualcosa dovesse andare male, viene richiamato un gestore d’errore con del codice d’emergenza che confrontando il sotto albero della cartella al momento dell’errore con quello stimato durante le esecuzioni corrette, lo aggiorna e genera le notifiche di conseguenza. 3.3.6 Formati dei file di comunicazione Il formato interno dei file di comunicazione si basa su XML. Una richiesta tipo, contenuto di un file .IN si presenta come nel listato 3.1. 4 Tecnica di intercettazione e modifica al volo di dati comunicati tra funzioni di sistema; può essere “installato” in una catena di messaggi o in mezzo ad una serie di chiamate a routine [10, 13]. 5 https://connect.microsoft.com/VisualStudio/feedback/ ViewFeedback.aspx?FeedbackID=337917 6 https://connect.microsoft.com/VisualStudio/feedback/ Workaround.aspx?FeedbackID=337917
  • Dettagli implementativi e tecnologie 29 Listing 3.1: Esempio di XML generato da SyncUtils per effettuare una richiesta. 1 <Request> 2 <ActionRequest xmlns:xsi="http://www.w3.org/2001/ XMLSchema-instance" xmlns:xsd="http://www.w3.org /2001/XMLSchema" xsi:type="SaveToFileActionRequest" ARQID="0"> 3 <Performer name="SaveToFileActionPerformer" /> 4 <RequiredFileFormat>png</RequiredFileFormat> 5 <Path>c:lockpath</Path> 6 </ActionRequest> 7 <AdditionalData xmlns:xsi="http://www.w3.org/2001/ XMLSchema-instance" xmlns:xsd="http://www.w3.org /2001/XMLSchema"> 8 <OwnerID>64</OwnerID> 9 <CustomerID>myCustomerID</CustomerID> 10 </AdditionalData> 11 <Timestamp>2009-02-25T16:59:48.375+01:00</Timestamp> 12 </Request> Il codice XML viene generato semi automaticamente per serializza- zione di oggetti (vedi 3.3.7 nella pagina 32), quindi in questo frangente, con un nome ci si potrà riferire indistintamente al tag o alla classe che lo ha generato. In 3.2 nella pagina 16 parlando della struttura del sistema di comu- nicazione, è stato detto che Request e Response reali contengono solo tipi specializzati di ActionRequest, ActionResponse ecc. . . Nel listato di esempio è immediato però notare che i nomi dei tag di alcune entità derivabili sono dati da quelli delle loro classi base; questo fa parte del meccanismo di serializzazione: il nome della classe specializzata è contenuto in questi casi dall’attributo xsi:type. Non si fa affidamen- to a xsi:type per la corretta serializzazione del campo Performer che contiene semplicemente il nome della classe preposta a gestire la ActionRequest che lo contiene. In 3.2 nella pagina seguente si può osservare un esempio di risposta, contenuto nel file .OUT corrispondente ad un file .IN di richiesta.
  • 30 3. SyncUtils Listing 3.2: Esempio di XML generato da SyncUtils per effettuare una risposta. 1 <Response xmlns:xsi="http://www.w3.org/2001/XMLSchema- instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema "> 2 <Documents> 3 <Document GID="0"> 4 <Pages> 5 <Page PID="0" /> 6 <Page PID="1" /> 7 <Page PID="2" /> 8 </Pages> 9 <ActionResponses> 10 <ActionResponse xsi:type=" SaveToFileActionResponse" ARSID="0" Request=" 0"> 11 <Errors /> 12 <Pages> 13 <ResponsePage xsi:type="FSPage" Page="0" Format="png" FileName="fileName1" /> 14 <ResponsePage xsi:type="FSPage" Page="1" Format="png" FileName="fileName2" /> 15 <ResponsePage xsi:type="FSPage" Page="2" Format="png" FileName="fileName3" /> 16 </Pages> 17 <Path>c:lockpath</Path> 18 </ActionResponse> 19 </ActionResponses> 20 </Document> 21 </Documents> 22 <Errors> 23 <Error IsCrytical="true" EID="0"> 24 Test error message 2 ! 25 </Error> 26 </Errors> 27 <Timestamp>2009-02-27T23:14:52.640625+01:00</Timestamp > 28 </Response> Si possono notare gli attributi PID, GID, EID ecc. . . che fungono
  • Dettagli implementativi e tecnologie 31 da identificatori univoci per l’entità cui appartengono all’interno del loro contenitore; in figura 3.2 nella pagina 17 le frecce indicano che l’oggetto da cui partono conserva un identificativo dell’oggetto punta- to, cui si riferiscono logicamente, come una ActionResponse indica l’ActionRequest da cui è originata. Il meccanismo di distribuzione degli identificativi è tale che un identificativo sia univoco tra quelli degli oggetti contenuti nello stes- so contenitore (ad esempio: non ci sono ripetizioni tra gli id del- le ActionResponse in un medesimo Document, ma due id di ActionResponse in due Document distinti possono essere uguali poiché non è comunque possibile equivocare tra loro). Dal punto di vista dell’implementazione, ogni oggetto che può funge- re da contenitore per altri oggetti mantiene internamente un oggetto di tipo IdFactory per la generazione consistente e la distribuzio- ne degli identificativi; tutti gli oggetti che possono venire identificati per qualche motivo implementano l’interfaccia IHasId; tutti gli oggetti che possono fare riferimento ad un altro oggetto implementante IHasId implementano a loro volta l’interfaccia IIdPointer. È forse doveroso ribadire che normalmente per individuare precisa- mente un oggetto manterremmo semplicemente una managed reference a quest’ultimo; ma questo tipo di riferimento, nella situazione proposta è di difficile espressione durante la serializzazione XML. Listing 3.3: Riassunto dei meccanismi di distribuzione consistente degli identificativi. 1 public interface IHasId 2 { 3 int Id { get; set; } 4 } 5 6 public interface IIdPointer 7 { 8 int PointedId { get; set; } 9 void PointTo(IHasId item); 10 } 11 12 public class IdFactory 13 {
  • 32 3. SyncUtils 14 int _count = 0; 15 ... 16 public virtual IHasId SetID(IHasId item) 17 { 18 item.Id = _count++; 19 return item; 20 } 21 ... 22 } 23 24 public class IdFactory<T> : IdFactory where T : IHasId, new() 25 { 26 ... 27 public override IHasId SetID(IHasId item) 28 { 29 ... 30 return base.SetID(item); 31 } 32 } 3.3.7 Serializzazione XML Le tecniche di serializzazione XML in generale, e quelle utilizzate secon- do le usanze del framework dotNet in particolare, sono estremamente note in letteratura [12], ma è bene soffermarsi su certi loro aspetti per gli interessanti risvolti scaturiti durante lo sviluppo di alcune parti del software di interscambio dati. La serializzabilità di un tipo in ambito dotNet è cosa diversa dalla sua serializzabilità XML: ai nostri scopi tutti i tipi primitivi sono XM- L-serializzabili e in aggiunta—grazie all’uso della reflection—anche molti tipi complessi non espressamente pensati in ottica di serializzazione XML sono comunque XML-serializzabili. Nel package System.Xml.Serialization si trovano gli strumen- ti per effettuare serializzazione e deserializzazione di oggetti secondo due modalità che potremmo definire “esplicita” ed “implicita”:
  • Dettagli implementativi e tecnologie 33 • nella modalità esplicita, gli oggetti candidati alla serializ- zazione devono implementare esplicitamente l’interfaccia IXmlSerializable, che prescrive l’implementazione di tre metodi per la serializzazione, la deserializzazione e la definizione di uno schema; • nella modalità implicita, il framework ispeziona tramite reflection le classi degli oggetti da serializzare e considerando solo campi e proprietà pubbliche genera al volo un oggetto serializzatore adatto. Nella maggior parte dei casi la modalità implicita è più che sufficiente, se si considera che è possibile istruire in certa misura il serializzatore su alcuni dettagli come nomi dei tag, tipo dei nodi ecc. . . mentre con la mo- dalità esplicita—che pur permette di risolvere problemi più complessi—è necessario scrivere molto codice, introducendo problemi di leggibilità e manutenibilità del sorgente. Solitamente si fa uso di una sola tecnica per volta, ma accade anche che le due vengano combinate, ad esempio quando si serializza un ogget- to implicitamente serializzabile che però serba in alcuni dei suoi campi oggetti esplicitamente serializzabili; ciò che accade è esemplificato nel seguente algoritmo: • se l’oggetto da serializzare è un tipo primitivo, allora è ben conosciuto: si richiama una procedura standard per la sua serializzazione e si termina la procedura; • se l’oggetto da serializzare implementa IXmlSerializable, al- lora si chiama il metodo IXmlSerializable.WriteXml() e si termina la procedura; • se l’oggetto non appartiene alle precedenti due categorie ma è comunque XML-serializzabile, allora si itera l’algoritmo su ogni campo. Analogamente per la deserializzazione. È ovvio che nel caso di un og- getto complesso con campi complessi tutti implicitamente serializzabili,
  • 34 3. SyncUtils l’algoritmo itera finché non è stato analizzato l’intero albero7 ; è altresì visibile che qualora venga incontrato un oggetto IXmlSerializable, i suoi metodi WriteXml/ReadXml si occuperanno della sua intera serializzazione/deserializzazione, fermando la ricorsione dell’algoritmo summenzionato. Nello sviluppo del progetto si sono affrontati dei casi in cui ciò rappre- senta un problema: gli oggetti che gestiscono richieste e risposte utilizza- no meccanismi di serializzazione complessi, poichè gli oggetti scambiati durante una comunicazione non sempre sono ben conosciuti da entram- bi gli interlocutori; quindi questi oggetti implementano esplicitamente i meccanismi con IXmlSerializable. Nonostante ciò, per semplificare lo sviluppo futuro di ulteriori oggetti contenuti in richieste e risposte, si desidera che tali oggetti non debbano necessariamente implementare essi stessi le loro procedure di serializzazione in maniera esplicita. È stato quindi adottato un espediente: nel codice di serializzazione personalizzato di Request e Response, ogni loro campo viene serializ- zato/deserializzato istanziando di volta in volta un nuovo serializzatore, che inizia nuovamente per ogni loro campo l’algoritmo descritto in pre- cedenza; i flussi di codice XML prodotto nelle singole serializzazioni di questi oggetti vengono poi fusi col flusso principale dell’oggetto padre a posteriori (analogamente, durante la deserializzazione avviene l’estrazio- ne di porzioni di XML dal flusso principale che generano oggetti reinseriti negli appositi campi dell’oggetto padre a posteriori). Tutti i tipi conosciuti da un’esecuzione della libreria di comunicazio- ne, vengono mantenuti in un registro statico e pubblico, accessibile da ogni serializzatore; l’insieme di queste tecniche permette la serializzazio- ne/deserializzazione di tipi non noti a compile time, e la corretta gestio- ne della loro ereditarietà, mantenendo comunque semplice lo sviluppo di funzionalità aggiuntive. 7 Per semplicità e compattezza vengono taciute le problematiche di visita come ad esempio riferimenti incrociati e similari, pur esistenti ed affrontati dall’implementazione reale dell’algoritmo.
  • Dettagli implementativi e tecnologie 35 3.3.8 COM COM è acronimo di Component Object Model, un’architettura che permette l’interfaccia di componenti software di varie parti senza che queste siano ricompilate in un progetto, al fine di promuoverne l’interoperabilità. Esso non è dipendente da un particolare linguaggio—ogni linguaggio che sia in grado di chiamare funzioni tramite puntatori può accedere alle sue funzionalità—e si basa sui concetti di componente ed interfaccia. Tali componenti ed interfacce sono conservati in delle DLL struttu- rate secondo un metodo prestabilito, e riferiti durante il loro utilizzo secondo un peculiare meccanismo di doppia indirezione: si mantiene il riferimento ad un componente tramite l’indice di una tabella—chiamata “vtable”—che a sua volta conserva un’indirizzo reale alla struttura dati desiderata: in questo modo le vtable possono essere condivise da più istanze di un oggetto riducendo l’utilizzo di memoria. Un componente COM somiglia in struttura a ciò che è una classe per la programmazione ad oggetti. In una gerarchia di classi, in qualsiasi contesto di programmazione esiste un ancestor, ovvero il modello di un’entità di base con un insieme minimo di caratteristiche e funzionalità di base che qualsiasi altro oggetto deve possedere; in ambito COM, si dice che tale oggetto implementa almeno l’interfaccia IUnknown. Listing 3.4: Interfaccia IUnknown come appare in C++. 1 class IUnknown 2 { 3 virtual HRESULT QueryInterface(IID& iid, void** ppvObj) = 0; 4 virtual ULONG AddRef() = 0; 5 virtual ULONG Release() = 0; 6 } Questa interfaccia prescrive l’implementazione di tre metodi:
  • 36 3. SyncUtils QueryInterface permette di interrogare il componente imple- mentante circa il supporto o meno di un’altra specifica interfaccia; AddRef viene chiamato per aumentare il conteggio interno degli uti- lizzatori (insieme a Release implementa un primitivo Garbage Collector); Release decrementa il conteggio interno degli utilizzatori e libera la memoria quando questo raggiunge lo 0. Un componente COM può supportare un numero qualsiasi di interfacce a partire da un minimo di una (IUnknown); le interfacce possono essere prese dal programmatore tra alcune predefinite, derivate da esse o create ex novo. Per permettere la risoluzione degli identificativi univoci che contrad- distinguono componenti ed interfacce COM a partire da nomi, tali ele- menti devono necessariamente essere registrati, cioè iscritti ad elenchi siti in determinate aree del registro di configurazione di Windows8 . La tecnologia dotNet, con il suo proprio concetto di riusabilità sta soppiantando di fatto il Component Object Model, nonostante que- st’ultimo sia stato forse una delle tecnologie più usate dal 1993 ad oggi9 . Il passaggio da una tecnologia all’altra viene reso più facile dalla prevista possibilità di interazione tra le due: questa opportunità si espli- cita con il Runtime Callable Wrapper (RCW) necessario per eseguire una chiamata a un server COM da un client dotNet, e il COM Callable Wrapper (CCW) per eseguire viceversa una chiamata a un server dotNet da client COM10 . Come suggerito dai nomi, queste due entità sono del 8 In realtà COM fu portato anche su sistemi operativi diversi da Windows e addi- rittura su macchine diverse dai PC compatibili; in questi sistemi la risoluzione degli identificativi avviene in modo analogo ma implementativamente diverso; l’uso più esteso di COM rimane quello su piattaforme Windows, dal 3.x fino alle più moderne XP, Vista e Seven. 9 Nata nel 1993, questa tecnologia è stata menzionata con enfasi da Microsoft solo a partire dal 1997. 10 Chiamiamo “client COM” una porzione di un programma in grado di utilizzare un servizio esposto da un oggetto COM; chiamiamo altresì “server COM” il componente che espone tale funzionalità.
  • Dettagli implementativi e tecnologie 37 codice di contorno aggiunto automaticamente ai componenti di una del- le due metodologie di sviluppo per essere interpretati ed utilizzati anche dalla controparte tecnologica. ￿ Component RCW Consumer Unmanaged Managed Consumer CCW Class Figura 3.3: Schema di funzionamento di RCW e CCW; nella prima riga un componente dotNet richiama funzionalità da un provider COM; nella seconda, del codice capace di richiamare funzionalità COM chiama codice dotNet tramite il CCW. Nonostante ogni CCW sia generato automaticamente dal runtime dotNet, le classi da esporre come componenti COM devono venire ade- guatamente preparate; l’ambiente integrato di sviluppo Visual Studio 2008 offre degli automatismi per assolvere a questo compito, ma resta preoccupazione del programmatore decorare le classi dotNet con attributi atti ad istruire il runtime nella creazione dei CCW ad esse dedicati. Fondamentali ComVisible che abilita il processo di wrapping e ProgId che assegna il nome al componente. Eventuali altri attributi da applicare possono essere Guid, che specifica un identificativo univoco altrimenti assegnato automaticamente e DispId che specifica in maniera esplicita l’ordine in cui i metodi devono comparire in un’interfaccia11 . 11 Per la corretta compilazione del codice, se almeno un metodo di una classe è stato decorato con DispId, allora lo devono essere tutti all’interno della stessa classe.
  • 38 3. SyncUtils Listing 3.5: Esempio della decorazione minima di un componente da esporre tramite COM. 1 [InterfaceTypeAttribute(ComInterfaceType. InterfaceIsIDispatch)] 2 public interface _JobId 3 { 4 string ToString(); 5 bool Equals(object value); 6 void Increment(); 7 JobId Clone(); 8 } 9 10 [ClassInterface(ClassInterfaceType.None)] 11 [ProgId("SyncUtils.JobID")] 12 [ComVisible(true)] 13 public class JobId : _JobId 14 { 15 ... 16 } Una particolare menzione meritano la gestione degli eventi e quella degli errori; mentre i secondi sono propagati automaticamente dal siste- ma semplicemente assegnando un codice d’errore ad ogni eccezione non gestita, l’utilizzo dei primi richiede del codice aggiuntivo. Proprietà di un’interfaccia C# come potrebbe essere 1 event JobCompletedDelegate OnJobCompleted; vengono viste in realtà dai client COM come una coppia di metodi per l’aggiunta e la rimozione di gestori d’evento 1 AddJobCompletedDelegate(); 2 RemoveJobCompleteDelegate(): ma per permettere ad un COM client di venire notificato da un componente, è necessario assegnare esplicitamente a quest’ultimo an- che un’interfaccia in cui si specifica il metodo da chiamare al verificarsi dell’evento; del tipo
  • Dettagli implementativi e tecnologie 39 1 [InterfaceTypeAttribute(ComInterfaceType. InterfaceIsIDispatch)] 2 public interface UserEvents 3 { 4 void OnJobCompleted(JobId job, Response response); 5 ... 6 } Dal punto di vista dell’utilizzo con Visual Basic 6, l’utilizzo di un oggetto COM richiede un file che ne specifichi le funzioni presenti: tale file—di estensione .tlb—viene chiamato “type library”, ed è generato automaticamente all’atto della registrazione di un componente. Impor- tando la type library, è poi possibile cominciare ad usare qualsiasi dei tipi ivi definiti, curandosi di dichiarare con la clausola WithEvents quelli dei quali si voglia usare la capacità di sollevare eventi. Nel progetto del sistema di archiviazione proposto, le classi ad essere predisposte per l’utilizzo come componenti COM sono essenzialmente quelle riguardanti la libreria della comunicazione, e in maggior dettaglio quelle che permettono a GEC di richiedere un servizio a GEC_Scan: ClientSideFileSync; Request; Response; ActionRequest; ActionResponse; Document; DocInfo; Page; ResponsePage; Error
  • 40 3. SyncUtils e classi da queste derivate. Lo sviluppatore che intendesse creare ulteriori plug in aggiuntivi dovrà curarsi che le sue classi siano accessibili a client COM per permetterne l’uso da parte di GEC. 3.4 Installazione e preparazione dell’ambiente di esecuzione Il software in tutte le sue parti è in fase di test, e non viene quindi ancora distribuito al di fuori del contesto di sviluppo. Per questo motivo non è fornito di un programma di installazione e di istruzioni user friendly, ma solo di note tecniche, la maggior parte delle quali messe insieme durante la soluzione di reali problematiche di set up, o collezione di alcuni brani rilevanti dalla documentazione e dai commenti al codice sorgente. I tre tipi di software prodotti eseguono in due ambienti eventualmente coincidenti: • il calcolatore che esegue GEC • il personal computer dove viene eseguito il software di acquisizione e i suoi plug in Per quanto riguarda il lato controllore, devono essere distribuiti assieme a GEC tutti gli assembly inerenti la libreria di comunicazione. Le seguenti DLL contenenti codice dotNet racchiuso in oggetti COM vanno copiate in una cartella apposita su un volume non rimovibile del PC e registrate nell’ordine: • CommonUtils.dll • SyncUtils.dll • SerializationSetupHelper.dll • BasicPlugins.dll • ClientSync.dll che in buona parte dipendono da (e quindi richiedono anche) SimpleLogger.dll Per la registrazione, è possibile utilizzare il comando
  • Installazione e preparazione dell’ambiente di esecuzione 41 1 RegAsm.exe nomeDll residente nella cartella %WINDIR%Microsoft.NETFrameworkv2.X Come si può notare, la cartella è parte integrante del framework dotNet 2.x (dove “x” sta per qualsiasi numero), palesando che l’installazione del runtime dotNet 2.0 o superiore è un prerequisito. Da osservare anche il fatto che gli assembly preparati per essere utiliz- zati come oggetti COM hanno bisogno di un trattamento particolare, uti- lizzando per la registrazione l’apposito regasm.exe al posto dell’usuale regsvr32.exe Molte di queste librerie sono utilizzate anche dal lato del software di acquisizione: anche se questo dovesse essere installato sulla stessa macchina di GEC, le librerie non possono essere condivise. Andranno quindi replicate nella cartella dedicata solo al programma di scansione.
  • 42 3. SyncUtils
  • Capitolo 4 GEC_Scan 4.1 Requisiti e progetto dell’interfaccia utente Il software di acquisizione GEC_Scan dev’essere funzionalmente inte- grato con GEC. L’utente, una volta installato il sistema, non deve avere coscienza che GEC_Scan è un programma diverso da GEC ma deve essere portato a considerarlo come una sua estensione: per ottenere que- sto risultato GEC_Scan deve essere eseguito all’avvio della macchina su cui è installato e rimanere nascosto finchè non si presenti un determinato evento (come l’arrivo di una comunicazione da GEC) nel qual caso dovrà essere mostrata una schermata adatta a gestire l’evento. In questo modo, la finestra di GEC_Scan si comporterà come se fos- se un form dell’applicazione principale, lasciando trasparente all’utente tutta la gestione delle comunicazioni tra un software e l’altro. GEC_Scan deve utilizzare SyncUtils o essere compatibile con il suo metodo di comunicazione a file system condiviso: deve quindi avere ac- cesso in lettura e scrittura alla porzione del file system sul suo elaboratore che viene acceduta da GEC tramite SyncUtils. GEC_Scan deve poter accettare da GEC un numero qualsiasi di richieste di inizio sessione di acquisizione a prescindere dal suo stato di occupazione: se arrivano ulteriori richieste mentre l’utente sta operando il programma per soddisfarne una, tali richieste vanno inserite in una 43
  • 44 4. GEC_Scan coda di tipo First In First Out, e vanno presentate all’utente in ordine una ogni volta che la precedente sia terminata. L’utente deve essere posto in grado di valutare la lunghezza della coda e di annullare una o tutte le richieste senza interrompere il suo lavoro corrente. A tale scopo deve essere presente un form separato da quello principale che permette di osservare in tempo reale l’occupazione della coda. GEC_Scan deve poter comunicare con il maggior numero possibile di periferiche di acquisizione come scanner, fotocamere e basi di dati; tale connettività deve però essere assolutamente trasparente, cioè: il com- portamento e l’apparenza grafica del programma devono essere sempre uguali e garantire un’esperienza di utilizzo consistente qualsiasi sia la sorgente di dati scelta. Le immagini acquisite singolarmente o in gruppi, devono essere presentate tramite piccole anteprime che permettano quantomeno di identificare in linea di massima ogni documento. Non ci deve essere un limite—se non quello fisico della macchina di esecuzione—al numero di immagini acquisibili e gestibili: allo sco- po di facilitare la gestione delle immagini (operazioni di ispezione, se- lezione, riordino, rotazione) il programma deve essere provvisto di un visualizzatore integrato, che permetta di ingrandire le piccole anteprime e presentare le immagini a più alta risoluzione a vari livelli di zoom. 4.1.1 Interfaccia grafica e usabilità Normalmente il programma di acquisizione documenti viene eseguito al- l’avvio del sistema dell’utente, ma la maggior parte delle sue funziona- lità è preclusa finché GEC—a seguito di un’operazione dell’utente—non inizia una sessione; per l’utente è tuttavia sempre possibile accedere tra- mite icona nella barra dell’orologio ad un menu di opzioni che permette di impostare: • il dispositivo da utilizzare tra una lista di scanner, fotocamere ed altre sorgenti TWAIN disponibili sul sistema;
  • Requisiti e progetto dell’interfaccia utente 45 Figura 4.1: Finestra di opzioni del programma di acquisizione. • la risoluzione (impostata alla prima esecuzione sulla risoluzio- ne disponibile più vicina—possibilmente per eccesso—a 300 dpi, risoluzione richiesta dalla maggior parte dei software OCR); • modalità di colore (impostata alla prima esecuzione sulla modalità più semplice che si avvicini a RGB); • orientamento pagine (impostato alla prima esecuzione a 0º); • utilizzo dell’alimentatore automatico fogli (se disponibile, imposta- to a vero alla prima esecuzione); • utilizzo della scansione automatica fronte e retro (inizialmente falso); • utilizzo dell’eliminazione automatica pagine vuote (inizialmente falso); • utilizzo della rotazione automatica pagine (inizialmente falso);
  • 46 4. GEC_Scan • utilizzo del rilevamento automatico delle dimensioni di una pagina (se disponibile inizialmente vero); Ogni controllo di impostazione risulta operabile se pertinente, e non presente o disegnato in grigio se non disponibile o non applicabile. Ogni impostazione si riferisce al dispositivo correntemente seleziona- to: scegliere un’altra periferica causerà l’adattamento dei parametri alle capacità della nuova sorgente scelta. Alla chiusura della finestra delle preferenze, queste vengono rese ufficiali e salvate su disco. Figura 4.2: Schermata principale del programma di acquisizione. Quando l’utente effettua in GEC un’operazione che causa l’inizio di una sessione di acquisizione, la schermata principale del programma di scansione compare in primo piano. Tramite il tasto “Acquisisci” si inizia la scansione dalla periferica sele- zionata di una o più pagine dipendentemente dalle preferenze impostate: eventuali operazioni lunghe comporteranno la comparsa di un indicato- re di avanzamento che consente anche di interrompere le operazioni in corso. Ad esempio, un’operazione di acquisizione molto lunga potrebbe pre- sentarsi nel caso in cui si sia scelto di scansionare molti documenti con
  • Requisiti e progetto dell’interfaccia utente 47 un trascinatore automatico, oppure nel caso si sia scelto di utilizzare una risoluzione molto alta. Una volta raccolte delle immagini, queste compaiono sotto forma di icone sullo sfondo grigio della finestra principale: passandoci sopra con il puntatore esse si ingrandiscono per permettere di comprendere meglio di che pagina si tratta e per consentire all’operatore di capire qual è il foglio correntemente puntato. Aumentando le dimensioni della finestra, aumentano anche quelle delle icone delle pagine. Facendo doppio click su un’immagine, questa viene mostrata in una finestra di anteprima, nella quale è possibile effettuare zoom (operando la rotella del mouse o i tasti + e -) sulle sue varie parti a fini di ispezione. Tenendo premuto il tasto sinistro del mouse e trascinando, l’imma- gine puntata viene rimossa dalla sua sede per consentire di posizionarla in altra locazione: rilasciando il tasto sinistro, essa verrà inserita su- bito prima dell’immagine correntemente sotto il puntatore (locazione evidenziata da una barra nera sfumata). Una barra di posizionamento compare al di sotto delle immagi- ni quando queste sono troppe per essere contenute in una sola scher- mata: sia quando si scorrono semplicemente le immagini, sia duran- te le operazioni di trascinamento, avvicinandosi al bordo sinistro o de- stro il programma effettua automaticamente una carrellata—tanto più velocemente quanto più si è vicini al bordo. Tenendo premuto il tasto sinistro del mouse sull’area recante la di- citura “inserisci separatore” e trascinando, viene creato un elemento se- paratore (delle sembianze di una barra nera verticale) che è possibile rilasciare tra due immagini secondo le modalità già viste: anche una volta rilasciato un separatore, è sempre possibile riprenderlo e spostarlo come una immagine qualsiasi. Verrà considerato “documento” o “gruppo” l’insieme delle pagine con- tenute fra due separatori (o tra un separatore e l’inizio/fine della lista): il programma può essere forzato da GEC a richiedere un numero mini- mo e un numero massimo di gruppi di pagine: è possibile sapere quali sono i vincoli lasciando il puntatore sull’area di inserzione separatore e aspettando la comparsa di un fumetto contenente tali informazioni. Facendo click destro su un’immagine o un separatore, appare un menu contestuale con diverse opzioni auto esplicative:
  • 48 4. GEC_Scan • ruota in senso orario; • ruota in senso antiorario; • elimina. La pressione del tasto “cancella immagini” causa la cancellazione di tutte le immagini acquisite. Una volta catturate, riordinate e divise le pagine è possibile annullare tutto il lavoro o proseguire con l’archiviazione dei dati; la differenza tra “cancella immagini” e “scarta” consiste proprio nel fatto che il primo co- mando lascia la finestra aperta per effettuare ulteriori operazioni, mentre il secondo chiude e manda contestualmente a GEC un messaggio di er- rore comunicandogli che non è stato possibile completare correttamente il lavoro per annullamento da parte dell’utente. Se nel frattempo erano pervenute altre richieste di inizio sessione (es: l’utente ha effettuato altre operazioni su GEC che hanno originato altre richieste), il programma lo palesa restando in attesa di istruzioni, altrimenti torna nel suo stato quiescente. In ogni momento è possibile controllare e gestire lo stato della coda di richieste tramite l’apposita finestra, richiamabile dalla tray icon. Figura 4.3: Finestra di gestione della coda in caso di richieste multiple.
  • Composizione di GEC_Scan 49 Infine, chiudendo semplicemente la finestra principale (con la classi- ca “x” in alto a destra) o tramite il tasto “Nascondi” senza scegliere di memorizzare o scartare il materiale raccolto, si sospende la sessione di lavoro nascondendo la finestra, recuperabile (senza perdere alcun dato) tramite icona nella barra dell’orologio. 4.2 Composizione di GEC_Scan Plug in Servizi Plug in manager esterni Plug in codice principale SyncUtils File System GEC dotNetTwain TWAIN Periferiche VisualSlideShow WinForms e interfaccia Utente GDI grafica GEC_Scan Figura 4.4: Composizione di GEC_Scan e sue relazioni con tecnologie ed altre parti del progetto. In figura 4.4, si può osservare l’architettura di GEC_Scan e le sue
  • 50 4. GEC_Scan varie relazioni con tecnologie e altre parti del progetto. Di SyncUtils si è parlato nel capitolo 3 nella pagina 13, mentre dei plug in e del loro metodo di gestione si parlerà nel capitolo 5 nella pagina 73. Di segui- to è possibile invece approfondire gli altri due aspetti rimanenti legati all’architettura di GEC_Scan: l’integrazione con TWAIN che permet- te l’accesso alle periferiche di acquisizione e la struttura dell’interfaccia utente. 4.2.1 TWAIN TWAIN è uno standard per l’interfacciamento con dispositivi di imaging quali fotocamere digitali, scanner, database di immagini, ecc. . . che dà il suo nome ad una libreria distribuita gratuitamente, punto di riferi- mento dal 1991 per chiunque voglia comunicare con suddetti dispositivi utilizzando un’interfaccia software comune e senza doversi cimentare con driver e protocolli proprietari. Il suo nome non è un acronimo, ma un arcaismo inglese per “due”, riferimento ad un racconto di Kipling1 , volto ad enfatizzare la difficoltà di interfacciamento tra i molti sistemi diversi all’epoca della sua nascita. Lo standard ha tentato di imporsi sulle due piattaforme PC e Mac, presentandosi sempre come una libreria dinamica e mantenendo nomen- clature e convenzioni consistenti nei due ambienti. In ambiente Micro- soft Windows, essa è presente come DLL a 16 bit dalle prime versioni di Windows 3.x e in epoca più recente anche a 32 bit. Da poco è stata resa disponibile la versione 2.0 ufficiale di TWAIN: il fatto che esistano così poche versioni della libreria e che siano tutte compatibili tra di loro (pur coprendo un arco di tempo di 18 anni), è esemplificativo del fatto che nonostante questo standard sia stato lar- gamente usato, non c’è stata nel suo campo un’evoluzione tecnologica consistente; purtroppo ciò significa anche che sono pochi gli adattamenti a nuove tecniche, metodologie e filosofie di programmazione ai quali esso è stato sottoposto. Le entità in gioco durante una sessione di utilizzo di TWAIN sono: 1 “OH, East is East, and West is West, and never the twain shall meet” [6].
  • Composizione di GEC_Scan 51 applicazione cioè il codice utilizzatore di TWAIN, che si preoccupa di caricare la libreria e di inizializzare il sistema; Data Source Manager (o Source Manager o DSM), ovvero la parte centrale della libreria TWAIN e sua principale complessità; si pre- occupa di mantenere un dialogo con l’applicazione da un lato e con i Data Source dall’altro; Data Source (o Source o DS): ognuno rappresenta una periferica di acquisizione, ed è implementata dal driver di ogni dispositivo che sia aderente alle specifiche TWAIN. message-pump DS application DSM DS Figura 4.5: Schema del sistema di comunicazione tra le parti del sistema TWAIN; DSM incarna la maggior parte delle funzionalità di TWAIN, anche se per TWAIN si intende pure l’insieme delle specifiche che riguardano ogni aspetto della comunicazione tra applicazione e DS. La comunicazione tra DS e DSM è trasparente per lo sviluppatore dell’applicazione, e pertanto in questa sede verrà ignorata; su piattaforma Windows la comunicazione tra applicazione e DSM avviene in due modi diversi a seconda della modalità di comunicazione: • per impartire al DSM un comando, o richiedere un parametro in maniera sincrona, l’applicazione effettua una chiamata a una fun-
  • 52 4. GEC_Scan zione esportata dalla DLL di TWAIN, passando strutture conte- nenti comandi e richieste, ed eventualmente puntatori a strutture vuote da riempire con le risposte; tali procedure sono bloccanti; • per ottenere notifiche asincrone dal DSM, l’applicazione predispone un meccanismo per l’ascolto di determinati messaggi tramite la message-pump di Windows. Anche quando il destinatario ultimo di un comando è un Data Source, tale comando viene impartito tramite il DSM; in ogni comando vengo- no specificati gli interlocutori (chi tenta di comunicare con chi, ad es: l’applicazione con un DS) tramite codici di identità assegnati nella fase iniziale, puntatori a strutture dati e la cosiddetta “tripletta operativa”, costituita da: dataGroup che identifica la macro categoria alla quale si sta facendo riferimento; dataArgumentType usato per stabilire il l’area funzionale dei dati (capacità di un dispositivo, evento, identità, ecc. . . ) forniti o desiderati; messageId specifica l’azione da effettuare (get, set, querySupport, ecc. . . ). dataGroup può essere: control, per il controllo della sessione TWAIN; image, per lavorare con dati costituenti immagini e periferiche pertinenti; audio (scarsamente utilizzato) per operare su dati audio. Alcuni esempi di dataArgumentType includono: Identity usato nella gestione dei Data Source; Capability usato nella negoziazione delle capacità richieste ad una periferica;
  • Composizione di GEC_Scan 53 PendingXfers col quale si richiedono dati sulla quantità di documenti pendenti; ecc. . . Esempi significativi di messageId possono essere Get per richiedere il valore di una certa variabile di stato; Set per impostare il valore di una certa variabile di stato; Reset per riportare alle impostazioni predefinite una variabile di stato; QuerySupport per richiedere al sistema se una certa funzionalità (es. la capability di un DS) è supportata o meno; ecc. . . Visti gli esempi di possibili valori, una tripletta operativa tipo potreb- be essere: DG_IMAGE/DAT_IMAGEFILEXFER/MSG_GET che istruisce una sorgente a trasferire un’immagine su un file. In tal caso, l’informa- zione inerente a quale DS deve essere usato nel trasferimento è passato contestualmente come parametro, mentre altre opzioni come il formato dell’immagine, la locazione del file da creare, ecc. . . sono state imposta- te durante l’evoluzione della comunicazione con altri comandi ed altre triplette operative. In figura 4.6 nella pagina successiva si può osservare lo schema del funzionamento di TWAIN come automa a stati finiti. Una volta carica- ta la libreria e verificata la presenza del punto di ingresso DSM_Entry (1),(2), per poter acquisire un’immagine da una sorgente bisogna inizia- lizzare il source manager (3) e una delle sorgenti a disposizione (4); a questo punto è spesso necessario effettuare una procedura di negozia- zione delle capacità del dispositivo associato alla sorgente dati (è una macchina fotografica o uno scanner? Dispone di caricatore automatico dei fogli?) al termine della quale si provvederà all’impostazione dei para- metri richiesti e all’abilitazione della sorgente (5), il che causerà l’inizio della procedura di acquisizione. Gli stati (6) e (7) sono raggiunti ciclica- mente durante il trasferimento dei dati acquisiti fino a che la procedura di acquisizione viene terminata dall’applicazione o dal data source.
  • 54 4. GEC_Scan Figura 4.6: Diagramma di TWAIN visto come macchina a stati (dalla documentazione ufficiale di TWAIN 1.9 [5]). Qualsiasi dato scambiato dall’applicazione da e verso TWAIN è carat- terizzata da tipo e contenitore; vi sono quattro tipologie di contenitore, ognuno dei quali può contenere un insieme di valori di uno tra quindici tipi. I tipi sono: bool: valore vero o falso; Fix32: numero di 32 bit a virgola fissa; (U)Int8/16/32: interi segnati e non di varie dimensioni;
  • Composizione di GEC_Scan 55 Frame: contiene due coppie di valori numerici che individuano un’area rettangolare; Str32/64/128/255/512/1024: stringhe di varie dimensioni fisse. Mentre i contenitori sono: OneValue: il contenitore più semplice, contenente un solo valore; Array: può contenere N elementi; Range: indica (tramite la specificazione degli estremi e di un passo) una gamma di valori consecutivi; Enumeration: indica N elementi possibili, il valore selezionato di default e quello corrente. Ad esempio, facendo una richiesta della capacità di risoluzione di un dispositivo ottico, si otterrà probabilmente in risposta un Array di Fix32. Da notarsi che le strutture accettate o restituite da TWAIN si com- portano come varianti. In alcuni casi, non si sa che tipo di contenitore viene ricevuto o il tipo di dati in esso contenuti se non leggendone il codice negli appositi campi di ogni struttura. Nell’esempio sopra riportato, il dispositivo avrebbe potuto anche ri- spondere con un OneValue contenente un intero; sebbene per la maggior parte delle operazioni siano ben documentati i tipi di contenitori e dati coinvolti, per compatibilità con periferiche che non si comportano rigo- rosamente come prescritto dallo standard, o per scalabilità sulle versioni future di TWAIN, è bene fare meno assunzioni possibile sui tipi di dati maneggiati. 4.2.2 VisualSlideShow e design delle classi coinvolte Nello sviluppo dell’interfaccia grafica si è voluto concentrare la maggior parte della complessità in un unico componente grafico riutilizzabile chia- mato VisualSlideShow; ogni caratteristica dell’interfaccia utente non co- perta da tale componente, è stata ottenuta tramite l’utilizzo del designer grafico di Visual Studio e codice ad hoc.
  • 56 4. GEC_Scan Questo controllo—destinato a contenere le icone di anteprima dei fogli scansionati—consente di mantenere e presentare in maniera este- ticamente piacevole e funzionale, una grossa lista ordinata di immagini divise per gruppi, effettuando automaticamente animazioni, caching e ri- dimensionamento delle icone di anteprima, e notificando autonomamente il programma utilizzatore di eventuali modifiche alla lista. Per far ciò si sono sviluppate delle classi dedicate, ed utilizzate altre di utilizzo più generale sviluppate per altre parti del progetto. In alcuni casi si è tentato di generalizzare il più possibile il design per massimizzare il riutilizzo del codice. Ciò che si voleva ottenere era un componente che graficamente pre- sentasse delle caratteristiche complesse, ma che dal lato del codice si comportasse come una semplice collezione di immagini. Questo è stato possibile fondamentalmente grazie a tre tecniche: • l’utilizzo di etichette; • l’implementazione di interfacce multiple; • l’utilizzo di eventi. Ci sono alcuni casi (cfr. in C# le classi sealed [11] come ad esempio Image) in cui una classe non si può estendere; in questo e in altri casi ove si vogliano aggiungere campi ad un entità senza ricorrere al polimorfismo, è possibile utilizzare il semplice meccanismo delle cosiddette etichette. Dev’essere creato un oggetto con due campi: uno per l’oggetto da etichettare ed uno per l’etichetta; entrambi possono venire lasciati di tipo parametrico per un più agevole riutilizzo. Oggetti etichettati di questo tipo possono servire ad aggiungere alle liste dati collegati ai loro corrispondenti meta dati; eventualmente si può fare in modo che un TaggableItem<TItem, TTag> ritorni vero in un test d’uguaglianza con un’altro TaggableItem<TItem, TTag> se i due TItem sono uguali, o che un TaggableItem<TItem, TTag> sia uguale a un oggetto di tipo TItem se quest’ultimo equivale all’oggetto contenuto nel TaggableItem. Le collezioni di cui necessitiamo devono:
  • Composizione di GEC_Scan 57 ITaggedItem<TItem, TTag> — TItem Item TTag Tag TaggedItem<TItem, TTag> — TItem Item TTag Tag Figura 4.7: Struttura di TaggedItem. • poter essere enumerate come collezioni di TaggableItem<TItem, TTag> o come semplici collezioni di TItem; • poter sollevare un evento ogniqualvolta la collezione venga in qualche modo alterata; • essere facilmente estendibili a loro volta per aggiungere ulteriori funzionalità. Si è scelto di creare la classe TriggeringCollection<T> sfruttando le caratteristiche di base della classe Collection<T> e implementando un’interfaccia ITriggeringCollection<T> come schematizzato in figura 4.8 nella pagina seguente. L’interfaccia ITriggeringCollection<T> fornisce i metodi di base per registrare un gestore di evento e per inibire o consentire la sollevazione di messaggi di evento. TriggeringCollection<ITaggedItem<Image,TTag>> è sta- ta usata come base per TTTImageCollection<TTag>, che aggiunge la possibilità di enumerare il contenuto della lista ignorando le etichette, e la generazione—e gestione—automatica delle anteprime.
  • 58 4. GEC_Scan IList<T> Collection<T> ITriggeringCollection<T> TriggeringCollection<T> Figura 4.8: Schema di ereditarietà ed implementazione interfacce di TriggeringCollection. TTTImageCollection<DisplayInfo> viene utilizzato da Vi- sualSlideShow per la gestione della sua collezione di immagini. 4.3 Tecnologie e tecniche implementative Uno dei problemi principali durante la stesura del codice è stato l’inter- pretare metodologie di programmazione datate (basate molto sull’uso di funzioni statiche, costanti e di macchine a stati piuttosto che su qualcosa che assomigli ad una gerarchia di classi) in modo coerente con la filosofia object oriented (di seguito anche OO oppure OOP) dell’ambito dotNet. L’interazione con lo standard TWAIN ad esempio prevede chiamate ad una sola funzione nativa che a seconda del valore di un buon numero di parametri fa da interfaccia con la totalità delle funzioni di controllo degli scanner. Le possibilità offerte dal framework per risolvere alcuni dei problemi come quello descritto partono da un livello piuttosto basso: l’interfac- ciamento del codice managed col codice nativo, la capacità di mappare una classe su un record e la possibilità di generazione dinamica a tempo di esecuzione di strutture e byte code eseguibile.
  • Tecnologie e tecniche implementative 59 TriggeringCollection<ITaggedItem<Image, TTag>> TTTImageCollection<TTag> Figura 4.9: Ereditarietà di TTTImageCollection: è una triggering list di im- magini etichettate; VisualSlideShow usa internamente una TTTImageCollec- tion<DisplayInfo> dove “DisplayInfo” sono meta dati associati alle immagini utili durante il rendering del componente. D’altra parte, la programmazione dell’interfaccia grafica non ha posto particolari problemi se non quelli relativi alla rapidità di esecuzione ed affidabilità anche in presenza di una mole importante di dati da gestire. Di seguito alcune tecniche e dettagli implementativi relativi a entrambi gli aspetti. 4.3.1 dotNetTwain La libreria dotNetTwain reinterpreta TWAIN permettendo al codice che la utilizza di mantenere le usanze diffuse in ambito dotNet—e C# in particolare—traducendo tipi varianti in generici, fornendo metodi di ap- poggio, ed in generale incapsulando gran parte dei complessi meccanismi di comunicazione di TWAIN. Nonostante ciò, la sua funzione principale resta quella di fare da tramite tra i mondi managed e nativo. L’interfacciamento di codice gestito con del codice nativo si esplicita in due modi: • eseguire del codice nativo richiamandolo da codice dotNet;
  • 60 4. GEC_Scan • eseguire del codice dotNet richiamandolo da codice nativo. Mentre la seconda questione concerne l’uso di COM, la prima— qualora il codice da richiamare sia già racchiuso in una DLL— si risolve utilizzando i servizi esportati dal package dotNet System.Runtime.InteropServices. A questo punto si possono seguire due strade a seconda delle necessità: • utilizzare il link statico di ogni funzione desiderata esportata dalla DLL; • utilizzare il link dinamico e ispezionare a run time le possibilità offerte dalla DLL. È doverosa a questo punto una precisazione: mentre il link statico è di rapido utilizzo per il programmatore e leggero da gestire per il sistema (le dimensioni del codice compilato sono ridotte e i tempi di inizializzazione sono minori rispetto al caso dinamico), esso comporta delle limitazioni. Ad esempio non consente una gestione user friendly degli errori: nel caso una libreria sia assente, o contenga codice sostanzialmente diverso da quello che ci si aspettava, in caso di link statico, il progetto utilizza- tore fallirà durante la sua inizializzazione senza permettere di gestire in qualche modo l’errore e senza consentire di avvisare l’utente con messaggi per lui più significativi. Viceversa, il link dinamico permette una gestione più completa delle dipendenze, ma è più complesso e dipende a sua volta da funzioni di cui è necessario fare il link statico. Entrambi i metodi sono dunque utilizzati per gestire la libreria TWAIN. Per effettuare il link statico con una funzione nativa presente in una DLL da codice dotNet, è necessario definire nei sorgenti il prototipo di una funzione statica all’interno di una classe, e decorarlo poi con l’at- tributo DllImport("libName.dll"), dove libName è il nome della libreria che come di consueto verrà cercata prima nella stessa cartella dell’eseguibile, poi in alcune cartelle di sistema. 1 using System.Runtime.InteropServices; 2 class DllWrapper {
  • Tecnologie e tecniche implementative 61 3 [DllImport("user32.dll"), EntryPoint="MessageBoxT"] 4 public static extern int MessageBox (int h, string m, string c, int type); 5 } Si può notare che nel prototipo della funzione i tipi dei parametri (come string) sono tipi dotNet: il marshalling di eventuali tipi com- plessi è quando possibile automatico, ed altrimenti specificabile tramite l’attributo MarshalAs. 1 [DllImport("Advapi32.dll", EntryPoint="GetUserName"] 2 static extern bool GetUserName( [MarshalAs(UnmanagedType .LPArray)] byte[] lpBuffer, [MarshalAs(UnmanagedType. LPArray)] Int32[] nSize ); Per utilizzare invece la tecnica del linking dinamico, ci si avvale di una funzione introdotta con la versione 2.0 del framework dotNet: 1 Marshal.GetDelegateForFunctionPointer(IntPtr address, Type type); questa caratteristica permette di tradurre in delegato (cioè puntatore a funzione managed ) un puntatore reale ad una funzione nativa. L’unico parametro mancante per poter usare questa tecnica è il pun- tatore alla funzione desiderata: nessuna funzione del framework ci con- sente di recuperarlo a partire—ad esempio—dal nome della funzione che interessa. Bisogna quindi adoperare il linking statico per avere acces- so a funzioni delle API di Windows che consentono di ottenere queste informazioni. Successivamente si potrà scrivere: 1 delegate int MyFunctionDelegate(int a, int b); 2 ... 3 int libraryID = LoadLibrary("myLib.dll"); 4 IntPtr fnAddress = GetProcAddress(libraryID, " functionName"); 5 MyFunctionDelegate myFunction = (MyFunctionDelegate) Marshal.GetDelegateForFunctionPointer( myFunctionRealAddress, typeof(MyFunctionDelegate)); 6 ... 7 c = myFunction(a, b);
  • 62 4. GEC_Scan Listing 4.1: Funzioni linkate staticamente per poter implementare un sistema di link dinamico. 1 class DynamicLinkHelpers 2 { 3 [DllImport("kernel32.dll", EntryPoint = "LoadLibrary") ] 4 static extern int LoadLibrary( [MarshalAs( UnmanagedType.LPStr)] string lpLibFileName); 5 6 [DllImport("kernel32.dll", EntryPoint = " GetProcAddress")] 7 static extern IntPtr GetProcAddress( int hModule, [ MarshalAs(UnmanagedType.LPStr)] string lpProcName); 8 9 [DllImport("kernel32.dll", EntryPoint = "FreeLibrary") ] 10 static extern bool FreeLibrary(int hModule); 11 } Se la libreria fondamentale kernel32.dll sarà assente, danneggia- ta o fondamentalmente diversa da quella che ci si aspetta, il program- ma terminerà in maniera repentina dando solo confuse informazioni tec- niche, mentre se la sola libreria denominata nell’esempio myLib.dll dovesse dare simili problemi, verrà sollevata un’eccezione normalmente catturabile e gestibile. Tutte le funzionalità offerte da TWAIN sono accessibili dall’unica fun- zione esportata da twain_32.dll, definita nel documento ufficiale [5] e nel file di definizione TWAIN.H come 1 DSM_Entry(IntPtr origin, IntPtr destination, uint32 dataGroup, uint16 dataArgumentType, uint16 messageId, IntPtr dataPointer); Richiede come primo parametro il puntatore ad una struttura che identifica l’applicazione: molte applicazioni possono fare uso contempo- raneamente di TWAIN, e per questo motivo esso mantiene uno stato associato ad ognuna delle applicazioni con esso comunicanti; si usa con
  • Tecnologie e tecniche implementative 63 origin impostata a null nella fase di acquisizione di un identificatore. Viene poi normalmente utilizzata in due possibili modi: • per inviare comunicazioni dall’applicazione al source manager, impostando destination a null; • per inviare comunicazioni dall’applicazione a una sorgente, impostando destination all’indirizzo di una struttura che la identifichi. La comunicazione tra i suddetti endpoint avviene secondo le già note modalità specificate dalla “tripletta operativa”, costituita dai successivi tre parametri dataGroup, dataArgumentType e messageId, ognu- no dei quali è semplicemente un codice specificato da un intero che in CC++ il programmatore può impostare con l’aiuto di alcune costanti (qui riportate in enum). L’ultimo campo dataPointer è un puntatore ad una struttu- ra che cambia a seconda degli attori e del tipo della comunicazione. Questa molteplicità di funzione viene esplicitata in dotNetTwain fa- cendo riferimento allo stesso codice con dieci diversi prototipi. Nel fi- le TwainNativeStubs.cs, la classe omonima TwainNativeStub racchiude • la definizione di dieci delegati; • dieci istanze di delegato (una per definizione); • il codice per inizializzare dinamicamente tutte le istanze con riferimento ad una DLL. Listing 4.2: Prototipi multipli della stessa funzione DSM_entry di TWAIN. 1 // DSM entry point DAT_ variants delegates: 2 public delegate ReturnCode DSMparentDelegate ([In, Out ] TwainNativeTypes.Identity origin, IntPtr zeroptr, DataGroup dg, DataArgument dat, TwainMessage msg, ref IntPtr refptr); 3 public delegate ReturnCode DSMidentDelegate ([In, Out ] TwainNativeTypes.Identity origin, IntPtr zeroptr, DataGroup dg, DataArgument dat, TwainMessage msg, [In , Out] TwainNativeTypes.Identity idds);
  • 64 4. GEC_Scan 4 public delegate ReturnCode DSMstatusDelegate ([In, Out ] TwainNativeTypes.Identity origin, IntPtr zeroptr, DataGroup dg, DataArgument dat, TwainMessage msg, [In , Out] TwainNativeTypes.Status dsmstat); 5 // DSM entry point DAT_ variants to DS: 6 public delegate ReturnCode DSuserifDelegate ([In, Out ] TwainNativeTypes.Identity origin, [In, Out] TwainNativeTypes.Identity dest, DataGroup dg, DataArgument dat, TwainMessage msg, TwainNativeTypes.UserInterface guif); 7 public delegate ReturnCode DSeventDelegate ([In, Out ] TwainNativeTypes.Identity origin, [In, Out] TwainNativeTypes.Identity dest, DataGroup dg, DataArgument dat, TwainMessage msg, ref TwainNativeTypes.Event evt); 8 public delegate ReturnCode DSstatusDelegate ([In, Out ] TwainNativeTypes.Identity origin, [In] TwainNativeTypes.Identity dest, DataGroup dg, DataArgument dat, TwainMessage msg, [In, Out] TwainNativeTypes.Status dsmstat); 9 public delegate ReturnCode DScapDelegate ([In, Out ] TwainNativeTypes.Identity origin, [In] TwainNativeTypes.Identity dest, DataGroup dg, DataArgument dat, TwainMessage msg, [In, Out] TwainNativeTypes.Capability capa); 10 public delegate ReturnCode DSixferDelegate ([In, Out ] TwainNativeTypes.Identity origin, [In] TwainNativeTypes.Identity dest, DataGroup dg, DataArgument dat, TwainMessage msg, ref IntPtr hbitmap); 11 public delegate ReturnCode DSpxferDelegate ([In, Out ] TwainNativeTypes.Identity origin, [In] TwainNativeTypes.Identity dest, DataGroup dg, DataArgument dat, TwainMessage msg, [In, Out] TwainNativeTypes.PendingXfers pxfr); 12 public delegate ReturnCode DSiinfDelegate([In, Out] TwainNativeTypes.Identity origin, [In] TwainNativeTypes.Identity dest, DataGroup dg, DataArgument dat, TwainMessage msg, [In, Out] TwainNativeTypes.ImageInfo imginf);
  • Tecnologie e tecniche implementative 65 Come si può vedere in 4.2 nella pagina 63, i prototipi sono simili, tut- ti accettano origine, destinazione e la tripletta operativa e restituiscono un codice di stato, ma differiscono principalmente per l’ultimo argomen- to, sempre un riferimento a un tipo complesso diverso di volta in volta (originariamente puntatore semplice). Costruite delle funzioni richiamabili in C# secondo le consuetudini di questo linguaggio, queste non sarebbero comunque utilizzabili senza la definizione di appropriati tipi. TWAIN adopera unicamente le su men- zionate costanti o tipi propri che mappano tipi primitivi (es. TW_UINT16 è semplicemente ciò che in C si direbbe unsigned short int e che in C# è un System.UInt16) o record costruiti da essi, mentre la libreria dotNetTwain utilizza fondamentalmente tre categorie di tipi: • enum che racchiudono i valori originariamente tenuti in costanti: questo permette una semplice enumerazione dei valori consentiti anche a tempo di design (completamento automatico) e a tempo di esecuzione, consente un raggruppamento per area funzionale e migliora al contempo la leggibilità generale del codice; ogni co- stante originariamente utilizzata in più contesti è stata replicata all’interno di più tipi di enumerazione, corrispondenti ai vari casi di utilizzo; • classi di collegamento: esse vengono usate per dialogare con TWAIN e costituiscono in pratica l’anello di congiunzione tra mondo managed e mondo nativo; • classi avanzate: sono classi totalmente gestite che incarnano le en- tità attrici delle varie operazioni TWAIN aggiungendo funzionalità e rispetto delle consuetudini proprie dell’OOP. Tutte le classi che mappano record semplici richiesti o restituiti da TWAIN sono contenuti nel file TwainNativeTypes.cs. È interes- sante analizzare più in dettaglio almeno una di queste classi (cfr. 4.3 nella pagina 72). La struttura è quella classica di una classe entità, che possiede dei campi, ma non particolari funzionalità se non quelle di base come con- versione a stringa e comparazione, più dei costruttori e una funzione di copia.
  • 66 4. GEC_Scan Ciò che è interessante notare, è che decorando la classe stessa e alcu- ni campi con appropriati attributi, il compilatore apprende esattamente come vogliamo che la classe—pur restando managed —venga rappresen- tata in memoria, così da essere compatibile col codice di TWAIN che non essendo cosciente di avere a che fare con una struttura gestita, tenta di popolarla semplicemente scrivendo dati a prefissate locazioni di memoria; il parametro Pack nell’attribuzione di StructLayout, ad esempio, in- dica di allineare a 2 byte, mentre CharSet fornisce indicazioni sul come trattare le stringhe. I field Manufacturer, ProductFamily e ProductName hanno bisogno di ulteriori specificazioni come la dimensione fissata a 34. Se passata alla funzione DSM_Entry di TWAIN tramite uno dei delega- ti predisposti, questa classe verrà ispezionata dal codice nativo come se fosse un semplice record, con le caratteristiche impostate ed i campi nell’ordine in cui sono stati definiti, mantenendo tuttavia dal lato dot- Net tutte le caratteristiche di una classe. Ciò permette di evitare la replicazione delle strutture durante ogni comunicazione. Il file TwainManagedClasses.cs contiene tutte le classi puramen- te managed prima definite “avanzate”; sono dedicate alla gestione ad alto livello delle comunicazioni con TWAIN, e sono tutte e sole le classi che modellano i contenitori già descritti. La gestione da parte di TWAIN di alcuni tipi come varianti ha crea- to non pochi problemi nel momento in cui invece nel codice dotNet si desiderava considerare i contenitori come dei tipi generici. Un ulteriore problema è che nello sviluppo del progetto si sarebbe sentita la necessità di tenere delle liste di risultati di un’interrogazione, e quindi di liste eterogenee di contenitori con diversi tipi di dati. Si è scelto di implementare un design per il quale: • la classe ContainerBase contiene campi e funzionalità di base per un contenitore, nonché del codice complesso che tramite re- flection e liste di funzioni di codifica e decodifica, è in grado di convertire varianti in generici e viceversa; • ogni contenitore specializzato è generico ma eredita dal tipo non generico ContainerBase, permettendo—ad esempio—di aggiungerne le istanze ad una List<ContainerBase>.
  • Tecnologie e tecniche implementative 67 ContainerBase OneValue<T> Array<T> Range<T> Enumeration<T> Figura 4.10: Diagramma delle classi per i contenitori di TWAIN. La classe principale di dotNetTwain è TwainWrapper; un’applica- zione che desideri utilizzare una periferica di acquisizione deve solo creare un oggetto di questo tipo ed utilizzarne le funzioni. Durante la costruzione, TwainWrapper tenta di caricare il file twain_32.dll (ma grazie al link dinamico, sarebbe possibile anche utilizzare un file diverso) ed in caso di successo continua installando un gestore di messaggi ed effettuando un’enumerazione dei dispositivi ottici presenti: a questo punto, la logica dell’applicazione può esaminare la li- sta delle periferiche, sceglierne una ed effettuare subito un’acquisizione, oppure impostare alcune preferenze. Esiste un metodo di TwainWrapper per ogni cambiamento di stato schematizzato nell’immagine 4.6 nella pagina 54; nonostante ciò, alcune transizioni sono gestite automaticamente in maniera trasparente. Ad esempio, una volta conclusa un’operazione di acquisizione, non ci si dovrà preoccupare di ripercorrere a ritroso la strada che ha portato a tale stato finalizzando il DSM e la DS; in caso di chiusura dell’appli- cazione, DS e DSM verranno finalizzati automaticamente (liberando nel contempo le eventuali corrispondenti risorse hardware). Quando TWAIN si trova nello stato “transfer ready” (etichetta- to col numero 6 nell’immagine), TwainWrapper attende la ricezio- ne di un messaggio appropriato per effettuare un’ulteriore chiamata a DSM_Entry ed ottenere i dati relativi alle immagini acquisite; i dati vengono ricomposti in una immagine gestita e viene sollevato un evento
  • 68 4. GEC_Scan di avvenuta ricezione. Altri messaggi possono essere inviati tramite message pump per ge- stire la fine della sessione di acquisizione, il reset forzato o altre evenienze più rare. 4.3.2 Grafica In ambito dotNet, i componenti grafici comuni (EditBox, Label, Button, ecc. . . ) non sono disegnati da codice managed come invece accade soli- tamente in Java: ogni componente gestito fa semplicemente da wrapper al codice nativo corrispondente nelle API di Windows: se da un lato que- sto non consente una grande flessibilità o portabilità, dall’altro permette una certa rapidità di esecuzione, e quindi di una maggiore scorrevolezza dell’interfaccia grafica in generale. Servendosi di molti componenti standard, quindi, gran parte dell’in- terfaccia utente di GEC_Scan si basa sull’ormai datato set di componen- ti WinForm; diversamente per il componente VisualSlideShow. Tramite i meccanismi di etichettatura e le collezioni in grado di sollevare even- ti in risposta ad una loro modifica, VisualSlideShow gestisce con logica interamente dotNet il posizionamento delle icone di anteprima nella sua area di disegno: dietro le scene, in maniera trasparente per lo sviluppa- tore, il disegno vero e proprio avviene—ancora una volta per questioni di prestazioni—tramite il collaudato GDI. Questa commistione di tecniche vecchie e nuove, garantisce una buo- na risposta da parte dell’interfaccia, a patto di non caricarla troppo; essendo il cambio di contesto tra mondo managed e nativo piuttosto dispendioso, si cerca di limitarlo al minimo. Ad esempio, il clipping delle immagini completamente invisibili è gestito da codice dotNet, mentre nel caso complesso di un’immagine parzialmente visibile ci si affida a GDI per una corretta visualizzazione. Anche a questo scopo, nell’oggetto di tipo DisplayInfo associato ad ogni immagine sono presenti • indicazione sulla tipologia di immagine (per ora solo distinzione tra immagine utile e separatore); • numero progressivo nella lista;
  • Tecnologie e tecniche implementative 69 • posizione grafica nel visualizzatore; • riferimenti ad eventuali finestre di ispezione aperte sull’immagine; • eventuali eventi cui registrare un gestore per essere notificati di modifiche nella struttura. Un’icona viene disegnata solo se considerata la sua posizione e la posizione del viewport ciò viene ritenuto opportuno. Ricordiamo che le icone sono immagini di dimensioni ridotte disegna- te per rendere l’idea delle sembianze di un’immagine ad alta risoluzione: per evitare di sovraccaricare il sistema ridimensionando al volo ogni im- magine ogni volta che ce ne sia il bisogno, è stata implementata una tecnica lazy: ogni volta che venga richiesta l’anteprima di un’immagine conservata, la si ricerca in una mappa che ha come chiavi le immagi- ni stesse, e come valori le versioni ridotte; solo se tale versione ridotta non viene trovata, si provvede a ricalcolarla. Ogni anteprima non vie- ne conservata per sempre, ma viene scartata dopo un certo tempo di inutilizzo. Listing 4.4: Estratto dalla classe TTTImageCollection: generazione al volo e conservazione di una anteprima ridotta. 1 public Image getThumb(int index) 2 { 3 Image img = this[index].Item; 4 Image generatedThumb = null; 5 if (_thumbnails.ContainsKey(img)) 6 { 7 generatedThumb = _thumbnails[img]; 8 } 9 else 10 { 11 generatedThumb = computeThumbnail(img); _thumbnails.Add(img, generatedThumb); 12 } 13 return generatedThumb; 14 } Le immagini a piena risoluzione vengono conservate non compresse.
  • 70 4. GEC_Scan L’utilizzo intensivo della memoria non viene gestito esplicitamente, ma si assume che l’insieme di memoria fisica e virtuale della macchina dell’utente sia bastante a gestire le immagini raccolte; per verificare che non vi fossero errori dovuti al codice in questo contesto critico, oltre agli unit test di base (condotti sulla maggior parte delle classi dell’intero pro- getto) è stato condotto un test di carico nel quale un processo di debug creato ad hoc simulava per mille volte la pressione del tasto “Acquisisci”. Su un Pentium IV core-duo con 1 Gb di RAM, sistema operativo Win- dows XP SP3 con 3 Gb di memoria virtuale, selezionando come sorgente dati una fotocamera digitale che fornisce una foto per acquisizione, il test è terminato senza problemi acquisendo 1000 foto di dimensioni 640 × 480 a 24 bit per pixel. 4.4 Installazione e preparazione dell’ambiente di esecuzione Le macchine su cui i due programmi—software di controllo e software di acquisizione—vengono eseguiti sono eventualmente coincidenti. Per questo motivo si potrebbe essere portati a pensare che in tal caso la maggior parte delle librerie utilizzate da entrambi i programmi possa essere condivisa evitando di replicare i file in ognuna delle rispettive car- telle di installazione. Per motivi tecnici legati anche all’utilizzo del CCW (3.3.8) questo non è possibile, e le cartelle dei due programmi devono ri- manere distinte, ognuna con la sua copia delle DLL; mentre nel caso del controllore le librerie vanno copiate tutte nella stessa cartella—e registra- te in ordine secondo la procedura descritta in 3.4 nella pagina 40—nel caso del programma di acquisizione la struttura delle cartelle e la posi- zione dei file deve rimanere quella preordinata in fase di compilazione del progetto, con la maggior parte delle librerie residenti nella stessa cartel- la dell’eseguibile principale insieme ad un file di configurazione ed una cartella plugins presente per contenere le estensioni ( 5.2 nella pagi- na 76); non sono richieste azioni di registrazione o simili, mentre resta un prerequisito l’installazione del dotNet runtime 2.0 o superiori. La cartella plugins contiene file .dll, nei quali risiedono gli as- sembly dotNet relativi alle estensioni e ai tipi aggiuntivi: anche se il
  • Installazione e preparazione dell’ambiente di esecuzione 71 codice che carica i plug in riesce a determinare se un assembly sia da caricare o meno, è opportuno che nella cartella plugins ci siano solo file contenenti classi di estensione valide ed eventuali altri file ad esse necessari. Il software di scansione documenti possiede un file di configurazione (chiamato al momento GECScan.CFG) che contiene molte impostazio- ni in formato XML; alcune di queste impostazioni sono accessibili da interfaccia grafica, mentre altre riguardanti esigenze più inusuali sono impostabili solamente a mano nel file (es: funzioni di debug). Il file di configurazione viene gestito in maniera intelligente: se duran- te l’esecuzione il programma dovesse rilevare modifiche nell’impostazione delle preferenze, queste saranno poste in atto immediatamente (a meno che non si vada ad operare su caratteristiche che chiedono un riavvio). Se il programma dovesse rilevare la cancellazione del file di configu- razione, ne ricreerà uno, ricostruendo—ad esempio—il percorso assoluto della sua cartella dei plug in ed impostando alcuni parametri ai loro valori di default. Per questo motivo, non è opportuno ridistribuire un file di configura- zione: è bene invece costruire al volo un file adeguato al sistema durante l’installazione, o lasciare che sia il software stesso a generarlo durante la prima esecuzione. I file .log presenti nelle cartelle ove alloggiano programma, plug in e oggetti COM contengono informazioni su errori verificatisi durante l’esecuzione: per attivare il log impostare l’apposito parametro nel file di configurazione, ed eventualmente osservare il file con appositi strumenti. A fini di debug può risultare utile anche installare il driver virtuale messo a disposizione dal gruppo TWAIN2 , che fornendo una immagine di test alle richieste di acquisizione senza utilizzare alcun hardware può aiutare a verificare il corretto comportamento del sistema. 2 TWAIN sample data source http://www.twain.org/downloads.shtm.
  • 72 4. GEC_Scan Listing 4.3: Riassunto della costituzione di una classe managed interfacciabile con codice nativo tramite System.Runtime.InteropServices.Marshal. 1 [StructLayout(LayoutKind.Sequential, Pack = 2, CharSet = CharSet.Ansi)] 2 public class Identity // TW_IDENTITY 3 { 4 public IntPtr Id; 5 public Version Version; 6 public Int16 ProtocolMajor; 7 public Int16 ProtocolMinor; 8 public Int32 SupportedGroups; 9 10 [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 34)] 11 public string Manufacturer; 12 13 [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 34)] 14 public string ProductFamily; 15 16 [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 34)] 17 public string ProductName; 18 19 public Identity() {...} 20 public Identity(Identity src) {...} 21 public Identity Clone() {...} 22 public override string ToString() {...} 23 public override int GetHashCode() {...} 24 public override bool Equals(object obj) {...} 25 }
  • Capitolo 5 Sottosistema di archiviazione e plug in 5.1 Caratteristiche del sottosistema di archivia- zione Il fine ultimo del sottosistema di archiviazione è quello di consentire la comunicazione tra GEC_Scan e un qualsiasi servizio di archiviazione. Questo servizio può essere locale, come un database sulla stessa macchina di GEC_Scan e un servizio interno ad una rete locale, oppure remoto, come un servizio web pubblicato su Internet. I requisiti fondamentali di tale sottosistema, evidenziati durante la fase di analisi toccano i seguenti temi: • scalabilità; • manutenibilità; • trasparenza per l’utente finale; • sicurezza; • autenticazione. 73
  • 74 5. Sottosistema di archiviazione e plug in Il sistema di archiviazione si basa sempre su servizi esterni: questo vuol dire che anche nel caso in cui venga utilizzato il servizio di archiviazione di base—realizzato contestualmente al resto del progetto ( 5.3.1 nella pagina 84)—a questo si avrà accesso come ad un modulo distinto. L’interfaccia con software o servizi web di terze parti non può essere staticamente decisa a tempo di compilazione; tali servizi sono infatti diversificati e mutevoli, come diverse e mutevoli sono le tecnologie da utilizzare per usufruirne. Per questo motivo il sottosistema di archiviazione di GEC_Scan non può avere la forma di un codice monolitico, ma deve necessariamente essere modulare e componibile per consentire di aggiungere, rimuovere o sostituire in qualsiasi momento il codice di comunicazione verso un qualsiasi servizio di archiviazione, sia esso locale o remoto. Le metodologie di comunicazione tra GEC_Scan e un servizio di ar- chiviazione sono ovviamente dipendenti dal servizio e dalla tecnologia che esso utilizza; nonostante questo, ci deve essere una base di funzionalità accessibili in maniera consistente dal resto del software GEC_Scan. Parimenti, c’è la necessità che ogni funzionalità specifica di un certo servizio di archiviazione sia accessibile anche se sconosciuta a tempo di compilazione di GEC_Scan; cioè: un modulo aggiuntivo di GEC_Scan potrebbe venire istruito da GEC al fine di utilizzare correttamente un servizio che oltre alle funzionalità di base esporta anche funzioni estese, non note al momento del rilascio di GEC_Scan. Un modulo rappresenta l’interfaccia tra GEC_Scan e un singolo ser- vizio: esso racchiude tutte e sole le funzionalità per tradurre il linguag- gio consistente di GEC_Scan nel linguaggio comprensibile dal servizio in esame (ad esempio una richiesta SOAP). La creazione di un modulo deve risultare un’operazione semplice e che consenta di riutilizzare gran parte del codice già prodotto, di rapido sviluppo ed indipendente dal resto del codice di GEC_Scan: le com- plessità tecniche come l’interpretazione dei comandi e la gestione della soddisfazione di ogni singola richiesta risiederanno invece in un blocco di codice comune e utilizzabile da tutti i moduli, in modo da semplificare le operazioni di creazione e manutenzione di ogni parte. L’intero sottosistema di archiviazione dev’essere trasparente per l’u- tente finale, nel senso che a quest’ultimo devono essere nascosti tutti i
  • Caratteristiche del sottosistema di archiviazione 75 meccanismi interni che gli permettono di effettuare le operazioni di ar- chiviazione; l’utente non partecipa mai esplicitamente alla selezione del servizio di archiviazione o alla configurazione del modulo corrispondente: tali istruzioni vengono impostate in GEC e spedite con la richiesta che di volta in volta GEC invia a GEC_Scan. Come le comunicazioni tra GEC e GEC_Scan sfruttano la sicurezza del canale messo a disposizione da ASP, così la sicurezza di ogni comuni- cazione tra un modulo di archiviazione di GEC_Scan ed il corrispondente servizio utilizzato sarà lasciato di volta in volta al canale trasmissivo. Ad esempio, se si sta utilizzando un servizio web, si può decidere di utilizzare una comunicazione https; di norma le questioni di sicurezza devono essere trattate in maniera assolutamente indipendente dal resto del codice. Grande importanza riveste la questione dell’autenticazione, che inve- ce dev’essere gestita esplicitamente. Ogni utente deve poter archiviare documenti a suo nome, qualsiasi sia il servizio che GEC_Scan usa: ov- viamente l’utente dovrà essere stato preventivamente registrato presso ogni servizio di archiviazione usato durante le sessioni di acquisizione. In aggiunta, determinati utenti devono poter essere delegati ad agire per conto di altri: si pensi al caso del commercialista che archivi la documentazione inerente ad un suo cliente. Al di la degli obiettivi di scalabilità nei confronti di servizi non ancora esistenti, una delle caratteristiche pratiche richieste al momento attuale è quella di poter salvare i documenti utilizzando due piattaforme molto popolari: Microsoft Sharepoint e Google Documents. 5.1.1 Funzionalità di manipolazione file Ogni modulo può implementare liberamente la connettività con un qual- siasi servizio, senza l’obbligo di dover sottostare a particolari vincoli; nonostante ciò, si è scelto di aggiungere un livello di astrazione tra i plug in e i servizi con cui si relazionano. Questo introduce dei requisiti aggiuntivi che riguardano le funzionalità minime di questo nuovo strato software. Sono richieste le medesime caratteristiche che di norma si ricercano in un file system:
  • 76 5. Sottosistema di archiviazione e plug in • esistenza di due tipi di oggetti: file e cartelle (o folder); • funzionalità di enumerazione degli oggetti presenti in un folder; • funzionalità di creazione di una cartella; • funzionalità di upload del contenuto di un file; • funzionalità di download del contenuto di un file; • funzionalità di rimozione di un oggetto; • funzionalità di aggiornamento del contenuto di un file. Gli oggetti relativi a diversi provider, devono essere intercambiabili e trasparenti: si deve cioè poter operare su un qualsiasi documento con le medesime modalità pur ignorando il servizio sottostante al livello di astrazione. 5.2 Design del sottosistema di archiviazione Per gestire i moduli in maniera indipendente dal codice principale si è scelto di organizzare il sottosistema di archiviazione come un plug in ma- nager, che può caricare e gestire ogni modulo di comunicazione chiamato plug in. Per architettura a plug in, si intende qualsiasi architettura software che permetta di ampliare le sue potenzialità con delle estensioni (chia- mate per l’appunto “plug in” o componenti aggiuntivi) che contengono codice eseguibile, senza per questo richiedere la ricompilazione del soft- ware stesso. Ciò permette ad uno sviluppatore che non sappia come è fatto tale software, di scrivere del codice che vi si integri semplicemente rispettando un’interfaccia concordata a priori. Un plug in è un file contenente codice eseguibile che può essere ca- ricato, identificato ed eseguito dal manager in qualsiasi momento: ogni plug in viene concepito, sviluppato, compilato in un progetto a se stante. Si è scelto di implementare ogni modulo in una singola classe che erediti da una classe base (nella quale sono assolti i compiti comuni
  • Design del sottosistema di archiviazione 77 come quelli inerenti la serializzazione): nel progetto questa classe è stata chiamata ActionPerformer. Ogni classe plug in estende ActionPerformer e ridefinisce un uni- co metodo di nome Perform, nel quale dovrà avvenire effettivamente l’archiviazione. ActionPerformer — +Perform SomeActionPerformer — +Perform Figura 5.1: Per costituire un plug in riconosciuto, una classe deve ereditare da ActionPerformer e Ridefinire Perform; ActionPerformer non è astratta e il suo metodo Perform se richiamato lancia un’eccezione (le motivazioni sono spiegate in 5.3 nella pagina 80). Il plug in manager carica tutti gli assembly presenti in una cartella e, per ogni classe presente, verifica la discendenza da ActionPerformer. Un’altra parte del manager recupera dagli stessi assembly ogni tipo ag- giuntivo utilizzato dai performer riconoscendone la discendenza da alcuni tipi fondamentali. Nel corpo del metodo Perform di ogni performer risiedono le istru- zioni per il colloquio vero e proprio col servizio esterno: al suo interno si fa uso di classi e metodi che implementano la comunicazione, come ad esempio chiamate a librerie, ad API, utilizzo di codice specifico per l’utilizzo di un certo servizio. Nella filosofia della libreria di isolamento, ogni classe in grado di co- municare con un servizio qualsiasi implementa un’interfaccia discendente
  • 78 5. Sottosistema di archiviazione e plug in IManager IUserManager IDocumentManager ICalendarManager Figura 5.2: Discendenza delle interfacce da IManager. da IManager ; sono attualmente definiti tre tipi di gestori: IDocumentManager fornisce i metodi per elencare, aggiungere, rimuo- vere, sostituire un documento su un servizio o una cartella/gruppo di documenti per i servizi che lo consentono; IUserManager permette di amministrare gli utenti di altri servizi (come quello di archiviazione); permette di elencare, aggiungere, rimuovere utenti; ICalendarManager esporta le funzionalità di base per accedere a servizi di tipo agenda, con funzioni per elencare, aggiungere, eliminare, aggiornare eventi in un calendario. Ogni fornitore di servizio ha una classe base—anche astratta—che implementa IManager, e da cui derivano le classi che implementano i vari servizi specifici; ad esempio, GoogleUserManager (implemen- tante IUserManager) e GoogleCalendarManager (implementante ICalendarManager) estendono la classe astratta GoogleManager che a sua volta implementa IManager.
  • Design del sottosistema di archiviazione 79 IManager GoogleManager IDocumentManager IUserManager ICalendarManager GoogleDocumentManager GoogleUserManager GoogleCalendarManager Figura 5.3: Diagramma delle classi per il fornitore di servizi Google; per com- pattezza l’implementazione delle interfaccie viene schematizzata con il cerchio in alto a sinistra (come in uso presso alcuni software) invece che con la freccia tratteggiata (come da disposizioni UML).
  • 80 5. Sottosistema di archiviazione e plug in I manager comunicano con i servizi che incapsulano utilizzando in gran parte le API messe a disposizione dai rispettivi fornitori, pre- via autenticazione. Per effettuare un’autenticazione è necessario un IAuthenticator: le classi che implementano questa interfaccia si oc- cupano di effettuare materialmente l’autenticazione quando necessario; ad esempio quello per SharePoint è MSAuthenticator. Ogni manager opera su entità da lui gestibili: un GoogleDocumentManager non potrà accettare di eseguire ope- razioni su un oggetto che non sia stato creato da esso. Ogni servizio ha le sue entità, ma le più comuni sono state dotate di un’interfaccia standard: IUser con informazioni di username e password; IDocument permette l’accesso a dati come nome originario del file, rappresentazione binaria del file, ecc. . . ICalendarEvent esporta proprietà di inizio e fine dell’evento, descrizione, proprietario, ecc. . . implementate poi dalle classi GoogleUser, SharePointUser, GoogleDocument, SharePointDocument, e così via. 5.3 Specifiche e tecnologie Prima dell’avvento dei contesti di programmazione gestiti e quindi della reflection (ovvero della capacità di ispezionare codice eseguibile e tipi a run time), un plug in era semplicemente una DLL nativa che esporta- va delle funzioni di nome noto a priori; un’applicazione che intendesse avvalersene necessitava solo di caricare dinamicamente la libreria e di chiamare la funzione desiderata. Essendo maturato il progetto in un contesto come dotNet che per- mette di caricare ed ispezionare codice e tipi complessi non noti a tempo di compilazione, è possibile trarre addizionale vantaggio da ereditarietà e polimorfismo e si è perciò scelto di implementare l’architettura estensibile facendo in modo che:
  • Specifiche e tecnologie 81 • ogni componente aggiuntivo sia una classe che implementa una certa interfaccia o che eredita da una certa classe base; • molte classi (e quindi molti plug in) possano risiedere in uno stesso assembly; • non ci sia limite al numero di assembly (e quindi di plug in) caricabili e utilizzabili dal programma host. Si è scelto di non scrivere del codice specifico per il tipo di plug in che viene usato dal programma di acquisizione, ma di creare una piccola libreria riutilizzabile di funzioni che aiutino a creare il proprio siste- ma modulare caso per caso. Due classi chiamate PluginsManager e RuntimeTypeManager forniscono una serie di funzioni per: • caricare una certa classe; • caricare tutte le classi di un certo assembly che implementino una certa interfaccia o ereditino da una certa classe base; • caricare tutte le classi di tutti gli assembly di una cartella che implementino una certa interfaccia o ereditino da una certa classe base. È possibile usare una delle due classi a seconda delle proprie esigenze: PluginsManager accetta come parametro generico il tipo di base che deve esser quindi noto a tempo di compilazione e resta tale per tutta la vita di ogni istanza. Ad esempio potremmo volere che ogni nostra esten- sione sia una classe specializzata che eredita da PluginBase: potremmo istanziare 1 PluginManager<PluginBase> myManager = new PluginManager< PluginBase>(); e poi fargli caricare da una cartella tutte le classi che ereditano da PluginBase con 1 myManager.loadFromDirectory(path, filter);
  • 82 5. Sottosistema di archiviazione e plug in dove filter è l’estensione dei file che possono contenere gli assembly. RuntimeTypesManager permette di caricare classi che implemen- tino almeno un’interfaccia presente tra una lista di tipi proposti o de- rivino da una classe base presente tra una lista di tipi proposti; questo può essere utile nel caso ci sia la necessità di gestire a run time dei tipi utilizzati soltanto nel codice non noto a tempo di compilazione (ad esem- pio nel progetto ciò è utile ai fini della serializzazione). Si può istanziare un RuntimeTypesManager e fargli caricare i tipi desiderati da una cartella con 1 RuntimeTypesManager myTypeManager = new RuntimeTypesManager(); 2 myTypeManager.loadFromDirectory(path, filter, types); dove types è una collezione di tipi (interfacce o classi base) che permette di individuare i tipi caricare. Il programma di acquisizione utilizza le funzioni messe a disposizione dalle classi viste per caricare dinamicamente dei plug in di esportazione; essi vengono chiamati ActionPerformer, come la classe base da cui discendono. Listing 5.1: Interfaccia di un ActionPerformer, classe base di un plug in. 1 public class ActionPerformer : IXmlSerializable 2 { 3 public ActionPerformer(); 4 public string Name { get; } 5 public XmlSchema GetSchema(); 6 public bool IsFaking(); 7 public virtual ActionResponse Perform(BaseSync syncObject, JobId job, int groupId, DocInfo additionalData, ActionRequest actionRequest, IList< ITaggedItem<Image, Page>> images); 8 public void ReadXml(XmlReader reader); 9 public void WriteXml(XmlWriter writer); 10 }
  • Specifiche e tecnologie 83 Metodi e proprietà sono tutti implementati nella classe base; gran parte del codice serve a gestire la serializzazione. Questa avviene sempli- cemente trasmettendo il nome dell’ActionPerformer: se il codice che deserializza lo conosce (cioè se ha caricato grazie a PluginManager una classe derivata da ActionPerformer il cui nome è quello specificato) provvede ad istanziarlo, altrimenti solleva un errore. XmlSchema è presente per poter implementare l’interfaccia IXmlSerializable ma non è utilizzato e torna null; WriteXml scrive nel flusso di serializzazione l’attributo name contenente la stringa recuperata da un campo privato _name; ReadXml scrive nel campo privato _name quanto recuperato dall’attri- buto name nello stream di serializzazione in input; IsFaking permette di verificare se l’oggetto su cui è invocato è la cor- retta istanza della classe derivata da ActionPerfomer o se invece è istanza della classe base, appena deserializzata e di cui dobbiamo considerare solo il nome (cioè: chiamare Perform genererebbe un errore); Name è proprietà che pubblica il campo privato _name, inizializzato al nome della classe nel costruttore ed eventualmente ridefinito in deserializzazione; Perform solleva semplicemente un’eccezione NotSupportedException; è cura della classe derivata ridefinire Perform in modo che compia le azioni desiderate. A Perform vengono passati: • un riferimento all’oggetto di sincronia che gestisce le comunicazioni; • il JobId del lavoro che sta venendo soddisfatto; • un numero che indica il progressivo del documento processato (può servire per la nomenclatura dei file); • dei meta dati relativi al documento processato;
  • 84 5. Sottosistema di archiviazione e plug in • un riferimento alla richiesta originaria che ha scatenato il processo di lavoro; • la lista ordinata delle immagini acquisite durante la sessione (eventualmente etichettate con informazioni aggiuntive). Ciò che si ottiene indietro dal plug in è un oggetto di tipo ActionResponse che contiene il dettaglio delle operazioni effettua- te con la chiamata a Perform (andrà poi inclusa in una Response e fatta pervenire a GEC). 5.3.1 Archiviazione locale e debug SaveToFileActionPerformer e TestActionPerformer (di de- bug), fanno parte di un pacchetto di nome BasicPlugins. Essi non hanno nessun vantaggio rispetto ad altri plug in, ma sono considerati capacità che un’istanza del programma di scansione non può non avere. Il primo salva i dati in una cartella specificata, mentre il secon- do fa semplicemente comparire una finestra di dialogo modale (recan- te l’informazione che si tratta di un test) all’utente del programma di scansione. Ogni Performer si avvale di tipi specializzati derivanti da ActionRequest, ActionResponse, ResponsePage. Nel caso di SaveToFileActionPerformer, questi sono: SaveToFileActionRequest : trasporta dal fruitore al fornitore del servizio indicazioni su percorso della cartella in cui vanno salvati i dati e formato richiesto. Il formato è indicato con un enum pre- definito noto al resto dell’applicazione, che viene comunicato sotto forma di stringa nei file di comunicazione; il campo Performer ereditato da ActionRequest viene impostato automaticamente a SaveToFileActionPerformer; SaveToFileActionResponse: trasporta dal fornitore al fruitore del servizio indicazioni sulla cartella in cui i dati sono stati effettiva- mente salvati; ereditando da ActionResponse, possiede una lista di ResponsePage cui vanno aggiunti degli oggetti di tipo FSPage e una lista di Error; a quest’ultima possono essere aggiunti oggetti rappresentanti errori di tipo base o specializzato;
  • Specifiche e tecnologie 85 FSPage: eredita da ResponsePage, per cui contiene un identificativo che indica la Page a cui si riferisce; contiene inoltre il percorso e il nome del file contenente i dati per questa pagina in questa azione. SaveToFileActionPerformer può essere usato per salvare una copia dei documenti acquisiti il locale (ovvero su un qualsiasi file system visibile dal GEC_Scan che esegue il plug in) anche in combinazione con altri tipi di archiviazione remota. 5.3.2 Funzionalità estese ed archiviazione Remota Le funzionalità più utili e complesse del sottosistema di archiviazione sono quelle relative all’integrazione con servizi remoti di terze parti. Come prova di fattibilità ci si è cimentati a realizzare delle strutture di interfaccia con i servizi di archiviazione ed organizzazione di Micro- soft Sharepoint e Google Apps: insieme essi costituiscono uno spaccato rappresentativo di questo tipo di servizi, funzionando entrambi con la collaudata tecnologia dei web server, seppur ognuno con diversa filosofia. La libreria di interfaccia prodotta isola l’utilizzatore dai dettagli im- plementativi dei due sistemi, e permette di appoggiarsi a questi servizi in maniera consistente per l’utilizzo dei servizi di gestione documenti e calendario. Allo stato attuale delle cose, le possibilità aperte dalla combinazione di questa libreria di interfaccia col resto del sistema di acquisizione ed archiviazione sono ancora in fase di studio per una applicazione pratica in produzione. Le funzioni complesse implementabili a partire dalle primitive fornite vanno dalla condivisione di documenti a vario titolo tra ditte e clienti, alla sincronia tra file system, dal back-up di dati, all’organizzazione dei flussi di lavoro/approvazione in una realtà aziendale. Con la gestione dei servizi relativi a calendario ed appuntamenti, è stata resa possibile la creazione automatica di note ed eventi. Per avere un’idea di cosa è possibile fare e come, seguono le descri- zioni dei due servizi menzionati, e degli argomenti tecnici affrontati per poterne fare un uso proficuo.
  • 86 5. Sottosistema di archiviazione e plug in 5.3.2.1 Sharepoint SharePoint è un prodotto di Microsoft originariamente nato come server per la nota suite Office; la sua funzionalità iniziale era quella di fare da repository per i documenti prodotti con gli strumenti Microsoft, ma si è oggi estesa ad un gran numero di possibilità. Uno degli sviluppi più evidenti di SharePoint è stato quello nel miglioramento continuo dell’interfaccia basata sul web, tanto che molte realtà aziendali anche ad alto livello1 , lo utilizzano per creare siti dedicati al proprio pubblico. Le sue funzionalità di gestione includono la possibilità di accettare dei file e di sottometterli ad un processo di revisione/accettazione completa- mente personalizzabile, la possibilità di gestire la redazione collaborativa e molte versioni di ogni documento, la possibilità di associare dei meta dati ad ogni singolo file e l’opportunità di gestire gruppi di utenti con diversi diritti di elenco/lettura/scrittura/approvazione. La versione utilizzata durante lo sviluppo è SharePoint 2007, in cui è presente una massiccia integrazione con la tecnologia dotNet. I metodi di dialogo e sottomissione documenti con questa versione sono fondamentalmente due: • utilizzando i servizi di base già esportati tramite web service predefiniti; • scrivendo un’estensione dotNet per SharePoint che dialoghi col mondo esterno come ritiene più opportuno. Si è scelto di seguire la prima strada per diversi motivi: • mantenere un pezzo di codice è più facile, immediato, economico che mantenerne due; • nei casi di utilizzo reale, quasi sempre SharePoint sarà installato su una macchina di terza parte e pertanto non configurabile al livello richiesto per installare funzionalità aggiuntive; • la comunicazione tramite web service è un’operazione ormai diffusa e facilmente integrabile anche con sistemi complessi. 1 Nei sorgenti delle pagine di http://www.ferrari.com ad esempio, è possibile leggere <meta name="GENERATOR" content="Microsoft SharePoint" />.
  • Specifiche e tecnologie 87 Le funzionalità di SharePoint sono accessibili—previa autenticazione— dal web tramite servizi SOAP2 (tutti accessibili all’indirizzo http: //server/_vti_bin/ o http://server/site/_vti_bin/; dove “server” è il nome del server e “site” l’url relativo di un sito Sharepoint): nome servizio descrizione Admin.asmx metodi amministrativi, come quelli per creare o eliminare siti Alerts.asmx metodi per lavorare con gli alert DspSts.asmx metodi per recuperare schemi DWS.asmx metodi per lavorare con i Document Workspace Forms.asmx metodi per gestire le interfacce utente di tipo Form Imaging.asmx metodi per gestire immagini Lists.asmx funzioni di gestione delle liste Meetings.asmx metodi per lavorare sui Meeting Workspace Permissions.asmx metodi per lavorare con SharePoint Services security SiteData.asmx metodi esportati per Windows SharePoint Portal Server Sites.asmx contiene un singolo metodo per ottenere template di siti UserGroup.asmx metodi per lavorare con utenti e gruppi3 versions.asmx consente di lavorare con il versioning dei file Views.asmx permette di gestire le viste associabili alle liste WebPartPages.asmx gestisce le WebPart4 Webs.asmx consente di gestire (non amministrare) siti e sotto siti 2 Qualcuno sta tentando di mettere insieme delle funzioni per accedere a Share- Point anche tramite interfaccia di tipo REST http://www.codeplex.com/ REST4SharePoint.
  • 88 5. Sottosistema di archiviazione e plug in Come si può notare dai molti riferimenti nella lista dei servizi di- sponibili, gli utenti di SharePoint hanno come punto di riferimento un “sito” con significato non dissimile da quello del classico sito web; per organizzare i documenti è necessario (tramite l’interfaccia web di Share- Point Server) configurare un sito SharePoint, permetterne l’accesso a determinati utenti e predisporre una lista. Le liste di documenti—che nelle ultime versioni di SharePoint hanno soppiantato i Document Workspace—sono in realtà liste di veri e pro- pri tipi (definibili nell’area di amministrazione) che possono usufruire addirittura di una forma rudimentale di ereditarietà: per l’utilizzo col software di archiviazione proposto, bisogna quindi creare anche un tipo che specifichi il tipo file sottomesso con dei campi atti a contenere i meta dati. I servizi utilizzati nel software prodotto sono principalmente Lists e Webs. Il primo per effettuare l’enumerazione di una lista, il secondo per ricavare alcuni URL necessari nell’utilizzo del primo servizio (sono a volte necessarie conversioni da stringhe di identificazione a nomi o viceversa). Per inviare un file, è sufficiente fare un semplice http PUT ad un ben preciso indirizzo corrispondente ad una lista, eventualmente modificato per aggiungere un percorso di cartelle interne alla lista; per associare dei meta dati, si opera successivamente sul file già caricato con i metodi di Lists. Per ricevere informazioni su un tipo, una lista o un sito, i servizi di SharePoint utilizzano un linguaggio di query basato su XML chiamato CAML5 del quale si è utilizzata solo una piccola parte, senza scrivere codice generico o specifico di funzionalità che non si intendesse usare. I calendari per Sharepoint sono semplicemente liste specializzate, che godono di un’interfaccia grafica dedicata, e pertanto vengono gestite in modo analogo a tutte le altre liste, comprese quelle utilizzate per immagazzinare documenti. Le richieste vengono materialmente costruite grazie alle classi gene- rate dal wizard per webserver di Visual Studio 2008: il loro utilizzo causa l’inizio di un colloquio SOAP come quello visibile nel listato 5.3. 5 Collaborative Application Markup Language; esempi in [7].
  • Specifiche e tecnologie 89 Listing 5.2: Esempio di codice CAML per l’update di due campi in un determinato oggetto. 1 <Batch OnError="Continue"> 2 <Method ID="1" Cmd="Update"> 3 <Field Name="ID"></Field> 4 <Field Name="field1">value1</Field> 5 <Field Name="field2">value2</Field> 6 </Method> 7 </Batch> Listing 5.3: Esempio di richiesta e risposta al server Sharepoint dal punto di vista http: il client con l’azione POST dichiara l’intenzione di effettuare una richiesta SOAP (si noti l’intestazione SOAPAction) ed attende un codice 100 dal server; all’arrivo di questo, il client provvede ad allegare nel corpo della richiesta codice XML che specifica la query (in questo caso l’enumerazione degli oggetti contenuti in un folder della lista il cui identificativo univoco è specificato nell’elemento listName). La risposta ottenuta è 200 OK e nel corpo, codice XML che rappresenta l’insieme degli oggetti restituiti dalla query (omesso per brevità). Si sono omesse le fasi di autenticazione. 1 (il client invia) 2 POST /ScanTodRepo/_vti_bin/Lists.asmx HTTP/1.1 3 User-Agent: Mozilla/4.0 4 Content-Type: text/xml; 5 charset=utf-8 6 SOAPAction: "http://schemas.microsoft.com/sharepoint/ soap/GetListItems" 7 Authorization: Basic U0hQVF9UZXN0VXNlcjE6c2hhcGU= 8 Host: mida-shpt1 9 Content-Length: 605 10 Expect: 100-Continue 11 12 (il server invia) 13 HTTP/1.1 100 Continue 14 15 (il client invia) 16 <?xml version="1.0" encoding="utf-8"?> 17 <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/ soap/envelope/" xmlns:xsi="http://www.w3.org/2001/
  • 90 5. Sottosistema di archiviazione e plug in XMLSchema-instance" xmlns:xsd="http://www.w3.org /2001/XMLSchema"> 18 <soap:Body> 19 <GetListItems xmlns="http://schemas.microsoft.com/ sharepoint/soap/"> 20 <listName>{F19B0826-253B-4C12-B2AE-585C4CDA3913}</ listName> 21 <viewName /> 22 <rowLimit /> 23 <queryOptions> 24 <QueryOptions xmlns=""> 25 <Folder>repository 1</Folder> 26 <DateInUtc>false</DateInUtc> 27 <IncludeMandatoryColumns>true</ IncludeMandatoryColumns> 28 </QueryOptions> 29 </queryOptions> 30 </GetListItems> 31 </soap:Body> 32 </soap:Envelope> 33 34 (il server invia) 35 HTTP/1.1 200 OK 36 Date: Tue, 10 Nov 2009 11:56:42 37 GMTServer: Microsoft-IIS/6.0 38 X-Powered-By: ASP.NET 39 MicrosoftSharePointTeamServices: 12.0.0.4518 40 X-AspNet-Version: 2.0.50727 41 Set-Cookie: WSS_KeepSessionAuthenticated=80; path=/Set- Cookie: MSOWebPartPage_AnonymousAccessCookie=80; expires=Tue, 10-Nov-2009 12:26:42 GMT; path=/Cache- Control: private, max-age=0Content-Type: text/xml; charset=utf-8Content-Length: 5824Ogn In Sharepoint i nomi assegnati agli oggetti non necessitano di essere unici, nemmeno internamente ad uno stesso contesto (es: due file nella stessa cartella, due oggetti nella stessa lista); ogni oggetto può essere indirizzato tramite: fileRef, un URL che fa riferimento al file che lo rappresenta;
  • Specifiche e tecnologie 91 UniqueId, un identificativo univoco per tutto il server Sharepoint (purtroppo scarsamente supportato nelle interfacce web); la coppia composta dall’identificativo univoco della lista contenitore e l’ID, un numero intero progressivo assegnato a ciascun oggetto di ogni lista, univoco solo all’interno di tale lista. Listing 5.4: Prototipi delle funzioni GetListItems e UpdateListItems create dal generatore automatico di Visual Studio 2008 per accedere alle omonime funzioni esportate dal servizio web Lists.asmx. 1 public XmlNode GetListItems(string listName, string viewName, XmlNode query, XmlNode viewFields, string rowLimit, XmlNode queryOptions, string webID) 2 3 public XmlNode UpdateListItems(string listName, XmlNode updates) Le azioni di base di enumerazione, creazione, cancellazione sono ot- tenute manipolando nomi, url, ID e identificativi univoci ed utilizzan- doli in congiunzione con i due metodi web esportati da Lists.asmx: GetListItems e UpdateListItems. Esse sono implementate nel seguente modo: Enumerazione: viene effettuata chiamando il metodo web GetListItems, specificando l’identificativo univoco della lista che si va ad enumerare, nessuna query ed eventualmente specificando il nome del folder da enumerare all’interno della struttura queryOptions; la risposta contiene del codice XML organizzato come entità contenute in elementi z:row contenuti a loro volta in un elemento rs:data; Creazione di una cartella: viene effettuata tramite chiamata al meto- do web UpdateListItems, specificando l’identificativo univoco della lista su cui si va ad operare ed una query CAML contenente no- me della nuova cartella ed eventualmente la sua posizione nella struttura delle cartelle già esistenti;
  • 92 5. Sottosistema di archiviazione e plug in Cancellazione di un oggetto (file o cartella): effettuato tramite Up- dateListItems e una query di cancellazione che fa riferimento all’oggetto da cancellare tramite il suo indirizzo URL; Download: fatto semplicemente tramite una http GET autenticata al- l’indirizzo URL di un oggetto, restituisce nel corpo della risposta il contenuto binario del file; Upload e aggiornamento del contenuto: ottenuti con un http PUT au- tenticato all’URL costruito dall’URL della cartella concatenato col nome che si vuole assegnare al file; restituisce l’URL del file nelle intestazioni della risposta in caso di successo. In Sharepoint ogni utente registrato al servizio dev’essere un utente di dominio o della macchina su cui è in esecuzione il server; l’autentica- zione avviene quindi a livello http con il consueto meccanismo Basic o NTLM per cui occorrono uno username ed una password, eventualmente corredati dal nome del dominio per cui essi valgono. Nel codice dotNet l’autenticazione avviene in modo trasparente tra- mite l’oggetto System.Net.NetworkCredential che implementa l’interfaccia ICredentials: tutte le richieste inviate al server Share- point vengono preparate in modo da utilizzare le credenziali fornite con tale oggetto, senza che sia visibile il meccanismo di autenticazione vero e proprio. Situazioni di necessità di una delega ad operare sui dati di un’al- tro utente vengono gestite grazie al complesso pannello di controllo che permette di stabilire i permessi di ogni utente con grana piuttosto fine. 5.3.2.2 Google Documents Google Documents è un pacchetto di servizi basati su un’interfaccia uten- te web che racchiude le funzionalità di word processing, foglio di calco- lo, calendario appuntamenti e presentazione. Nato nel 2006 in seguito all’acquisizione da parte di Google di Upstartle e di XL2Web (autori rispettivamente delle versioni originarie di quelli che oggi sono il word processor “writely” e il foglio elettronico) oggi il sistema gode della pre- senza di molte caratteristiche desiderabili, come ad esempio la possibilità di interfaccia tramite web service.
  • Specifiche e tecnologie 93 Il fatto di essere un insieme di servizi mantenuto da Google—e non un software—lo rende ideale per un immediato utilizzo a causa dell’assenza di installazione, delle minime richieste di manutenzione e dell’assenza per l’utente di problematiche inerenti l’integrità dei dati o la sicurezza (che vengono ovviamente coperte dal proprietario). L’intero protocollo di interfaccia ad ogni funzionalità di Google si basa su REST6 , ed in particolare su delle estensioni di ATOM7 chiamate APP8 che permettono la pubblicazione e la gestione di risorse condivise sul web. Nonostante negli intenti degli sviluppatori i servizi avrebbero dovu- to essere fruibili utilizzando solo funzioni di alto livello (le cosiddette “Google Data API” [4]), non tutte le funzioni vengono implementate in maniera soddisfacente nel kit di sviluppo per dotNet e si è provveduto quindi a scrivere del codice che in alcune parti si interfaccia con i servizi Google ad un livello più basso. L’ultima versione delle API9 è la 3.0, ma per il progetto in esame è stata comprensibilmente usata l’ultima stabile, la 2.0. Il fatto che protocollo ed API siano un prodotto giovane, in rapida evoluzione e non del tutto documentato, ha introdotto notevoli difficoltà nel raggiungimento degli obiettivi minimi di manipolazione dei dati; il sistema possiede numerosi difetti tra i quali l’impossibilità di enumera- re gli oggetti che risiedono nella cartella principale (cioè che non sono all’interno di alcuna cartella), l’impossibilità di gestire richieste troppo ravvicinate nel tempo10 e la grande latenza tra il momento in cui si crea 6 REpresentational State Transfer; filosofia di costruzione servizi web che utilizza richieste codificate direttamente nella richiesta HTTP e la risposta come contenuto in formato XML. 7 Il nome non ha un’origine chiara; specifiche in [2]. 8 Atom Publish Protocol; specifiche in [1]. 9 API o Application Program Interface(s); c’è talvolta ambiguità sull’utilizzo del termine: in alcuni casi sta ad indicare in maniera astratta l’insieme di convenzioni di comunicazione da adottare verso una certa tecnologia, mentre a volte nell’uso comune esso vuole riferirsi al codice che materialmente permette di accedere a determinate funzionalità. Diciamo in questa sede “protocollo” l’insieme delle convenzioni di comu- nicazione tra un client e i servizi di Google, mentre definiamo “API” il codice fornito da Google che implementa tale protocollo. 10 Google dichiara che la rapidità massima è di 5 query al secondo, ma sperimentalmente questo numero cala ulteriormente.
  • 94 5. Sottosistema di archiviazione e plug in un oggetto e il momento in cui diventa possibile recuperarlo. Si è cercato di ovviare queste ed altre problematiche utilizzando di volta in volta le API per dotNet o funzioni implementate da zero a seconda delle necessità. Le richieste, a prescindere dal codice che le ha originate, si presentano come http PUT, GET, POST, DELETE. Listing 5.5: Esempio di una richiesta di enumerazione documenti effettuata tramite http GET all’indirizzo del feed dei documenti; i documenti restituiti dipendono dall’utente autenticato, e vengono forniti nel corpo della risposta come codice XML nella forma di un AtomFeed contenente delle AtomEntry. Questa richiesta restituisce tutti i documenti a prescindere dalla loro ubicazione nella struttura delle cartelle: i risultati vanno quindi filtrati sul client. Per recuperare solo il contenuto di una certa cartella si effettua la stessa richiesta all’indirizzo del feed di quella cartella. La cartella di root non possiede feed. 1 GET /feeds/documents/private/full?showfolders=true HTTP /1.1 2 Content-Type: application/atom+xml; 3 charset=UTF-8 4 User-Agent: G-Mida4Worker/GDataGAuthRequestFactory-CS- Version=1.4.0.2 5 Authorization: GoogleLogin auth=DQAAAIcAAABFjdyD6oB... 6 GData-Version: 2.0 7 Host: docs.google.com 8 9 HTTP/1.1 200 OK 10 ... Anche nell’ambiente dei servizi Google un oggetto non necessita di possedere un nome univoco: per riferirsi ad un oggetto preciso è neces- sario indicare uno dei suoi indirizzi URL (solitamente quello chiamato “EditUrl”); questo è a sua volta composto dall’URL del feed per gli og- getti di un certo tipo (esistono diversi feed per documenti, cartelle, eventi del calendario ecc. . . ) seguito da una lunga stringa che costituisce un identificativo univoco per quel feed. Le funzionalità di base per la manipolazione di cartelle e file, dipen- dono da un oggetto di tipo DocumentService—che si cura tra le altre cose dell’autenticazione—e sono state ottenute nel seguente modo:
  • Specifiche e tecnologie 95 Enumerazione: si costruisce un oggetto DocumentsFeedQuery— messo a disposizione dalle API—e si provvede eventualmente ad aggiungere dei dettagli sulle modalità desiderate dell’enumerazione (es: la cartella da enumerare, opzioni sulla ricorsione, . . . ) trami- te la classe AtomCategory; si effettua la richiesta vera e propria passando l’oggetto DocumentsFeedQuery così creato al metodo DocumentService.Query(); questo restituisce una collezione di DocumentEntry, contenente i dati richiesti sugli oggetti inte- ressati dalla query; nel protocollo versione 2.0 l’enumerazione di una cartella avviene specificando il nome e non l’identificativo uni- voco, perciò in caso di cartelle omonime, ci si può attendere qualche inesattezza; Creazione di una cartella: si crea un oggetto AtomEntry il cui ti- tolo viene impostato al nome da assegnare alla nuova cartella, e la cui categoria viene impostata a FOLDER_CATEGORY; si utilizza DocumentService.EntrySend() per effettuare materialmente la richiesta di creazione verso un certo URL: /feeds/folders/ private/full/folder%3Afolder_id (dove folder_id è una stringa che funge da identificativo univoco della cartella contenitore) se si intende creare una sottocartella, /feeds/ documents/private/full/folder se si desidera creare una cartella in root; non è possibile recuperare un oggetto cartella immediatamente dopo la sua creazione; Cancellazione di un oggetto (file o cartella): effettuato chiamando il metodo Delete() che ciascun DocumentEntry possiede; questo causa l’invio di una http DELETE all’indirizzo chiamato editUri del DocumentEntry; Download: fatta semplicemente tramite una http GET autenti- cata all’indirizzo di un oggetto, eventualmente accodando &exportFormat=format per specificare il formato in cui si vuo- le convertire il file: non è possibile risalire al formato originario del file caricato, e si è quindi creato un sistema di formati di default per caricare/scaricare le varie tipologie di documento (testo, presenta-
  • 96 5. Sottosistema di archiviazione e plug in zione, foglio di calcolo, pdf); restituisce nel corpo della risposta il contenuto binario del file; Upload e aggiornamento del contenuto: ottenuti tramite chiamata a DocumentService.Insert(); l’indirizzo passato al metodo (che corrisponde all’indirizzo al quale verrà effettuato l’http POST) si calcola con riflessioni analoghe al caso della creazione folder. Listing 5.6: Esempi di richieste http originate dalle chiamate alle funzioni delle API per i documenti di Google; nell’ordine: creazione di una cartella di nome “testFldr3”, cancellazione di una cartella dato il suo identificativo univoco e caricamento di un file. 1 POST /feeds/documents/private/full 2 ... 3 <entry xmlns="http://www.w3.org/2005/Atom"> <title type ="text">testFldr3</title> <category term="http:// schemas.google.com/docs/2007#folder" scheme="http:// schemas.google.com/g/2005#kind" /></entry> 4 5 DELETE /feeds/documents/private/full/folder%3 A0B96YFnY2kRSbMjJkND... 6 7 POST /feeds/documents/private/full 8 Slug: New Rich Text Document 9 Location: http://docs.google.com/feeds/documents/private /full/document%3 A0Ad6YFnY2kRSbZGhtbTVqbnBfNmoyd2I5N2R2 10 (contenuto) Sebbene a tutt’oggi i servizi di Google si siano evoluti introducendo il concetto di cartelle condivise, nel momento della prima stesura del codice e di questo testo, il sistema di account non permetteva la condivisione di un intero spazio di lavoro, nè di stabilire una gerarchia tra utenti con suddivisione dettagliata dei privilegi; se si pensa a questo in termini di casi di utilizzo, è facile immaginare che per condividere documenti e spazi di lavoro, fosse necessario un sistema di delega. Tale sistema di delega viene implementato con il sistema di autenti- cazione chiamato “OAuth 2-legged authentication”. Quando si parla di
  • Setup dei sistemi di archiviazione 97 OAuth, di solito ci si riferisce ad un sistema di autenticazione chiama- to “3-legged authentication” nel quale si compie una cosiddetta “danza” nella quale l’utente che voglia consentire al sito B accesso ai suoi dati presso il sito A, si vede ridirezionato (anche dal punto di vista grafico) varie volte tra i due siti. Nel nostro caso invece si fa uso della cosiddetta “2-legged authentica- tion” (chiamata ai primordi anche “Signed Fetch” o “Phone Home”) che consente l’autenticazione per delega senza la danza descritta. Il sistema si basa sulla condivisione di un segreto tra delegato e de- legante, per cui il delegato non necessita di possedere le credenziali di accesso del delegante, mantenendo il diritto ad accedere ai suoi dati anche nel momento in cui il delegante cambiasse password. Tale sistema richiede una sorta di firma dei parametri di ogni richiesta e un token di autorizzazione ottenuto da nome utente delegato, chiave cosiddetta “consumer key” e chiave cosiddetta “consumer secret”. Tutti i dati necessari a preparare l’utilizzo di questa funzionalità sono forniti da Google per le versioni “premium” ed “educational” delle loro so- luzioni web: per la versione base non è possibile usare la 2-legged authen- tication per impersonare un’altro utente, ed è quindi necessario possedere le sue credenziali per avere accesso ai suoi dati o anche semplicemente caricare un file. La classe DocumentsService può essere istruita ad occuparsi au- tonomamente dell’autenticazione con queste modalità, sostituendo il suo generatore di richieste RequestFactory con un oggetto creato ad hoc in tal senso; una volta che GooglePremiumAuthenticator dietro le scene ha compiuto queste operazioni, l’autenticazione avverrà in maniera trasparente. 5.4 Setup dei sistemi di archiviazione Allo stato attuale delle cose, non è stato ancora rilasciato un prodotto finito (come ad esempio un plug in) che permetta il fluire dei dati di- rettamente da GEC a uno dei sistemi di terza parte proposti secondo modalità certe e definitive, poiché non sono ancora chiari i dettagli sul come trarre maggior vantaggio dal sistema creato.
  • 98 5. Sottosistema di archiviazione e plug in Esistono invece una serie di esempi e scheletri di codice pronti da riempire con pochissime righe di codice, per permettere a qualsiasi sviluppatore—anche con scarsa formazione sul progetto—di condizionare il sistema a operare nel modo che egli ritenga più opportuno. Il setup del sistema di plug in dal lato di GEC_Scan è banale, e con- siste semplicemente nell’assicurarsi che gli assembly contenenti i plug in desiderati risiedano nella cartella plugins, all’interno della cartella prin- cipale del programma, o eventualmente in un’altra cartella specificata nell’apposita sezione del file di configurazione ( 4.4 nella pagina 70). Per quanto riguarda i servizi esterni, si rimanda alle guide di Goo- gle e Microsoft rispettivamente per la registrazione e l’utilizzo del ser- vizio Google Documents e per l’istallazione e configurazione di base di Sharepoint. Per entrambi i servizi è necessario creare utenti corrispondenti agli utilizzatori finali del sistema: con il solo Sharepoint è necessario del lavoro aggiuntivo per la creazione di un sito e di una lista all’interno dello stesso, operazioni facilitate dall’utilizzo di templates. A meno di esigenze particolari, una semplice lista di file è sufficiente per cominciare ad utilizzare il servizio in congiunzione col sistema software proposto. L’indirizzo http esatto del sito creato e il nome o l’identificatore univoco della lista in esso contenuta sono necessari per il successivo accesso al servizio Sharepoint, anche se probabilmente la loro gestio- ne sarà resa automatica e trasparente nell’incarnazione finale del si- stema; analoghe considerazioni per entrambi i servizi di archiviazione vanno fatte per username e password, e—nel caso dell’utilizzo della 2-legged-authentication—anche per il cosiddetto “consumer secret”.
  • Capitolo 6 Conclusioni Nel corso del tirocinio svolto presso MIDA4 si è affrontato un caso di studio concreto nato dalla reale necessità di tale ditta. Questo caso di studio ha portato in maniera naturale al porsi l’obiettivo principale del lavoro poi svolto come tesi: la produzione di un software che implemen- tasse alcune funzioni di archiviazione documenti. Tale software doveva integrarsi nel modo più indolore possibile con più grandi realtà software già esistenti e funzionanti. È stato creato non un software, ma un intero kit di sviluppo che consta di tre moduli indipendenti e di una lunga serie di software per il test e la configurazione di aspetti secondari legati al lavoro principale. Ciò è stato possibile utilizzando numerose e diverse tecnologie: re- centi, come l’ambiente di sviluppo Microsoft, il framework dotNet, i web server, e meno recenti—per esigenze particolari o ragioni di compatibilità con altre componenti del progetto—come TWAIN o COM. Il risultato è un sistema che fa dell’interoperabilità il suo punto di forza. Col software prodotto è possibile acquisire ed organizzare i flussi di dati forniti dagli utenti del programma di scansione, ed indirizzarli verso server locali e remoti a fini di validazione, archiviazione, interpretazione. Si può cioè richiedere ad un utente di fornire documenti e dati che, una volta acquisiti, potranno essere memorizzati, inseriti in un meccanismo di approvazione o analizzati (ad esempio da un software OCR). 99
  • 100 6. Conclusioni Ci si è concentrati sull’archiviazione semplice, ignorando cioè le di- sposizioni di legge che riguardano la cosiddetta conservazione ottica so- stitutiva, necessaria qualora si desiderasse conservare informaticamente copie uniche di documenti con valore legale; nonostante ciò, l’architettu- ra generale del sistema non vieta di aggiungere in un secondo momento le funzionalità di firma ed apposizione di marca temporale—necessarie a tale fine—qualora la software house MIDA4 ne sentisse il bisogno. Possibili sviluppi futuri del lavoro esposto potrebbero quindi toccare i temi della conservazione sostitutiva, della produzione di un sistema che gestisca la dematerializzazione dei documenti o ancora lo sviluppo di ser- vizi web dedicati alla conversione di immagini in documenti strutturati, a fini di indicizzazione ed interpretazione automatica.
  • Ringraziamenti In primis un ringraziamento speciale al prof. Alberto Bartoli che ha sa- puto creare nel suo “Laboratorio Reti” una realtà come poche all’inter- no dell’Università di Trieste, mettendo insieme collaboratori brillanti ed affiatati, ed ottenendo i risultati che giustamente ne conseguono. Un grazie anche alla MIDA4 che ha reso possibile lo sviluppo di que- sto elaborato, nonché all’ ing. Giorgio Davanzo che più che un correlatore di forma è stato un collaboratore reale, presente e sempre disponibile ed all ing. Eric Medvet, che per onestà (?) ha rifiutato di essere citato come correlatore pur avendo subito abbondantemente le mie continue richieste d’aiuto. Un grazie al resto del laboratorio sempre pronto a dare una mano, ad Elisa per il “consulto legale” (e per il computer!) ed a tutti quelli tra genitori, parenti ed amici che mi hanno sopportato in questi lunghi mesi. Grazie a tutti. 101
  • Bibliografia [1] The atom publishing protocol - http://tools.ietf.org/ html/rfc5023. [2] The atom syndication format - http://tools.ietf.org/ html/rfc4287. [3] Accademia della crusca. Risposte ai quesiti - http: //www.accademiadellacrusca.it/faq/faq_risp.php? id=4092&ctg_id=44. [4] Google. Google data apis overview - http://code.google. com/intl/it-IT/apis/gdata/overview.html. [5] TWAIN Working Group. Twain specification version 1.9a - http: //www.twain.org/docs/TWAIN19.pdf. [6] Rudyard Kipling. The ballad of east and west. [7] MSDN. Batch updating list items in windows sharepoint ser- vices 3.0 - http://msdn.microsoft.com/en-us/library/ cc404818.aspx. [8] MSDN. Createfile function - http://msdn.microsoft.com/ en-us/library/aa363858(VS.85).aspx. 103
  • 104 BIBLIOGRAFIA [9] MSDN. Filesystemwatcher - http://msdn.microsoft.com/ it-it/library/system.io.filesystemwatcher(VS.80) .aspx. Riferimento a .NET Framework. [10] MSDN. Hooks - http://msdn.microsoft.com/en-us/ library/ms632589(VS.85).aspx. [11] MSDN. sealed - http://msdn.microsoft.com/en-us/ library/88c54tsw(VS.71).aspx. C# Programmer’s Referen- ce. [12] MSDN. Xml serialization in the .net framework - http://msdn. microsoft.com/en-us/library/ms950721.aspx. XML and the .NET Framework. [13] Wikipedia. Hooking - http://en.wikipedia.org/wiki/ Hooking.