MokaByte 93 - Febbraio 2005
JetSpeed2
II parte
di

Massimiliano Dessì

L' investimento tecnologico ed economico richiesto dalla costruzione di un portale internet/intranet, portano a valutare molto attentamente i cambiamenti della piattaforma in cui le applicazioni e i servizi, costruiti nel corso del tempo, sono integrati. In questo articolo analizzeremo una applicazione J2EE funzionante in Jetspeed1.5, in particolare un mini-cms costruito su Spring, MX4J e con accesso a Database con tre modalità differenti. Per coprire una casistica più ampia possibile, l'applicazione può accedere ai dati con JDBC in modo classico, con Spring e i JDBCTemplate, e con un O/R mapping come JPOX con specifiche JDO2.0.

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

MokaByte® è un marchio registrato da MokaByte s.r.l. 
Java®, Jini® e tutti i nomi derivati sono marchi registrati da Sun Microsystems.
Tutti i diritti riservati. E' vietata la riproduzione anche parziale.
Per comunicazioni inviare una mail a info@mokabyte.it