martedì 27 settembre 2011

Wicket e Form: Dipendenze di validazione

Salve a tutti.
Oggi torno con un veloce esempio per spiegare una casistica che può presentarsi spesso e che è utile analizzare.

SITUAZIONE: Ho una form con un campo di dipo TEXT che di base non è richiesto ma diventa obbligatorio sulla base di un altro campo.


La richiesta quindi è quella di cambiare il valore settato normalmente da setRequired sulla base di un dato evento che può verificarsi in fase di submit.

Nel mio caso avevo 2 diversi submit nella form. Uno dei due richiedeva che il campo fosse riempito. Quindi di default il campo aveva setRequired a false ma se premevo quello specifico submit, doveva diventare true.

La cosa che un normale programmatore si aspetterebbe di fare sarebbe intercettare il metodo onSubmit del bottone submit incriminato e metterci un bel nomeInputText.setRequired(true);
Questo approccio è per sbagliato e potenzialmente dannoso. Contrariamente a quanto si possa credere, Wicket valida gli input prima di lanciare il tasto onSubmit del bottone. Questo significa che il campo verrebbe impostato come obbligatorio solo DOPO la validazione.
In questo caso si verificherebbero due casistice:
- La form era corretta: In questo caso la form viene submitata ed il campo rimane vuoto;
- La form era incorretta per altre ragioni: In questo caso viene riproposta la form (con il relativo pannello feedback) ed il nostro input viene effettivamente messo su "richiesto". Al successivo submit anche lui genererà errore se vuoto.

Ma cosa succede se l'utente preme sull'altro tasto (ovvero quello che non richiedeva il campo)? In questo caso non c'è niente che rimetta a false la setRequired anche mettendolo nella onSubmit del bottone (lo metterebbe solo al successivo errore della form).

Quindi questo approccio è sbagliato. Il metodo più semplice è fare l'override del metodo dell'input stesso, che viene richiamato dal validatore.

In ogni input infatti esiste un metodo chiamato isRequired() che ritorna un boleano. Il boleano ritornato è lo stesso settato dalla setRequired.

TextField<Date> scadenzaText = new TextField<Date>(SCADENZA){
 @Override
 public boolean isRequired() {
   //Di base l'input non è richiesto 
   //ma se premo uno dei due bottoni per 
   //devo renderlo obbligatorio
   Form form = (Form) findParent(Form.class);
   return ( (form.getRootForm().findSubmittingButton() == nomeOggettButtonSensibile));
 }
};

Nel codice sopra vediamo come fare.
Il nostro campo scadenzaText di tipo DATE, non ha richiamato nessuna setRequired. Facciamo però ovverride del metodo isRequired e dentro imponiamo la nostra condizione.
In questo caso creiamo al volo una variabile di tipo FORM che inizializziamo come la FORM che contiene noi stessi (tramite findParent). Su questo oggetto richiamiamo il metodo findSubmittingButton controllando se esso è uguale all'oggetto BUTTON che deve richiedere l'obbligatorietà del campo.  Se l'ugualianza è verificata, allora viene ritornato TRUE e quindi il campo diventa obbligatorio.

Ovviamente qui abbiamo condizionato il campo TEXT con un submit. Nessuno ci vieta per esempio di fare controlli su altri campi. Per esempio possiamo vedere tramite un semplice nomeAltroInput.getValue() se il valore di un altro input è settato o uguale a qualcosa.

Un esempio pratico si ha, per esempio, quando ci sono dei range di date da inserire. Spesso durante un filtro ricerca uno può cercare all'interno di uno specifico periodo o semplicemente cercare indipendentemente dalla data. Se però inseriamo la data di partenza di un periodo, dobbiamo rendere obbligatorio inserire anche la data di fine periodo. In questo caso possiamo rendere obbligatorio questo secondo campo se il primo è stato riempito ecc...

martedì 13 settembre 2011

Wicket - Form e Validazioni (parte 2)

Questo post è il continuo del post Wicket - Form e Validazioni

Continuiamo ad analizzare le form espandendo il controllo di prima.
Per prima cosa aggiungiamo un nuovo campo input che chiameremo età nella quale andremo a mettere un intero. Impostando la proprietà myAge ad Integer, Wicket attuerà in automatico il controllo se il dato inserito è effettivamente un numero. Per aggiungere un ulteriore controllo similmente a quello sul name, useremo il validatore chiamato RangeValidator che è una classe di Wicket che gestisce le validazioni di Interi all'interno di un range indicato.

Il codice corrispettivo sarà quindi:

 TextField age    = new TextField ("age",new PropertyModel(this,"myAge"));
 age.setRequired(true);
 age.add(new RangeValidator(5,50));

Come vedete è anche qui molto semplice aggiungere controlli. Non ci dovremo preoccupare di nient'altro e sarà Wicket a fare controlli al posto nostro.

A questo punto però complichiamo le cose. Per il campo surname vogliamo aggiungere un controllo customizzato non presente nei controlli base di Wicket. Per fare questo occorre definire un nostro Validator e aggiungerlo quindi all'oggetto. E' questa una pratica molto usata e che sfrutta molto la componentistica di Wicket perché definendo un validator esterno, lo potremo usare anche in altre form oppure, cosa altrettanto comune, possiamo ridefinire un nostro TextField particolare aggiungendogli un validator inscritto direttamente nella classe.
Andiamo a vedere entrambe le soluzioni.

