Una volta definiti i requisiti di elaborazione dei meta-dati da parte del prototipo, è necessario procedere con la progettazione dell'applicativo, per arrivare al diagramma delle classi ed alla successiva implementazione.
Nella figura 5.15 vediamo il diagramma UML delle classi risultante da questa fase di progettazione.
La classe principale che verrà eseguita è DWers, che contiene il metodo main per l'esecuzione dell'interfaccia grafica. Da questa classe sarà possibile aprire i file contenenti le history, inviate tramite argomenti a linea di comando (args), oppure aperti attraverso l'apposito menu.
Le history hanno solamente due attributi: l'indirizzo del file di riferimento ed una struttura dati che contiene una lista delle versioni presenti nella history. Ogni history deve contenere almeno una versione dello schema (schemaVersion), che come abbiamo mostrato nella definizione dei meta-dati è composta da un identificativo di versione (version), il periodo di validità
, uno schema aumentato (schemaAUG) opzionale ed uno schema Graph (schemaGraph). Una differenza tra la progettazione della classe schemaVersion e la sua struttura di memorizzazione è l'intervallo di validità. Mentre nei meta-dati viene memorizzato solo l'istante iniziale dell'intervallo, qui sarà conveniente avere entrambi gli estremi. Questo è utile perché nella maggior parte del tempo di utilizzo del prototipo verrà mantenuta in memoria una versione alla volta, ed il recupero di quella successiva per l'individuazione del secondo estremo sarebbe dispendioso.
La classe XMLData è molto importante, ed è il cuore dell'elaborazione dei meta-dati. Viene istanziata al momento dell'apertura del file di elaborazione, e permette di eseguire interrogazioni sulla meta-conoscenza dei grafi della history. Per ogni history, quindi, esiste una classe XMLData che carica l'albero della struttura XML e permette di gestire gli elementi a basso livello. Successivamente vedremo la gestione degli attributi nei vari livelli che li compongono: nodo del grafo, meta-dato ed istanza nel database. I metodi della classe XMLData possono essere suddivisi in tre categorie, in base alla funzione svolta:
Per quanto riguarda la creazione di un nuovo elemento XML, invece, è necessario distinguere tra le varie tipologie di nodi (proprietà, misure, dipendenze funzionali), ed i metodi relativi a questa operazione sono inseriti nelle classi specifiche.
La classe XMLData fa largo uso delle API messe a disposizione dal framework Dom4J e dell'interfaccia di i/o di questa libreria. Per l'interrogazione dei meta-dati in fase di implementazione è stato utilizzato il linguaggio XPath. Questo potente strumento, assieme alla definizione della struttura attraverso l'XML Schema, ci permette di recuperare le informazioni salvate nel file XML. In aggiunta a questa possibilità, l'interrogazione dei meta-dati può avvenire anche durante una transazione, quando le nuove aggiunte ancora non sono state salvate nel file. Tutte le modifiche, infatti, vengono riportate nel org.dom4j.Document appena modellate, prima ancora della chiusura della transazione. In questo modo è possibile sempre trovare tutti le dipendenze funzionali relative ad un attributo, tutte le proprietà di una dimensione, etc. Tutte le modifiche apportate al Document da writeXMLElement e deleteElement, vengono salvate solamente con il metodo saveXML, ma sono disponibili per le interrogazioni anche prima di questa operazione. Le stringhe XPath per le interrogazioni sono gestite in maniera relativa. Questo significa che dovrò salvare un riferimento Element (org.dom4j.Element) per ogni punto di interesse del file dei meta-dati, che mi permetterà di interrogare solo quella porzione della mia meta-conoscenza. Quando apro uno schema graph, ad esempio, salvo in memoria anche un puntatore all'Element nel documento XML.
mySchemaGraph.getXMLData().getNodeInstances(
mySchemaGraph.element, ''property '');
Se invece volessi trovare tutte le proprietà che sono immediatamente precedenti ad una proprietà selezionata nella gerarchia delle granularità di una dimensione, avrei ad esempio:
schemaGraph.getXMLData().getContent( schemaGraph.element,
''functionalDependence/to[.=' '' + attributeName + '' ']//../from'');
La classe schemaGraph rappresenta i grafi così come formalmente introdotti, e contiene i metodi necessari alla modifica ed alle elaborazione specifiche di questo elemento. Questa classe deve contenere sia le informazioni relative alla definizione dello schema, sia quelli relativi al suo disegno e modellazione su schermo.
Nella figura 5.16 possiamo vedere come la vista del grafo sia indipendente dal suo modello, che è definito univocamente. Due differenti viste del grafo possono avere i nodi disegnati o ordinati in maniera diversa, ma questo non incide sulla meta-conoscenza che modellano.
Per la visualizzazione del grafo e l'interfaccia del sistema di versioning in fase di implementazione si è deciso di utilizzare il framework JGraph.
La maggior parte dei metodi contenuti nella classe schemaGraph servono per controllare la consistenza del grafo prima o dopo l'inserimento o la cancellazione di un elemento:
Il metodo getAttributeFromName permette il recupero di un attributo a partire dal suo nome per l'eventuale modifica o cancellazione, insertIntoGraph inserisce un nuovo elemento o una dipendenza funzionale nella rappresentazione grafica, mentre applyLayout ridisegna il grafo secondo un algoritmo ottimale specificato in una classe a parte, per separare i dettagli implementativi della vista da quelli del modello.
La classe SchemaGraphVisualFactory fornisce le operazioni di modifica sul grafo che hanno bisogno di una interfaccia di supporto, come la modifica di un valore di un attributo oppure il cambiamento da proprietà a misura, o l'inserimento di un nodo in un punto. GraphEditor rappresenta invece una interfaccia grafica che permette le operazioni di undo, redo, di raggruppamento e di zoom del grafo, così come la raccolta degli eventi di modifica sul grafo.
La classe schemaAUG è una specificazione della classe schemaGraph, con l'aggiunta dei metodi per ricavare lo schema comune tra due versioni diverse. Come visto nel capitolo precedente, il risultato di questa operazione è determinato univocamente, e serve sia in fase di interrogazione dei dati sia in fase di modellazione delle versioni.
Il fatto che schemaGraph, lo schemaAUG e lo schema comune abbiano praticamente gli stessi metodi e gli stessi attributi permette di gestire le operazioni di modellazione in maniera estremamente flessibile: il progettista può fare composizioni di schemi, fare modifiche ai risultati e salvarli, applicando le novità alle versioni precedenti, senza che questo comporti inconsistenze.
Prima di procedere con la descrizione delle classi che manipolano attributi e dipendenze funzionali, è necessario mostrare come si è deciso di suddividere la gestione dei livelli di informazione relativi a questi elementi.
La figura 5.17 mostra i tre livelli che separano la gestione di un attributo d'esempio, la proprietà year. Il primo livello di dettaglio, rappresentato dalle informazioni grafiche del nodo preso in esame e dalla vista del grafo, è importante per l'utente ma di scarso interesse per le informazioni che modella. Per questo motivo le informazioni relative alla gestione grafica della rappresentazione dello schema non vengono mai salvate, e ad ogni sessione di utilizzo del prototipo vengono irrimediabilmente perse. A livello dei meta-dati, invece, è importante mantenere ogni dettaglio di conoscenza utile inserito dall'utente, da mettere a disposizione in ogni successiva sessione di lavoro. Per ultimo, ma non per ordine di importanza, nel livello del database vengono inseriti i dati relativi alle istanze dell'attributo modellato, così come tutte le informazioni necessarie in fase di interrogazione. Queste informazioni sono disponibili anche in maniera indipendente dai livelli precedenti, e sono il cuore della conoscenza acquisita nel corso degli anni, immagazzinata nel data warehouse.
La progettazione delle classi che gestiscono gli attributi e le dipendenze funzionali, pertanto, dovranno contenere metodi relativi ad ognuno di questi livelli, possibilmente in maniera separata per facilitare la portabilità delle scelte implementative.
Nel primo livello, dove l'implementazione è fortemente legata al framework JGraph, il grafo è composto da nodi (GraphCell), archi (Egde) e magneti (Port), come mostrato in figura 5.18. Gli attributi, quindi, sono una estensione delle GraphCell definite, mentre le dipendenze funzionali sono una estensione degli archi del grafo (Edge). I magneti servono per fornire i punti di connessione degli archi ai nodi, visibili quando si seleziona un attributo o un gruppo di attributi. In altri modelli i magneti permetterebbero di connettere archi a gruppi di nodi, ma la definizione di schema graph impedisce questo tipo di relazione. I magneti sono i primi figli dei nodi ai quali sono attaccati nella gerarchia del modello del grafo. Per questo motivo si può utilizzare il metodo derivato dal DefaultGraphCell getChildAt(0). Nelle funzioni del prototipo che fanno una scansione del grafo per visualizzarlo, quindi, in fase di implementazione si è utilizzato il comando Java instanceof, per verificare la classe specifica degli oggetti selezionati. Nell'implementazione del prototipo, quindi, è stato fatto largo uso del polimorfismo del Java, strumento potente che ci ha permesso di gestire tutti i nodi attraverso un'unica interfaccia comune, specificandone poi i diversi comportamenti a seconda delle necessità. Quando si seleziona un nodo del grafo, sia esso una proprietà o una misura, in realtà si seleziona anche il magnete, e durante l'eliminazione si deve provvedere alla cancellazione di entrambi.
E' importante capire la connessione gerarchica tra i nodi della rappresentazione grafica e gli attributi. Questa scelta è legata alla necessità di recuperare tutta la classe relativa ad un nodo, una volta che questo viene selezionato attraverso l'interfaccia grafica. Una GraphCell, infatti, non permette l'inserimento di dati aggiuntivi al nome, e se le due strutture dati non fossero state legate da una gerarchia di ereditarietà sarebbe stato necessario fare una ricerca su tutto il grafo per ogni operazione di modifica del livello dei meta-dati.
La classe Attribute, quindi, contiene anche le caratteristiche grafiche del nodo, come ad esempio draw, oltre a quelle specifiche per la modifica del nome (getName, setName) e del tipo di dato (setDataType, getDataType). getPatentAttr serve per trovare gli eventuali nodi che precedono nella gerarchia delle dipendenze l'attributo, mentre getAttrTreeRoot trova nella gerarchia l'attributo radice, che corrisponde alla dimensione nel caso delle proprietà e la misura nel caso delle misure derivate.
La classe Measure estende la classe Attribute con le funzioni relative alla gestione della formula di derivazione, che è obbligatoria solo nel caso delle misure derivate.
Il fact node concettualmente non è un vero e proprio attributo, ma rappresenta l'evento centrale di tutto lo schema. Il comportamento di questo tipo di nodo è del tutto simile a quello degli attributi, tranne per il fatto che non può essere né eliminato né inserito. Per questo motivo in fase di progettazione è stato scelto di specificare con la classe del fatto la classe dell'attributo attraverso l'ereditarietà. Questo permette di evitare la creazione di un altro livello nella gerarchia dei nodi del grafo, che concettualmente potrebbe essere divisa in due differenti sotto-alberi:
Nel nostro caso, quindi, attributo e nodo sono utilizzati in maniera indistinta.
Il concetto di nodo è stato eliminato completamente dalla modellazione in fase di progettazione per non creare confusione tra la classe Node del framework XML ed il concetto di attributo presente nel nostro prototipo.