Consumare un webservice RESTFul con Jersey e PrimeFaces

Nel precedente articolo abbiamo dimostrato con un semplice esempio come creare un webservice con Jersey. Ora vediamo come sia altrettanto facile “consumare” i metodi esposti dal webservice realizzando una web application. L’applicazione avrà il compito di esporre in forma tabellare i dati restituiti dal servizio, visualizzare l’età media delle persone calcolata dal webservice e predisporre l’inserimento di un nuovo record. Per costruire la webapp ci serviremo di PrimeFaces, una suite open source che utilizza il framework Java Server Faces 2.0, e che si sta imponendo nello scenario delle tecnologie web di presentazione.

Il nostro lavoro si suddivide in tre parti: la definizione della webapp e dei file di configurazione, l’implementazione del client per la chiamata al servizio, e infine la costruzione dell’interfaccia utente.

Parte 1: la definizione della webapp

Iniziamo costruendo un nuovo progetto Maven con Eclipse che chiameremo jsfJersey. Poichè non disponiamo di un archetipo specifico procediamo spuntando il flag “Create a simple project (skip archetype selection)”.
Dalle properties del progetto selezioniamo la scheda “Project facets” e modifichiamo come da figura:

Facets

A seguito di questa impostazione è possibile ricevere la segnalazione: “Dynamic Web Module 3.0 requires Java 1.6 or newer”, pertanto vi consiglio di impostare il vostro progetto puntando ad un JDK 1.7.

Nel file pom.xml inseriamo le dipendenze richieste per il client Jersey e per l’installazione di PrimeFaces:

    <dependency>
      <groupId>com.sun.jersey</groupId>
      <artifactId>jersey-client</artifactId>
      <version>1.13</version>
	</dependency>
    <dependency>
      <groupId>com.sun.jersey</groupId>
      <artifactId>jersey-server</artifactId>
      <version>1.13</version>
    </dependency>
    <dependency>
      <groupId>com.sun.faces</groupId>
      <artifactId>jsf-api</artifactId>
      <version>2.2.4</version>
    </dependency>
    <dependency>
      <groupId>com.sun.faces</groupId>
      <artifactId>jsf-impl</artifactId>
      <version>2.2.4</version>
    </dependency>
	<dependency>
      <groupId>org.primefaces</groupId>
      <artifactId>primefaces</artifactId>
      <version>5.0</version>
    </dependency>

Per elaborare i dati forniti dal servizio forniti in formato JSON, utilizzeremo la libreria Gson, riportiamo di seguito la dipendenza.

<dependency>
	<groupId>com.google.code.gson</groupId>
	<artifactId>gson</artifactId>
	<version>2.3</version>
</dependency>

Abbiamo terminato la configurazione delle dipendenze, passiamo ora a definire le caratteristiche della nostra webapp integrando il file web.xml. Innanzitutto inseriamo la chiamata a FacesServlet, la servlet che gestisce il ciclo di vita dei processi e le interfacce delle applicazioni JSF2, aggiungiamola nella corrispondente sezione del file.

  <servlet>
    <servlet-name>Faces Servlet</servlet-name>
    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>

Includiamo ora alcuni parametri per la determinazione delle pagine JSF: STATE_SAVING_METHOD specifica che lo stato dei componenti JSF verrà salvato nel client. Il tag url-pattern indica la richiesta URL che l’utente deve usare per invocare la FacesServlet, mentre il parametro STATE_SAVING_METHOD specifica che il suffisso fisico delle pagine JSF è .xhtml. In questo modo assegnando come welcome-file la pagina index.jsf verremo reindirizzati alla pagina index.xhtml.

  <context-param>
    <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
    <param-value>client</param-value>
  </context-param>
  <context-param>
    <param-name>javax.faces.DEFAULT_SUFFIX</param-name>
    <param-value>.xhtml</param-value>
  </context-param>
  <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>*.jsf</url-pattern>
  </servlet-mapping>

JSF può richiedere delle configurazioni aggiuntive come convertitori, validatori o managed beans. Queste impostazioni vanno specificate in un file a parte chiamato faces-config.xml da posizionare all’interno della cartella WEB-INF. Nel nostro esempio tuttavia non abbiamo bisogno di impostazioni particolari.

Parte 2: la creazione del client

Vediamo ora come richiamare il servizio REST definito nel post precedente e rendere disponibili i dati alla nostra applicazione. Partiamo inserendo una classe POJO Persona con la stessa struttura definita nel webservice.

public class Persona implements Serializable {
	private String id;
	private String nome;
	private String cognome;
	private String eta;
	private String telefono;	
	private String email;
	
