Visualizzazione post con etichetta jee. Mostra tutti i post
Visualizzazione post con etichetta jee. Mostra tutti i post

Un piccolo tutorial MVC

Il terzo capitolo di Head First - Servlets and JSP edito dalla O'Reilly, é dedicato alla creazione di una piccola web application usando il pattern MVC.

L'applicazione ha lo scopo di fornire suggerimenti su quale birra scegliere, usa:
  • BeerExpert.java (un POJO che fa da Model);
  • beerForm.html e result.jsp (View);
  • una servlet (Controller).
Le componenti suddette interagiscono tra loro in questo modo:
  • l'utente richiede l'accesso alla pagina web beerForm.html;
  • il container reperisce la risorsa;
  • il container rende disponibile beerForm.html all'utente;
  • l'utente specifica le informazioni richieste quindi genera una request per il container;
  • il container determina quale servlet richiamare e le passa la request;
  • la servler chiama BeerExpert per determinare la risposta;
  • la servlet aggiunge il risultato ottenuto da BeerExpert alla request;
  • la servlet inoltra le request a result.jsp;
  • la JSP legge la risposta dalla request;
  • la JSP genera una pagina che passa al container;
  • il container ritorna la pagina all'utente.
Per la creazione dell'ambiente di sviluppo utilizzo Netbeans, per il rilascio su Tomcat uso le funzionalità amministrative di Tomcat che permettono di fare il deploy di una web application (in formato WAR) con estrema semplicità.

Continuo a lavorare sullo stesso progetto creato nel capitolo precedente, HeadFirstWeb.

Iniziamo da beerForm.html, una pagina che contiene un form che richiama, via POST, la servlet SelectBeer.do, che creeremo in seguito. L'unico attributo che possiamo variare é il colore della birra, i cui possibili valori sono presentati in un menù a tendina:
<html>
<head>
<title>Beer selector</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<h1>Beer Selection Page</h1>
<form method="POST" action="SelectBeer.do">
Select beer color:
<select name="color">
<option value="light">light</option>
<option value="amber">amber</option>
<option value="brown">brown</option>
<option value="dark">dark</option>
</select>
<p><input type="SUBMIT"></p>
</form>
</body>
</html>
Facciamo il test la pagina, modificando leggermente il layout secondo il gusto personale. Evidentemente se clicchiamo sul bottone otteremo un messaggio di errore che ci dice che la risorsa che abbiamo richiesto non é disponibile.

Pensiamo ora alla servlet. Decidiamo di darle il nome interno BeerSelector, di mettere il file che la implementa nel package hf.beer con nome BeerSelect.java. L'URL utilizzato dall'utente per accederla é, come abbiamo già visto, SelectBeer.do.

Creo dunque la servlet hf.beer.BeerSelect.java e specifico i nomi come indicato sopra a Netbeans che mi aggiorna il DD, web.xml, in questo modo:
<web-app ...>
...
<servlet>
<servlet-name>BeerSelector</servlet-name>
<servlet-class>hf.beer.BeerSelect</servlet-class>
</servlet>
...
<servlet-mapping>
<servlet-name>BeerSelector</servlet-name>
<url-pattern>/SelectBeer.do</url-pattern>
</servlet-mapping>
</web-app>
Faccio un nuovo test del progetto, e verifico che ora cliccare sul bottone non mi dà un errore ma mi visualizza una pagina bianca. Questo perché l'ho lasciato creare la servlet da Netbeans che mi ha generato il codice di default che non fa nulla.

Dal nome logico al file del servlet:
  • Dalla pagina HTML il browser genera la richiesta di /HeadFirstWeb/SelectBeer.do dove la prima parte del path é la root della Web App e la parte finale é il nome logico della risorsa.
  • Il container cerca nel DD il nome logico della servlet tra gli url-pattern negli elementi servlet-mapping trovando il servlet-name (nome interno).
  • Andiamo a cercarci tra gli elementi servlet quello che ha il servlet-name che abbiamo trovato nel passo precedente.
  • E finalmente troviamo il servlet-class (nome fisico) che viene usato dal container. Se la servlet non é già stata inizializzata la classe viene caricata e la servlet inizializzata.
  • Il container inizia un nuovo thread per gestire la richiesta e passa l'oggetto request al thread.
  • Al termine dell'elaborazione da parte della servlet il container manda la risposta al cliente.
Prima versione della servlet

Modifico il codice di default proposto da Netbeans in questo modo:
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>Beer Selection Advice</title>");
out.println("</head>");
out.println("<body>");
out.println("<h1>Got beer color: " + request.getParameter("color") + "</h1>");
out.println("</body>");
out.println("</html>");
} finally {
out.close();
}
}
Il punto fondamentale é che si usa il metodo getParameter() sulla request per accedere il parametro color passato dall'utente, che viene scritto nella response.

Compilo, faccio il deploy, e adesso cliccando sul bottone possiamo avere un primo feedback positivo.

Architettura delle Web App

Il secondo capitolo di Head First - Servlets and JSP edito dalla O'Reilly, é dedicato ad una introduzione ad alto livello sull'architettura di una web application.

Container

Le servlet non hanno un main(), sono sotto il controllo di un'altra applicazione Java, detta Container. Un esempio di Container é Tomcat.

L'applicazione web server delega al container le richieste che arrivano dal client per una servlet. Il Container si fa carico di passare alla servlet la request e la response HTTP chiamando i metodi della servlet.

