L'obiettivo di questo articolo è la realizzazione di un progetto che utilizzi Struts 2 e Hibernate: implementeremo passo dopo passo un web responder, ovvero una applicazione web che consenta agli utente l'attivazione del proprio profilo utente.
L'applicazione web responder
L'applicazione presenta una pagina di login con informazioni tipo:
- Nome utente
- Cognome
- Codice Fiscale
L'applicazione quindi "raccoglie" queste informazioni e, se l'utente non è presente nel database di riferimento (nel nostro caso è il database Utenti
già realizzato ed utilizzato per l'articolo "Hibernate per principianti", con in più il campo codice fiscale
) invierà una e-mail con un link da cliccare che permetterà l'inserimento nell'utente nel database.
>> Leggi l'introduzione a Hibernate per principianti
L’applicazione è al solito volutamente semplice, però ci permetterà di affrontare diverse questioni molto interessanti tra cui:
- Alter della tabella
Utenti
inserendo il campocodice fiscale
con nuovo reverse engineer - Trattazione del framework struts 2
- Integrazione struts 2 - Hibernate
Di materiale da trattare quindi ce ne sarà parecchio.
Iniziamo subito aprendo MyEclipse, creiamo un nuovo web project e chiamiamolo WebResponder
:
L'intento ora è di aggiungere al nostro database Utenti
un campo Codice Fiscale
e quindi nella nostra applicazione web dovremo aggiungere una pagina contenente una struts action che si occuperà dell'inserimento nel database.
Questa purtroppo non è la via migliore! Il problema è rappresentato dal fatto che è necessario effettuare una serie di operazioni (non problematiche attenzione) per integrare struts 2 ed hibernate quindi l’approccio migliore si chiama scaffolding.
Lo scaffolding è un termine che viene adottato per indicare una struttura di base o, per meglio dire, fondamenta sulle quali costruire il nostro software ad esempio supponiamo di voler creare una applicazione web che invia una email utilizzando una struttura software pre-esistente che invia e-mail in maniera generica, questa struttura è lo scaffolding sulla quale noi interveniamo apportando o modifiche oppure del codice customizzato.
Il termine in realtà è anche più ampio, quando ad esempio abbiamo aggiunto Hibernate Capabilities
al nostro progetto abbiamo "fuso" la nostra applicazione con uno scaffolding (il codice Hibernate) di MyEclipse.
Vediamo adesso come utilizzare questa tecnica in maniera più esplicita all'interno della applicazione che stiamo realizzando.
Partiamo da una metodologia che è utile usare in fase di
progettazione: creare la struttura di comunicazione, lo scheletro funzionale ed a questo vi aggiungiamo di volta in volta le funzionalità business.
Preparazione del database
Ma procediamo con ordine, apriamo MySql Workbench e avviamo il server se necessario:
Nel nostro schema mioschema
clicchiamo col tasto destro del mouse sulla tabella utenti
e selezioniamo la voce Alter table
:
Inseriamo quindi un nuovo field codiceFiscale
, sempre di tipo varchar
(scegliete la dimensione più appropriata) e selezionando la voce NN
acronimo di Not Null
a indicare che deve avere necessariamente un valore pena un errore che viene rilanciato a runtime dal database.
Aggiungiamo anche un ulteriore campo che chiamiamo visible
di tipo boolean
, se true indica che il record è effettivamente presente (vedremo poi a cosa ci servirà...)
Cliccando sul tasto Apply
la modifica
verrà resa effettiva (ricordiamo che l'operazione è di tipo DDL acronimo di Data Description Language)
Ora andremo incontro ad una piccola inconsistenza: con il tasto destro sulla tabella selezionate Select Rows
:
L'alter della tabella è andato a buon fine come possiamo notare dalla presenza del campo aggiunto codiceFiscale
, però essendoci dei record pre-esistenti il campo risulta vuoto (nemmeno Null
è un valore valido, pur essendo considerato tale in molti linguaggi) quindi bisogna capire come intervenire. Ovviamente se i record fossero stati centinaia avremmo avuto un problema molto serio, al momento per fortuna non è il nostro caso, e procediamo scegliendo tra:
- inserire manualmente un codice fiscale per ciascun record
- Effetuare una operazione di UPDATE con una operazione che calcoli un codice fiscale per ciascun record (molto più complesso)
- Cancellare i record esistenti
Scegliamo la prima opzione e procediamo (non dimentichiamoci di fare il commit dell'insert!).
Apriamo ora MyEclipse e creiamo un web project che chiamiamo WebResponder
, riprendendo il concetto di scaffolding agiremo in questo modo:
- Aggiungiamo la Hibernate capabilities al nostro progetto riutilizzando la connessione esistente (vedi articolo Hibernate per principianti)
- Testeremo l'inserimento nel database utilizzando un form contenuto in una pagina Jsp
Apriamo la Palette ed inseriamo un Form nella pagina index.jsp che viene inizialmente caricata (viene specificata nel deployment descriptor web.xml
nel tag xml <welcome-file>
)
Diamo un nome al form, una action nothing
che non ha nessun significato (la utilizziamo come Segnaposto) e
scegliamo come metodo POST
invece di GET
, questo perchè il form invierà delle informazioni al server ed è più logico usare post piuttosto che get, anche se quest'ultimo per un numero basso di
informazioni andava comunque bene.
Apriamo il tab HTML-BASIC
nella Palette e trasciniamo l’elemento Table all’interno del form specificando i valori visualizzati in figura:
Ora cliccate su ogni casella a sinistra della tabella e inserite da tastiera il seguente contenuto:
Successivamente trascinate un textfield in ciascuna casella adiacente e date ad ognuno un nome corrisopndente associato ad esempio nel grafico successivo il textfield ha come nome nomeTextfield
(quindi i restanti saranno cognomeTextfield
, codicefiscaleTextfield
e emailtextField
):
Inseriamo allo stesso modo un pulsante di tipo Submit
con label invia
.
Ora creiamo una servlet Storer
che verrà invocata da index.jsp e che riceverà i parametri del form stampandoli sulla console di Tomcat, questo per verificare la corretta interazione tra i due componenti, da notare che MyEclipse viene distribuito in Bundle con Apache Tomcat ma possiamo tranquillamente configurare uno o più server Server esterni.
Prima di procedere molti di voi si saranno chiesti del perchè non creare direttamente una servlet con dati fissi, il motivo è che dopo avremo necessità della pagina index.jsp
e inoltre avremo collaudato l'interazione della stessa con altri componenti oltre che il corretto recupero dei parametri del form da parte di un altro componente (vedi servlet).
Usiamo il tasto destro sul nostro progetto e selezioniamo new/other/servlet
:
Selezioniamo come mapping url il valore /Storer
, invece che /servlet/Storer
, come consigliato da
MyEclipse. Questo per un motivo di sicurezza: in questo modo per richiamare la servlet l'url nel browser dovrà essere:
http://:8080/WebResponder/servlet/Storer
Quindi sarà visibile quale tecnologia stiamo utilizzando mentre secondo la nostra scelta sarà:
http://:8080/WebResponder/Storer
Quindi modifichiamo il codice nella servlet in modo da fargli stampare un messaggio personalizzato.
Carichiamo in MyEclipse la pagina index.jsp
cliccandoci due volte sopra quindi tasto destro del mouse all'interno della pagina che ne mostra il codice e selezioniamo format
:
Aggiorniamo la pagina index.jsp
in modo che richiami la servlet:
Con il tasto destro sul progetto selezioniamo Run As/MyEclipse server Application
, da cui otteniamo la schermata iniziale e quella successiva premendo invia
:
Possiamo quindi modificare il codice di Storer
, per gestire le request in arrivo:
out.println("");
out.print("Salve sono la servlet storer! ");
String nomeTF = request.getParameter("nomeTextfield");
String cognomeTF = request.getParameter("cognomeTextField");
String codiceFiscaleTF = request.getParameter("codiceFiscaleTextfield");
String emailTF = request.getParameter("emailTextfield");
out.print("nome [" + nomeTF +"] ");
out.print("cognome [" + cognomeTF +"]");
out.print("codice fiscale [" + codiceFiscaleTF +"]");
out.print("email [" + emailTF +"]");
out.println("");
Per il textfield associato all’email ho incrementato la dimensione specificando l'attributo size
col valore di 30:
<input type="text" name="emailTextfield" size="30">
Ora trasformiamo il nostro progetto in modo da basarlo su Struts 2. Abbiamo scelto la via più lunga, e il motivo è semplice: spesso ci si ritrova ad effettuare il porting di una web
application scritta con servlet e jsp in Struts.
Con il tasto destro sul progetto selezioniamo My eclipse/add struts capabilities
, nella seguente schermata scegliamo struts 2.1
con url pattern *.action
, questo significa che qualsiasi url diretto alla nostra applicazione che termina con questo postfisso verrà interpretata automaticamente dal motore di struts.
Clicchiamo due volte sul file struts.xml
e selezioniamo il tab flow
quindi trasciniamo l'icona package
nell'area di lavoro:
Nella schermata successiva selezioniamo come nome webresponder
e come package che estende struts-default
:
Trasciniamo sull'area di lavoro all’interno del package l'icona della action:
E selezionaimo i seguenti parametri per la nostra struts action:
MyEclipse creerà una classe come questa:
package com.strutsActions;
public class StorerAction extends ActionSupport {
public String execute() {
return SUCCESS;
}
}
E il file di configurazione struts.xml
aggiornato con la configurazione della action:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC ".../dtds/struts-2.1.dtd">
<struts>
<package name="webresponder" extends="struts-default">
<action name="StrutsStorer" class="com.strutsActions.StorerAction">
</action>
</package>
</struts>
Ora velocemente aggiungiamo alla nostra action dei campi interni che seguono le specifiche dei javabeans con i nomi dei textfield presenti nella pagina index.jsp
. Ad esempio nella index.jsp
è definito il seguente textfiedl
:
<input type="text" name="codiceFiscaleTextfield">
Quindi aggiungere alla nostra action i campi seguente con relativi metodo getter/setter:
public class StorerAction extends ActionSupport {
/...
private String codiceFiscaleTextfield;
private String nomeTextfield;
private String cognomeTextField;
private String codiceFiscaleTextfield;
private String emailTextfield;
// metodi getter/setter
Modificare quindi l'attributo action
del form nella pagina index.jsp
come segue:
Questo lo schema per capire il mapping url che consente di richiamare la action dal form:
Nel metodo execute
della action inseriamo delle istruzioni di log:
public String execute() {
System.out.println("OK STRUTS ACTION CALLED!!!");
System.out.println("nomeTextfield:" + nomeTextfield);
System.out.println("cognomeTextField:" + cognomeTextField);
System.out.println("codiceFiscaleTextfield:" + codiceFiscaleTextfield);
System.out.println("emailTextfield:" + emailTextfield);
return SUCCESS;
}
Rideployando e lanciando l'applicazione:
L'output della console ci conferma che il binding dei paramteri del form con i corrispondenti campi della action è perfettamente riuscito!
Bisogna sistemare ancora alcune cose, innanzitutto nel browser compare la schermata Error 404- NotFound
questo perchè dobbiamo mappare la stringa "success
" restituita dal metodo execute della action ad una risorsa quindi creiamo una pagina success.jsp
sotto la cartella WEB-INF
e scriviamo il testo elaborazione riuscita!
Rideployando l’applicazione e premendo submit:
Dopo aver corretto la action nel caso di gestione positiva, la stessa procedura va applicata in caso di esito negativo, quindi se la action restituisce failure
.
Integrazione con hibernate
Procediamo rifattorizzando il codice, utilizziamo cioè il binding per mappare i dati del form direttamente con il javabean Utenti
creato nel refactoring di Hibernate:
Primo passo: utilizziamo i javabean generati da hibernate
La modifica è semplice basta utilizzare un javabean di classe Utenti
al posto dei campi utilizzati, quindi la nostra action conterrà:
private Utenti utente;
// ...
System.out.println("nome:" + utente.getNome());
System.out.println("cognome:" + utente.getCognome());
System.out.println("codiceFiscaleTextfiel:" + utente.getCodiceFiscale());
System.out.println("email:" + utente.getEmail());
Infine modifichiamo la pagina index.jsp
per implementare il binding dei textfield con i campi del javabean utenti inserendo per ciascun textfield un riferimento al corrispondente campo del javabean, ad esempio per il textfield cognome:
<input type="text" name="utente.cognome">
e analogamente per i restanti TextField
Rideployando la nostra applicazione si comporterà allo stesso modo:
Integrazione tra Struts 2 e Hibernate
Ora occupiamoci dell'integrazione tra struts 2 e hibernate.
Integrare Hibernate a Struts 2 non è intuitivo come sembra, il problema e che hibernate per effettuare operazioni sul database utilizza una propria Session
mentre Struts ne utilizza una sua. Bisogna quindi da Struts
recuperare la Session di Hibernate.
Questa attività puo essere fatta in diversi modi, quello migliore prevede l'utilizzo di un plugin Hibernate.
Bisogna prima di tutto scaricare il seguente jar:
http://code.google.com/p/full-hibernate-plugin-for-struts2/
Scarichiamo il jar e inseriamolo sotto la directory WEB-INF/lib
assicurandoci che è stato aggiunto al build path:
Configurazione del layer di persistenza con Hibernate
Ora creiamo una classe PersistenceLayer
che si occuperà di recuperare la sessione di Hibernate:
package com.hibernate.layer.persistence;
public class PersistenceLayer {
// va esplicitamente istanziata la classe DAO per gestire le operazioni sulla tabella del database.
private UtentiDAO utentiDAO = new UtentiDAO();
// Vanno dichiarate le variabili di sessione e transazioni nella classe in modo che il plugin installato possa 'istanziarle' via Inversion Of Control e renderle disponibili utilizzando le annotazioni.
@SessionTarget
private Session hibernateSession;
@TransactionTarget
private Transaction hibernateTransaction;
public Session getHibernateSession() {
return hibernateSession;
}
public void setHibernateSession(Session hibernateSession) {
System.out.println("setHibernateSession() called!");
this.hibernateSession = hibernateSession;
}
public Transaction getHibernateTransaction() {
return hibernateTransaction;
}
public void setHibernateTransaction(Transaction hibernateTransaction) {
System.out.println("setHibernateTransaction() called!");
this.hibernateTransaction = hibernateTransaction;
}
public void addCliente(Utenti cliente) {
utentiDAO.save(cliente);
}
public UtentiDAO getClienteDAO() {
return utentiDAO;
}
public void setClienteDAO(UtentiDAO clienteDAO) {
this.utentiDAO = clienteDAO;
}
public Utenti findUserById(Integer id) {
return utentiDAO.findById(id);
}
public void updateCliente(Utenti cliente) {
utentiDAO.merge(cliente);
}
public void deleteCliente(Utenti cliente) {
utentiDAO.delete(cliente);
}
}
Quindi mappiamo la nostra action nel file struts.xml
, e facciamo un'altra importante modifica. Il package di riferimento della nostra action non deve estendere struts-default
, ma hibernate-default
, questo perché l'ultimo contiene degli interceptor per configurare le variabili di sessione e transazione nel persistence layer:
Aggiungiamo infine la seguente riga al file hibernate.cfg.xml
:
<property name="hibernate.connection.autocommit">true</property>
In questo modo che hibernate effettua il commit quando richiamiamo utentiDAO.save()
: questa riga aggiunta ci permetterà di non effettuare il commit in maniera manuale recuperando la sessione e la transazione hibernate, se non inserito dovremo occuparcene personalmente!
Un ultimo step consiste nel download di tre librerie:
Infine modificando il codice della action, in particolare il metodo execute
:
public String execute() {
System.out.println("nome:" + utente.getNome());
System.out.println("cognome:" + utente.getCognome());
System.out.println("codiceFiscaleTextfiel:" +
utente.getCodiceFiscale());
System.out.println("email:" + utente.getEmail());
System.out.println("inserisco l'utente in sessione");
utente.setPresente("n");
PersistenceLayer pl = new PersistenceLayer();
pl.addCliente(utente);
// ...
return success;
}
Possiamo quindi verificare a che punto siamo rifacendo il deploy, ed aprendo mysql qorkbench:
Il campo ‘presente’ può assumere due valori:
s
: record presente nel databasen
: record non presente nel database
Nel momento in cui l'utente clicca sul link contenuto nell'email verrà richiamata una nuova action InsertAction
che imposta per l'utente il valore n
invece di s
.
Il link avrà reso possibile recuperare un parametro che rappresenta l'ID
(Primary Key
), che verrà impostato dalla action StorerAction
.
Ricapitolando: StorerAction
riceve i parametri dal form, costruisce Utente
e lo inserisce nel database con il valore n
nel campo presente. Quindi invia l'email all'utente con il link a InsertAction
, agganciando all'url (URL rewriting) l'id del record, quindi InsertAction
carica il record in funzione dell'ID
, e cambia il valore del campo presente da n
a s
.
Il modulo per l'invio delle email all'utente
Occupiamoci ora dell’invio e-mail all’utente, utilizzeremo javamail disponile tra le librerie utilizzate quindi non dobbiamo scaricare nulla da internet, questa la nostra action con il codice in rosso che si occupa dell’invio della email:
package com.strutsActions;
public class StorerAction extends ActionSupport {
private Utenti utente;
static Properties properties = new Properties();
static {
properties.put("mail.smtp.host", "smtp.gmail.com");
properties.put("mail.smtp.socketFactory.port", "465");
properties.put("mail.smtp.socketFactory.class","javax.net.ssl.SSLSocketFactory");
properties.put("mail.smtp.auth", "true");
properties.put("mail.smtp.port", "465");
}
private void sendEmail() {
try {
Session session = Session.getDefaultInstance(
properties,
new javax.mail.Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication("astaritagiuseppe@gmail.com", "asterix1111");
}
}
);
Message message = new MimeMessage(session);
message.setFrom(new InternetAddress("astaritagiuseppe@gmail.com"));
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse("astarita_giuseppe@libero.it"));
message.setSubject("messaggio");
message.setText("clicca il link! http://192.168.1.140:8080/WebResponder/StrutsStorer.action ");
Transport.send(message);
} catch(Exception e) {
System.out.println("ATTENZIONE: ERRORE NELL'INVIO DELL'EMAIL!");
e.printStackTrace();
}
System.out.println("EMAIL INVIATA CONTROLLA!");
}
/
public String execute() {
// ...
Il codice per l'invio email non è tanto difficile da capire: in sostanza si "preparano" delle properties per l'invio, quindi si definiscono i valori del mittente (from
), destinatario (to), l'oggetto dell'email (subject
) e il
contenuto (text
).
Nel nostro caso abbiamo utilizzato Gmail come Mail provider ma si possono utilizzare anche altri provider l’importante è fornire i parametri di connessione correttamente oltre ad un account valido, nel nostro caso abbiamo utilizzato il mio mascherando la password per ovvi motivi di privacy.
Infine da notare che il corpo dell'email contiene un link di prova, ma in realtà dovrà puntare alla action che si occuperà di inserire l’utente nel database recuperandolo dalla sessione (dove precedentemente è stato inserito).
Il metodo inviaEmail()
è private e verrà richiamato dal metodo execute()
, rideployando il tutto:
E nella mia casella email:
Verifichiamo il contenuto:
Questa la InsertAction:
Se rideployamo l'applicazione modificando la pagina success.jsp
in modo da visualizzare un messaggio più comprensibile l'applicazione non funzionerà! Il motivo è che Hibernate mantiene in cache i risultati dell'update quindi bisogna chiamare in maniera esplicita il metodo flush()
dal PersistenceLayer
su UtentiDAO
, quindi andiamo prima di tutto in UtentiDAO
ed aggiungiamo il metodo:
public void flush() {
getSession().flush();
}
Analogamente in PersistenceLayer
:
public void flush() {
gutentiDAO.flush();
}
In modo che la action insertAction possa richiamarlo:
Fatto questo ecco l’output:
Cliccando sul link:
Mentre sul nostro database:
Considerazioni finali
L'applicazione ovviamente è suscettibile di miglioramenti. Ad esempio:
- Possiamo aggiungere la validazione del form sia lato client con javascript che lato server sfruttando il metodo
validate()
della action - Si poteva utilizzare una terminologia migliore per le nostre action ed integrare log4j
- Cè una questione di sicurezza da gestire, l'utente manda in chiaro una operazione su database
attraverso un url - Se l'utente clicca due volte sul link cosa succede? Eese lo fa in due sessioni distinte? Bisogna
quindi gestire anche queste situazioni...
A voi il compito di implementare queste nuove funzionalirtà!