	public Persona(String Id, String Nome, String Cognome, String 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
}

Passiamo quindi alla chiamata del servizio testJersey: implementeremo la nostra classe PersService che utilizza le librerie client di Jersey incapsulandone l’utilizzo.

Per accedere correttamente al servizio dobbiamo creare un’istanza della classe Client, e passando a questa un URI, possiamo ottenere un oggetto WebResource: utilizzeremo quest’ultimo per tutte le chiamate che ci servono.

PersService contiene tre metodi. I primi due eseguono le richieste HTTP GET per estrarre la lista di oggetti Persona e l’età anagrafica media, il terzo consente di inviare dati.
Vediamo i due metodi di lettura dati: mentre l’età media è restituita come testo semplice e non richiede ulteriori elaborazioni, la lista di persone è serializzata in formato JSON. Per effettuarne la deserializzazione, come detto, ci affideremo a GSON. Per i dettagli su come funziona GSON rimandiamo ai post già pubblicati su questo argomento.

L’invio dei dati avviene tramite il metodo processAction, che, assegnando un oggetto Persona ad una istanza della classe Form, effettua la spedizione di parametri multipli tramite una richiesta di tipo POST.

@ManagedBean(name = "persService")
@ApplicationScoped
public class PersService {


	public String MY_REST = "/rest";
	final String PERS   = "persona";
	final String ETAPERS   = "persona/etamedia";
	
	
	ClientConfig config = new DefaultClientConfig();
	Client client       = Client.create(config);
	WebResource service = client.resource(getBaseURI());
	WebResource restWS  = service.path(MY_REST);
	
    public String etaPers() {
    	String etapers = restWS.path(ETAPERS).accept(MediaType.TEXT_PLAIN).get(String.class);
    	return etapers;
    }
     
    public List<Persona> createPers() {

        List<Persona> people = new ArrayList();

       	Gson gson = new Gson();
       	String Json = restWS.path(PERS).accept(MediaType.APPLICATION_JSON).get(String.class);
        JsonParser parser = new JsonParser();
        JsonObject rootObejct = parser.parse(Json).getAsJsonObject();
        JsonElement personaElemento = rootObejct.get("persona");
        Type personaListaType = new TypeToken<List<Persona>>() {}.getType();
        people = gson.fromJson(personaElemento, personaListaType);

        return people;
    }
    
	public void processAction(Persona pers) {
		Form form = new Form();
		form.add("id", pers.getId());
		form.add("nome", pers.getNome());
		form.add("cognome", pers.getCognome());
		form.add("eta", pers.getEta());
		form.add("telefono", pers.getTelefono());
		form.add("email", pers.getEmail());
		ClientResponse response = restWS.path(PERS).type(MediaType.APPLICATION_FORM_URLENCODED)
								   .post(ClientResponse.class, form);
    }
     

	private static URI getBaseURI() {
		return UriBuilder.fromUri("http://localhost:8080/testJersey").build();
	}
}

Parte 3: la definizione delle interfacce utente

Nell’ultima parte di questo tutorial, costruiremo l’interfaccia utente che consiste in due pagine JSF e i relativi ManagedBean di cui riporteremo solamente le parti salienti, mentre potrete consultare il progetto completo su GitHub. La prima pagina rappresenta i dati restituiti dal servizio, la seconda contiene il form di raccolta dati per la spedizione verso il webservice.

La prima pagina è index dove inseriamo due componenti per la visualizzazione dei dati. Nello specifico utilizziamo un oggetto Datatable per l’esposizione della lista di persone, e una finestra di messaggio azionata da un pulsante per la visualizzazione dell’età media.

Una menzione particolare merita DataTable, un oggetto del framework JQuery che espone i dati in forma tabellare ed interattiva. Questo è uno strumento molto importante, poiché ci aiuta in una delle necessità più comuni nello sviluppo di un’applicazione, ovvero l’esposizione tabellare dei dati risultato di una query nel database. L’uso di entrambi i componenti è documentato nello ShowCase di PrimeFaces.

<p:dataTable var="persona" value="#{dtBasicView.pers}">
    <p:column headerText="Id">
        <h:outputText value="#{persona.id}" />
    </p:column>
 
    <p:column headerText="Nome">
        <h:outputText value="#{persona.nome}" />
    </p:column>
 
    <p:column headerText="Cognome">
        <h:outputText value="#{persona.cognome}" />
    </p:column>
    
    <p:column headerText="Età">
        <h:outputText value="#{persona.eta}" />
    </p:column>
 
    <p:column headerText="Telefono">
        <h:outputText value="#{persona.telefono}" />
    </p:column>    
  
    <p:column headerText="Email">
        <h:outputText value="#{persona.email}" />
    </p:column>    

</p:dataTable>
<h:form id="form">
 
