Hibernate Tips - Select new(..) ..

Hibernate - Mappare il risultato di una 'Select' su un oggetto
G.Morreale

Introduzione:

Hibernate è una potente libreria per la gestione del mapping oggetti/dati relazionali.
La libreria consente di effettuare una gestione ad oggetti del proprio database relazionale.
In questo post voglio mostrare una semplicissima tecnica che consente allo sviluppatore di mappare il risultato di una select non all'interno di un entity ma all'interno di una propria classe (es. DTO).


L'Esempio

Supponiamo di avere una tabella, TELEFONATE, sul db contenente le seguenti colonne

  • id
  • numero_destinatario
  • numero_mittente
  • note
  • data

Tale tabella è mappata sull'entity Telefonate

@Entity
@Table(name = "telefonate")
public class Telefonate implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @Column(name = "id", nullable = false)
    private Integer id;
    @Column(name = "numero_destinatario", nullable = false)
    private String numeroDestinatario;
    @Column(name = "numero_mittente", nullable = false)
    private String numeroMittente;
    @Column(name = "note", nullable = false)
    private String note;
    @Column(name = "data", nullable = false)
    @Temporal(TemporalType.TIMESTAMP)
    private Date data;
//... più i metodi get/set


Supponiamo adesso di volere creare un session bean tale da interagire con l'entity manager e restituire l'aggregato del conteggio del numero di telefonate per mittente.


native query sql:
SELECT count(*) as c, numero_mittente FROM telefonate t group by numero_mittente;
Il session bean in questione avrà il metodo getAggregateBySender():
package sessionbean;

import dto.AggregateDTO;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

@Stateless
public class myBeanBean implements myBeanLocal 
{
    @PersistenceContext
    private EntityManager em;

    public AggregateDTO[] getAggregateBySender()
    {
        return null;
    }    
}
Implementazione:
package sessionbean;

import dto.AggregateDTO;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

@Stateless
public class myBeanBean implements myBeanLocal 
{
    @PersistenceContext
    private EntityManager em;

    public AggregateDTO[] getAggregateBySender()
    {
        //da implementare
        return null;
    }    
}
Quindi i dati che il session bean dovrà esporre mediante il metodo sono il conteggio e il numero del mittente(sender), al fine di trasferire questi due dati(utilizzando il design pattern DTO), si dovrà creare una classe AggregateDTO in grado di trasportare i dati in questione.
Il codice del DTO sarà il seguente:
package dto;

public class AggregateDTO 
{
    
private long count;
    
private String mittente;
    
    public AggregateDTO(long count, String mittente)
    
{
        
this.count = count;
        
this.mittente = mittente;
    
}
    
    public long getCount()
    
{
        
return count;
    
}
    
    public void setCount(long count)
    
{
        
this.count = count;
    
}

    public String getMittente()
    
{
        
return mittente;
    
}

    public void setMittente(String mittente)
    
{
        
this.mittente = mittente;
    
}
}
La query ejb-ql che ci consente di estrarre i dati interessati è la seguente:
SELECT count(*), t.numeroMittente FROM Telefonate t GROUP BY t.numeroMittente
In tal modo però l'entityManager restituirà un array di array (o una list di list o qualcosa di simile a seconda del persistence provider utilizzato), si dovrà in seguito scorrere l'array ed effettuando una serie di casting costruire gli oggetti DTO da restituire.
Si verrebbe quindi a creare una situazione per cui il codice necessario sarebbe il seguente:
    public AggregateDTO[] getAggregateBySender()
    {
        Query q = em.createQuery("SELECT count(*), t.numeroMittente FROM Telefonate t GROUP BY t.numeroMittente");
        List list = q.getResultList();
        int len = list.size();
        AggregateDTO[] array = new AggregateDTO[len];
        
        for (int i = 0; i < len; i++)
        {
           Object[] o = (Object[]) list.get(i);
           array[i] = new AggregateDTO((Long)o[0], (String)o[1]);
        }
        return array;
    }    