Per la prima proviamo a creare un validatore esterno.


package it.trewps.testajax.web;
import org.apache.wicket.validation.IValidatable;
import org.apache.wicket.validation.IValidator;
import org.apache.wicket.validation.ValidationError;

public class SurnameValidator implements IValidator<String>{

    private static final long serialVersionUID = 1L;
    @Override
    public void validate(IValidatable<String> validatable) {
        String value = validatable.getValue();        
        if (!value.matches("[A-Z]{3}[0-9]{2}")){
            ValidationError error = new ValidationError();
            error.setMessage("Non va molto bene");
            validatable.error(error);
        }
    }

}

I validatori devono implementare l'interfaccia IValidator a cui ovviamente va impostato il tipo di oggetto da validare (in questo caso stringa). Il metodo obbligatorio da implementare è ovviamente validate che si occupa ovviamente di fare l'effettivo controllo sul dato che viene passato come parametro in ingresso nell'oggetto validatable sui cui possiamo lanciare la getValue() per farci tornare il valore effettivo.
In questo esempio controlliamo il campo con una espressione regolare ed in caso di incorrettezza, istanziamo un oggetto error impostando poi il parametro validatable.error. Se questo dato è settato, allora il validatore avverte l'oggetto a cui è associato, che il campo è sbagliato ritornando poi il testo contenuto nel messaggio dentro l'oggetto error passato al metodo.

Associare questo Validator all'oggetto TextField è molto semplice. Basterà aggiungere l'oggetto validator per implementare automaticamente l'operazione.
 
TextField surname = new TextField ("surname",new PropertyModel(this,"mySurname"));
            surname.setRequired(true);
            surname.add(new SurnameValidator());

Il controllo viene ovviamente operato dopo che Wicket avrà controllato l'effettivo riempimento del campo dato che la setRequired agisce ovviamente prima.

Ecco al lato pratico cosa succede.
Il campo cognome ha effettivamente un valore ma non essendo conforme al Validator, viene ritornato il messaggio di errore che avevamo impostato.

Qua sotto invece abbiamo un esempio molto comune in ambito Wicket. Se il validator è molto personalizzato su uno specifico input, possiamo pensare di ridefinire direttamente tutto l'imput implementandovi dentro il validator stesso.

Qua sotto abbiamo creato un nuovo TextField chiamato MySurnameTextField estendendo il TextField base di Wicket. Privatamente alla classe abbiamo ricreato il validatore che altri non è che la copia pari pari della classe vista sopra con la differenza che questa è private.

Ridefiniamo poi tutti i costruttori dell'oggetto originale (anche se non siamo obbligati) ed aggiungiamo ad ognuno l'istruzione per aggiungere il validatore.

package it.trewps.testajax.web;

import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.model.IModel;
import org.apache.wicket.validation.IValidatable;
import org.apache.wicket.validation.IValidator;
import org.apache.wicket.validation.ValidationError;

public class MySurnameTextField extends TextField<String>{

    private static final long serialVersionUID = 1L;
    
    public MySurnameTextField(final String id)
    {
        super(id);
        add(new SurnameValidator());
    }

    public MySurnameTextField(final String id, final Class<String> type)
    {
        super(id);
        setType(type);
        add(new SurnameValidator());
    }

    public MySurnameTextField(final String id, final IModel<String> model)
    {
        super(id, model);
        add(new SurnameValidator());
    }

    public MySurnameTextField(final String id, IModel<String> model, Class<String> type)
    {
        super(id, model);
        setType(type);
        add(new SurnameValidator());
    }
    
    private class SurnameValidator implements IValidator<String>{

        private static final long serialVersionUID = 1L;
        @Override
        public void validate(IValidatable<String> validatable) {
            String value = validatable.getValue();        
            if (!value.matches("[A-Z]{3}[0-9]{2}")){
                ValidationError error = new ValidationError();
                error.setMessage("Non va molto bene personale");
                validatable.error(error);
            }
        }

    }
    
    

}

Al lato del richiamo basterà creare un nuovo oggetto del tipo che abbiamo creato. Non c'è bisogno di aggiungere il validator dato che sarà incluso direttamente nell'oggetto MySurnameTextField.

 MySurnameTextField mySurname = new MySurnameTextField("surname",new PropertyModel(this,"mySurname"));
 mySurname.setRequired(true);


Wicket - Form e validazioni

Nell'articolo precedente abbiamo visto un esempio di form e lo abbiamo evoluto per aggiungere un Behaviour Ajax che controllasse in tempo reale la validità di un campo.

Oggi facciamo un passo indietro riproponendo una form ed utilizzando il sistema di validazione classico che Wicket mette a disposizione e che viene lanciato all'atto dell'invio della form.

