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 1 - Vista dell'utente
Figura 2 - Vista dell'amministratore
Figura 2 - Vista dell'amministratore
Figura 3 - Lista delle ricette vista dall'amministratore
Figura 3 - Lista delle ricette vista dall'amministratore
Figura 4 - Inserimento di una nuova ricetta
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
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 doUpdate(RunData data, Context context) {
    // logica di aggiornamento
  }

  public void doInsert(RunData data, Context context) {
    // logica di inserimento
  }

  public void doDelete(RunData data, Context context) {
    // logica di cancellazione
  }
}

ACCESSO AL DATABASE

L'accesso al database è stato realizzato in tre modalità differenti, tutte configurabili tramite Spring. Le tre modalità sono:

1. JDBC classico - utilizzo diretto delle API JDBC 2. Spring JdbcTemplate - utilizzo dei template Spring per semplificare l'accesso JDBC 3. JPOX con JDO 2.0 - utilizzo di un framework di Object-Relational Mapping

DAO

Il pattern DAO (Data Access Object) è stato utilizzato per separare la logica di accesso ai dati dalla logica di business. Ogni implementazione del DAO (JDBC, Spring, JPOX) implementa la stessa interfaccia:

public interface IRicettaDao {
  public Ricetta caricaRicetta();
  public void salvaRicetta(Ricetta ricetta);
  public void cancellaRicetta(int id);
  public List elencoRicette();
}

Implementazione JDBC classica

public class RicettaJdbc implements IRicettaDao {

  private DataSource dataSource;

  public void setDataSource(DataSource dataSource) {
    this.dataSource = dataSource;
  }

  public Ricetta caricaRicetta() {
    Connection conn = null;
    PreparedStatement ps = null;
    ResultSet rs = null;
    try {
      conn = dataSource.getConnection();
      ps = conn.prepareStatement("SELECT * FROM ricette WHERE data = CURDATE()");
      rs = ps.executeQuery();
      if (rs.next()) {
        Ricetta r = new Ricetta();
        r.setId(rs.getInt("id"));
        r.setTitolo(rs.getString("titolo"));
        r.setDescrizione(rs.getString("descrizione"));
        return r;
      }
      return null;
    } catch (SQLException e) {
      throw new DataAccessException(e);
    } finally {
      // chiusura risorse
    }
  }
}

Implementazione con Spring JdbcTemplate

L'utilizzo di Spring JdbcTemplate semplifica notevolmente il codice eliminando il boilerplate di gestione delle connessioni:

public class RicettaJdbcTemplate extends JdbcDaoSupport implements IRicettaDao {

  public Ricetta caricaRicetta() {
    return (Ricetta) getJdbcTemplate().queryForObject(
      "SELECT * FROM ricette WHERE data = CURDATE()",
      new RowMapper() {
        public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
          Ricetta r = new Ricetta();
          r.setId(rs.getInt("id"));
          r.setTitolo(rs.getString("titolo"));
          r.setDescrizione(rs.getString("descrizione"));
          return r;
        }
      }
    );
  }

  public void salvaRicetta(Ricetta ricetta) {
    getJdbcTemplate().update(
      "INSERT INTO ricette (titolo, descrizione, data) VALUES (?, ?, ?)",
      new Object[] { ricetta.getTitolo(), ricetta.getDescrizione(), new Date() }
    );
  }
}

JDO 2.0 JPOX

JPOX è un'implementazione open source delle specifiche JDO (Java Data Objects) 2.0. Permette di mappare oggetti Java su database relazionali in modo trasparente.

Configurazione JPOX

La configurazione di JPOX avviene tramite file XML che descrivono il mapping tra classi e tabelle:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jdo PUBLIC
  "-//Sun Microsystems, Inc.//DTD Java Data Objects Metadata 2.0//EN"
  "http://java.sun.com/dtd/jdo_2_0.dtd">
<jdo>
  <package name="org.casamia.cucina.ricettario.vo">
    <class name="Ricetta" table="ricette">
      <field name="id" primary-key="true"/>
      <field name="titolo"/>
      <field name="descrizione"/>
      <field name="data"/>
    </class>
  </package>
</jdo>

Implementazione DAO con JPOX

public class RicettaJPOX implements IRicettaDao {

  private PersistenceManager pm;

  public void setPersistenceManager(PersistenceManager pm) {
    this.pm = pm;
  }

  public Ricetta caricaRicetta() {
    Query query = pm.newQuery(Ricetta.class);
    query.setFilter("data == oggi");
    query.declareParameters("java.util.Date oggi");
    Collection results = (Collection) query.execute(new Date());
    if (!results.isEmpty()) {
      return (Ricetta) results.iterator().next();
    }
    return null;
  }

  public void salvaRicetta(Ricetta ricetta) {
    pm.makePersistent(ricetta);
  }
}

Configurazione Spring

La configurazione di Spring permette di scegliere quale implementazione DAO utilizzare semplicemente modificando il file di configurazione XML:

<?xml version="1.0" encoding="UTF-8"?>
<beans>

  <!-- DataSource configuration -->
  <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost/cucina"/>
    <property name="username" value="root"/>
    <property name="password" value=""/>
  </bean>

  <!-- DAO implementation: scegliere una delle tre -->

  <!-- Opzione 1: JDBC classico -->
  <bean id="ricettaDao" class="org.casamia.cucina.ricettario.db.dao.RicettaJdbc">
    <property name="dataSource" ref="dataSource"/>
  </bean>

  <!-- Opzione 2: Spring JdbcTemplate -->
  <!--
  <bean id="ricettaDao" class="org.casamia.cucina.ricettario.db.dao.RicettaJdbcTemplate">
    <property name="dataSource" ref="dataSource"/>
  </bean>
  -->

  <!-- Opzione 3: JPOX JDO -->
  <!--
  <bean id="ricettaDao" class="org.casamia.cucina.ricettario.db.dao.RicettaJPOX">
    <property name="persistenceManager" ref="persistenceManager"/>
  </bean>
  -->

</beans>

Conclusioni

In questo articolo abbiamo visto come realizzare un'applicazione portlet per Jetspeed 1.x utilizzando Spring come framework di integrazione. L'utilizzo di Spring permette di disaccoppiare i vari componenti dell'applicazione e di scegliere facilmente tra diverse strategie di accesso ai dati.

Le tre modalità di accesso al database presentate (JDBC classico, Spring JdbcTemplate, JPOX JDO) offrono diversi livelli di astrazione e complessità. La scelta dipende dalle esigenze specifiche del progetto:

  • JDBC classico: massimo controllo ma maggiore verbosità del codice
  • Spring JdbcTemplate: buon compromesso tra controllo e semplicità
  • JPOX JDO: massima astrazione ma richiede configurazione aggiuntiva

Nel prossimo articolo vedremo come migrare questa applicazione a Jetspeed 2, analizzando le differenze architetturali e le modifiche necessarie per adattare il codice al nuovo standard JSR-168.

Bibliografia

  • [1] Spring Framework Reference Documentation - http://www.springframework.org/documentation
  • [2] JPOX - Java Persistent Objects - http://www.jpox.org
  • [3] JSR-168: Portlet Specification - http://jcp.org/en/jsr/detail?id=168
  • [4] Apache Jetspeed 2 - http://portals.apache.org/jetspeed-2/
  • [5] Articolo precedente: "Jetspeed2 - I parte" - http://www.mokabyte.it/2004/10/spring.htm