Questa versione del metodo sicuramente può essere resa più performante ed elegante utilizzando la sintassi oggetto di questo articolo.
La query sarà riscritta usando direttamente il costruttore new AggregateDTO all'interno della query stessa.
nota:Non è una soluzione standard ejb-ql ma una soluzione possibile solo in hibernate.
La query sarà la seguente
SELECT new dto.AggregateDTO(count(*), t.numeroMittente) FROM Telefonate t GROUP BY t.numeroMittente
Di conseguenza il metodo verrà così riscritto
    public AggregateDTO[] getAggregateBySender()
    {
Query q = em.createQuery("SELECT new dto.AggregateDTO(count(*), t.numeroMittente) FROM Telefonate t GROUP BY 
t.numeroMittente");
        
     AggregateDTO[] array = (AggregateDTO[]) q.getResultList().toArray(new AggregateDTO[0]);        
       return array;
}
Conclusione
L'articolo dimostra come utilizzare una feature di hibernate al fine di scrivere meno codice e in modo più elegante.
Tale tecnica viene spesso utilizzata quando entra in gioco il pattern DTO. Bisogna però prestare attenzione al fatto che la query non è compatibile con lo standard EJB-QL quindi qualora si vuole mantenere la compatibilità cross-persitenceProvider la soluzione non è forse ideale.

Java EE - WebServices - Esempi

WebService - Realizzare un webservice in Java EE
G.Morreale

Introduzione:

Introduzione ai webservice: http://it.wikipedia.org/wiki/Web_service


Java EE e i webservice:

Le specifiche JSR 109 riguardanti i webservice su Java EE, definiscono due metodi per implementare un webservice:

  • Attraverso una classe Java
  • Attraverso l'uso delle tecnologia EJB


L'Esempio

Supponiamo di voler realizzare un webservice in grado di sommare due numeri interi.
Il metodo java in questione a prescindere dal webservice è il seguente:

  public int sommaNumeri(int a, int b)
  {    
    return a + b;
  }

Implementazione Esempio attraverso una classe Java

nota:
Ovviamente la classe Java in questione deve essere distribuita su un apposito container (glassfish, jboss etc.)

Codice della classe a prescindere dal webservice:

public class MyWebService
{    
  public int sommaNumeri(int a, int b)
  {    
    return a + b;
  }
        }

        Trasformazione delle classe in webservice:

import javax.jws.WebMethod;
import javax.jws.WebService;

@WebService
public class WS 
{
  @WebMethod
  public int sommaNumeri(int a, int b){    
    return a + b;
  }    
}

Come è possible notare, banalmente sono state aggiunte due annotation, una per indicare che la classe implementa un webservice(@WebService) l'altra per indicare che il metodo in questione è un operazione che il webservice mette a disposizione(@WebMethod). Ovviamente sono state aggiunte anche le apposite import.

Implementazione Esempio attraverso uno Stateless Bean

Codice del session bean a prescindere dal webservice:

L'interfaccia

import javax.ejb.Local;

@Local
public interface WsLocal {

    public int sommaNumeri(int a, int b);
    
L'implementazione
}


import javax.ejb.Stateless;

@Stateless
public class WsBean implements WsLocal 
{     
   public int sommaNumeri(int a, int b)
   {    
    return a + b;
   }
}

Aggiunta disponibilità sotto forma di webservice

import javax.ejb.Stateless;
import javax.jws.WebMethod;
import javax.jws.WebService;

@Stateless
@WebService
public class WsBean implements WsLocal 
{     
    @WebMethod
   public int sommaNumeri(int a, int b)
   {    
    return a + b;
   }

Anche in questo con un paio di annotation la componente software, in questo caso il session bean, è stato reso disponibile anche come webservice.


Un pò di chiarezza su nomi e versioni

Sul web si trovano diverse sigle e api:

  • JAX-RPC: E' la specifica riguardante le api java per la Remote procedure call(RPC) basata su xml, la sua prima release è stata rilasciata nel giugno 2002.

  • JAXB(Java API for XML binding): E' una specifica rilasciata inizialmente nell'ottobre 2003 per mettere a disposizione dei tool e delle api per il mapping classi java <-> documenti xml. Ovviamente è utilizzata e fà parte delle api per i webservice.

  • WSTI(Web services interoperability technology):E' parte dello stack webservice in grado di garantire alcune importanti features riguardanti l'interoperabiltà tra webservice(sicurezza,transazioni etc)

  • JAX-WS: E' la nuova versione delle JAX-RPC, infatti con la versione 2.0 si cambia nome (JAX-RPC => JAX-WS) e vengono aggiunte una serie di nuove feature.

Differenze tra JAX-RPC 1.1 e JAX-WS

JAX-RPC è stata rilasciata come ver 1.0, la versione successiva è stata la 1.1, la 2.0 è stata rinominata in JAX-WS 2.0, e al momento è stata rilasciata la versione 2.1
JAXB attualmente è alla versione 2.1 (https://jaxb.dev.java.net/ )

  • Metro: E' un webservice stack comprendente le implementazioni di JAX-WS, JAXB e WSIT


Conclusione
Negli esempi si mostra come con il semplice uso delle annotation si riesce a creare data una classe java o uno stateless bean un interfaccia webservice per rendere disponibili le funzionalità di un metodo attraverso la tecnologia in questione

MDB(Message Driven Bean)

MDB - Un introduzione su Message Driven Bean attraverso un Esempio
G.Morreale

Introduzione:

Un MDB(Message Driven Bean) è un particolare tipo di bean in grado di eseguire determinate operazioni in relazione alla ricezione di un messaggio JMS.
Esso quindi non risponde alle richieste di un client ma agisce il relazione all'evento "ricezione di un messaggio".

La ricezione a secondo del dominio scelto avviene o su un topic o su una coda.

L'MDB non è altro che un client JMS che opera in fase di ricezione su una coda.

L'Esempio

Supponiamo di voler realizzare un MDB in grado di elaborare le richieste web che arrivano su un determinato url(una servlet).
L'MDB collezionerà i dati presenti nell'oggetto HttpServletRequest al fine di conservarli su un file di log.

Quindi ad ogni accesso l'MDB apre il file, e scrive all'interno delle informazioni: ip del client, user-agent, timestamp della richiesta etc.

Ma perchè far eseguire tali operazioni all'MDB? L'asincronicità del sistema JMS consente di non bloccare l'esecuzione della servlet in attesa delle operazioni(apertura, scrittura file etc.) di log(inutili per l'utente).

Predisporre i progetti

Al fine di realizzare il progetto creiamo un applicazione EAR dotata di modulo WEB e modulo EJB.
Nel modulo WEB inseriamo il produttore di messaggi nell'EJB inseriremo il consumatore, ovvero l'MDB.

Elementi dell'esempio

Come in ogni applicazione JMS si avrà 3 elementi fondamentali:

  • provider dei messaggi 
  • client che produce i messaggi
  • client che consuma i messaggi

Ma commentiamo l'elenco rapportandolo all'esempio

  • provider dei messaggi - Coda da creare tra le risorse dell'application server in grado di raccogliere i messaggi prodotti.
  • client che produce i messaggi - Servlet (o meglio un suo filtro) che invia il messaggio di accesso all'url
  • client che consuma i messaggi - MDB che elabora il messaggio memorizzando alcuni dati su file.

Configurazione del Provider JMS

Premetto che il provider, o meglio la sua configurazione, è legata all'application server utilizzato; in questo caso viene usato Glassfish V2.

Apriamo il pannello di amministrazione. Ad esempio su localhost:4848.
Su Resources -> JMS Resources -> Connection Factories 
è possibile creare una nuova connection factory:



inseriamo il JNDI name, ovvero il valore che consente di ritrovare all'interno del container la risorsa, impostiamo come tipologia di risorsa una QueueConnectionFactory in quanto vogliamo utilizzare nello specifico una coda e lasciamo invariate tutte le altre opzioni.

nota:
Glassfish crea un pool di connessioni verso la coda in modo da ottimizzare il riuso di connessioni aperte verso la factory allo stesso modo in cui ottimizza ad esempio quelle jdbc verso un db.

Allo stesso modo è possibile creare una Destination:

Scegliendo come al solito il nome JNDI, il nome fisico della destination e la tipologia di destination che in questo caso è una coda.

Client che produce i messaggi

Il messaggio verrà prodotto in relazione all'accesso a un determinato url, useremo 

http://localhost/mdb/test

Quindi dovremo creare la servlet test e un filtro per la servlet test, ovviamente nel progetto web(war).

nota:
I filtri intecettano le request e response di un determinato url pattern al fine di modificarli e ripassarli alla servlet o jsp chiamata o adirittura ad uno o più filtri. Forniscono la possibilità di creare unità di codice riusabile su più url (servlet o jsp), nonchè modularizzare il codice e creare delle vere e proprie catene di filtri(strutturando lo sviluppo in una sorta di plugins)

Riguardo la servlet realizziamo una banalissima hello world servlet:

package servlet;

import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 *
 * @author PeppeM
 */
public class test2 extends HttpServlet {
   
    /** 
    * Processes requests for both HTTP <code>GET</code> and <code>POST</code> methods.
    * @param request servlet request
    * @param response servlet response
    */
    protected void processRequest(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        try {            
            out.println("<html>");
            out.println("<head>");
            out.println("<title>Servlet test2</title>");  
            out.println("</head>");
            out.println("<body>");
            out.println("<h1>ESEMPIO MDB - WEB TRACKING</h1>");
            out.println("</body>");
            out.println("</html>");            
        } finally { 
            out.close();
        }
    } 

    // <editor-fold defaultstate="collapsed" desc="HttpServlet methods. Click on the + sign on the left to edit the code.">
    /** 
    * Handles the HTTP <code>GET</code> method.
    * @param request servlet request
    * @param response servlet response
    */
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        processRequest(request, response);
    } 

    /** 
    * Handles the HTTP <code>POST</code> method.
    * @param request servlet request
    * @param response servlet response
    */
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        processRequest(request, response);
    }

    /** 
    * Returns a short description of the servlet.
    */
    public String getServletInfo() {
        return "Short description";
    }// </editor-fold>

}