Create quindi un nuovo progetto da zero e generate le solite classi di base (Consultate il post Wello World). Creiamo quindi una pagina HTML semplice con dentro una form.
<div wicket:id="formFeedback"></div>
<form wicket:id="testForm">
    <label>Nome:</label><input wicket:id="name" />
    <label>Cognome:</label><input wicket:id="surname" />
    <input wicket:id="submit">
</form>

Come vedete il codice HTML è molto semplice. Due input, un submit ed un DIV che conterrà il nostro Feedback Panel. Quest'ultimo altro non è che un placeholder che conterrò un oggetto che Wicket mette a disposizione per visualizzare gli errori di una form.

Creiamo quindi una pagina con dentro una form secca sotto forma di classe privata.

package it.trewps.testajax.web;

import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.form.Button;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.panel.FeedbackPanel;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.validation.validator.StringValidator;

public class HomePage extends WebPage{
    
    
    public HomePage(){
        add(new FeedbackPanel("formFeedback").setOutputMarkupId(true));
        add(new InputForm("testForm"));
    }
    
    private class InputForm extends Form{
        
        private String myName;
        private String mySurname;
        
        private static final long serialVersionUID = 1L;
        
        public InputForm(String id) {
            super(id);
            TextField name = new TextField ("name",new PropertyModel(this,"myName"));
            name.setRequired(true);
            name.add(StringValidator.lengthBetween(5, 50));
            
            TextField surname = new TextField ("surname",new PropertyModel(this,"mySurname"));
            surname.setRequired(true);
            Button submit = new Button("submit");
            this.add(name);
            this.add(surname);
            this.add(submit);
        }
        
        @Override
        protected void onSubmit() {
            System.out.println("Ciao");
        }
        
        public String getMyName() {
            return myName;
        }

        public void setMyName(String myName) {
            this.myName = myName;
        }

        public String getMySurname() {
            return mySurname;
        }

        public void setMySurname(String mySurname) {
            this.mySurname = mySurname;
        }
        
    }
    
}

Ho messo la classe al completo compresi i package per semplificare le cose. Abbiamo una WebPage di Wicket che verrà richiamata dalla ClassApplication.
Il costruttore si occupa semplicemente di aggiungere un FeedbackPanel e la form.

IMP: Essendo nel codice il feedbackpanel esterno alla form, occorre addarlo alla pagina esternamente. Wicket funziona a gerarchie quindi le add al flusso della pagina devono rispecchiare le gerarchie del codice. All'interno della stessa gerarchia le add possono essere in qualsiasi ordine quindi nella form non è importante che name venga addato prima di surname.

La form ha due proprietà interne di tipo stringa che sono mappate, come vedete dal PropertyModel, sui due oggetti di tipo TextField. Ovviamente occorre come sempre implementare i get e set relativi. A questo punto aggiungiamo un controllo ai due campi TextField rendendoli obbligatori ed aggiungendo un ulteriore validatore al campo name. Tramite il metodo add possiamo aggiungere dei validatori aggiuntivi. Mentre la required indica semplicemente che il campo deve contenere qualcosa, con i validatori possiamo perfezionare ulteriormente i controlli. Come primo esempio forziamo la lunghezza del nome ad essere compreso tra 5 e 50 caratteri.

All'invio della form, il feedbackpanel viene automaticamente riempito con i messaggi di errore.
Sotto l'errore legato al validator sulla lunghezza.

martedì 6 settembre 2011

Wicket: Form con Ajax

NB: Questo esempio è legato al post Wicket: Form con misto PropertyModel e CompoundModel quindi se volete provare il codice vi conviene guardarvi prima quello.

Andiamo ad evolvere il form dei due post precedenti aggiungendo una semplicemente componente Ajax.

Per il nostro esempio mettiamo un label comandato da un controllo che ci dice se il campo mail inserito è uscito indenne da un qualche controllo.
IMP: Wicket mette a disposizione dei validatori di default tra cui quello per le email. Il nostro è solo un esempio proforma.

Per prima cosa inseriamo uno span HTML di fianco alla input della email nella quale andremo a scrivere il messaggino nella quale diciamo se va bene o male.

Questo LABEL dovrà essere legato ad un PropertyModel ovvero ad un'area "sensibile" legata ad una variabile come abbiamo visto nei primissi esempi.


<wicket:panel>
<form wicket:id="countryForm">
    <input wicket:id="country" />
    <br />
    <input wicket:id="email" /><span wicket:id="goodemail"></span>
    <br />
    <input wicket:id="submit" type="submit" />
</form>
</wicket:panel>

Associamo quindi la ID goodemail.
Occorre ora legare questo ID ad un Label sensibile a sua volta legata ad una variabile della classe.

Per farlo creiamo una proprietà della classe Panel che contiene la form e la chiameremo goodEmailFeedback.

Creiamo quindi il Label a cui associamo la variabile.

 PropertyModel<String> goodEmailFeedbackModel = 
                            new PropertyModel<String>(this,"goodEmailFeedback");
 final Label goodEmailLabel = new Label("goodemail", goodEmailFeedbackModel); 
 goodEmailLabel.setOutputMarkupId(true);
 cForm.add(goodEmailLabel);