Il Container gestisce:
  • il supporto alle comunicazioni tra le servlet e il web server;
  • il ciclo di vita delle servlet;
  • il supporto multithreading, creando un nuovo thread per ogni richiesta per servlet che riceve;
  • la sicurezza dichiarativa, usando un descrittore XML per il deployment;
  • il supporto a JSP.
Come il Container gestisce una request:
  • crea un oggetto HttpServlerResponse e un HttpServlerRequest;
  • in base alla URL passata determina la servlet richiesta, crea o alloca un thread e passa gli oggetti creati al passo precedente al thread della servlet;
  • chiama il metodo service() della servlet che, a seconda del tipo della richiesta, invoca doGet() o doPost();
  • il metodo della servlet genera la pagina dinamica nell'oggetto response;
  • il Container riprende il controllo, termina il thread, converte l'oggetto response in una response HTTP e la manda indietro al client, infine distrugge gli oggetti request e response.
I tre nomi di una servlet

Una servlet é identificata da un URL, un nome interno, e un nome reale:
  • dal punto di vista del programmatore Java, una servlet é una classe e avrà quindi un path name determinato dal nome del package in cui si trova e dal nome della classe stessa (classes/registration/SignupServlet.class);
  • a livello di progettazione del sistema, alla servlet può essere associato un nome diverso, slegato dai dettagli implementativi (EnrollServlet);
  • per l'utente la servlet ha un URL (register/registerMe).
Lo scopo di questa organizzazione é avere una migliore flessibilità e sicurezza.

Il mapping tra questi nomi é fatto in un documento XML, detto DD (Deployment Descriptor), che contiene un elemento servlet e un servlet-mapping per ogni servlet definita nella web application.

MVC: Model View Controller

Il principio di MVC sta nella separazione della business logic dalla presentazione, mettendoci qualcosa in mezzo, in modo da garantire l'indipendenza delle parti. In questo modo le applicazioni sono più semplici da gestire e si tende a favorire il riutilizzo delle classi.
  • Model: gestisce lo stato dell'applicazione e la sua logica business, é l'unica parte del sistema che parla con il database. Implementato con classi Java normali.
  • Controller: prende l'input dalla request e lo valuta in funzione del Model a cui dice di cambiare in funzione dell'input; rende disponibile il nuovo stato alla View. Implementato dalla servlet.
  • View: responsabile della presentazione, prende l'input e lo gira al Controller, mostra l'output all'user. Implementato via JSP.
J2EE

Un Application Server J2EE include un Web Container (per servlet e JSP) e un EJB Container (per Enterprise JavaBean). In una architettura J2EE "completa" sono gli EJB a farsi carico di implementare la business logic.

Tomcat é un Web Container, non gestisce EJB.

Una semplice servlet

Continuo la lettura di Head First - Servlets and JSP edito dalla O'Reilly, finisco il primo capitolo affrontando il semplice esempio che lì trova.

Secondo gli autori sarebbe meglio non usare alcun IDE e scriversi tutto a mano. Potrebbero aver ragione, ma é una seccatura immane. Uso perciò Netbeans per semplificarmi la vita.

Creo un nuovo progetto, una Web Application nella categoria Java Web, che chiamo HeadFirstWeb. Come server uso Tomcat 6 e faccio riferimento a Java EE 5.

Nel progetto mi creo una nuova servlet, la cui classe ha nome s01, che metto nel package hf. La servlet ha nome ASimpleServlet e pattern URL /servlet01, confermiamo e andiamo a vedere il codice generato.

web.xml

Il file si trova nel folder WEB-INF e contiene queste informazioni:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<servlet>
<servlet-name>ASimpleServlet</servlet-name>
<servlet-class>hf.s01</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ASimpleServlet</servlet-name>
<url-pattern>/servlet01</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>30</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>

Ci interessano le sezioni relative alle servlet. Il primo blocco definisce la correlazione tra il nome della servlet, ASimpleServlet, e la sua classe Java di riferimento, hf.s01; nel blocco servlet-mapping vediamo la correlazione tra il nome della servlet e l'URL utilizzata per accederla.

In pratica il nome della servlet fa da tramite tra il nome della classe Java che la implementa e l'indirizzo con cui é possibile richiamarla.

s01.java

Il file si trova nella sezione Source Packages, nel package hf.

Modifichiamo solo leggermente il codice proposto. L'intestazione del file resta tale e quale:

package hf;

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;

La classe viene definita come una estensione di HttpServlet:

public class s01 extends HttpServlet {

Quello che cambiamo é il metodo processRequest() che viene utilizzato per processare sia POST che GET. In realtà a noi, a questo punto, interesserebbe solo considerare la GET, quindi potremmo semplificare il codice, eliminando la gestione per la POST e spostando il codice definito in processRequest() nel corpo di doGet(). Ma non ci pare un impresa particolarmente utile, e quindi non cambiamo questo codice:

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}

Nella processRequest() rendiamo attivo il codice che genera una response e facciamo in modo che venga visualizzato il timestamp corrente:

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 s01</title>");
out.println("</head>");
out.println("<body>");
out.println("<h1>Servlet @ " + new java.util.Date() + "</h1>");
out.println("</body>");
out.println("</html>");
} finally {
out.close();
}
}