Predisponiamo un oggetto DTO(Data Transfer Object)

In grado di incapsulare i dati che serviranno al client consumatore!
Tale oggetto è opportuno inserirlo nel progetto EJB e non in quello WEB.


import java.io.Serializable;
import java.util.Date;
import javax.servlet.http.HttpServletRequest;


public class RequestDTO implements Serializable{
    String user_agent;
    private String remote_addr;
    private String query_string;
    private String method;
    private Date date;

    public String getMethod()
    {
        return method;
    }

    public void setMethod(String method)
    {
        this.method = method;
    }

    public String getQuery_string()
    {
        return query_string;
    }

    public void setQuery_string(String query_string)
    {
        this.query_string = query_string;
    }

    public String getRemote_addr()
    {
        return remote_addr;
    }

    public void setRemote_addr(String remote_addr)
    {
        this.remote_addr = remote_addr;
    }

    public String getUser_agent()
    {
        return user_agent;
    }

    public void setUser_agent(String user_agent)
    {
        this.user_agent = user_agent;
    }
   
    public RequestDTO(HttpServletRequest request)
    {
        user_agent = request.getHeader("user-agent");
        remote_addr = request.getRemoteAddr();
        query_string = request.getQueryString();
        method = request.getMethod();
        date = new Date();
    }