La prima istruzione serve a creare un modello legato ad una variabile che guarda caso è proprio quella della classe. Quel this indica quale classe ha la proprietà. Essendo questo codice dentro il Panel, con this prendo direttamente la variabile.
La seconda istruzione crea la label associandoci la variabile e mappandola sul wicket id "goodemail". Questo permette di rendere "sensibile" la label così alla modifica della proprietà goodEmailFeedback, il contenuto della label viene modificato.
La terza istruzione serve a Wicket per capire che la label è "targettabile" da uno script Ajax.
Infine aggiungiamo il label al flusso Wicket.
IMP: Essendo il label figlio della form nel codice HTML, essa deve essere aggiunta all'oggetto form e non all'oggetto panel.

A questo punto aggiungiamo il controllo Ajax che in Wicket è gratuito. Non serve né Javascript né un controllo del risultato.

eField.add(new AjaxFormComponentUpdatingBehavior("onblur"){
            private static final long serialVersionUID = 1L;
            @Override
            protected void onUpdate(AjaxRequestTarget target) {                
                target.addComponent(goodEmailLabel);
                if ("ciccio@ciccio.it".equals(myTinyUser.getEmail()) )
                    goodEmailFeedback = "giusta";
                else 
                    goodEmailFeedback = "falsa";
            }
        });

All'oggetto eField aggiungiamo un behavior ajax chiamato AjaxFormComponentUpdatingBehavior ovvero "Fai qualcosa di Ajax se il componente viene aggiornato". L'evento scatenante che abbiamo deciso è onblur ovvero quando l'input NON ha più il focus (leggasi, si clicca da un'altra parte).
Dobbiamo quindi sovrascrivere il metodo onUpdate (la IDE ve lo suggerisce se aprite la graffa). Il sistema ci regala l'oggetto di tipo target che rappresenta il suo oggetto di risposta.

NB: Nella vecchia programmazione Ajax ad un dato evento si faceva tramite javascript una chiamata ad una qualche risorsa esterna. Diciamo che per esempio si chiamava un url PHP chiamato risposta.php. In questo file PHP si era soliti fare operazioni e buttare in output il risultato che poi era ricevuto dallo script javascript che aveva lanciato la richiesta. Solitamente era un XML.
L'oggetto target è esattamente questa cosa. Il nome purtroppo fa cadere in errore perché in realtà si sarebbe dovuto chiamare "response" oppure qualcosa tipo "quello che poi ritorno".
Essendo però l'oggetto maneggiato e maneggiabile da Wicket, con il metodo addComponent dobbiamo dargli l'elenco di tutte le componenti che sono state influenzate da questo evento. Nel nostro caso gli diciamo che il Label precedentemente creato dovrà essere modificato o comunque che l'evento lo ha influenzato in qualche modo.

Nell'esempio mettiamo un semplice IF ed aggiorniamo di conseguenza la nostra proprietà della classe goodEmailFeedback che essendo legata al label ne modificherà il contenuto.

NB: Wicket mette a disposizione molti Behavior in Ajax. In seguito ne vedremo degli altri.

Wicket: Form con misto PropertyModel e CompoundModel

NB: Questo esempio è legato al post Wicket + Form quindi se volete provare il codice vi conviene guardarvi prima quello.

Nell'esempio precedente abbiamo implementato una form semplice completamente bindata su un oggetto chiamato Country.

Abbiamo passato al costruttore della FORM un modello basato sull'oggetto stesso così da poter legare facilmente e velocemente le proprietà dell'oggetto sui campi della form.

Purtroppo capita spesso che questo giochetto non si possa fare perché non tutti i campi di una form sono bindati su un solo oggetto. Alcune input potrebbero appartenere ad altri oggetti che in qualche modo devono essere "visti" dai campi della form e dalla form stessa.

Per risolvere questo problema si usa un altro tipo di object-model di Wicket chiamato PropertyModel.

Nell'esempio precedente aggiungiamo un campo email alla form. 

<wicket:panel>
<form wicket:id="countryForm">
    <input wicket:id="country" />
    <input wicket:id="email" />
    <input wicket:id="submit" type="submit" />
</form>
</wicket:panel>

La form è identica tranne per un nuovo campo email.

La parte logica della form è identica alla precedente tranne per l'aggiunta del seguente pezzo:


 final MyUser myTinyUser = new MyUser();
 PropertyModel<String> emailPropertyModel = new PropertyModel<String>(myTinyUser, "email");
 final TextField<String> eField = new RequiredTextField<String>("email",emailPropertyModel);

Creiamo un oggetto MyUser che per l'occasione è il nostro oggetto che ha dentro una property email. In pratica è il nostro oggetto esterno a cui dobbiamo bindare un campo della form.

Creiamo quindi un PropertyModel (di tipo String) mappato sul campo "email" dell'oggetto myTinyUser. Associamo quindi questo oggetto PropertyModel al nuovo campo RequiredTextField della form.

Cosa succederà quindi all'invio della form?
La cosa è molto semplice. La form riempirà sia l'oggetto Country bindato direttamente sulla form e sia l'oggetto myTinyUser a cui abbiamo bindato il campo email.

Per andare a leggere i valori, useremo il seguente codice:

Button sButton = new Button("submit"){
            private static final long serialVersionUID = 1L;
            public void onSubmit(){                    
                Country ciao = (Country) cForm.getModelObject();
                System.out.println("Hai inserito: "+ciao.getCountry());
                System.out.println("La mail: "+myTinyUser.getEmail());
            }
        };

Come possiamo vedere dal codice sopra, per stampare la mail ci basta richiamare il metodo getEmail() dell'oggetto myTinyUser che sarà stato riempito.

IMP: L'oggetto myTinyUser va impostato come final. Wicket non può riempire un oggetto NON final (per lo stesso motivo anche la form deve essere messa final avendo un modello associato) perché deve essere sicuro che quell'oggetto non venga in qualche modo cambiato a runtime.

Wicket: Semplice Form

In questo esempio vediamo velocemente una semplice form.
Prima però occorre fare una distinzione per capire come funzionano le cose in Wicket a differenza di quanto accade in altri linguaggi come PHP.
In quest'ultimo si crea una form in qualche modo e nella pagina di arrivo della action si leggono i dati tramite variabili come $_POST o $_REQUEST o $_GET in base al method della form.

In Wicket tutto questo è bypassato dal fatto che la form è una componente stessa di Wicket e quindi vive di vita propria ed è provvista di tutte le utility per gestire i dati, la validazione ed i messaggi di output.

La cosa che occorre capire prima di tutto è che una FORM deve essere bindata su un modello. Un modello è in pratica una maschera dell'output della form stessa e sul cui oggetto è possibile ricavare i dati inseriti.

Nel nostro esempio creeremo una semplice form con un solo campo bindato sul modello di una classe Country con una sola proprietà di tipo Stringa.

private String country;

    public void setCountry(String country) {
        this.country = country;
    }

    public String getCountry() {
        return country;
    }
    
}

La classe Country è banale e ci serve più che altro per far capire come funziona il modello della form. Nel caso più comune, una form lancia poi una stampa o un inserimento/salvataggio da qualche parte. Solitamente si ha un oggetto con le sue proprietà che sono poi mappate sui campi della form.
Se per esempio abbiamo una classe "Utente" con proprietà userid, password ed email, è presumible che la form richieda questi tre campi.

In questi casi, che sono i più semplici, si crea una form in Wicket associandogli l'oggetto di bind che in gergo viene chiamato Model.

I campi della form vengono poi legati ad una proprietà dell'oggetto modello che verrà riempito in automatico al momento della submit.

Nell'esempio sopra il nostro oggetto modello ha una sola proprietà di tipo stringa mentre la nostra form avrà un solo campo TEXT legato ad esso.

<wicket:panel>
<form wicket:id="countryForm">
    <input wicket:id="country" />
    <input wicket:id="submit" type="submit" />
</form>
</wicket:panel>

Per l'occasione ho creato un oggetto Panel con dentro la form.
Il codice come vedete è molto semplice. Occorre solo ricordarsi di associare i wicket:id agli elementi opportuni.

        final Form cForm = new Form("countryForm",new CompoundPropertyModel(new Country()));
        final TextField<String> cField = new RequiredTextField<String>("country");
        
        Button sButton = new Button("submit"){
            private static final long serialVersionUID = 1L;
            public void onSubmit(){                    
                //String value = cField.getValue();
                Country ciao = (Country) cForm.getModelObject();
                System.out.println("Hai inserito: "+ciao.getCountry());
            }
        };
        
        cForm.add(cField);
        cForm.add(sButton);
                
        return cForm;

