Articoli Manifesto Tools Links Canali Libri Contatti ?
Strutture Dati / Java

OptionsList: per semplificare lo sviluppo web

Abstract
Lavorando con Struts è comodo disporre di una struttura dati che modelli una combo box in modo intelligente. In questo articolo presentiamo le OptionsList, una specializzazione di un'ArrayList.
Data di stesura: 12/07/2003
Data di pubblicazione: 17/11/2003
Ultima modifica: 04/04/2006
di Giovanni Giorgi Discuti sul forum   Stampa

Presentazione

Nell'ultimo progetto che ho dovuto affrontare, mi trovavo a dover prototipare rapidamente una interfaccia web in tecnologia J2EE1.2 (Servlet+EJB). Le specifiche erano molto mobili. Ed ero da solo a dover fare una quindicina di schermate. Così ho deciso di avvalermi di Struts 1.0.2 [1].

L'applicativo era pieno di combo box html, create a partire da un database. Questo consentiva di avere un'applicazione molto amichevole e guidata, ma era assai lenta da sviluppare.

Per risolvere il problema, ho ideato una estensione di una ArrayList, chiamata OptionsList. Una OptionsList modella una combo-box html, mantiene l'ordinamento imposto ed inoltre:

  1. Dato il valore dell'opzione (=es un codice numerico) consente di ricavare al volo la descrizione (=ciò che vede l'utente). Fa anche il viceversa.
  2. Impedisce di avere due opzioni con lo stesso valore e/o la stessa descrizione.
  3. Implementando l'interfaccia Collection, è utilizzabile come bean direttamente da struts
  4. È compatibile con ArrayList al 100%.
  5. È serializzabile.
  6. Gli elementi descrizione,valore che memorizza possono essere eterogenei (basta che implementino l'interfaccia OptionBean che non è clonabile).
  7. È in grado di caricare i dati da un database senza dover scrivere una linea di SQL.
  8. È in grado di caricare dati effettuando semplice join, in modo molto compatto.
  9. È ottimizzata in tempo e spazio.
  10. Dispone di un test case junit, può essere stampata con toString(), il metodo add è ottimizzato in tempo.

Si tratta di un oggetto a metà tra la presentation (view) e il modello di business (il model).

Genesi

La OptionsList nasce dall'esigenza di mantenere la compatibilità con molto codice legacy, che ritornava ArrayList di oggetti eterogenei. Gli oggetti erano quasi sempre paia { descrizione, codiceDB } e modellavano aspetti di business logic. Per esempio venivano modellate così sia una lista di province che un insieme di prodotti.

Era necessario garantire che il codice usava OptionsList fosse compatibile al 100% con le ArrayList e non contenesse bug: per questa ragione ho sviluppato un test case JUnit [2]

Utilizzo

Partiamo dal backend, e vediamo come creare una combo-box con la lista delle province. Supponiamo di avere una tabella relazionale di nome "PROVINCE" con le due colonne NOME_PROVINCIA (es "TORINO") e CODICE_PROVINCIA (es "TO"). Il seguente frammento di codice fa il lavoro per noi:

  1. OptionsList l = OptionsList.fromSQL( 
  2.   conn, "PROVINCE", "CODICE_PROVINCIA", "NOME_PROVINCIA"); 

ove conn è la connessione SQL al database.

Se vogliamo ordinare l'output per CODICE_PROVINCIA basta scrivere:

  1. OptionsList.fromSQL( 
  2.   conn, "PROVINCE", "CODICE_PROVINCIA", "NOME_PROVINCIA", 
  3.   "CODICE_PROVINCIA"); 

Se vogliamo filtrare le province, e prendere solo quelle il cui nome inizia per A si può usare:

  1. OptionsList.fromSQL( 
  2.   conn, "PROVINCE", "CODICE_PROVINCIA", "NOME_PROVINCIA", 
  3.   "CODICE_PROVINCIA", 
  4.   "NOME_PROVINCIA LIKE 'A%'"); 

È anche possibile filtrare senza ordinare (basta inserire null come quinto parametro).

Una volta che abbiamo la nostra lista, è possibile effettuare interessanti operazioni.

Supponiamo che nel db vi sia una linea del tipo:

NOME_PROVINCIA CODICE_PROVINCIA
MILANO MI

Allora il seguente codice:

  1. OptionsList l = OptionsList.fromSQL( 
  2.   conn, "PROVINCE", "CODICE_PROVINCIA", "NOME_PROVINCIA"); 
  3.  
  4. String nome   = l.getName("MI"); 
  5. String prov   = l.getValue("MILANO"); 

assegnerà "MILANO" a nome e "MI" a prov.

Vediamo il codice del metodo notevole fromSQL:

  1. /* Nota: orderby e whereFilter possono essere null */ 
  2. public static OptionsList fromSQL(Connection conn, String tableName, 
  3.   String codeRowName, String descRowName, String orderby, String whereFilter) 
  4.   throws SQLException 
  5.   PreparedStatement stm = null; 
  6.   OptionsList aList     = null; 
  7.   long tStart           = System.currentTimeMillis(); 
  8.   String query          = "SELECT " + codeRowName + ", " + descRowName + 
  9.     " FROM " + tableName; 
  10.  
  11.   if (whereFilter != null) { 
  12.     query += " WHERE " + whereFilter; 
  13.   if (orderby != null) { 
  14.     query += " ORDER BY " + orderby; 
  15.  
  16.   Logger log = Logger.getLogger("OptionsList.static"); 
  17.   ResultSet rs; 
  18.  
  19.   try { 
  20.     stm = conn.prepareStatement(query); 
  21.     rs  = stm.executeQuery(); 
  22.   } catch (SQLException e) { 
  23.     log.error("Cannot execute:" + stm, e); 
  24.     throw e; 
  25.  
  26.   aList = new OptionsList(); 
  27.  
  28.   while (rs.next()) { 
  29.     // L'assegnamento e' sempre: descrizione, codice. 
  30.     aList.addOption(rs.getString(2), rs.getString(1)); 
  31.  
  32.   log.debug( 
  33.     System.currentTimeMillis() - tStart + 
  34.     " ms OptionsList.fromSQL <--< " + stm); 
  35.  
  36.   return aList; 

Si inizia costruendo la query di select, dopodiché si crea il ciclo che popola la lista. Per semplicità ho introdotto il metodo OptionsList.addOption che fa semplicemente:

  1. public boolean addOption(String descrizioneLunga, String codiceDB) 
  2.   return this.add(new SimpleOptionBean(descrizioneLunga, codiceDB)); 

la classe SimpleOptionBean implementa in modo "ingenuo" l'interfaccia OptionBean:

  1. public class SimpleOptionBean implements OptionBean 
  2.   private String name; 
  3.   private String value; 
  4.  
  5.   public  SimpleOptionBean(String desc, String cost) 
  6.     this.name   = desc; 
  7.     this.value  = cost; 
  8.  
  9.   public String toString() 
  10.     return "\"" + name + "\" -> \"" + value + "\""; 
  11.  
  12.   public String getName() 
  13.     return name; 
  14.    
  15.   public String getValue() 
  16.     return value; 

Inserimento nella session della lista

Al fine di usare la lista atraverso i custom tag, la inseriamo nella session prima di chiamare la jsp:

  1. session.setAttribute( 
  2.   "provLIST", 
  3.   OptionsList.fromSQL( 
  4.     conn, "PROVINCE", "CODICE_PROVINCIA", "NOME_PROVINCIA")); 

Utilizzo nella jsp: espansione con il custom tag html:select e html:options

Infine, ecco un frammento di jsp che crea una combo box ed assegna al campo "provinciaCliente" la provincia selezionata:

  1. <html:select property="provinciaCliente" size="1"> 
  2.   <html:options collection="provLIST" labelProperty="name" property="value" /> 
  3. </html:select> 

Notate come a struts si debbano dare i nomi delle due proprietà bean (name e value) che poi il custom tag "trovera'" attraverso la reflection.

Note sull'implementazione

Le OptionsList sono implementate estendendo un'ArrayList e aggiungendo due TreeMap che indicizzano sia i Value (codici) che i Name (descrizioni testuali). Inoltre accetta al suo interno solo classi che implementino l'interfaccia OptionBean e si rifiutano di inserire valori duplicati.

Sono pensate per essere create, e date alle action ed ai form. Per esempio è comodo riconvertire un codice (value) in una descrizione se si deve presentare un riepilogo all'utente prima di inviare un ordine: e questo lo può fare anche il form, purché possa accedere alla lista in qualche modo.

È possibile rimuovere elementi o riordinarli, ma non sono molto efficenti in operazioni di questo tipo, perché gli indici sono ricostruiti ad ogni operazione come remove(), retainAll() ecc. Comunque le ArrayList non sono molto indicate per operazioni di questo tipo.

La classe è pensata solo per aggregare coppie String,String ma è facile estendere questo scrivendo una classe concreta che implementi un OptionBean più potente.

Rispetto alle SequencedHashMap del progetto Jakarta Common [3], le OptionsList hanno il vantaggio che implementando Collection possono essere usate da Struts!

È possibile ottimizzre ultieriormente i metodi di addAll(...), remove(...), set(...) in modo da non ricostruire gli indici ogni volta, ma siccome tipicamente le combo contengono solo un centinaio di valori, non ne vale la pena.

Nota sulle join

Il metodo fromSQL è molto comodo, e piuttosto che scrivere una join a mano, iterando tra due OptionsList, usate un codice di questo tipo, che è ASSAI più veloce:

  1. OptionsList.fromSQL( 
  2.   conn, 
  3.   "FRUTTA_DA_PRESENTARE, LISTA_FRUTTA", 
  4.   "LISTA_FRUTTA.CODICE", 
  5.   "LISTA_FRUTTA.DESC", 
  6.   "LISTA_FRUTTA.ORDINE_NUMERICO", 
  7.   "LISTA_FRUTTA.CODICE = FRUTTA_DA_PRESENTARE.CODICE AND FRUTTA_DA_PRESENTARE.FLAG = 'S'"); 

L'ultima stringa rappresenta la condizione di join, mentre il secondo parametro lista le due tabelle su cui si fa la proiezione.

Il codice di cui sopra estrae i dati con una query del tipo:

  1. SELECT LISTA_FRUTTA.CODICE, LISTA_FRUTTA.DESC 
  2.   FROM FRUTTA_DA_PRESENTARE, LISTA_FRUTTA 
  3.   WHERE 
  4.     LISTA_FRUTTA.CODICE = FRUTTA_DA_PRESENTARE.CODICE AND 
  5.     FRUTTA_DA_PRESENTARE.FLAG = 'S' 
  6.   ORDER BY LISTA_FRUTTA.ORDINE_NUMERICO

Come si vede le OptionsList risolvono almeno la metà delle query che ci troviamo a fare tutti i giorni!

Informazioni sull'autore

Giovanni Giorgi, classe 1974. Dopo il diploma di liceo Classico, si è laureato in Informatica nel febbraio 2000, e attualmente lavora nel campo del software finanziario (trading on line, soluzioni web).
Appassionato di linguaggi di programmazione, si interessa anche di politica e letteratura.

È possibile consultare l'elenco degli articoli scritti da Giovanni Giorgi.

Altri articoli sul tema Strutture Dati / Java.

Risorse

  1. Struts.
    http://jakarta.apache.org/struts/
  2. JUnit.
    http://www.junit.org
  3. Jakarta Commons, libreria di componenti riusabili.
    http://jakarta.apache.org/commons/
  4. Archivio con i sorgenti in della OptionsList.
    http://www.siforge.org/articles/2003/11/java_options_list/java_options_list_src.zip (6Kb)
Discuti sul forum   Stampa

Cosa ne pensi di questo articolo?

Discussioni

Questo articolo o l'argomento ti ha interessato? Parliamone.