    @Override
    public String toString()
    {
        return date + " " + this.remote_addr + " " +  this.getMethod() + " " +  this.getUser_agent() + " " +  this.query_string + "\r\n";
    }
}


E' un banalissimo oggetto che data un HttpServletRequest estrae i dati necessari e li rende disponibili mediante i metodi accessor.
Nota bene: L'oggetto deve essere serializable!

Da notare anche l'override del metodo toString in grado di copiare il valore di tutti i campi del DTO in una Stringa.

Il filtro invece dovrà occuparsi della produzione dei messaggi.

Creare un nuovo filtro, magari usando il wizard di netbeans.
Sempre avvalendosi dei wizard di netbeans è possibile generare il automatico il codice per ottenere la connection factory e la coda configurate al passo precedente.
In tal caso cliccare con il tasto destro del mouse sul codice del filtro e selezionare

Enterprise Resources -> Send JMS Message


Configurare correttamente i valori come nell'immagine in modo da referenziare le risorse create sull'application server.

In alternativa è possibile scrivere a mano i seguenti metodi:

 private Message createJMSMessageFormyDestination(Session session, Object messageData) throws JMSException
    {
        ObjectMessage m = session.createObjectMessage((Serializable)messageData);                
        return m;
    }

    private void sendJMSMessageToMyDestination(Object messageData) throws NamingException, JMSException
    {
        Context c = new InitialContext();
        ConnectionFactory cf = (ConnectionFactory) c.lookup("java:comp/env/myQueueFactory");
        Connection conn = null;
        Session s = null;
        try
        {
            conn = cf.createConnection();
            s = conn.createSession(false, s.AUTO_ACKNOWLEDGE);
            Destination destination = (Destination) c.lookup("java:comp/env/myDestination");
            MessageProducer mp = s.createProducer(destination);
            mp.send(createJMSMessageFormyDestination(s, messageData));
        } finally
        {
            if (s != null)
            {
                s.close();
            }
            if (conn != null)
            {
                conn.close();
            }
        }
    }

generati in automatico da netbeans.

riepilogando il codice di invio del messaggio si eseguono seguenti step:

  1. Lookup della risorsa ConnectionFactory
  2. creazione della connessione
  3. creazione della sessione
  4. Lookup della destination
  5. Creazione del client produttore
  6. Invio messaggio
  7. Chiusura sessione e connessione

Quindi all'interno del metodo principale del filtro, doFilter(ServletRequest request, ServletResponse response,                FilterChain chain)

è possibile richiamare il metodo di invio del messaggio

sendJMSMessageToMyDestination(new RequestDTO(request));

Il contenuto del messaggio sarà l'oggetto request di tipo HttpServletRequest.
Tale oggetto contiene tutte le informazioni utili alla scrittura su log necessaria su MDB.

Test della produzione dei messaggi

Prima di generare il client in grado di consumare i messaggi prodotti dal filtro, è opportuno verificare quantomeno se funziona la produzione, quindi lanciare il progetto EAR e chiamare la servlet di test sulla quale è mappato il filtro.

Potrebbe verificarsi il seguente problema:

javax.naming.NameNotFoundException: myQueueFactory not found
        at com.sun.enterprise.naming.TransientContext.doLookup(TransientContext.java:216)
        at com.sun.enterprise.naming.TransientContext.lookup(TransientContext.java:188)

Ciò indica che il valore JNDI utilizzato in fase di lookup è erroneo.
Nel nostro caso ad esempio netbeans di default considera le risorse trovarsi nel percorso JNDI java:comp/env/nomerisorsa, durante la creazione però glassfish ha inserito le risorse nella root, quindi bisogna apportare le seguenti modifiche in fase di lookup:

ConnectionFactory cf = (ConnectionFactory) c.lookup("myQueue");

Destination destination = (Destination) c.lookup("myDestination");



Client che consuma i messaggi - L'MDB

All'interno del progetto EJB, creiamo un MDB.

Ancora una volta è possibile usare i wizard di netbeans.


Oppure scrivere a mano il seguente codice sorgente:

package MDB;

import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.jms.Message;
import javax.jms.MessageListener;

@MessageDriven(mappedName = "myDestination", activationConfig =  {
        @ActivationConfigProperty(propertyName = "acknowledgeMode", propertyValue = "Auto-acknowledge"),
        @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue")
    })

