Introduzione
L'applicazione che andiamo ad analizzare, espone
su un portale, delle ricette di cucina.
Le ricette devono cambiare ad un intervallo prestabilito
(es. ogni giorno), il gestore dei contenuti deve
poter inserire, modificare cancellare le ricette
quando vuole e soprattutto deve poterle caricare
una volta sola anzichè aggiornare personalmente
ogni giorno i contenuti.
Con questi presupposti, l' applicazione, deve
esporre agli utenti un contenuto per l' intervallo
prestabilito, ed esporre una interfaccia di gestione
quando sia acceduta dall' amministratore dei contenuti
del portale.
Figura 1 - Vista dell' utente
Figura 2 - Vista dell' amministratore
(clicca sull'immagine per ingrandire)
Figura 3 - Lista delle ricette vista dall'
amministratore
Figura 4 - Inserimento di una nuova ricetta
Architettura
Per la costruzione della applicazione è
stato scelto Spring in maniera da poter beneficiare
del massimo disaccoppiamento delle classi. I metodi
della portlet in cui è contenuta la logica
di business collaborano con le classi necessarie
al loro funzionamento attraverso un Servizio che
espone questa interfaccia:
public
interface IServizio {
public void start(ServletContext context);
public ApplicationContext getApplicationContext();
}
Facciamo
a questo punto delle precisazioni, è auspicabile
che il servizio parta con o prima della applicazione,
perciò la sua inizializzazione (metodo
start) verrà fatta da un ServletContextListener,
Jetspeed1.x supporta correttamente i Filtri Servlet
e i ContextListener, previa modifica del web.xml
sostituendo :
<!DOCTYPE
web-app PUBLIC "-//Sun Microsystems, Inc.//DTD
Web Application 2.2//EN" "http://java.sun.com/j2ee/dtds/web-app_2.2.dtd">
con
<!DOCTYPE
web-app PUBLIC "-//Sun Microsystems, Inc.//DTD
Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
e ponendo sempre nel web.xml
<listener>
<listener class>xxx.xxx.xListener</listener-class>
</listener>
una
volta istanziato e fatto partire dal ContextListener,
verrà posto nel ServletContext, da cui
lo riprenderà la portlet.
Per semplicità i metodi di servizio sono
in una classe che chiameremo CucinaAction che
verrà estesa dalla classe RicettaAction
che è la portlet contente la logica di
business.
Figura 5 - CucinaAction
La nostra portlet RicettaAction sarà quindi:
/*
* Copyright 2000-2004 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0
(the "License");
* you may not use this file except in compliance
with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed
to in writing, software
* distributed under the License is distributed
on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
either express or implied.
* See the License for the specific language governing
permissions and
* limitations under the License.
*/
package org.casamia.cucina.ricettario.modules.actions.portlets;
import
org.apache.turbine.util.*;
import
org.apache.velocity.context.*;
import
org.springframework.context.*;
import
org.casamia.cucina.ricettario.db.dao.*;
import org.casamia.cucina.ricettario.util.*;
import org.casamia.cucina.ricettario.vo.*;
/**
* @author <a href="mailto:desmax74@yahoo.it">Massimiliano
Dessì</a>
* @created 13 gennaio 2005
*/
public
class RicettaAction extends CucinaAction {
public void doPerform(RunData data, Context context)
{
ApplicationContext aContext = getApplicationContext(data);
IRicettaDao dao = (RicettaJdbcTemplate) aContext.getBean(Costanti.SPRING_JDBC_TEMPLATE);
context.put(Costanti.RICETTA, dao.caricaRicetta());
}
public void doList(RunData data, Context context)
{
ApplicationContext aContext = getApplicationContext(data);
IRicettaDao dao = (RicettaJdbcTemplate) aContext.getBean(Costanti.SPRING_JDBC_TEMPLATE);
context.put(Costanti.RICETTE, dao.caricaRicette());
}
public void doPrepare(RunData data, Context context)
{
context.remove(Costanti.RICETTE);
context.remove(Costanti.DETTAGLIO);
context.remove(Costanti.MESSAGGIO);
context.put(Costanti.NUOVA, "true");
}
public void doInsert(RunData data, Context context)
{
Ricetta ricetta = costruisciRicettaDaRequest(data.getRequest());
if (null != ricetta) {
ApplicationContext aContext = getApplicationContext(data);
IRicettaDao dao = (RicettaJdbcTemplate) aContext.getBean(Costanti.SPRING_JDBC_TEMPLATE);
int risultato = dao.inserisciRicetta(ricetta);
inserisciMessaggio(context, risultato,
"La ricetta è stata inserita",
"La ricetta non è stata inserita");
} else {
context.put(Costanti.MESSAGGIO,
"Inserire tutti i dati della ricetta");
}
}
public
void doUpdate(RunData data, Context context) {
Ricetta ricetta = costruisciRicettaDaRequest(data.getRequest());
if (null != ricetta) {
ApplicationContext aContext = getApplicationContext(data);
IRicettaDao dao = (RicettaJdbcTemplate) aContext.getBean(Costanti.SPRING_JDBC_TEMPLATE);
int risultato = dao.aggiornaRicetta(ricetta);
inserisciMessaggio(context, risultato,
"La ricetta è stata aggiornata",
"La ricetta non è stata aggiornata");
} else {
context.put(Costanti.MESSAGGIO,
"Inserire tutti i dati della ricetta");
}
}
public void doDetail(RunData data, Context context)
{
String idRicetta = recuperaIdRicetta(data);
if (!idRicetta.equals(Costanti.NON_VALORIZZATO_STRING))
{
ApplicationContext aContext = getApplicationContext(data);
IRicettaDao dao = (RicettaJdbcTemplate) aContext.getBean(Costanti.SPRING_JDBC_TEMPLATE);
Ricetta vo = dao.caricaRicetta(Integer.parseInt(idRicetta));
setRicettaInContesto(context, vo);
}
}
public void doDelete(RunData data, Context context)
{
String idRicetta = recuperaIdRicetta(data);
if (!idRicetta.equals(Costanti.NON_VALORIZZATO_STRING))
{
ApplicationContext aContext = getApplicationContext(data);
IRicettaDao dao = (RicettaJdbcTemplate) aContext.getBean(Costanti.SPRING_JDBC_TEMPLATE);
int risultato = dao.eliminaRicetta(Integer.parseInt(idRicetta));
inserisciMessaggio(context, risultato,
"La ricetta con id:" + idRicetta + "
è stata eliminata",
"La ricetta con id:" + idRicetta + "
non è stata eliminata");
}
}
private void setRicettaInContesto(Context context,
Ricetta vo) {
if (null != vo.getNome()) {
context.remove(Costanti.RICETTE);
context.put(Costanti.DETTAGLIO, "true");
context.put(Costanti.RICETTA, vo);
} else {
context.put(Costanti.DETTAGLIO, "false");
context.put(Costanti.RICETTA, "La ricetta
richiesta non è stata trovata");
}
}
}
Come
si può vedere, in tutti i metodi pubblici
la prima cosa, oltre al reperimento dei dati necessari
provenienti dalla request, è l' acquisizione
dell' ApplicationContext , da cui vengono recuperati
i DAO che grazie all' Inversion Of Control vengono
forniti già valorizzati di tutto il necessario.
Una volta recuperati, i dati vengono posti nel
contesto di Velocity e visualizzati tramite il
template .vm.
Da notare che i DAO vengono acceduti come interfacce,
questo permette di cambiare la tecnica di reperimento
dati semplicemente con una riga di codice.
Per
i JDBC Template:
IRicettaDao
dao = (RicettaJdbcTemplate) aContext.getBean(Costanti.SPRING_JDBC_TEMPLATE);
per
JDBC Classic:
IRicettaDao
dao = (RicettaJdbcClassic) aContext.getBean(Costanti.JDBC_CLASSIC);
per
JDO:
IRicettaDao
dao = (RicettaJDOTemplate) aContext.getBean(Costanti.SPRING_JDO_TEMPLATE);
Come
si vede grazie a Spring basta passare al getBean
il nome dell' oggetto mappato (nel nostro caso)
sul beans.xml, in questo modo possiamo accedere
a tutte le classi che eventualmente potrebbero
servirci.
Il modello dei dati su cui agiscono i tre tipi
di DAO è Ricetta che come si vede dalle
immagini
contiene la descrizione della ricetta con gli
ingredienti, la preparazione dove è descritto
il procedimento per realizzarla, e il numero di
persone per cui la quantità degli ingredienti
è necessaria, oltre a questi campi sono
presenti altri necessari per l' identificazione
nel database o per altri O/R framework che necessitano
di identificativi.
Accesso
al database
Come
abbiamo già detto la portlet accede ai
DAO tramite l' interfaccia IRicettaDao
/*
* Copyright 2000-2004 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0
(the "License");
* you may not use this file except in compliance
with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed
to in writing, software
* distributed under the License is distributed
on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
either express or implied.
* See the License for the specific language governing
permissions and
* limitations under the License.
*/
package
org.casamia.cucina.ricettario.db.dao;
import
java.util.*;
import
org.casamia.cucina.ricettario.vo.*;
/**
* @author <a href="mailto:desmax74@yahoo.it">Massimiliano
Dessì</a>
* @created 13 gennaio 2005
*/
public
interface IRicettaDao {
public
int inserisciRicetta(Ricetta vo);
public
int aggiornaRicetta(Ricetta vo);
public
int eliminaRicetta(int Ricetta);
public
Ricetta caricaRicetta(int idRicetta);
public
ArrayList caricaRicetta();
public
List caricaRicette();
}
Si
può notare che questa interfaccia non contiene
nessun settaggio del datasource, e non vengono
dichiarate eccezioni. Vediamo i motivi di questa
soluzione.
Per quanto riguarda il datasource, il DAO RicettaJdbcClassic
vi accede indirettamente in quanto ha bisogno
di un ConnectionProvider che fornisca la connessione
e che la chiuda, il RicettaJDOTemplate mediante
un PersistenceManagerFactory, a RicettaJdbcTemplate
è trasparente in quanto usa direttamente
un JdbcTemplate.
Perciò
avremo nel beans.xml:
<!--Datasource
-->
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName">
<value>java:comp/env/jdbc/ricettario</value>
</property>
</bean>
.
Il valore JNDI si riferisce al ResourceParams
della web-app, facendo così, otteniamo
il risultato che è la classe org.apache.commons.dbcp
a gestire il pool di connessioni.
Il motivo della mancanza delle eccezioni è
dovuto al fatto che il DAO RicettaJdbcClassic
avrebbe lanciato eccezioni di tipo SQL, mentre
RicettaJdbcTemplate e RicettaJDOTemplate basandosi
su Spring, possono (potrebbero essere anche unchecked)
lanciare DataAccessException, oltre ad avere la
lista di messaggi errore interni dei database
più conosciuti.
Figura 6 - Gerarchia delle eccezioni in Spring
(clicca sull'immagine per ingrandire)
perciò teniamo la gestione delle eccezioni
internamente ai DAO.
DAO
A
scopo illustrativo vediamo a confronto alcuni
metodi dei DAO, senza entrare nel merito delle
performance.
RicettaJdbcClassic
public int aggiornaRicetta(Ricetta vo) {
Connection connection = null;
try {
connection = _cp.getConnection();
PreparedStatement stat = connection.prepareStatement(AGGIORNA_RICETTA);
stat.setString(1, vo.getNome());
stat.setInt(2, vo.getNumeroPersone());
stat.setString(3, vo.getDescrizione());
stat.setString(4, vo.getPreparazione());
stat.setInt(5, vo.getId());
risultato = stat.executeUpdate();
stat.close();
} catch (Exception ex) {
_log.severe("Eccezione in aggiornaAzienda
:" + ex.getMessage());
} catch (Throwable t) {
_log.severe("Eccezione Throwable in aggiornaAzienda
:" + t.getMessage());
} finally {
_cp.closeConnection(connection);
}
return risultato;
}
RicettaJdbcTemplate
public int aggiornaRicetta(Ricetta vo) {
try {
risultato = _jt.update(
AGGIORNA_RICETTA,
ricettaToArray(vo, false),
getTipi(false));
} catch (DataAccessException ex) {
_log.severe(" Eccezione in aggiornaRicetta
RicettaJdbcTemplate" + ex.getMessage());
}
return risultato;
}
RicettaJdoTemplate
public int aggiornaRicetta(Ricetta vo) {
Transaction tx = this.getPersistenceManager().currentTransaction();
try {
tx.begin();
getPersistenceManager().attachCopy(vo, true);
tx.commit();
risultato = 1;
} catch (Exception ex) {
_log.severe(" Eccezione in aggiornaRicetta
RicettaJdoTemplate" + ex.getMessage());
} finally {
if (tx.isActive()) {
tx.rollback();
}
}
return risultato;
}
La
differenza nelle dimensioni è nelle istruzioni
necessarie è evidente tra le tre classi,
bisogna sottolineare che, se la tabella che contiene
i dati della Ricetta aumentasse, solo la classe
RicettaJDBCClassic aumenterebbe di dimensione.
JDO
2.0 JPOX
La versione ufficiale corrente delle specifiche
JDO (1.0) non è adeguata alle applicazioni
web, in quanto non riesce a riassociare gli oggetti
al "grafo" degli oggetti persistenti,
questo problema è indicato come Attach/Detach.
Nelle specifiche 2.0 invece questo problema viene
risolto.
JPOX che è stato scelto per essere l' implementazione
di riferimento delle specifiche JDO 2.0, già
nella versione 1.1.0 usata in questi esempi implementa
questa caratteristica per risolvere il riassociamento
degli oggetti.
Bisogna però ricordare che per il funzionamento
delle classi persistenti, con i JDO non basta
la semplice compilazione delle classi, ma vanno
modificate tramite l' enhancer che va lanciato
da riga di comando o con l' uso di ANT.
Conclusioni
In questo articolo abbiamo visto una parte della
applicazione, nel prossimo articolo completeremo
la trattazione della applicazione, vedendo come
MX4J si occuperà della rotazione delle
ricette usando il sistema di notifiche JMX.
Vedremo sopratutto come modificare il codice della
portlet per renderla jsr-168 compliant, usando
Eclipse e plutoeclipse, un plug-in per le portlet.Come
sostituire il template vm con le jsp, e i file
di configurazione per farla funzionare dentro
Jetspeed2
Bibliografia
[1]Rod Johnson, Juergen Hoeller - "J2EE Development
without EJB", WROX, 2004
[2] Spring e Inversion of Control : http://www.mokabyte.it/2004/10/spring.htm
[3] Portlet MVC: http://www.mokabyte.it/2003/07/jportlet-2.htm
Apache Portals : http://portals.apache.org
Jetspeed1: http://portals.apache.org/jetspeed-1/
Spring
: http://www.springframework.org/
JPOX:
http://www.jpox.org/
JDO 2.0 ( JSR 243 ): http://www.jcp.org/en/jsr/detail?id=243
JDO 1.0.x: http://java.sun.com/products/jdo/index.jsp
Massimiliano
Dessì ha iniziato a lavorare presso
la Sistemi Informativi S.p.A Società IBM
come programmatore Java.
Dal 2001 lavora presso Atlantis S.p.A., dove utilizzando
metodologie agili quali l'eXtreme programming
(Xp), sviluppa applicazioni enterprise Web-based
con tecnologia J2EE quali portali e content management
system per la promozione del territorio. Nel poco
tempo libero, cura la versione italiana di Jetspeed,
tiene seminari e corsi su tecnologie J2EE e collabora
con società e enti italiani ed esteri per
la realizzazione di portali J2EE,
è studente di Ingegneria Informatica al
Politecnico di Torino.
Pagina personale: http://www.jugsardegna.org/vqwiki/jsp/Wiki?MassimilianoDessi
|