JBoss e Web Services REST? Ci pensa RESTEasy!

Per scrivere Servizi Web RESTful in Java esistono già diversi strumenti che permettono di facilitare la realizzazione sia della parte producer che di quella consumer, come Jersey, RESTEasy o Restlet. Tutti implementano la specifica JAX-RS, che estendono con API che facilitano la realizzazione della parte client per esempio, non coperte da specifica. Inoltre, il primo è integrato nativamente da Glassfish 3, mentre il secondo da JBoss 6, anche se possono essere tranquillamente usati in qualsiasi Servlet Container, come Tomcat.

Vediamo in JBoss 6 come è facile scrivere un servizio REST!

REST Service Producer

Per simmetria, riproponiamo lo stesso codice discusso nel post dedicato ai servizi SOAP con JBossWS e vediamo quali sono le annotazioni necessarie per ottenere lo stesso servizio in REST con RESTEasy, sia che si tratti del modulo Web o EJB.

Servizi nel modulo Web

Riproponiamo il POJO del post precedente con le nuove annotazioni JAX-RS:

@Path(value="/service")
public class MyRESTService {
   
   @GET
   @Path(value="/echo/{message}")
   public String echo(@PathParam(value="message") String message) {
      return "Echo " + message;
   }
   
   @POST
   @Path(value="/sort")
   @Consumes(value="application/json")
   @Produces(value="application/xml")
   public Output sort(Input input) {
      Arrays.sort(input.getVector());
      return new Output(input.getVector());
   }
}

Questo esempio contiene le annotazioni più importanti:

@Path
E’ l’annotazione principale che associa i metodi della classe ai path. E’ possibile usarla sia a livello di classe che di metodo: nel primo caso crea una sorta di namespace per le annotazioni su metodi. I path possono essere definiti anche parametrici inserendo il nome del parametro tra parentesi graffe {param}.
@PathParam
Usato insieme ai path parametrici, associa un parametro ad una variabile di ingresso del metodo.
@GET, @POST
Associa i verbi HTTP ai metodi della classe. Altri valori ammessi sono @DELETE o @PUT.
@Consumes, @Produces
Definisce il MIME-TYPE dei valori che arrivano al server e delle risposte prodotte. Nel caso del servizio di “echo“, non viene specificato nessun tipo: essendo una stringa, RESTEasy assume automaticamente che sia text/html.

Una volta annotata la classe MyRESTService con queste annotazioni e Input e Output con le annotazioni JAXB (come visto nel post precedente), basta avviare il server e provare per esempio a chiamare il metodo di “echo” da browser:

http://localhost:8080/JBossWebServiceProducerWeb/service/echo/sayonara

Se tutto va bene il server ci risponderà

Echo sayonara

Servizi nel modulo EJB

Come JBossWS per SOAP, anche RESTEasy permette di esporre un EJB come servizio web RESTful. Tutto quello che dobbiamo fare è annotare l’interfaccia di un EJB come prima:

@Local
@Path("ejb/service")
public interface MyRESTServiceEJBLocal {
   
   @GET
   @Path(value="/echo/{message}")
   public String echo(@PathParam(value="message") String message);
   
   @POST
   @Path(value="/sort")
   @Consumes(value="application/json")
   @Produces(value="application/xml")
   public Output sort(Input input);

}

da notare quindi che un EJB di tipo @LocalBean (cioè quelli senza interfaccia, permessi dalla specifica EJB 3.1) non è al momento (JBoss 6.1) supportato. Una piccola configurazione da inserire nel web.xml:

<context-param>
   <param-name>resteasy.jndi.resources</param-name>
   <param-value>JBossWebServiceProducer/MyRESTServiceEJB/local</param-value>
</context-param>

e avviamo il server! La versione attuale di RESTEasy infatti obbliga a registrare il nome JNDI dell’EJB come context-param: pare che nelle prossime versioni questo non sarà più necessario. Speriamo, altrimenti siamo costretti ad avere per forza un progetto Web nonostante il servizio sia a livello EJB!

REST Service Consumer

Fin qua eravamo coperti dalla specifica JAX-RS: lato client se abbiamo bisogno di consumare il servizio lato Java, siamo alla mercé dei vendor! Una prima differenza che si nota tra Jersey e RESTEasy in JBoss 6.1 è che il secondo non supporta la generazione del file WADL (Web Application Description Language), ovvero il corrispettivo del WSDL per REST. Con Jersey infatti, è possibile avere la descrizione base dell’interfaccia di un servizio REST chiamando in GET un indirizzo del tipo

http://path.to.your/restapp/application.wadl

Il file che otteniamo può essere dato in pasto ad un generatore di codice come wadl2java per ottenere le classi necessarie a consumare il servizio. Nel caso di RESTEasy invece, al momento la generazione del WADL non è supportata. Comunque poco male: se siamo proprietari del codice del producer, possiamo creare una interfaccia con le stesse annotazioni applicate nella parte server!! Vediamo per esempio:

@Path("ejb/service")
public interface MyRESTService {
   
   @GET
   @Path(value="/echo/{message}")
   public String echo(@PathParam(value="message") String message);
   
   @POST
   @Path(value="/sort")
   @Consumes(value="application/json")
   @Produces(value="application/xml")
   public Output sort(Input input);

}

che è identica (o quasi) alle classi viste precedentemente. Con un po’ di accortezza, possiamo quindi progettare un’interfaccia da poter usare sia nella parte server che nella parte client! Come si fa però ad invocare il servizio? Chi dà l’implementazione concreta a questa interfaccia che consuma il servizio? Per prima cosa è necessario registrare RESTEasy una volta all’avvio dell’applicazione (in un ServletContextListener o in un EJB Sigleton annotato con @Startup) con il seguente codice:

RegisterBuiltin.register(ResteasyProviderFactory.getInstance());

Dopodiché, è possibile usare il servizio semplicemente creando un proxy della nostra interfaccia:

MyRESTService restService = ProxyFactory.create(MyRESTService.class, "http://localhost:8080/JBossWebServiceProducerWeb");

Il proxy di RESTEasy creerà le chiamate HTTP opportune attraverso la libreria Apache HTTPClient, che JBoss promette di aver già integrata nel server. Purtroppo, al primo test del consumer scopriamo che non è così!! 🙁 Ci attende in console

java.lang.NoClassDefFoundError: org/apache/commons/httpclient/HttpMethod

Che fare? Non ci resta che scomodare gli HTTP Components di Apache: purtroppo sembra che dalla versione 4 siano cambiati diversi package, che ovviamente non sono compatibili con la versione di RESTEasy che stiamo usando. Riesumiamo quindi la versione 3.0.1 e proseguiamo: carichiamola sul server tra le librerie (sotto common/lib o la lib del proprio profilo non fa differenza), riavviamo il server e riproviamo il servizio: godiamoci il nostro primo consumer REST!

REST consumer e Javascript

Dato che l’architettura REST è basata su HTTP, se abbiamo a che fare con servizi che interagiscono solo per GET o POST, possiamo creare un semplice consumer con un po’ di HTML e Javascript. Creiamo per esempio una semplice pagina web con un campo di input (con id="numbers" per esempio) nel quale inseriremo una serie di numeri che vogliamo ordinare con il nostro servizio, separati da virgola. Con jQuery (o quello che preferite) basta creare una funzione che invochi il servizio al click di un bottone (con id="submit" per esempio), passando come parametri json i valori inseriti dall’utente

$(document).ready(function() {
	$('#submit').click(function() {
		var string = $('#numbers').val();
		if (string.indexOf(',') != -1) {
			$.ajax({
				url: 'http://localhost:8080/JBossWebServiceProducerWeb/service/sort',
				contentType: 'application/json',
				data: '{"vector" : [' + string + ']}',
				type: 'POST',
				success: function(data, textStatus, jqXHR) {
					alert(data.sortedVector.toString());
				},
				error: function(jqXHR, textStatus, errorThrown){
					alert(errorThrown);
				}
			});
		} else {
			alert('Bad format! Must be x,y,z');
		}
	});
});

Semplice no?

Conclusioni

Alla fine dei conti: REST o SOAP? I puristi già direbbero: “la domanda corretta è: HTTP o SOAP?”. Apparte la semantica, la scelta dipende dai punti di vista e da quello che vogliamo fare. Per lo sviluppatore, il supporto dato da Eclipse per i servizi SOAP è un grande vantaggio: con pochi click si riesce a creare un servizio web, con una interfaccia ben definita dal WSDL. Ma questo non deve essere un buon motivo per decidere che strada prendere! Lato REST d’altro canto, questa comodità è vista come una rigidità, perché mina l’evoluzione stessa del servizio (anche WADL non è visto bene infatti…).

A mio avviso sono entrambe delle tecnologie molto valide, che permettono l’interoperabilità tra sistemi diversi, senza bisogno di middleware proprietari come DCOM, RMI o CORBA, come usava negli anni ’90.
La domanda vera quindi è: quando scelgo uno o l’altro? Dipende dal target del servizio che stiamo realizzando. Abbiamo bisogno di creare un servizio che esponga funzionalità ben precise, consultabili one-shot come per esempio il classico convertitore di gradi Celsius/Fahrenheit? Non è importante che il modello di Business sia fortemente accoppiato e visibile dall’interfaccia XML del servizio? Allora SOAP va benissimo. In una architettura basata su SOAP infatti, l’interfaccia del servizio (WSDL) è fortemente accoppiata con la logica di business: basti pensare che i nomi dei metodi del servizio coincidono con quelli delle classi Java, come per le eccezioni lanciate per gestire gli errori. In ambito puramente web può essere un problema, ma in un contesto Enterprise ritengo sia altamente accettabile.
Se invece stiamo pensado ad un tipo di servizio più dinamico, che, a partire da un unico entry-point, è possibile navigare, allora il “muro” proposto da WSDL (e anche WADL) non è ciò che cerchiamo. Una comunicazione tra client e server basata su URI è ciò che ci vuole. Pensiamo ad una applicazione mobile che interagisce con un server: da esso riceverà i dati (probabilmente in formato json per risparmiare traffico) e gli URI per avanzare nell’interazione. L’architettura REST sembra pensata proprio per questo tipo di scenario: il protocollo HTTP è abbastanza espressivo da coprire tutte le esigenze di interazione tra le due parti.

Andrea Como

Sono un software engineer focalizzato nella progettazione e sviluppo di applicazioni web in Java. Presso OmniaGroup ricopro il ruolo di Tech Leader sulle tecnologie legate alla piattaforma Java EE 5 (come WebSphere 7.0, EJB3, JPA 1 (EclipseLink), JSF 1.2 (Mojarra) e RichFaces 3) e Java EE 6 con JBoss AS 7, in particolare di CDI, JAX-RS, nonché di EJB 3.1, JPA2, JSF2 e RichFaces 4. Al momento mi occupo di ECM, in particolar modo sulla customizzazione di Alfresco 4 e sulla sua installazione con tecnologie da devops come Vagrant e Chef. In passato ho lavorato con la piattaforma alternativa alla enterprise per lo sviluppo web: Java SE 6, Tomcat 6, Hibernate 3 e Spring 2.5. Nei ritagli di tempo sviluppo siti web in PHP e ASP. Per maggiori informazioni consulta il mio curriculum pubblico. Follow me on Twitter - LinkedIn profile - Google+