Webservice RESTful con Jersey

In questo tutorial vi mostrerò, con l’aiuto di un semplice esempio, quanto sia immediato realizzare WebService con Jersey. Con altre tecnologie, che richiedono settaggi complessi, spesso si ricorre a generatori automatici che complicano il codice e lo rendono illeggibile. In questo post, simulando l’interazione con una semplice anagrafica di persone, dimostreremo che con Jersey bastano poche annotazioni per costruire un WebService. Prima di passare all’esempio ripassiamo qualche semplice concetto di base.

Introduzione all’architettura REST

I WebService consentono l’interazione tra applicazioni sviluppate con linguaggi di programmazione differenti e che risiedono su sistemi operativi eterogenei. Attualmente esistono due approcci: SOAP (Simple Object Access Protocol) e REST (REpresentational State Transfer).

I sistemi che seguono i principi REST, spesso definiti anche “RESTful“, sono caratterizzati dalla maggiore leggerezza e facilità di implementazione del codice rispetto a SOAP.
REST mappa le tipiche operazioni CRUD (creazione, lettura, aggiornamento, eliminazione) sui metodi HTTP:

  • POST – crea un nuova risorsa,
  • GET – ottiene una risorsa esistente,
  • PUT – aggiorna una risorsa,
  • DELETE – elimina una risorsa.

La rappresentazione dei dati è fornita nei formati: testo, XML, XHTML e JSON.

Il nostro esempio

Per questo esempio introduttivo utilizzeremo Jersey, un framework open source che implementa JAX-RS, API annotation-based grazie a cui è veramente semplice creare WebService di tipo RESTFul.

Per facilitarci il lavoro utilizzeremo Apache Maven. Iniziamo definendo un nuovo progetto con archetype “maven-archetype-webapp” che chiameremo “testJersey” (per chi non avesse familiarità con la costruzione di progetti Maven su Eclipse consiglio ) e con le seguenti caratteristiche:

Group Id: it
Artifact Id: testJersey
Version: 0.0.1-SNAPSHOT
Packaging: war

Nel file pom.xml inseriamo le dipendenze richieste per creare un progetto con Jersey:

    
      asm
      asm-all
      3.1
    
    
      com.sun.jersey
      jersey-json
      1.13
    
    
      com.sun.jersey
      jersey-server
      1.13
    
    
      com.sun.jersey
      jersey-servlet
      1.13
    
    
      javax.ws.rs
      jsr311-api
      1.1.1
    
    
      javax.servlet
      javax.servlet-api
      3.0.1
    

Per attivare Jersey è necessario modificare il file web.xml aggiungendo il parametro com.sun.jersey.config.property.package che definisce in quale pacchetto Jersey cercherà le classi del WebService.

  
    UsersDataService
    com.sun.jersey.spi.container.servlet.ServletContainer
    
      com.sun.jersey.config.property.packages
      it.test.rest.webservice
    
    1
  

