JUG Sardegna supports Devoxx 2013
Vuoi ricevere uno zainetto? Clicca qui

Java User Groups
Java.net Partner
Get Firefox!

Jetspeed2_ I

Seconda Parte

Articolo di MassimilianoDessi




Abstract

Con il rilascio di Jetspeed 2, può sorgere la necessità di migrare le portlet da Jetspeed 1.x alla nuova versione aderente alle specifiche JSR 168.
Analizzeremo una applicazione J2EE basta su Spring, MX4J e un O/R come JPOX (JDO2.0), funzionante in Jetspeed1.x , e vedremo come portarla su Jetspeed 2.




Introduzione

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.
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.



Vista dellÂ’ utente



Vista dellÂ’ amministratore



Lista delle ricette vista dallÂ’ amministratore



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.



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 = (RicettaJdbcTemplateaContext.getBean(Costanti.SPRING_JDBC_TEMPLATE);
      context.put(Costanti.RICETTA, dao.caricaRicetta());

   }



   public void doList(RunData data, Context context) {
    
      ApplicationContext aContext = getApplicationContext(data);
      IRicettaDao dao = (RicettaJdbcTemplateaContext.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 = (RicettaJdbcTemplateaContext.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 = (RicettaJdbcTemplateaContext.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 = (RicettaJdbcTemplateaContext.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 = (RicettaJdbcTemplateaContext.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 = (RicettaJdbcTemplateaContext.getBean(Costanti.SPRING_JDBC_TEMPLATE);


per JDBC Classic:

RicettaDao dao = (RicettaJdbcClassicaContext.getBean(Costanti.JDBC_CLASSIC);


per JDO:

IRicettaDao dao = (RicettaJDOTemplateaContext.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.


Gerarchia delle eccezioni in Spring

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


<!--Datasource -->
  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



Riferimenti

Apache Portals : http://portals.apache.org
Jetspeed1: http://portals.apache.org/jetspeed-1/
Spring : http://www.springframework.org

Spring, Inversion of Control e Jetspeed2: http://www.jugsardegna.org/vqwiki/jsp/Wiki?SpringIoCJet2
Portlet MVC: http://www.mokabyte.it/2003/07/jportlet-2.htm

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


VeryQuickWiki Version 2.7.8 | Admin
Copyright © 2003-20013 Java User Group Sardegna Onlus. - Java, the Java Coffee Cup Logo and the Duke Logo are trademarks or registered trademarks of Oracle corporation in the U.S. and other countries.