Il codice della form è molto semplice.
Come prima cosa creiamo un oggetto FORM (che è un oggetto di Wicket) dandogli il nome (ovvero il wicketID del codice HTML e successivamente il CompoundPropertyModel.
NB: Wicket ha diversi tipi di modelli. Se la vostra FORM è mappabile 1:1 con le proprerty di un oggetto, allora conviene usare la CompoundPropertyModel

Nell'esempio sopra come modello gli passiamo una istanza al volo dell'oggetto Country. La form adesso sa che nelle sue input possiamo creare un legame tra un input ed una proprietà dell'oggetto.

Subito sotto infatti creiamo un input di tipo text obbligatorio (RequiredTextField) di tipo String, bindato su "country". Quel country altro non è che il nome della proprietà dell'oggetto Country ovvero la sua variabile Stringa interna.

Il secondo input è il tasto submit (Button per Wicket). Sovrascrivendo il metodo onSubmit possiamo effettuare operazioni dopo che la form è stata inviata ed è stata validata da Wicket.
Nell'esempio sopra chiediamo quindi alla form stessa, di ritornarci un oggetto di tipo Country che ovviamente sarà automaticamente riempito da ciò che avevamo messo nella form.
L'istruzione getModelObject() significa proprio: "Dammi l'oggetto su cui eri basato". La variabile ciao è a tutti gli effetti un oggetto Country il cui parametro interno è stato riempito dal valore del campo TEXT che gli avevamo legato.

Usando poi il metodo getCountry possiamo stamparlo in console.

Wicket + Spring: Un utile metodo

Un brevissimo post per introdurre un metodo molto utile per interfacciare Wicket con il contesto Spring.

Come sappiamo Spring è un contesto basato su classi Singletone. Un componente Wicket che inizializza una proprietà interna ad un Singletone Spring, non potrebbe essere dealloacato alla fine dell'utilizzo mandando presto in out-of-memory.
La soluzione sarebbe quella di utilizzare i bean del contesto solo dentro ai metodi del componente Wicket oppure creare un comodo metodo universale che passa dalla Wicket Application.

Questa seconda opzione è preferibile dato che un componente Wicket ha visibilità della Application che lo sta usando e quindi ne può lanciare facilmente un metodo.

Quindi nella nostra Wicket Application legata a Spring, inseriremo questo metodo:

public Object getSpringObject(String beanName) {
          ApplicationContext context = 
                             new ClassPathXmlApplicationContext("test.xml");
          return (Object) context.getBean(beanName);
    }

Questo metodo universale chiede un BEAN dal contesto dato una Stringa in ingresso. Il metodo ritorna un oggetto castato direttamente nel return.

Al componente Wicket che deciderà di usarlo basterà usare questa sintassi:

WicketApplicationExample wApp = (WicketApplicationExample) Application.get();
BeanExample gDS = (BeanExample) app.getSpringObject("idDelBean");


La prima istruzione serve a istanziare l'oggetto della Class Application di Wicket castato ovviamente sul nome della Classe della Wicket Application.
La seconda istruzione chiama il metodo di cui sopra chiedendo un BEAN dato l'ID che lo identifica nel contesto.

venerdì 2 settembre 2011

Spring + Ibatis: Come farli parlare

Oggi lasciamo un attimo da parte Wicket per fare un esempio di semplice Contesto Spring misto a Ibatis per fare una query al database.

Non mi soffermerò a spiegare Spring né a spiegare iBatis ma solo ad usarli assieme per fare una insert ed una query ad un DB Oracle. La stampa sarà fatta su console.

Parte 1 - web.xml

Torniamo a parlare di questo file dove andremo ad aggiungere la componente per far partire il contesto Spring ovvero la "nuvola" applicativa del nostro progetto.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4">
    <display-name>Wello World</display-name>
    
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
             WEB-INF/classes/primo_esempio.xml    
        </param-value>
    </context-param>

</web-app>

Il codice è molto semplice dato che non abbiamo volutamente messo la parte di Wicket che è più sostanzione. In generale quindi il nostro web.xml avrà solo la chiamata al listener di spring e la parametrizzazione dello stesso che avverte il server che le specifiche del contesto master sono da ricercare nel file primo_esempio.xml.

Parte 2 - primo_esempio.xml

Andiamo ora a vedere come comporre il nostro contesto di esempio.

<?xml version="1.0" encoding="UTF-8"?>
<bean TUTTALAROBA>
    <import resource="dati_connessione.xml" />   
       
    <bean id="gestioneDatiSemplice" class="it.trewps.primoesempio.manager.GestioneDatiSemplice" lazy-init="true">
        <property name="lunghezza" value="100" />
        <property name="prendiDati" ref="prendiDati" />
    </bean>
    
    <bean id="sqlMapClient"  class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
        <property name="configLocation" value="classpath:elenco_query.xml" />
        <property name="dataSource" ref="datiConnessione" />
    </bean>
    
    <bean id="prendiDati" class="it.trewps.primoesempio.dao.PrendiDati">
       <property name="sqlMapClient" ref="sqlMapClient" />
    </bean>
    
</beans>

Dove ho scritto TUTTALAROBA va inserita tutta melassa che ovviamente potete copia-incollare:

xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:dwr="http://www.directwebremoting.org/schema/spring-dwr" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd http://www.directwebremoting.org/schema/spring-dwr http://www.directwebremoting.org/schema/spring-dwr-2.0.xsd"

La spiegazione del contesto è molto semplice ed intuitiva. Stiamo definendo 3 bean ovvero 3 diverse entità della nuvola. Ogni bean è mappata su una classe Java indicata nel parametro class e può avere già settata delle proprietà che altro non sono che variabili interne alla classe. Nella definizione del bean Spring permette molte altre cose ma per questo esempio ci limiteremo a pre-settare delle proprietà.

La cosa interessante è che alcune proprietà non hanno un value ma un ref. Il value permette di impostare un valore fisso al parametro della classe direttamente al momento della chiamata di Spring. La seconda crea un riferimento ad un altra entità bean definita o nello stesso xml o in altri xml di contesto importati in quello master (come potete vedere dall'import in alto).

Il secondo BEAN rappresenta il punto di aggancio tra Spring e iBatis in quanto il BEAN è mappato direttamente su una classe del framework a cui ovviamente occorre specificare le impostazioni del dataSource (un bean definito nel contesto importato) e la mappa delle query ovvero l'elenco di file XML (strutturati per logica) che contengono le vere e proprie query scritte in iBatis.

Il terzo BEAN rappresenta invece la classe che farà da ponte tra quella base e quella di interfacciamento con il DB (attraverso iBatis).

NB: Questo file va messo nella directory strettamente legata ad un path montato su WEB-INF/classes/ . Nel mio caso il banale src.

Parte 3 - elenco_query.xml

Qui vediamo nel dettaglio la mappa delle query che il BEAN legato a iBatis caricherà.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sqlMapConfig PUBLIC "-//iBATIS.com//DTD SQL Map Config 2.0//EN"
    "http://www.ibatis.com/dtd/sql-map-config-2.dtd">

<sqlMapConfig>
    <settings useStatementNamespaces="true"  defaultStatementTimeout="30"/>
    <sqlMap resource="maps/semplice_query.xml"/>
</sqlMapConfig>

Questo file è molto semplice ed avverte il sistema che avremo un file di query chiamato semplice_query.xml. Possiamo ovviamente avere tanti file sqlMap e solitamente questi sono divisi per area semantica di utilizzo.

Parte 4 - semplice_query.xml


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sqlMap      
    PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"      
    "http://ibatis.apache.org/dtd/sql-map-2.dtd">

<sqlMap namespace="it.trewps.primoesempio.dao">
    
    <select id="dammiUtenti"     resultClass="it.trewps.primoesempio.dao.UtenteEsempio" >
        select idutente,nomeutente,cognomeutente from tabella_esempio
    </select>
    
    <insert id="inserisciUtente" parameterClass="it.trewps.primoesempio.dao.UtenteEsempio">
        insert into tabella_esempio (nomeutente,cognomeutente) values (#nomeutente#,#cognomeutente#)
    </insert>
</sqlMap>


Questo è un esempio di file XML di query. Il namespace indica dove andremo ad avere le classi che gestiranno questa query. Nel nostro caso abbiamo creato un package ad HOC chiamato it.trewps.primoesempio.dao.

Abbiamo quindi due tag che identificano il tipo di query. La prima è una select da una tabella chiamata tabella_esempio nella quale andiamo a prendere due campi. La seconda è una insert. Entrambe le query devono avere un ID UNIVOCO associato che sarà poi quello richiamato dalla nostra classe DAO.

La seconda query è invece più interessante perché possiede due placeholder dove la classe parametro in ingresso andrà a mapparci il proprio valore. Il placeholder si può scrivere in due modi:

#nomeparametro#

oppure

$nomeparametro$

La differenza tra i due è sottile ma fondamentale se avete DB potenti. Nel primo caso stiamo dicendo al DB che questa è un'area da mappare. Non gli stiamo passando direttamente il dato dentro ma gli stiamo solo passando una query parametrizzata. Questo è utile perché il DB trasforma la query in una funzione che prende in ingresso i parametri. Se avete tante insert questo è utile perché permette al DB di fare la cache della query velocizzando le operazioni.
Nel secondo modo invece, il sistema crea la query già riempita quindi query consecutive dovranno sempre essere interpretate da zero anche se a cambiare sono solo i dati.

Concludiamo parlando di resultClass e parameterClass. Questi due parametri avvertono il sistema di quali classi mapperanno rispettivamente il risultato della query o gli eventuali dati in ingresso alla query.

Una query può avere entrambi i parametri (la select le ha quasi sempre) perché è tramite la classe ivi specificate che è possibile parametrizzare una select o dare i dati ad una insert/update.
La class data come parametro ha ovviamente l'obbligo di avere parametri interni con lo stesso nome del placeholder usato nella query ed ovviamente un metodo GET relativo.

La resultClass deve invece essere mappata sulle colonne del DB. Questa deve quindi avere dei parametri interni identici a quelli della tabella su cui facciamo la query.
E' lecito pensare che una classe capace di accogliere i dati di una tabella sia anche quella usata come parametro per fare una insert. La cosa è ovviamente lecita ma occorre ricordare che la result per funzionare deve avere tutti i metodi get e set dei parametri ma soprattutto un costruttore vuoto senza parametri.
Nel nostro esempio saremo in questa casistica.

Parte 6 - daticonnessione

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
       
  <bean id="datiConnessione" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
        <property name="url" value="jdbc:oracle:thin:@192.168.1.214/orcl" />
        <property name="username" value="test" />
        <property name="password" value="test" />
        <property name="initialSize" value="1" />
        <property name="maxActive" value="5" />
        <property name="maxIdle" value="5" />
    </bean>

</beans>


Il bean di datiConnessione richiesto dal bean di iBatis messo nel contesto, sarà una semplice sequenza di dati definiti nelle proprietà di un altro oggetto standard di Java ovvero BasicDataSource.
I dati sono abbastanza semplici. Ricordatevi solo che il driverClassName cambia in base al vostro DB e che l'URL è l'indirizzo del server del database dove dopo lo slashes finale compare il nome del database (in questo caso orcl).

Parte 7 - UtenteEsempio.java

Come abbiamo visto sopra entrambe le query usavano questa classe come result o parameter class. Andiamo a vederla nel dettaglio:

package it.trewps.primoesempio.dao;

public class UtenteEsempio {
    
    Integer idutente;
    String nomeutente;
    String cognomeutente;
    
    public UtenteEsempio(){
    }
    
    public UtenteEsempio(String nome, String cognome){
        nomeutente = nome;
        cognomeutente = cognome;
    }
    
    /*Tutti i metodi GET e SET */

}

E' una classe semplicissima. Nel codice sopra ho omesso tutti i GET e SET delle proprietà interne. E' importante però notare come esistano due costruttori. Uno utile ad istanziare l'oggetto all'interno dell'applicativo ed uno utile a iBatis (ovvero il costruttore vuoto).

Parte 8 - PrendiDati.java

Andiamo ora a vedere la classe che in effetti comunica con il database tramite iBatis e mette a disposizione alle classi dell'appllicativo, queste interazioni.

package it.trewps.primoesempio.dao;

import java.util.List;

import org.springframework.orm.ibatis.support.SqlMapClientDaoSupport;

public class PrendiDati extends SqlMapClientDaoSupport{
    

    public static final String NAMESPACE = "it.trewps.primoesempio.dao";
    public static final String UTENTI = NAMESPACE + ".dammiUtenti";
    public static final String IMMETTI = NAMESPACE + ".inserisciUtente";
    
    public PrendiDati(){
        
    }
    
    public List<UtenteEsempio> caricaUtenti (){
        return getSqlMapClientTemplate().queryForList(UTENTI);
    }
    
    public void insertisciUtente(UtenteEsempio utente){
        getSqlMapClientTemplate().insert(IMMETTI,utente);
    }
    
}

Può sembrare strana ma in realtà è molto semplice. Partendo dal basso abbiamo due metodi mappati come logica sulla select e sulla insert. La prima ritorna una LIST di oggetti UtenteEsempio andandoli a chiedere al DB tramite l'istruzione sotto chiamata queryForList che banalmente dice a iBatis di darci una query mappata su una lista. La costante UTENTI utilizzata è solo una facilitazione per evitare di dover scrivere tutto il namespace ed il nome dell'ID della query.
NB: Il namespace è lo stesso espresso nel parametro del file XML di cui sopra.

Cosa analoga nel metodo di inserimento che ovviamente non ha una return ma una semplice insert che accetta due parametri ovvero la stringa che identifica la query da eseguire e l'oggetto che sarà, guarda caso, la parameterClass della query. Ovviamente dammiUtenti e inserisciUtente definiti nelle costanti non sono altro che gli ID delle query nell'xml.

Parte 9 - GestioneDatiSemplice.java

Ecco il file che abbiamo visto associato al bean nel contesto e che rappresenta l'arbitro del nostro applicativo. Nel contesto abbiamo visto che Spring gli darà gratuitamente la classe PrendiDati che verrà usata per dare il via alle operazioni sul DB.


package it.trewps.primoesempio.manager;

import java.util.List;

import it.trewps.primoesempio.dao.PrendiDati;
import it.trewps.primoesempio.dao.UtenteEsempio;

public class GestioneDatiSemplice {

    private Integer lunghezza;
    PrendiDati prendiDati;
    
    public GestioneDatiSemplice(){
    }
    
    public void faiQuellaCosa(){
        UtenteEsempio unocosi = new UtenteEsempio("Marina","Massironi"); 
        prendiDati.insertisciUtente(unocosi);
        List <UtenteEsempio> listaDati = prendiDati.caricaUtenti();
        System.out.println("Lungo: "+listaDati.size());
    }

    public void setLunghezza(Integer lunghezza) {
        this.lunghezza = lunghezza;
    }

    public Integer getLunghezza() {
        return lunghezza;
    }
    
    public PrendiDati getPrendiDati() {
        return prendiDati;
    }

    public void setPrendiDati(PrendiDati prendiDati) {
        this.prendiDati = prendiDati;
    }
    
}

Il metodo faiQuellaCosa, che chiaremo da un main, crea un oggetto di tipo UtenteEsempio e lo da in pasto alla query di inserimento. Crea poi una varibile lista che riceverà il risultato della select ovvero una lista di oggetti di tipo UtenteEsempio e lancia quindi la query stampando poi la lunghezza della lista ovvero 1 (dato che abbiamo appena inserito Marina Massironi).

Parte 10 - La classe Main

Potrebbe sembrare superfluo scriverla ma in realtà è fondamentale perché in essa possiamo vedere il codice con la quale una classe qualsiasi chiede al contesto un oggetto bean da usare.


package it.trewps.primoesempio.manager;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class PrimoEsempio {
    
    public static void main(String[] args){
        
        ApplicationContext context = new ClassPathXmlApplicationContext("primo_esempio.xml");
        GestioneDatiSemplice hmw = (GestioneDatiSemplice) context.getBean("gestioneDatiSemplice");
        hmw.faiQuellaCosa();
        
        System.out.println("Ciao Mondo");
    }

}

Il main lancia due importanti metodi. Il primo è quello che crea un oggetto di tipo ApplicationContext con la quale interagire con la nuvola Spring. Sia chiaro, qui NON stiamo creando la nuvola ma solo un oggetto con la quale comunicare con lei.
Il secondo metodo invece serve a chiedere alla nuvola di darci un oggetto bean con ID gestioneDatiSemplice che noi sappiamo essere mappato sulla classe GestioneDatiSemplice.

Come ultima istruzione avremo il lancio del metodo ed una print inutile Ciao Mondo.