public class myMDBBean implements MessageListener {

   
    public myMDBBean() {
    }

    public void onMessage(Message message) {
    }
   
}



Grazie all'uso della dependency injection con poche annotazioni si riesce a istanziare il client JMS (ovvero l'MDB).

@MessageDriven(mappedName = "myDestination", activationConfig =  {

        @ActivationConfigProperty(propertyName = "acknowledgeMode", propertyValue = "Auto-acknowledge"),

        @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue")

    })

Si indica quindi che il bean è MessageDriven e si aggiungono un paio di proprietà per indicare la tipologia di destinazione (coda o topic) e la destinazione stessa(configurata precedentemente dall'amministratore di sistema).
Con solo un altro step si termina la configurazione dell'MDB, infatti è necessario implementare l'interfaccia MessageListener
public class myMDBBeanimplements MessageListener {
public void onMessage (Message msg) {
FileWriter fw = null;
try
{
ObjectMessage m = (ObjectMessage) message;//casting per estrarre la corretta tipologia di messaggio
RequestDTO requestDTO = (RequestDTO) m.getObject();//estrazione dei dati dal messaggio

//apertura e scrittura su file, chiusura
fw = new FileWriter("c:\\logmdb.txt", true); //occhio al path per i sistemi unix.
fw.append(requestDTO.toString());

} catch (IOException ex)
{
Logger.getLogger(myMDBBean.class.getName()).log(Level.SEVERE, null, ex);
} catch (JMSException ex)
{
Logger.getLogger(myMDBBean.class.getName()).log(Level.SEVERE, null, ex);
} finally
{
try
{
fw.close();
} catch (IOException ex)
{
Logger.getLogger(myMDBBean.class.getName()).log(Level.SEVERE, null, ex);
}
}

}

Conclusione

L'esempio è in grado di elaborare dei dati senza "far perdere tempo" alla servlet in modo tale da scriverli in un file di log.
Lanciando infatti 'n' volte la servlet sarà possibile notare l'inserimento di n righe all'interno del file di log.


nota: Possibili Problemi
Se nel file server.log di glassfish notate la seguente stringa

DirectConsumer:Caught Exception delivering messagecom.sun.messaging.jmq.io.Packet cannot be cast to com.sun.messaging.jms.ra.DirectPacket

sappiate che si tratta di un know issue https://glassfish.dev.java.net/issues/show_bug.cgi?id=3988

Con la tecnologia JMS anche se il client consumatore non è attivo al momento dell'invio del messaggio i messaggi saranno comunque consegnati in seguito.
Per dimostrare ciò, è possibile

  • cancellare temporanemante l'MDB dal progetto;
  • cancellare il file di log "logmdb.txt"
  • effettuare il build e redeploy del progetto sul server
  • lanciare la servlet di test (che genererà un messaggio ad ogni chiamata).

  • ricreare l'MDB
  • effettuare il build e redeploy del progetto sul server

SENZA lanciare la servlet sarà possibile notare che nel log sono state inserire le righe relative alle chiamate effettuate quando l'MDB 
non era completamente presente.


JMS - Un introduzione su Java Message Service

JMS - Un introduzione su Java Message Service
G.Morreale


Scopo dell'articolo è introdurre il lettore sul sistema di messagistica proposto da Sun.

JMS è un sistema di scambio messaggi, è un metodo di comunicazione tra componenti(o applicazioni) software come la mail lo è per le persone.

Un sistema basato sui messaggi offre la possibilità di comunicazioni peer-to-peer, ogni client può ricevere e spedire messaggi da e per ogni altro client.

JMS API: Sono delle API che consentono la creazione, spedizione, ricezione e lettura di messaggi.
Esse garantisco una comunicazione

  • Loosely Coupled - La correlazione tra i componenti che comunicano tra loro è molto bassa.
  • Asincrona (Il provider è in grado di consegnare i messaggi non appena arrivano, i client non devono fare alcuna richiesta per la ricezione)
  • Affidabile (JMS API garantiscono che il messaggio è consegnato, la garanzia vale per una sola volta!)

E' facile fare un analogia con le email, infatti il sistema dell'email garantisce lo scambio dei messaggi anche quando il mittente e il ricevente non sono contemporaneamente online(Asincronicità - a differenza di una chat), gli utenti possono usare client di posta differenti (outlook, thunderbird, una webmail etc.) senza obbligare il ricevente ad usare lo stesso client del mittente.

E' possibile già delineare e elementi che entrano in gioco in questa tecnologia:

  • JMS Provider - Offre funzioni di amministrazione e controllo. (Una sua implementazione è fornita con J2EE a partire dalla ver. 1.3)
  • JMS Clients - Componenti che producono e/o consumano messaggi
  • Messaggi - Oggetti che contengono informazioni che i vari clients intendono scambiare


Link di riferimento per JMS.
http://java.sun.com/products/jms/ 


Tipologie di approccio per scambio di messaggi (Il Dominio)

Esistono due tipologie di approcci per lo scambio di messaggi:

  • Point-to-Point
  • Publish/Subscribe

Nel primo ciascun messaggio è inviato ad una determinata coda, il ricevente estrae il messaggio da tale coda e notifica la coda circa tale ricezione. In questo sistema un messaggio è consumato da un solo client.


Nella seconda metodologia il mittente invia il messaggio al topic come nel primo caso, ma i riceventi saranno solo coloro che hanno sottoscritto la ricezione e solo coloro che l'hanno sottoscritta prima dell'invio del messaggio(E' un pò come l'abbonamento a Sky!!).

Quindi un messaggio può avere più client in ricezione.

immagine proveniente da: http://java.sun.com/products/jms/tutorial/1_3_1-fcs/doc/basics.html

La ricezione dei messaggio può essere

Sincrona - quando il client esplicitamente richiede il fetch del messaggio chiamando il metodo bloccante receiver
Asincrona - quando il client registra un Message Listener.

Elementi dell'infrastruttura

Come già accennato 3 elementi subentrano nell'uso del JMS:

  • JMS Provider - Offre funzioni di amministrazione e controllo. (Una sua implementazione è fornita con J2EE a partire dalla ver. 1.3)
  • JMS Clients - Componenti che producono e/o consumano messaggi
  • Messaggi - Oggetti che contengono informazioni che i vari clients intendono scambiare

Al fine di fare interagire questi 3 elementi bisogna introdurre gli Administred Object, ovvero oggetti particolari per la creazione delle connessioni e delle "destinazioni", tali oggetti sono gestiti a livello amministrativo, nel senso, è il sistemista che ne effettua la configurazione sull'application server.

(L'immagine rappresenta una scherma di configurazione delle risorse JMS: Connection Factories e Destination in Glassfish)

nota:
Attraverso il namespace JNDI però è possibile effettuare il lookup (o usare la dependency injection se possibile) per ottenere tali oggetti, quindi il sistemista predispone e il programmatore deve solo richiamare tali oggetti nei suoi client.


Tali oggetti sono due

Vediamo nello specifico le due tipologie di Administred Object:

Connection Factory

E' una factory che il client usa per creare una connessione con il provider. Come detto prima, la configurazione della connessione viene fatta in fase di amministrazione.
La connection factory nello specifico è un istanza dell'interfaccia QueueConnectionFactory oppure TopicConnectionFactory a seconda del tipo di approccio utilizzato (p2p, o publisher/subscriber)

nota:
Creata la connessione si instaura una comunicazione attraverso TCP/IP quindi è oppurtuno prestare attenzione ogni talvolta che non è più necessaria (es. il client produce il messaggio o lo consuma).


Destinations

Con la connection factory si crea il canale di comunicazione, la "destination" invece serve a indicare il target dei messaggi.

Nel caso di p2p si tratterà di una coda (queue) nel caso di publisher/subscriber le destinazioni sono chiamate topic.


Oggetti ottenuti dalla Factory:

Una volta creato il canale di comunicazione con la connection factory e istanziata la destinazione("destination") è possibile creare una sessione(QueueSession o  TopicSession)ovvero un contesto per la produzione e consumazione dei messaggi. Attraverso la sessione è possibile creare il message producer(QueueSender o TopicPublisher) ovvero l'oggetto responsabile dell'invio del messaggio verso la destinazione. 
Riguardo invece la ricezione è necessario creare un message receiver(QueueReceiver o TopicSubscriber) il receiver gestisce la ricezione attraverso il metodo bloccante receive() o attraverso la registrazione di un MessageListener (implementando l'interfaccia onMessage());
Un altro elemento su cui far cenno è il message selectors, da utilizzare nei casi in cui il client intende filtrare secondo determinati criteri i messaggi che riceve.

,
L'immagine rappresenta un infrastruttura p2p, nel caso di pub/sub l'immagine è analoga cambiano ovviamente le componenti, da sopra verso sotto(TopicConnectionFactory,TopicConnection,TopicSession, TopicPublisher, TopicSubscriber,Topic, Message è equivalente)


Messages

Il contenuto dello scambio sono proprio i messaggi, essi sono composti essenzialmente da 3 parti

  • Header - contiene i dati dei clients e dei providers al fine di garantire il corretto routing, nonchè la tipologia del messaggio, un timestamp , l'id del messaggio etc.
  • Properities(opzionale) - tale parte è compilata a favore del programmatore al fine di trasportare informazioni aggiuntive rispetto agli header.
  • Body(opzionale) - Il corpo del messaggio rappresentabile in 5 diversi formati:
    • TextMessage - in sostanza una String
    • MapMessage - Una sorta di hash table contenente un insieme di coppie chiave/valore
    • BytesMessage - Uno stream di bytes.
    • StreamMessage - Uno stream di tipi primitivi java
    • ObjectMessage - Un oggetto java che implementi l'interfaccia Serializable
    • Message - Un messaggio senza body contenente solo header e opzionalmente il campo properities.


Conclusione
In tale articolo si è cercato di introdurre i principali concetti che entrano in gioco quando si parla di JMS.




Il Pattern "Observer" attraverso un Esempio

Il Pattern "Observer" attraverso un Esempio
G.Morreale

Introduzione:

L'obiettivo di questo articolo è quello di avvicinarsi al design pattern "observer" cercando di scrivere meno teoria a favore di un esempio concreto circa l'uso del pattern.

Ovviamente è d'obbligo una piccola introduzione teorica prima di procedere con l'esempio.

Observer è un design pattern utilizzato quando un oggetto vuole notificare i suoi cambiamenti di stato ad un gruppo di oggetti che dipendono da lui.

Quindi in tale pattern esiste

  • Un oggetto che cambia stato
  • E degli oggetti che vengono notificati

Il primo viene chiamato "subject" o "publisher", infatti è il soggetto del pattern, colui che pubblica i cambiamenti, 
I secondi invece vengono indicati con il nome di "observer","subscriber" o "listener"; sono gli oggetti che osservano i cambiamenti, sottoscrivono una sorta di contratto con il publisher al fine di essere avvisati sui suoi cambiamenti o se vogliamo sono ascoltatori di nuove notifiche.

Come è già facile intuire in questo pattern si crea una relazione uno a molti tra soggetto e listeners.
Tale relazione viene stabilita a run-time.

Nel caso specifico in cui l'observer è uno solo si usa più frequentemente il nome di callback piuttosto che observer.


Esempio

Supponiamo di voler realizzare una piccola applicazione in grado di notificare l'utente attraverso l'apertura di un popup quando arriva una nuova mail sulla sua casella di posta elettronica.


Esisterà una classe che rappresenta il Subject, ovvero il gestore di posta, che in relazione all'arrivo di una nuova mail (cambio di stato) notifica un oggetto grafico (observer) al fine di avvisare l'utente sull'arrivo di una nuova mail.

Cosa è necessario per far funzionare l'esempio ?

Una versione aggiornata di JDK
Le librerie Java MAIL
Un account di posta elettronica in grado di funzionare con il protocollo pop3(o altri supportati da javamail)

nota:
La libreria java mail deve essere nel classpath, nel caso di netbeans è possibile aggiungere la libreria cliccando con il tasto destro sul progetto e accedendo tra le proprietà alla sezione library.

Si consideri di avere due oggetti, uno che si occupa di ricevere la posta, ad ogni arrivo di nuove mail il suo stato interno cambierà; e uno che si occupa di mostrare graficamente il cambio di tale stato.
Al di là del pattern si realizzano in seguito la classe per la ricezione della mail e la classe per la visualizzazione destinata all'utente.

La ricezione della mail

1) Creare un nuovo progetto Java SE
2) Importare le librerie Java Mail
3) Effettuare il copia e incolla della classe MailManager

package esempio;

import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.mail.Folder;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Store;

/**
 *
 * @author Giuseppe M.
 */
class MailManager implements Runnable
{
    //numero di millisecondi tra un check della cartella inbox ed un altro
    private long interval;    //numero corrente di mail all'interno della cartella. 
    private int total;
    private boolean alive = true; //determina se il thread deve continuare a lavorare o meno.
    private Folder folder; //contiene il riferimento alla cartella INBOX della casella di posta.
    private Store store;//contiene riferimento alla connessione imap

    public MailManager()
    {
        this(2000);
    }

    public MailManager(long millis)
    {        
        interval = millis; //di default ogni due secondi
        initConnection(); //init dell connessione.
        this.total = check();
    }

    /**
     * Inizializza la connessione verso la casella di posta
     */
    private void initConnection()
    {
        try
        {

            String host = "imap.gmail.com";
            String username = "la vostra username";
            String password = "la vostra pwd,.";
            String protocol = "imaps"; //o pop3s
            int count = -1;


            //Otteniamo un oggetto session
            Session session = Session.getInstance(new Properties(), null);

            //Otteniamo lo store
            store = session.getStore(protocol);
            store.connect(host, username, password);

            //Otteniamo la cartella INBOX            
            folder = store.getFolder("INBOX");
            folder.open(Folder.READ_ONLY);
        } catch (MessagingException ex)
        {
            Logger.getLogger(MailManager.class.getName()).log(Level.SEVERE, null, ex);
        }

    }

    /**
     * Calcola il numero di mail ogni "interval" millisecondi
     * @return
     */
    public int check()
    {
        // Contiamo i messaggi presenti nella cartella
        int count = -1;
        try
        {
            count = this.folder.getMessageCount();
        } catch (MessagingException ex)
        {
            Logger.getLogger(MailManager.class.getName()).log(Level.SEVERE, null, ex);
        }
        return count;
    }

    private void setIntervals(long millis)
    {
        this.interval = millis;
    }

    public void stop()
    {
        this.alive = false;
        
        try
        {//diamo tempo di effettuare l'ultima lettura prima di chiudere le connessioni
            Thread.sleep(interval + 100);
            folder.close(false);
            store.close();
        } catch (InterruptedException ex)
        {
            Logger.getLogger(MailManager.class.getName()).log(Level.SEVERE, null, ex);
        } catch (MessagingException ex)
        {
            Logger.getLogger(MailManager.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public void run()
    {
        //effettua il calcolo ogni interval millisecondi alla ricerca di nuove mail
        //determina l'arrivo di nuove mail calcolando la differenza con il valore 
        //calcolato al passo precedente
        while (alive)
        {
            int newTot = check();

            if ((newTot - this.total) > 0)
            {
                System.out.println((newTot - this.total) + " Nuove Mail");
                this.total = newTot;
            }

            try
            {
                Thread.sleep(interval);
            } catch (InterruptedException ex)
            {
                Logger.getLogger(MailManager.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
}

4) Incollare il seguente codice nel metodo main

    public static void main(String[] args) 
    {
        MailManager pop3 = new MailManager();
        Thread mailThread = new Thread(pop3);
        mailThread.run();
    }

In tal modo a prescindere dal risultato finale dell'esempio, è possibile testare la comunicazione con la propria casella di posta.
Provando a ricevere una nuova mail (ad esempio auto-inviandosela) noterete nello standard output una nuova riga per ogni mail arrivata:

>> 1 Nuove Mail
>> 2 Nuove Mail
..

Il Popup

Si realizzi adesso un componente grafico in grado di comparire in basso a sinistra dello schermo e mostrare un testo di notifica.
Il codice da utilizzare è il seguente:

public class PopUp extends JDialog
{
    private JLabel label; //etichetta sulla quale scrivere il testo
    
    PopUp()
    {
        label = new JLabel("NO MAIL");
        label.setAlignmentX(JLabel.CENTER);
        label.setAlignmentY(JLabel.CENTER);
        label.setBackground(Color.BLACK);
                
        JPanel panel = new JPanel();
        panel.add(label);
        
        panel.setBackground(Color.WHITE);
        this.add(panel);
        
        //otteniamo le dimensioni dello schermo
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();        
        this.setSize(100, 100);       
        this.setLocation( 3,(int)screenSize.getHeight() - this.getHeight() - 35);        
        this.setResizable(false);
        this.setDefaultCloseOperation(PopUp.DISPOSE_ON_CLOSE);
        this.repaint();
    }
}

Per provare le funzionalità del popup aggiungere nel metodo main le seguenti righe di codice:

 PopUp pop = new PopUp();        
 pop.setVisible(true);

L'implementazione del pattern

Adesso è il momento di miscelare il tutto, rispettando i ruoli di ciascun elemento del design pattern.

Si proceda creando la classe astratta "soggetto" e l'interfaccia al fine di raggiungere lo scopo attraverso l'utilizzo del design pattern observer.

La classe soggetto : Subject, viene implementata come classe astratta in modo da unificare l'implementazione dei metodi addListener e removeListener.
E' possibile comunque definirla attraverso un interface e delegare l'implementazione di tutti i metodi alla classe concreta.


nota:
I nomi dei metodi addListener e removeListener sono analoghi a attach, detach o addObserver.
Allo stesso modo la classe Observer è chiamata Listener.

public abstract class Subject
{

    private MailListener listener;

    public void addListener(MailListener l)
    {
        listener = l;
    }

    public void removeListener()
    {
        this.listener = null;
    }

    public abstract void mailNotify();
}

L'interfaccia MailListener, ovvero l'Observer, banalmente sarà implementata con il seguente codice java:

public interface MailListener {

    public void update();
}

Adesso è necessario procedere con l'implementazione delle classi concrete.

La classe MailManager sarà classe concreta del Subject, quindi responsabile dell'implementazione del metodo notify:


    @Override
    public void mailNotify()
    {
        if (this.listeners != null)
        {
            for (MailListener l : this.listeners)
            {
                l.update();
            }
        }
    }


La classe PopUp, invece, necessita di essere notificata circa i cambiamenti di stato del subject quindi dovrà implementare l'interfaccia mailListener al fine di comportarsi di conseguenza.

public class PopUp extends JDialog implements MailListener
{
    private int newMailCount;
    ..
    public PopUp()
    {
        newMailCount = 0;
        ..
        ..
    }    
    public void update()
    {
        label.setText(++newMailCount + " Nuove Mail Nella casella di posta");
        this.setVisible(true);        
    }     
}

Conclusione

Il pattern observer è stato implementato attraverso un esempio concreto e credo interessante.
Nel particolare caso la relazione instaurata tra il subject e l'observer è uno a uno, quindi si è creata una situazione chiamata callback.

L'esempio può comunque essere esteso a più observer, giusto per fare un esempio si potrebbe creare un altro observer che implementa MailListener al fine di inviare un sms(tramite apposito provider) ad un utente per avvisarlo sulla presenza di nuove mail nella casella di posta elettronica.