 <p:messages id="messages" showDetail="true" autoUpdate="true" closable="true" />
 <p:commandButton value="Calcola l'età media" actionListener="#{dtBasicView.info}" />    
 <p:commandButton id="submitButton" value="Inserisci nuovo record" action="#{dtBasicView.processAction}"/>
 
</h:form>

Eseguendo la webapp, il codice appena visto genera la pagina seguente:

index

La gestione dei dati visualizzati nella pagina index è a carico del bean BasicView che richiama i metodi della classe PersService per ottenere i dati dal webservice. Da notare l’annotazione @PostConstruct in fase di caricamento della lista di oggetti Persona, che ci garantisce il popolamento del Datatable dopo che il bean è stato istanziato.

@ManagedBean(name="dtBasicView")
@ViewScoped
public class BasicView implements Serializable {

    private List<Persona> pers;
     
    @ManagedProperty("#{persService}")
    private PersService service;
 
    @PostConstruct
    public void init() {
        pers = service.createPers();        
    }      
    

Il metodo info assegna il dato relativo all’età media al componente messages di PrimeFaces.

    public void info() {
    	String etaPers = service.etaPers();
        FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO, "", "L'età media delle persone è " + etaPers));
    }

Cliccando il pulsante “Inserisci nuovo record” si viene reindirizzati alla seconda pagina aggiungiPersona che contiene invece il form di inserimento dati da inviare al webservice.

<h:form>
    <h:panelGrid columns="2" cellpadding="5">
        <p:outputLabel for="id" value="Id: " />
        <p:inputMask id="id" value="#{dtMaskView.id}" mask="999"/>
 
        <p:outputLabel for="nome" value="Nome:" />
        <p:inputText id="nome" size="20" value="#{dtMaskView.nome}"/>
 
        <p:outputLabel for="cognome" value="Cognome:" />
        <p:inputText id="cognome" value="#{dtMaskView.cognome}"/>
 
        <p:outputLabel for="eta" value="Eta:" />
        <p:inputMask id="eta" value="#{dtMaskView.eta}" mask="99"/>
 
        <p:outputLabel for="telefono" value="Telefono:" />
        <p:inputText id="telefono" value="#{dtMaskView.telefono}"/>
 
        <p:outputLabel for="email"  value="Email: " />
        <p:inputText id="email" value="#{dtMaskView.email}"/>
 
        <p:commandButton id="submitButton" value="Inserisci" action="#{dtMaskView.addPersona}"/>

 
    </h:panelGrid>

</h:form>

Di seguito la pagina risultato:
form

Il bean MaskView ha il compito di mappare i dati del form in un oggetto Persona e richiamare il metodo processAction, già visto in precedenza, per la spedizione al servizio.

   	
	public String addPersona() {	
		pers = new Persona(id, nome, cognome, eta, telefono, email);
		service.processAction(pers);
		return "index";	
	}

Conclusioni

Molti tutorial che illustrano i webservice, si focalizzano sulla costruzione del servizio, ma spiegano poco sulla costruzione del client. Per questo, dopo aver illustrato la definizione del servizio, abbiamo costruito un’applicazione web client completa introducendo anche PrimeFaces, una suite di componenti open source per Java Server Faces dalle grandi potenzialità.

Il progetto jsfJersey è 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.

  • Articolo molto interessante, grazie 🙂
    Volevo segnalare una piccola incongruenza nel codice. Per il commandButton di aggiungiPersona.xhtml
    è riportato nello snippet:

    mentre nel codice su GitHub è:

    Immagino la seconda versione sia quella corretta. Saluti

  • MauroCognolato

    Hai ragione! C’era un disallineamento nel repository. Ora è sistemato. Grazie della segnalazione.

  • Mauro cosa dire?

    Grazie

    PS Aspettiamo l’approfondimento in merito alla sicurezza (“Spring Security nel Web Service”) 😉

    • MauroCognolato

      Work in progress…. stay tuned!

    • MauroCognolato

      L’articolo sulla sicurezza è online. Buona lettura

  • Alessandro Mattiuzzi

    Scusate da profano faccio due domande:
    1-cosa significa RESTful?
    2-si può usare jetty come web server?

    • MauroCognolato

      Ciao Alessandro, la definizione di RESTFul l’ho data nel primo articolo dove introducono Jersey. Non conosco Jetty, ma suppongo non ci siano grosse difficoltà, ti invito a provare poi ne riparliamo.

  • light_cursor

    ciao mauro, siccome sono nuovo dell’ambiente… come faccio a mandare in esecuzione questa parte del codice ? la parte del “webservice” già funziona!