Notiamo che settiamo il tipo di contenuto della response a testo html codificato in UTF-8, e che scriviamo il codice HTML nella response approfittando dell'oggetto PrintWriter che la response ci mette a disposizione.

Notiamo anche che la scrittura sulla response é racchiusa in un blocco try a cui corrisponde un finally in cui chiudiamo il writer. Così anche in caso di eccezioni durante la scrittura chiudiamo correttamente il writer.

Facciamo una build del progetto e otteniamo un .war (Web Archive) che contiene la nostra piccola web application. Ne facciamo un deploy per la nostra installazione Tomcat e possiamo vedere la nostra servlet in azione.

C'é una varietà di modi per eseguire l'installazione di un war su Tomcat. Di solito io faccio così:
  • startup di Tomcat (nel mio caso gli ho riservato la porta 8084)
  • accesso di Tomcat via browser (http://localhost:8084/)
  • accesso del Tomcat Web Application Manager (http://localhost:8084/manager/html). Nota che un utente Tomcat deve avere i privilegi di admin per poter accedere a questa pagina, vedi il file conf/tomcat-users.xml nell'installazione di Tomcat.
  • verso il fondo della pagina c'é la sezione Deploy, sottosezione WAR file to deploy. Basta selezionare il nostro file war, cliccare sul bottone Deploy e il gioco é fatto.


A questo punto per accedere la servlet dovrebbe bastare specificare questo URL:
http://localhost:8084/HeadFirstWeb/servlet01


Il difetto principale di questo approccio, ci fanno notare gli autori del libro, sta nella innaturalità dell'inserimento di codice HTML all'interno di codice Java. Generare una pagina complessa, e mantenerla, diventa un compito veramente spiacevole.

Fortunatamente si può fare il contrario, ovvero inserire codice Java all'interno di HTML, ricorrendo a JSP.

Torniamo al nostro progetto HeadFirstWeb, notiamo che Netbeans ha generato anche un index.jsp nella sezione WebPages. Andiamo a modificarla leggermente, per inserire il codice Java che ci interessa (la generazione del timestamp) e, già che ci siamo, un link alla servlet. Il corpo della pagina diventa questo:

<body>
<h1>Hello World!</h1>
JSP @ <%= new java.util.Date() %>
<br />
<a href="servlet01" target="_blank">Servlet</a>
</body>

Compiliamo, rimuoviamo il vecchio war da Tomcat e installiamo il nuovo e ora, specificando questa URL:
http://localhost:8084/HeadFirstWeb/

Dovremmo vedere la nostra pagina JSP, con link alla servlet che abbiamo creato in precedenza.

Head First - Servlets and JSP

Un buon libro introduttorio su questa parte di Java EE é Head First - Servlets and JSP edito dalla O'Reilly.

Lo leggo e scrivo qui qualche nota, a partire dal capitolo 1: perché usare servlets e JSP?

Il processo: un browser web permette all'utente di richiedere una risorsa (pagina html, immagine, suono, un documento pdf, ...); il server web riceve la richiesta, trova la risorsa e ritorna qualcosa all'utente.

Se non la trova l'utente ottiene l'errore 404 Not Found.

Il meccanismo richiesta (request) risposta (response) é gestito via HTTP per mezzo di metodi, i più comuni tra i quali sono GET e POST.
  • GET: semplice richiesta al server per una risorsa.
  • POST: é una richiesta a cui sono associati dei parametri.
In realtà é possibile mandare dati al server anche via GET ma non é solitamente una buona idea, dato che:
  • il numero di caratteri che si possono mandare é basso, e dipendente dal server;
  • i dati mandati via GET sono appesi all'URL, sono quindi visibili all'utente;
D'altro canto, se si vuole ripetere spesso una richiesta parametrizzata, per una GET si può creare un bookmark, mentre con la POST occorre rieseguire la pagina in cui sono settati i parametri.

Per una GET la richiesta é strutturata in questo modo:

Request line:
GET [path]?[param1]&[param2]& ... [paramN] HTTP/1.1

Request headers:
Host: ...

Per una POST, invece, le cose funzionano così:

Request line:
POST [path] HTTP/1.1

Request headers:
Host: ...
...
Accept: text/html, ...
...

Message body (o "payload"):
[param1]&[param2]& ... [paramN]

La risposta HTTP é strutturata così:

Response Headers:
HTTP/1.1 200 OK
Set-Cookie: ...
Content-Type: text/html
...

Response body:
<html>
...
</html>

Il Content-Type indica il tipo (MIME) che identifica la risorsa reperita. Vedi anche il campo Accept della request.

La richiesta di pagine web statiche é gestita direttamente dall'applicazione web server. Nel caso di richiesta di pagine dinamiche, si appoggia ad un altra applicazione sul server.

Le applicazioni classiche che si occupano della generazione di pagine dinamiche sono programmi CGI (Common Gateway Interface) scritti tipicamente in Perl, C, Python e PHP. Per Java si parla di servlet e JSP.

Tomcat: installazione

Apache Tomcat é un container per servlet e JSP. La versione 6 di Tomcat implementa le specificazioni Servlet 2.5 e JSP 2.1 e include funzionalità che lo rendono una utile piattaforma per lo sviluppo e la distribuzione di applicazioni web e servizi web.

Struttura delle directory

La radice dell'installazione é messa nella variabile di ambiente CATALINA_HOME, sotto questa directory troviamo:
  • bin: startup, shutdown e altri script
  • conf: file di configurazione
  • logs: destinazione di default per i file di log
  • webapps: qui si mettono le nostre applicazioni web
Dato che mi sono installato Tomcat su una macchina Windows Vista, mi sono scritto un file batch per preparare l'esecuzione dell'applicativo settando alcune variabili d'ambiente:
set PATH=%PATH%;C:\Program Files\Apache\Tomcat6.0.xx\bin;
set JAVA_HOME=C:\Program Files\Java\jdk1.6.0_xx
set CATALINA_HOME=C:\Program Files\Apache\Tomcat6.0.xx
set CATALINA_BASE=C:\dev\tomcat
Definisco CATALINA_BASE per non dover dare a Tomcat i privilegi di amministratore, che sarebbero necessari per accedere in read/write ai file dove i programmi sono installati. Questo mi obbliga però a copiare la struttura vista sopra (a parte bin) in questa altra directory.

I Web Service RESTful

Note dalla lettura di Beginning Java EE 6 Platform with GlassFish 3 di Antonio Goncalves. Capitolo 15 - I Web Service RESTful

REST sta per Representational State Transfer e indica uno stile architetturale basato su come funziona il web. Per supportare questo stile nello sviluppo di web service, Java mette a disposizione JAX-RS (Java API for RESTful Web Services).

In REST ogni informazione é una risorsa, e di fa a riferimento a queste risorse usando URI (Uniform Resource Identifier), tipicamente link nel web.

HTTP

Lo stile architetturale REST client-server é progettato per usare un protocollo di comunicazione stateless, tipicamente HTTP.

Il client manda un richiesta al server, aspettandosi una risposta. La risposta é costituita da un codice (p.es.: 200 OK); un certo numero di header, tra cui il content-type che tipicamente é txt/html, ma può anche essere application/xml o image/jpeg; e la rappresentazione.

I principale metodi HTTP sono:
  • GET: per leggere una rappresentazione della risorsa. Dovrebbe essere implementato in modo sicuro, ovvero non dovrebbe cambiare lo stato della risorsa, e idempotente, ovvero chiamare più volte get per la stessa risorsa dovrebbe lasciare la risorsa nello stesso stato.
  • POST: crea una nuova risorsa come subordinata ad una risorsa principale identificata dalla URI richiesta. Di conseguenza non é sicura, dato che la risorsa é aggiornata, né idempotente.
  • PUT: aggiorna lo stato di una risorsa memorizzata ad un dato URI. Non é sicura ma é idempotente.
  • DELETE: richiede l'eleminazione della risorsa. É quindi idempotente ma non sicura.
Altre azioni HTTP sono:
  • HEAD: simile a GET, ma non ritorna il corpo del messaggio.
  • TRACE: ritorna una eco della richiesta ricevuta.
  • OPTIONS: é una richiesta di informazioni sulle opzioni di comunicazione disponibili.
  • CONNECT: usata in congiunzione con un proxy per usare il protocollo HTTP come wrapper per altri protocolli di rete per gestire il tunnelling.
Tipi di contenuto

I principali tipi di media internet, quelli che erano orginalmente chiamati tipi MIME) utilizzati sono
  • text/html
  • text/plain: testo semplice, é il default.
  • image/gif, image/jpeg, image/png
  • text/xml, application/xml
  • application/json: JSON, che sta per JavaScript Object Notation, é un formato di testo usato per lo scambio di dati per linguaggi di programmazione.
Codici di stato
  • 1xx: informazionale
  • 2xx: successo; ad es. 200 OK
  • 3xx: ridirezione; ad es. 301 Moved Permanently
  • 4xx: errore lato client; ad es. 404 Not Found
  • 5xx: errore lato server; ad es. 500 Internal Server Error
L'implementazione di riferimento di JAX-RS é Jersey.

SOAP Web Services

Note dalla lettura di Beginning Java EE 6 Platform with GlassFish 3 di Antonio Goncalves. Capitolo 14: SOAP Web Services.

SOA (Service-Oriented Architecture) é una architettura che può essere implementata usando servizi web.

I servizi web sono detti debolmente accopiati (loosely coupled) perché il cliente non ha bisogno di conoscere i suoi dettagli implementativi. Il cliente, normalmente chiamato consumer, e il servizio scambiano dati usando documenti XML.

I servizi web forniscono un modo standard per connettere diversi prodotti standard.

In pratica, i web service sono un modo di mettere a disposizione la logica business per mezzo di una interfaccia al consumer. Differentemente da oggetti o EJB, i servizi web usano XML per implementare un accoppiamento debole. L'interfaccia a cui il messaggio viene mandato definisce il formato del messaggio in input e output, e il meccaniscmo per pubblicare e scoprire le interfacce al servizio web.

Il meccanismo opzionale di registrazione dei servizi web (UDDI) permette al cunsumer di trovare il servizio. Una volta trovato lo può contattare via HTTP mandandogli un messaggio XML.

I principali protocolli e tecnologie utilizzati dai servizi web sono:
  • UDDI: Universal Description Discovery and Integration. Una sorta di pagine gialle usate per categorizzare e memorizzare le interfacce ai servizi web.
  • WSDL: Web Services Description Language. Definisce l'interfaccia al servizio web, i tipi di messaggi e dati, interazioni e protocolli.
  • SOAP: Simple Object Access Protocol. Un protocollo basato su XML che definisce la busta (envelope) utilizzata per le comunicazioni del servizio web.
  • HTTP: Hypertext Transfer Protocol. É il protocollo di trasporto normalmente utilizzato per scambiare messaggi, ma é possibile usarne altri come SMTP, FTP o JMS.
  • XML: Extensible Markup Language: É il fondamento su cui sono costruiti i servizi web.
Il registry UDDI punta ad un file WSDL pubblicamente disponibile su internet che il potenziale consumer può scaricare. Si può pensare a WSDL come ad una sorta di interfaccia Java scritta in XML. SOAP definisce il modo in cui i messaggi sono spediti da un computer ad un altro.

JMS con Glassfish

Note dalla lettura di Beginning Java EE 6 Platform with GlassFish 3 di Antonio Goncalves. Capitolo 13: Sending Messages.

Con l'acronimo JMS, che sta per Java Message Service, intendiamo parlare delle specifiche Java per l'implementazione di un MOM (Message-oriented middleware).

MOM é un provider (detto anche broker) che permette uno scambio asincrono di messaggi tra sistemi eterogenei.

OpenMQ é l'implementazione di riferimento per JMS.

Chi manda un messaggio é detto producer, lo manda a una destinazione (destination) e chi riceve il messaggio é un consumer.

Ci sono due modelli architetturali per JMS, che implicano una differente destinazione.
  • Modello point-to-point (P2P): Il producer mette il messaggio in una coda (queue) che il consumer legge. Quando il consumer conferma la lettura (to acknowledge) il provider rimuove il messaggio dalla coda.
  • Modello publish-subscribe (pub-sub): Il producer pubblica il messaggio su di un topic, e tutti i sottoscrittori lo ricevono.
Le interfacce Java usate variano a seconda del modello utilizzato. Nell'elenco qui sotto in prima posizione é l'interfaccia generica, seguita da quella propria del modello point-to-point e quindi dal pub-sub.
  • Destination, Queue, Topic
  • ConnectionFactory, QueueConnectionFactory, TopicConnectionFactory
  • Connection, QueueConnection, TopicConnection
  • Session, QueueSession, TopicSession
  • MessageConsumer, QueueReceiver, TopicSubscriber
  • MessageProducer, QueueSender, TopicPublisher

Un'occhiata a Glassfish

Note dalla lettura di Beginning Java EE 6 Platform with GlassFish 3 di Antonio Goncalves.

Un'occhiata a Glassfish.

Per default, la porta admin é 4848, la HTTP 8080.

Per far partire il server il comando é: asadmin start-domain domain1

Per accedere la console dell'amministratore (se vale il default) si va sulla pagina http://localhost:4848

L'utente standard é admin - adminadmin

Per terminare la sessione: asadmin stop-domain

Nota che se c'é un solo dominio non é necessario specificarlo.

Proprietà del messaggio

Oltre ai campi standard definiti nell'header di un messaggio, é possibili definire dei campi custom, usando le funzionalità messe a disposizione da JMS per la gestione delle proprietà.

Il nome di una proprietà deve rispettare le regole definite per la sintassi di un selettore.
Una proprietà può assumere valori di tipo boolean, byte, short, int, long, float, double e String.

I valori di una proprietà per un messaggio sono fissati prima della sua spedizione. Per il cliente sono disponibili in sola lettura, il suo tentativo di modificarli risulta in una eccezione di tipo MessageNotWriteableException.

Per iterare la lettura delle proprietà di un messaggio si usi il metodo getPropertyNames() per ottenere un enumeratore dei nomi delle proprietà, e quindi si legga il valore associato per ognuno di essi.

E' possibile eliminare tutti i valori delle proprietà di un messaggio usando il metodo clearProperties().

Cercare di leggere il valore di una proprietà non definita per uno specifico messaggio fa ottenere null come risultato.

I campi dell'intestazione del messaggio

Gran parte dei campi dell'intestazione sono inizializzati dal metodo che lo manda:
  • JMSDestination
  • JMSDeliveryMode
  • JMSExpiration
  • JMSPriority
  • JMSMessageID
  • JMSTimestamp
Altri tre sono settati dal cliente:
  • JMSCorrelationID
  • JMSReplyTo
  • JMSType
E infine il provider setta
  • JMSRedelivered.
L'amministratore del sistema può configurare JMS per dare un valore specifico per JMSDeliveryMode, JMSExpiration e JMSPriority.
  • JMSDestination: destinazione a cui é stato mandato il messaggio.
  • JMSDeliveryMode: modo di spedizione secondo il quale é stato mandato il messaggio.
  • JMSMessageID: identifica univocamente ogni messaggio spedito da un provider
  • JMSTimestamp: é il momento in cui il messaggio é stato passato a un provider per essere spedito.
  • MSCorrelationID: può essere usato dal client per creare una relazione tra messaggi.
  • JMSReplyTo: la destinazione dove si dovrebbe mandare la risposta a un determinato messaggio.
  • JMSRedelivered: settato se il messaggio é stato mandato più volte, e quindi forse già recepito dal client.
  • JMSType: tipo del messaggio.
  • JMSExpiration: Settato a zero nel caso il messaggio sia sempre valido.
  • JMSPriority: priorità dal messaggio definita nell'intervallo [0..9], con 0 ad indicare il valore più basso.

Il background del modello del messaggio

Nella messaggistica enterprise i messaggi sono entità che consistono di una intestazione (header) e un corpo (body).

L'header contiene i campi che sono usati per il routing e l'identificazione del messaggio; il body contiene i dati che sono spediti.

Ogni sistema tende a implementare questo modello in un modo diverso, dove le maggiori differenze sono nel contenuto e nella semantica dei campi dell'intestazione.

Obiettivi del modello del messaggio JMS
  • Fornire una API unica che sia adatta alla creazione di messaggi che sia in linea con il formato usato da applicazioni esistenti non JMS
  • Supportare lo sviluppo di applicazioni eterogenee che operino su differenti macchine, sistemi operativi, linguaggi di programmazione.
  • Supportare l'uso di oggetti Java
  • Supportare XML
Messaggi JMS

I messaggi JMS sono composti da queste parti:
  • Header: i campi dell'header contengono valori usati dai clienti e dal provider per identificare e fare il routing dei messaggi.
  • Proprietà: oltre ai campi standard dell'header é possibile aggiungere campi aggiuntivi all'header.
    • Proprietà specifiche dell'applicazione
    • Proprietà standard
    • Proprietà specifiche del provider
  • Body: sono definiti da JMS svariati tipi di body che coprono la maggior parte dei tipi di messaggi correntemente in uso.

Request/Reply

Il campo JMSReplyTo specificato nell'header del messaggio permette di specificare al Destination dove debba essere mandata una risposta.

Il campo JMSCorrelationID della risposta può essere usato per dare un riferimento a quale fosse la richiesta originale. Parleremo meglio più avanti dei campi nell'header dei messaggi.

Inoltre JMS fornisce anche funzionalità per create code e topic temporanei che possono essere usati come destinazione univoca per le risposte.

JMS definisce alcune classi di base per la gestione di Request/Reply, implementazioni più specializzate sono lasciate alla cura del provider JMS e dei client.

Multithreading

Per ridurre la complessità del sistema JMS richiede che sia supportato l'accesso concorrente solo per quegli oggetti che viene naturale pensare vengano condivisi da client che operano in multithreading.

Gli oggetti JMS che supportano uso concorrente sono: ConnectionFactory, Connection e Destination.

Gli oggetti JMS progettati per essere acceduti sequenzialmente da diversi thread sono: Session, MessageProducer, MessageConsumer.

Per gli oggetti di tipo Session sono definite da JMS alcune specifiche regole per limitarne l'uso in condizioni di concorrenza. Ne parleremo in un post successivo, quando ne sapremo abbastanza di JMS per poterne parlare.

Ci sono due motivi per limitare l'accesso in concorrenza a oggetti Session.
  1. JMS supporta le transazioni via Session, ed é molto difficile implementare transazioni in condizioni di multithreading.
  2. Session supporta il consumo asincrono di messaggi. Implementare la gestione del consumo asincrono di messaggi in ambiente multithreading sarebbe estremamente complesso sia per il server JMS che per il client.
Imporre che la Session non sia multithreading ne semplifica l'uso per il client tipico. Un client più evoluto può ottenere il livello di concorrenza desiderato usando più sessioni.

Sviluppare applicazioni JMS

In generale una applicazione JMS consiste di uno o più client JMS che scambiano messaggi. L'applicazione può includere anche client non-JMS che facciano uso della API nativa del provider JMS invece che usare JMS.

Una applicazione JMS può essere progettata e rilasciata come un'unità ma spesso i client JMS sono aggiunti incrementalmente a una applicazione esistente.

La definizione del messaggio usato da una applicazione può venire sia dalla parte JMS dell'applicazione sia dalla non-JMS.

Sviluppo di un client JMS

Un tipico client JMS esegue la seguente procedura di setup JMS:
  • trova un oggetto ConnectionFactory via JNDI;
  • trova uno o più oggetti Destination via JNDI;
  • usa la ConnectionFactory per creare una JMS Connection con la consegna dei messaggi non abilitata;
  • usa la Connection per creare una o più JMS Session;
  • usa la Session e le Destination per creare i MessageProducer e i MessageConsumer necessari;
  • far partire la consegna dei messaggi sulla Connection.
A questo punto il client é pronto per produrre e consumare messaggi.

Due stili per i messaggi

Una applicazione JMS può usare due modalità diverse per scambiare messaggi: la point-to-point (PTP) o la publisher/subscriber (Pub/Sub).

Naturalmente é possibile usare entrambe le modalità nella stessa applicazione, basta far riferimento a destinazioni diverse.

Nell'ambito JMS, la destinazione PTP é chiamata Queue (coda) e quella Pub/Sub Topic.

In JMS é possibile usare sia interfacce generiche sia specializzate per gestire i differenti elementi del modello.

Interfacce comuniPTPPub/Sub
ConnectionFactoryQueueConnectionFactoryTopicConnectionFactory
ConnectionQueueConnectionTopicConnection
DestinationQueueTopic
SessionQueueSessionTopicSession
MessageProducerQueueSenderTopicPublisher
MessageConsumerQueueReceiver / QueueBrowserTopicSubscriber

Come consigliano le buone pratiche di progettazione Object-Oriented, bisognerebbe cercare di lavorare con le astrazioni più generiche, e quindi con le interfacce comuni, per permettere una maggiore indipendenza del codice scritto dagli oggetti sottostanti.

Ecco una breve descrizione delle interfacce:

  • ConnectionFactory: un oggetto amministrato usato dal client per creare una connessione;
  • Connection: una connessione a JMS;
  • Destination: un oggetto amministrato che incapsula l'identità della destinazione di un messaggio;
  • Session: un contesto single-threaded usato per mandare e ricevere messaggi;
  • MessageProducer: un oggetto creato da una sessione che viene usato per mandare messaggi a una destinazione;
  • MessageConsumer: un oggetto creato da una sessione che viene usato per ricevere messaggi da una destinazione.
Il flusso di esecuzione segue solitamente uno schema come questo:

La ConnectionFactory crea una Connection, che crea una Session, che crea un Message.

La Session crea anche un MessageProducer che manda un Message a una Destination, e crea pure un MessageConsumer che riceva un Message da una Destination.

Si dice che una applicazione JMS consuma un messaggio quando un cliente manda una ricevuta di accettazione per un messaggio. Si dice che produce un messaggio quando il cliente manda un messaggio ad una destinazione JMS.

Amministrazione

Ci sono due tipi di oggetti amministrati:
  • ConnectionFactory: usato da un client per creare una connessione ad un provider;
  • Destination: usato da un client per specificare la destinazione di una messaggio che sta mandando e l'origine di un messaggio che riceve.
Gli oggetti amministrati sono messi in un namespace JNDI da un amministratore. Un client JMS dichiara nella sua documentazione gli oggetti amministrati JMS che richiede e come i nomi JNDI di questi oggetti gli devono essere messi a disposizione.

Cos'é una applicazione JMS?

Il documento "Java Message Server Specification", un pdf di 140 pagine, é disponibile sul sito Sun.

Una applicazione JMS é composta da quattro parti:
  • Client JMS: i programmi Java che mandano e ricevono messaggi.
  • Messaggi: ogni applicazione definisce un insieme di messaggi che sono usati per comunicare informazioni tra i suoi client.
  • Provider JMS: un sistema di messaggistica che implementa JMS in aggiunta alle altre funzionalità amministrative e di controllo richieste da un prodotto per la gestione della messaggistica.
  • Oggetti amministrati: sono oggetti JMS preconfigurati creati dall'amministratore per essere utilizzati dai client.

Una sottoscrizione durable

Altro esempio tratto dalla sezione "Robust JMS Applications" del tutorial Sun su Java EE, parte dedicata a JMS é il Durable Subscriber che trattiamo in questa serie di post.

Il punto di questo esempio é che vogliamo che il subscriber possa disconnettersi da JMS senza perdere messaggi a lui indirizzati. Usiamo perciò una connessione durable, in modo da poter creare un TopicSubscriber durable.

La gestione di basso livello per JMS, con la creazione del contesto iniziale, delle connection factory e del topic é la medesima che abbiamo già visto in precedenza, inglobata nella classe JmsConnectionManager, perciò non ne ripetiamo qui i dettagli.

MultiplePublisher

Il publisher é piuttosto semplice, usa la connessione non durable, su cui crea una sessione AUTO_ACKNOWLEDGE, il producer é un MessageProducer costruito sulla session specificando il topic che abbiamo definito precedentemente via Admin Console di GlassFish.

In pratica ci si limita a mandare un certo numero di messaggi sul topic ogni volta che viene invocato il metodo publishMessages(); ecco qui a seguire il codice della classe:

package durable;

import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;

public class MultiplePublisher {
private Connection connection = null;
private Session session = null;
private MessageProducer producer;
private JmsConnectionManager cm = JmsConnectionManager.getInstance();

private int msgBaseIndex = 0;

public MultiplePublisher() throws JMSException {
connection = cm.connFact.createConnection();
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

producer = session.createProducer(cm.topic);
}

public void publishMessages() {
final int NUMMSGS = 3;
final String MSG_TEXT = "This is message ";

try {
TextMessage message = session.createTextMessage();

for(int i = 1; i <= NUMMSGS; ++i) {
message.setText(MSG_TEXT + (msgBaseIndex + i));
System.out.println("Publishing message: " + message.getText());
producer.send(message);
}
msgBaseIndex += NUMMSGS;

// non-text message as end of messages
producer.send(session.createMessage());
}
catch(JMSException e) {
System.err.println("Exception in publishMessages: " + e.toString());
}
}

public void finish() {
if(connection != null) {
try {
connection.close();
}
catch(JMSException e) {}
}
}
}

DurableSubscriber

Il subscriber di questo esempio é simile al subscriber dell'esempio precedente, e ricicliamo per il suo uso le classi TextListener e Monitor, che mettono a disposizione rispettivamente il metodo onMessage() che gestisce i messaggi come indirizzati dal topic e il meccanismo per sincronizzare DurableSubscriber con il listener.

Il suo costruttore crea una connessione durable, e da questa una sessione di tipo AUTO_ACKNOWLEDGE.

La ricezione di messaggi é gestita da due metodi, startSubscriber() e closeSubscriber().

La startSubscriber() crea un durable subscriber per il topic usato dal publisher, crea un listener, di tipo TextListener - che abbiamo già trattato nel post precedente, e lo associa al subscriber. Quindi facciamo partire la connessione.

La closeSubscriber() aspetta che il listener abbia completato la ricezione dei messaggi, quindi chiude il subscriber e ferma la connessione.

Vediamo il codice della classe:

package durable;

import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.Session;
import javax.jms.TopicSubscriber;

public class DurableSubscriber {
private Connection connection = null;
private Session session = null;
private TextListener listener = null;
private TopicSubscriber subscriber = null;

private JmsConnectionManager cm = JmsConnectionManager.getInstance();

public DurableSubscriber() throws JMSException {
connection = cm.drbConnFact.createConnection();
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
}

public void startSubscriber() throws JMSException {
System.out.println("Starting subscriber");

subscriber = session.createDurableSubscriber(cm.topic, "MakeItLast");
listener = new TextListener();
subscriber.setMessageListener(listener);
connection.start();
}

public void closeSubscriber() throws JMSException {
listener.waitTillDone();

System.out.println("Closing subscriber");
subscriber.close();
connection.stop();
}

public void finish() {
if (connection != null) {
System.out.println("Unsubscribing from durable subscription");
try {
session.unsubscribe("MakeItLast");
connection.close();
}
catch(JMSException je) {
System.out.println(je.getMessage());
}
}
}
}

Main

Il main fa partire e poi chiude due volte il subscriber. Se non fosse durable i messaggi mandati dal publisher quando il subscriber é giù andrebbero persi. Eseguendo il codice vediamo che, come ci aspettiamo, i messaggi restano sul topic in attesa che il subscriber venga fatto ripartire.

public static void main(String[] args) {
DurableSubscriber subscriber = null;
MultiplePublisher publisher = null;

try {
subscriber = new DurableSubscriber();
publisher = new MultiplePublisher();

subscriber.startSubscriber();
publisher.publishMessages();
subscriber.closeSubscriber();

publisher.publishMessages();

subscriber.startSubscriber();
subscriber.closeSubscriber();
}
catch(JMSException je) {
System.out.println("Exception: " + je.getMessage());
}
finally {
if(subscriber != null)
subscriber.finish();
if(publisher != null)
publisher.finish();
}

System.exit(0);
}

Sincronizzarsi su di una coda: Subscriber/2

Ultimo passo nel completare l'esempio che abbiamo cominciato a vedere un paio di post fa, sullo scambio di messaggi tra un publisher e un subscriber per mezzo di un topic, usando una coda per sincronizzarsi.

Ci resta da vedere come implementare il listener che deve effettivamente leggere e gestire i messaggi che arrivano dal publisher per conto del subscriber.

TextListener

Abbiamo visto che il metodo receive() del nostro subscriver delega a questa classe la lettura dei messaggi, e resta in attesa, chiamando il suo metodo waitTillDone(), che l'operazione venga completata. Vediamo nel dettaglio come ciò avviene:

package jmsrobust;

import javax.jms.JMSException;
import javax.jms.MessageListener;
import javax.jms.Message;
import javax.jms.TextMessage;

public class TextListener implements MessageListener {
private Monitor monitor = new Monitor();

public void waitTillDone() {
monitor.waitTillDone();
}

public void onMessage(Message message) {
if(message instanceof TextMessage) {
TextMessage msg = (TextMessage)message;

try {
System.out.println("Processing message: " + msg.getText());
}
catch (JMSException je) {
System.err.println("Exception in onMessage(): " + je.toString());
}
}
else {
// non-text message == end of the message stream
monitor.allDone();
}
}
}

Sembra tutto molto semplice, e in effetti lo é. Il metodo onMessage(), dichiarato nell'interfaccia MessageListener che la nostra classe implementa, processa i messaggi che ci vengono passati da JMS - ricordiamo che questo listener viene associato dal metodo receive() del nostro subscriber al topic usato come canale si comunicazione - finché non si vede un messaggio non testuale che abbiamo stabilito essere la convenzione che ci indica il termine delle trasmissioni. Segnaliamo questo al monitor che usiamo allo scopo di sincronizzare il TextListener con chi lo possiede.

Vediamoci dunque come implementiamo il monitor:

package jmsrobust;

public class Monitor {
private boolean done = false;

public synchronized void waitTillDone() {
while(!done) {
System.out.println("Waiting for notification");
try {
this.wait();
}
catch (InterruptedException ie) {
System.out.println("Wait interrupted");
}
}
System.out.println("Notification received");
}

public synchronized void allDone() {
done = true;
System.out.println("allDone notification");
this.notify();
}
}

In realtà mi sembra un po' sovrabbondante definire i metodi come sincronizzati, dato che non vengono chiamati concorrentemente, ma potrebbe essere utile in una situazione più complessa. Per il momento manteniamo il codice così, in attesa di valutare meglio i pro e i contro della decisione.

Per il resto, il codice dovrebbe essere abbastanza intuitivo. Con waitTillDone() mettiamo il thread che lo chiama in attesa. E questa attesa viene risolta con una chiamata a allDone() che cambia lo stato della variabile di controllo e notifica all'oggetto stesso che ha cambiato stato.

Test

Per testare la fuzionalità usiamo questo metodo:

private static void pubSub() {
System.out.println("Start multi publisher example");
Thread subscriber = new AsynchSubscriber();
Thread publisher = new MultiplePublisher();

try {
subscriber.start();
publisher.start();

subscriber.join();
publisher.join();
}
catch (InterruptedException e) {
e.printStackTrace(System.out);
}

System.out.println("Multi publisher example completed");
}