Primi Passi con BeanIO (parte 2)

Dopo aver sinteticamente introdotto il framework nell’articolo Primi Passi con BeanIO (parte 1), procediamo trattando alcuni aspetti avanzati della libreria, che sono di sicuro interesse per lo sviluppatore.

Gestione delle Collection

La prima caratteristica che affrontiamo è come mappare le proprietà dei bean di tipo array o collection. Aggiorniamo la classe Customer inserendo la proprietà someNumbers di tipo List:

Il mapping nel file customer-mapping.xml si realizza inserendo un nuovo tag <field> associato alla proprietà:

L’elemento chiave per il mapping è l’attributo collection che può essere impostato col nome completo della classe che implementa l’interfaccia java.util.Collection o con uno degli alias previsti dal framework che sono: collection, list, set o array. E’ possibile inoltre dichiarare il numero di occorrenze della proprietà da riportare nello stream utilizzando gli attributi del campo minOccurs e maxOccurs. Se non dichiarato, minOccurs assume il valore 1, mentre maxOccurs assume il valore minOccurs o 1, a seconda di quale dei due è maggiore.

Tornando all’esempio, supponiamo che la proprietà someNumbers di Customer assuma il valore [1, 2, 3, 4, 5]. Si noti che il numero di valori è inferiore a minOccurs che è 10, quindi lo stream cvs prodotto sarà del tipo:

Nel caso in cui esistano diverse proprietà di tipo collection nel bean, il mapping dovrà essere fatto in modo che il risultato della serializzazione renda comunque riconoscibile, dalla posizione di un valore, a quale proprietà appartiene. Ad esempio se inseriamo la proprietà someOtherNumbers:

e lo mappiamo sul file xml nel modo seguente:

il framework solleverà l’eccezione:

org.beanio.BeanIOConfigurationException: Cannot determine field position, field is preceded by another component with indeterminate occurrences

Per evitarla è necessario fissare il numero di occorrenze previste per la proprietà someNumbers, ad esempio assegnando a maxOccurs il valore 10. In questo caso nello stream avremo sempre 10 posizioni per someNumbers e le restanti apparterranno sicuramente a someOtherNumbers. Ad esempio se someOtherNumbers assume il valore [6,7,8,9,10], la serializzazione in cvs sarà:

Record Multipli

Accade molto spesso nell’esportazione di dati (massivi) che i record esportati appartengano a bean di tipo differente. Ad esempio il file prodotto potrebbe prevedere un header ed un trailer. Il primo che riporta la data di generazione del file, mentre la seconda che riporta il numero di record esportati. Per farlo introduciamo i bean Header e Trailer, che non riporto data la loro semplicità, e vediamo come sia possibile mapparli nell’xml:

Innanzitutto nella definizione dello stream  customerFile abbiamo inserito due nuove definizioni di record, una per l’header ed una per il trailer, ed inoltre abbiamo utilizzato l’attributo order. Questo attributo può assumere valori interi maggiori o uguali a zero, ed indica la posizione del record nello stream. Quindi l’header sarà sempre il primo record mentre il trailer sarà sempre l’ultimo. tale ordine dovrà essere rispettato anche in fase di scrittura dello stream altrimenti sarà sollevata una eccezione. Per cui la classe CustomerWriter dovrà essere modificata nel seguente modo:

Il file csv prodotto sarà del tipo:

Sfortunatamente la modifica della classe CustomerReader non è altrettanto semplice. In quanto al framework non abbiamo indicato quanti header aspettarsi nel file csv da parsare. Quello che accade è che la libreria dopo aver letto l’header si aspetta ancora un nuovo header e quindi solleva l’eccezione:

org.beanio.InvalidRecordException: Invalid 'header' record at line 2

Proviamo a risolvere valorizzando gli attributi minOccurs=1 e maxOccurs=1 nel tag <record> associato all’header. Ancora una volta avremo l’eccezione:

org.beanio.InvalidRecordException: Invalid 'customer' record at line 4

perchè a questo punto è il trailer che non viene, giustamente, riconosciuto come un record di tipo Customer. Se ci trovassimo nella situazione di conoscere esattamente quanti record di tipo customer sono esportati, e tale valore non cambia mai, potremmo ancora valorizzare opportunamente gli attributi  minOccurs e maxOccurs sul tag <record> di customer, nel nostro caso ad esempio dovrebbero essere valorizzati a 2, e tutto funzionerebbe.

Sfortunatamente questa è una situazione abbastanza improbabile, quindi è necessario utilizzare il framework in modo differente. Innanzitutto inseriamo nella definizione di ciascun record un tag <field> che fittiziamente definisce una proprietà recordType non presente nei bean e che assumono un valore costante. Ad esempio per il record header sarà del tipo:

dove:

  • l’attributo rid indica che il campo è utilizzato come record identifier;
  • l’attributo literal definisce la costante utilizzata per riconoscere il tipo di record;
  • l’attributo ignore indica che il campo non è mappato in una proprietà del bean.

Con questa nuova definizione il csv prodotto sarà del tipo:

A questo punto riconoscere il record associato a ciascuna riga è abbastanza semplice poichè è indicato dal primo campo di ciascuna riga che assumerà i valori: Header, Customer e Trailer.

La meccanismo che mette in campo BeanIO per consentirci di riconoscere il tipo di record letto consiste nell’utilizzare il metodo getRecordName() della classe BeanReader. Tale metodo restituisce il nome del record che è stato riconosciuto dal framework e che è definito nell’attributo name di ciascun tag <record>. In ragione di ciò la classe CustomerReader può essere modificata nel seguente modo:

Codice Sorgente

Il codice sorgente con l’esempio presentato è scaricabile qui bean-io.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.

*