Mentre nel parametro url-pattern definiamo la porzione di base dell’URL, nel nostro caso useremo il percorso “rest” per esporre il servizio.

  
    UsersDataService
    /rest/*
  

Passiamo ora alla definizione dei moduli applicativi. All’interno della cartella src.main.java definiamo il pacchetto it.test.rest.pojo nel quale inseriamo la classe POJO Persona che contiene alcuni dati per gestire una semplice anagrafica di persone:

import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;

@XmlRootElement
@XmlType(propOrder = { "id", "nome", "cognome", "eta", "telefono", "email"  })
public class Persona {

        private int id;
        private String nome;
        private String cognome;
        private int eta;
        private String telefono;        
        private String email;
        public Persona() {}
        
        public Persona(Integer Id, String Nome, String Cognome, Integer Eta, String Telefono, String Email) {
                this.id = Id;
                this.nome = Nome;
                this.cognome = Cognome;
                this.eta = Eta;
                this.telefono = Telefono;               
                this.email = Email;     
        }

        // i getter e setter non sono riportati

All’interno della classe Persona sono presenti annotazioni secondo lo standard JAXB.

JAXB (Java Architecture for XML Binding) è uno standard che definisce come gli oggetti Java vengono convertiti da e verso XML e JSON grazie all’uso delle annotazioni.

Nello specifico le annotazioni che abbiamo usato sono:

  • @XmlRootElement: esegue automaticamente il bind della classe nei formati XML o JSON,
  • @XmlType(propOrder = {}): definisce l’ordine di visualizzazione dei campi.

Per simulare l’accesso ai dati di un database utilizzeremo la classe PersonaDao, su questo aspetto non ci soffermeremo molto per non perdere il focus sull’argomento.

Definiamo un pacchetto it.test.rest.dao nel quale creiamo la classe PersonaDao, che contiene 2 metodi:

  • il primo carica una lista di oggetti persona da esporre nelle chiamate GET del servizio,
  • il secondo simula il caricamento di un oggetto Persona a fronte di una chiamata POST.
package it.test.rest.pojo;


package it.test.rest.dao;
import it.test.rest.pojo.Persona;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class PersonaDAO {
        public List PersonaDAO() {
                List personaList = new ArrayList();
                Persona persona = new Persona(1, "Mauro", "Rossi", 40, "02/456327819", "");
                personaList.add(persona);
                persona = new Persona(2, "Luca", "Bianchi", 45, "02/918273645", "");
                personaList.add(persona);
                persona = new Persona(3, "Franco", "Neri", 32, "02/987123546", "");
                personaList.add(persona); 
                persona = new Persona(4, "Paolo", "Verdi", 35, "02/129834765", "");
                personaList.add(persona);       
                return personaList;
        }
        public void addPersona(Persona persona) {
                // Eseguo l'inserimento nel Database
                System.out.println("Dettagli persona inserita: " + persona.getId() + "; " + persona.getNome()  + "; " + persona.getCognome()  + "; " + persona.getEta()  + "; " + persona.getTelefono()  + "; " +  persona.getEmail());
        }
}

A questo punto creiamo il WebService che consiste in una semplice classe con metodi annotati con @GET, @POST, @PUT e @DELETE già visti in precedenza.

Ogni metodo rappresenta un’interfaccia per l’elaborazione richiesta. I primi due metodi restituiscono la lista delle persone in formato testo (utilizzata per chiamate da browser) o XML/JSON (per chiamate applicative). Il terzo metodo calcola l’età media ed espone all’esterno il risultato in formato stringa.

L’ultimo metodo esegue il salvataggio di nuovi record a fronte di una chiamata POST attraverso un form di inserimento dati. L’annotazione @Path indica la URI a cui la risorsa sarà raggiungibile, e può essere posta a livello di metodo o della dichiarazione della classe. L’annotazione @Produces specifica uno o più MIME-Type con cui la risorsa può essere trasferita al client che ne fa richiesta. Questo ci permette di definire il tipo di chiamata da effettuare.

package it.test.rest.webservice;

import it.test.rest.dao.PersonaDAO;
import it.test.rest.pojo.Persona;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.Consumes;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.UriInfo;


@Path("/persona")
public class PersDataService {

        / /  Restituzione lista degli persone in formato testo.  
        @GET
        @Produces(MediaType.TEXT_XML)
        public List getPeopleBrowser() {
                List people = new ArrayList();
                PersonaDAO PersDao = new PersonaDAO(); 
                people.addAll(PersDao.PersonaDAO());
                return people; 
        }
        // restituzione lista in formato XML/JSON per chiamate applicative. 
        @GET
        @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
        public List getPeople() {
                List people = new ArrayList();
                PersonaDAO PersDao = new PersonaDAO(); 
                people.addAll(PersDao.PersonaDAO());
                return people; 
        }
                
        // Questo metodo calcola ed espone l’età media delle persone     
        @GET
        @Path("etamedia")
        @Produces(MediaType.TEXT_PLAIN)
        public String getEta() {
                List people = new ArrayList();
                PersonaDAO perdao = new PersonaDAO();
                people.addAll(perdao.PersonaDAO());
                int toteta = 0;
                for (Persona pers : people) {
                        toteta += pers.getEta();
                }
                int etamedia = toteta / people.size();
                return Integer.toString(etamedia);
        }
        
        // Metodo di salvataggio di nuovi record a fronte di una chiamata POST
        @POST
        @Produces(MediaType.TEXT_HTML)
        @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
        public void nuovaPersona(
                        @FormParam("id") String id,
                        @FormParam("nome") String nome,
                        @FormParam("cognome") String cognome,
                        @FormParam("eta") String eta,
                        @FormParam("telefono") String telefono,
                        @FormParam("email") String email,
                        @Context HttpServletResponse servletResponse
        ) throws IOException {
                Persona persona = new Persona(new Integer(id),nome,cognome, Integer.parseInt(eta),telefono,email);
                PersonaDAO perdao = new PersonaDAO();
                perdao.addPersona(persona);

                
        }
}

La struttura finale dell’esempio è la seguente:

A questo punto è possibile testare il progetto. Per chi come me utilizza Eclipse, è necessario eseguire il comando “Run As, Maven install”, che attiva una serie di fasi del processo di build tra cui compilazione, esecuzione di unit test. Se tutto è stato configurato correttamente, eseguendo l’applicazione e richiamando l’URL: testJersey/rest/persona, otterremo il risultato visualizzato dalla seguente immagine.

Conclusioni

Eccoci arrivati al termine, con tre semplici classi annotate abbiamo creato un WebService! Per chiudere il cerchio è importante capire come utilizzare i servizi appena creati. Nel prossimo articolo creeremo una Web Application con PrimeFaces e vedremo come estrarre, manipolare e inserire i dati grazie all’interazione con il WebService appena definito.

Il progetto testJersey è interamente consultabile su GitHub.

Mauro Cognolato

Mi chiamo Mauro Cognolato laureato in Statistica e Gestione delle Imprese presso l'Università di Padova, e da 14 anni impiegato come consulente informatico, analista, programmatore nel settore bancario. Ho iniziato la mia attività come sviluppatore su sistemi Mainframe IBM: Cobol, CICS, DB2. Attualmente lavoro in ambiente dipartimentale sviluppando applicazioni web J2EE e processi ETL IBM Datastage su database Oracle e MS SQL Server. Inoltre sviluppo siti web su sistemi LAMP con HTML5 e JQuery.

  • “Nel prossimo articolo creeremo una Web Application con PrimeFaces e vedremo come estrarre, manipolare e inserire i dati grazie all’interazione con il WebService appena definito”

    Fallo al più presto… ti prego 😉

    Grazie, saluti e complimenti per l’eccezionale lavoro che fate.

    • Grazie Alessandro del riscontro! Ci sto già lavorando!

    • Giampaolo Trapasso

      Grazie mille Alessandro per i complimenti!

  • MauroCognolato

    Grazie Alessandro del riscontro! Ci sto già lavorando!

  • fly66

    Innanzitutto complimenti per l’articolo (e anche per la controparte PrimeFaces)! Vorrei chiedere se potresti integrarlo anche con la parte di autenticazione dell’utente e magari fornire qualche accenno sulla sicurezza. Con queste aggiunte diventerebbe il top degli articoli e più vicino ad un’implementazione reale!
    Comunque grazie per il grandissimo lavoro!

    • MauroCognolato

      Ciao fly66, la tua è sicuramente una grande idea e potrebbe essere un ottimo spunto per un nuovo post. Si potrebbe pensare di implementare Spring Security nel nostro Web Service! Seguici su CNJ! Potresti trovare la sorpresa, magari sotto l’albero di Natale…

    • MauroCognolato

      Come promesso è online l’articolo sulla sicurezza.

  • Andrea Scarafoni

    Mauro grazie veramente per il bel post, hai fatto un tutorial veramente chiaro ed immediato.
    Vorrei chiederti una cosa per un’incertezza che mi è rimasta.. con quale meccanismo si discrimina tra le due possibilità di ricevere un xml o un json? puoi fare un brevissimo esempio? grazie ancora, ciao

    • MauroCognolato

      Grazie 1000 Andrea del complimento e scusami se ti rispondo solo ora! XML è un protocollo più conosciuto e più adatto se si vuole inviare dati verso un’ampia gamma di utenti con software eterogeneo. D’altro canto JSON ha una rappresentazione dei dati più compatta in termini di byte, per cui può essere utile quando si vede scambiare un’intensa mole di dati e la banda è limitata, come ad esempio per i dispositivi mobile.

  • DanielePNZ

    scusate ma è possibile trasformare un job datastage in un servizio REST. Cioè posso esporre un job come servizio? Come si può fare?

  • Buongiorno Mauro, anzitutto complimenti per l’articolo, ho seguito il tutorial passo passo e ho messo in piedi l’applicazione.
    Ha forse infine realizzato l’altra webapp per la manipolazione dei dati?
    Saluti!
    CDP