Skip to content

17. Pubblicare un database sul web

17.1. Architettura del servizio web / jSON

Implementeremo la seguente architettura:

  • in [1], i livelli [DAO, [JPA], JDBC] sono implementati da una delle 24 configurazioni presentate nei capitoli precedenti e in particolare al paragrafo 15;
  • il livello [DAO] [3] del client remoto implementa la stessa interfaccia del livello [DAO] [1], il che ci permette di utilizzare lo stesso livello di test dei capitoli precedenti. È come se i livelli [2-3] fossero trasparenti per il livello [4];

Ci baseremo sui seguenti progetti:

  • il progetto [sgbd-config-jdbc] che configura il livello JDBC di uno dei sei SGBD;
  • il progetto [sgbd-config-jpa-*] che configura il livello JPA del SGBD scelto per una delle tre implementazioni JPA esaminate (Hibernate, EclipseLink, OpenJpa);
  • il progetto generico [spring-jdbc-04] che implementa il livello [DAO] [1];
  • il progetto generico [spring-jpa-generic] che implementa il livello [DAO] e [2];
  • il progetto generico [spring-webjson-server-jdbc-generic] che implementa un servizio web basato sul progetto [spring-jdbc-04];
  • il progetto generico [spring-webjson-server-jpa-generic] che implementa un servizio web basato sul progetto [spring-jpa-generic];
  • il client generico [spring-webjson-client-generic], che sarà l’unico client delle 24 configurazioni del servizio web;

17.2. Configurazione dell'ambiente di lavoro

Lavoreremo con i seguenti elementi:

  • SGBD MySQL 5.6.25;
  • implementazione JPA Hibernate;

Importare i seguenti progetti in STS:

  
  • i progetti [spring-webjson-*] si trovano nella cartella [<exemples>\spring-database-generic\spring-webjson];
  • eseguire [Alt-F5], quindi rigenerare tutti i progetti sopra indicati;

Per verificare la corretta installazione dell'ambiente di lavoro, procedere come segue:

  • avviare il servizio web con la configurazione di esecuzione [spring-webjson-server-jpa-generic-hibernate], che si basa su un'implementazione JPA / Hibernate;

quindi:

  • avviare il client di questo servizio web con la configurazione di esecuzione [spring-webjson-client-generic], che è un test di JUnit:

Il test deve avere esito positivo:

  • in [1], arrestare il servizio web, quindi avviare il servizio web con la configurazione di esecuzione [spring-webjson-server-jdbc-generic], che si basa su un'implementazione JDBC:

quindi avviare il client di questo servizio web con la configurazione di esecuzione [spring-webjson-client-generic]:

Il test dovrebbe avere esito positivo:

 

17.3. Implementazione del servizio web / jSON / JDBC

Ci concentriamo innanzitutto sulla seguente architettura:

dove il livello [DAO] [1] comunica direttamente con il livello JDBC del SGBD.

17.3.1. Il progetto Eclipse del servizio web

Il progetto Eclipse del servizio web / jSON / JDBC è il seguente:

  

Si tratta di un progetto Maven il cui file [pom.xml] è il seguente:


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>dvp.spring.database</groupId>
    <artifactId>spring-webjson-server-jdbc-generic</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <name>spring-webjson-server-jdbc-generic</name>
    <description>démo spring mvc</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.3.RELEASE</version>
    </parent>

    <dependencies>
        <!-- livello web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- livello [DAO] -->
        <dependency>
            <groupId>dvp.spring.database</groupId>
            <artifactId>spring-jdbc-generic-04</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>
    <!-- plugin -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
    </build>

</project>
  • righe 11-15: il progetto Maven padre;
  • righe 24-28: la dipendenza dal livello [DAO / JDBC] implementato dal progetto [spring-jdbc-generic-04];
  • righe 19-22: la dipendenza dall'artefatto [spring-boot-starter-web]. Questo artefatto include tutte le dipendenze necessarie per la creazione di un servizio web / jSON. Include però anche alcune librerie non necessarie. Sarebbe quindi necessaria una configurazione più precisa, ma questa configurazione è utile per iniziare.

Le dipendenze incluse in questa configurazione sono le seguenti:

1
  • in [1], si nota che Eclipse ha rilevato la dipendenza dall'archivio del progetto [spring-jdbc-generic-04];

Le dipendenze sopra indicate sono comuni sia al livello [DAO] che al livello [web].

17.3.2. Configurazione del livello [web]

Il livello [web] è configurato da due file di configurazione Spring:

  

17.3.2.1. La classe [WebConfig]

La classe [WebConfig] ha come funzione principale quella di configurare:

  • il server Tomcat su cui verrà distribuito il servizio web;
  • i filtri jSON per la serializzazione/deserializzazione degli oggetti [Produit] e [Categorie]:

package spring.webjson.server.config;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    // -------------------------------- configurazione del livello [web]
    @Autowired
    private ApplicationContext context;

    @Bean
    public DispatcherServlet dispatcherServlet() {
        DispatcherServlet servlet=new DispatcherServlet((WebApplicationContext) context);
        return servlet;
    }

    @Bean
    public ServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) {
        return new ServletRegistrationBean(dispatcherServlet, "/*");
    }

    @Bean
    public EmbeddedServletContainerFactory embeddedServletContainerFactory() {
        return new TomcatEmbeddedServletContainerFactory("", 8081);
    }

    // -------------------------------- configurazione filtri [json]
    ...
}
  • riga 25: la classe è una classe di configurazione Spring;
  • riga 26: l'annotazione [@EnableWebMvc] indica che il livello web è implementato con Spring MVC. Ciò comporterà configurazioni implicite che non dovremo effettuare;
  • righe 30-31: iniezione del contesto Spring dell'applicazione;
  • righe 33-37: definizione del bean [dispatcherServlet] che, nelle applicazioni Spring MVC, svolge il ruolo di [FrontController], il cui compito è indirizzare le richieste dei client verso il controller in grado di elaborarle;
  • righe 39-42: viene registrato il servlet del servizio web insieme ai URL che esso gestisce. Qui è stato scritto [/*], che indica tutti i URLs;
  • righe 44-47: definizione del bean [embeddedServletContainerFactory] che specifica il server web da utilizzare. In questo caso si tratterà del server web Tomcat [http://tomcat.apache.org/]. È possibile utilizzare anche il server Jetty [http://www.eclipse.org/jetty/]. Entrambi sono server integrati nelle dipendenze Maven. Quando [Spring Boot] avvia il progetto, avvia automaticamente il server web specificato nella configurazione e distribuisce su di esso il servizio o l’applicazione web;

La configurazione dei filtri jSON avviene nel modo seguente:


package spring.webjson.server.config;

import java.util.List;
...

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    // -------------------------------- configurazione del livello [web]
...
    // -------------------------------- configurazione filtri [json]
    // mappatura jSON
    @Bean
    public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
        final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        final ObjectMapper objectMapper = new ObjectMapper();
        converter.setObjectMapper(objectMapper);
        return converter;
    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(mappingJackson2HttpMessageConverter());
        super.configureMessageConverters(converters);
    }

    // filtri jSON
    @Bean
    public ObjectMapper jsonMapper(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
        return mappingJackson2HttpMessageConverter.getObjectMapper();
    }

    @Bean
    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    ObjectMapper jsonMapperShortCategorie(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
        ObjectMapper jsonMapper = jsonMapper(mappingJackson2HttpMessageConverter);
        jsonMapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterCategorie",
                SimpleBeanPropertyFilter.serializeAllExcept("produits")));
        return jsonMapper;
    }

    @Bean
    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    ObjectMapper jsonMapperLongCategorie(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
        ObjectMapper jsonMapper = jsonMapper(mappingJackson2HttpMessageConverter);
        jsonMapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterCategorie",
                SimpleBeanPropertyFilter.serializeAllExcept()).addFilter("jsonFilterProduit",
                SimpleBeanPropertyFilter.serializeAllExcept("categorie")));
        return jsonMapper;
    }

    @Bean
    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    ObjectMapper jsonMapperShortProduit(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
        ObjectMapper jsonMapper = jsonMapper(mappingJackson2HttpMessageConverter);
        jsonMapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterProduit",
                SimpleBeanPropertyFilter.serializeAllExcept("categorie")));
        return jsonMapper;
    }

    @Bean
    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    ObjectMapper jsonMapperLongProduit(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
        ObjectMapper jsonMapper = jsonMapper(mappingJackson2HttpMessageConverter);
        jsonMapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterProduit",
                SimpleBeanPropertyFilter.serializeAllExcept()).addFilter("jsonFilterCategorie",
                SimpleBeanPropertyFilter.serializeAllExcept("produits")));
        return jsonMapper;
    }
}
  • riga 8: la classe [WebConfig] estende la classe [WebMvcConfigurerAdapter]. Quest’ultima classe configura l’applicazione web con valori predefiniti. Quando si desidera personalizzare questa configurazione, è necessario ridefinire alcuni metodi di questa classe. In questo caso, vogliamo ridefinire il metodo [configureMessageConverters] della classe [22-26] (si noti l’annotazione @Override), che definisce un elenco di «convertitori». Il servizio web /jSON e il suo client si scambiano righe di testo. Un convertitore è uno strumento in grado di creare un oggetto a partire da una riga di testo ricevuta (deserializzazione) e di creare una riga di testo a partire da un oggetto (serializzazione). In questo caso, le righe di testo saranno stringhe jSON. Si parlerà quindi di serializzazione/deserializzazione jSON;
  • riga 23: il metodo [configureMessageConverters] riceve come parametro un elenco di convertitori;
  • righe 24-25: il convertitore jSON [MappingJackson2HttpMessageConverter] delle righe [14-20] viene aggiunto a questo elenco. Ciò consentirà gli scambi jSON tra il client e il server;
  • righe [14-20]: definiscono un convertitore jSON implementato dalla classe [MappingJackson2HttpMessageConverter]. Questa classe (riga 10) si trova nelle dipendenze Maven del progetto;
  • righe [17-18]: viene creato un mappatore jSON e assegnato al convertitore [MappingJackson2HttpMessageConverter];
  • righe [29-32]: definiscono il mappatore jSON creato alla riga 17 come un bean Spring. Ciò lo inserisce nel contesto Spring e lo rende disponibile per essere iniettato in altri bean o utilizzato nel codice dell’applicazione web;
  • righe 34-41: definiscono un filtro jSON per il mappatore jSON precedente;
  • riga 35: l’annotazione [@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)] fa sì che il bean qui definito non sia un singleton. Ogni volta che verrà richiesto al contesto, il metodo [jsonMapperCategorieWithoutProduits] verrà rieseguito. Ciò è necessario in questo caso poiché definiamo quattro filtri jSON. Tuttavia, in un dato momento ne deve essere attivo solo uno. Assegnando al bean l’ambito [ConfigurableBeanFactory.SCOPE_PROTOTYPE], si garantisce che il metodo venga rieseguito e che il filtro precedente venga sostituito da quello nuovo;
  • per comprendere questi filtri, occorre ricordare che nel livello [DAO]:
    • l’entità [Produit] è stata decorata con l’annotazione [jsonFilterProduit];
    • l’entità [Categorie] è stata contrassegnata con l’annotazione [jsonFilterCategorie];

È quindi necessario definire dei filtri con questi nomi.

  • righe [34-41]: definiscono un filtro denominato [jsonMapperShortCategorie] che consente di ottenere la rappresentazione jSON di una categoria senza i relativi prodotti;
  • righe [43-51]: definiscono un filtro denominato [jsonMapperLongCategorie] che consente di ottenere la rappresentazione jSON di una categoria con i relativi prodotti;
  • righe [53-60]: definiscono un filtro denominato [jsonMapperShortProduit] che consente di ottenere la rappresentazione jSON di un prodotto senza la relativa categoria;
  • righe [62-70]: definiscono un filtro denominato [jsonMapperLongProduit] che consente di ottenere la rappresentazione jSON di un prodotto con la relativa categoria;

17.3.2.2. La classe [AppConfig]

La classe [AppConfig] configura l’intera applicazione, ovvero i livelli [web] e [DAO]:


package spring.webjson.server.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@ComponentScan(basePackages = { "spring.webjson.server.service" })
@Import({ spring.jdbc.config.AppConfig.class, WebConfig.class })
public class AppConfig {

}
  • riga 7: la classe è una classe di configurazione Spring;
  • riga 9: si importano i bean del livello [DAO / JDBC] e quelli definiti dalla classe [WebConfig]. Tutti i bean del livello [DAO] saranno quindi disponibili nell'applicazione web / jSON;
  • riga 8: indica in quali pacchetti si trovano altri bean Spring;

17.3.3. L'eccezione [ServerException]

  

Come nei capitoli precedenti, il livello [DAO] generava un'eccezione non controllata [DaoException]; il livello [web] genererà un'eccezione non controllata [ServerException]:


package spring.webjson.server.infrastructure;

import generic.jdbc.infrastructure.UncheckedException;

public class ServerException extends UncheckedException {

    private static final long serialVersionUID = 1L;

    // produttori
    public ServerException() {
        super();
    }

    public ServerException(int code, Throwable e, String simpleClassName) {
        super(code, e, simpleClassName);
    }
}
  • riga 5: la classe [ServerException] estende la classe [UncheckedException] definita nel progetto che configura il livello JDBC (riga 3);

17.3.4. I controllori

  

Qui avremo due controller:

  • [CategorieController] gestirà le richieste relative alle categorie;
  • [CategorieController] gestirà le richieste relative ai prodotti;

I URL esposti dai controller corrispondono uno a uno ai metodi delle interfacce [DaoCategorie] e [DaoProduit] del livello [DAO]:

Pertanto, come sopra indicato:

  • il metodo web [deleteAllCategories] richiamerà il metodo [deleteAllEntities] della classe [DaoCategorie];
  • il metodo web [getShortCategoriesById] richiamerà il metodo [getShortEntitiesById] della classe [DaoCategorie];

Lo stesso vale per i prodotti:

17.3.4.1. I URL esposti dal controller [CategorieController]

URL
Méthode

@RequestMapping(value = "/saveCategories",
 method = RequestMethod.POST, consumes =
 "application/json; charset=UTF-8")

public Response<List<CoreCategorie>>
 saveCategories
(HttpServletRequest request)
====
La méthode reçoit par un POST les catégories à persister. 
Celles-ci sont accessibles dans l'objet [HttpServletRequest
 request]. Les catégories sont persistées par la méthode 
[saveEntities] de la couche [DaoCategorie]. Seules les clés
 primaires des objets persistés (catégories / produits) sont
 renvoyées au client.

@RequestMapping(value = "/deleteAllCategories",
 method = RequestMethod.GET)

public Response<Void> deleteAllCategories()
====
L'URL n'a aucun paramètre. Les catégories sont supprimées par
 la méthode [deleteAllEntities] de la couche [DaoCategorie].

@RequestMapping(value = "/deleteCategoriesById",
 method = RequestMethod.POST, consumes =
 "application/json; charset=UTF-8")

public Response<Void> deleteCategoriesById
(HttpServletRequest request)
====
La méthode reçoit par un POST les clés primaires des
 catégories à supprimer. Celles-ci sont accessibles dans
 l'objet [HttpServletRequest request]. Les catégories sont
 supprimées par la méthode [deleteEntitiesById] de la couche
 [DaoCategorie].

@RequestMapping(value = "/deleteCategoriesByName",
 method = RequestMethod.POST, consumes =
 "application/json; charset=UTF-8")

public Response<Void> deleteCategoriesByName
(HttpServletRequest request)
====
La méthode reçoit par un POST les noms des catégories à 
supprimer. Ceux-ci sont accessibles dans l'objet
 [HttpServletRequest request]. Les catégories sont supprimées
 par la méthode [deleteEntitiesByname] de la couche 
[DaoCategorie].

@RequestMapping(value = "/getAllShortCategories",
 method = RequestMethod.GET)

public Response<List<Categorie>> getAllShortCategories()
====
L'URL n'a aucun paramètre. Les catégories courtes sont 
obtenues par la méthode [getAllShortEntities] de la couche
 [DaoCategorie].

@RequestMapping(value = "/getAllLongCategories",
 method = RequestMethod.GET)

public Response<List<Categorie>> getAllLongCategories()
====
L'URL n'a aucun paramètre. Les catégories longues sont
 obtenues par la méthode [getAllLongEntities] de la couche 
[DaoCategorie].

@RequestMapping(value = "/getLongCategoriesById",
 method = RequestMethod.POST)

public Response<List<Categorie>> getLongCategoriesById(HttpServletRequest request)
====
La méthode reçoit par un POST les clés primaires des
 catégories désirées. Celles-ci sont accessibles dans l'objet
 [HttpServletRequest request]. Les catégories longues sont
 obtenues par la méthode [getLongEntitiesById] de la couche
 [DaoCategorie].

@RequestMapping(value = "/getLongCategoriesByName",
 method = RequestMethod.POST)

public Response<List<Categorie>> getLongCategoriesByName
(HttpServletRequest request)
====
La méthode reçoit par un POST les noms des catégories
 désirées. Ceux-ci sont accessibles dans l'objet
 [HttpServletRequest request]. Les catégories longues sont 
obtenues par la méthode [getLongEntitiesByName] de la couche
 [DaoCategorie].

@RequestMapping(value = "/getShortCategoriesByName",
 method = RequestMethod.POST)

public Response<List<Categorie>> getShortCategoriesByName
(HttpServletRequest request)
====
La méthode reçoit par un POST les noms des catégories
 désirées. Ceux-ci sont accessibles dans l'objet
 [HttpServletRequest request]. Les catégories courtes sont
 obtenues par la méthode [getShortEntitiesByName] de la
 couche [DaoCategorie].

@RequestMapping(value = "/getShortCategoriesById",
 method = RequestMethod.POST)

public Response<List<Categorie>> getShortCategoriesById
(HttpServletRequest request)
====
La méthode reçoit par un POST les clés primaires des
 catégories désirées. Celles-ci sont accessibles dans l'objet
 [HttpServletRequest request]. Les catégories courtes sont
 obtenues par la méthode [getShortEntitiesById] de la couche
 [DaoCategorie].

17.3.4.2. I URL esposti dal controller [ProduitController]

URL
Méthode

@RequestMapping(value = "/saveProduits", method =
 RequestMethod.POST, consumes = "application/json;
 charset=UTF-8")

public Response<List<CoreProduit>> saveProduits
(HttpServletRequest request)
====
La méthode reçoit les produits à persister par un POST. Ceux-
ci sont accessibles dans l'objet [HttpServletRequest
 request]. Les produits sont persistés par la méthode
 [saveEntities] de la couche [DaoProduit]. Seules les clés
 primaires des produits sont renvoyées au client.

@RequestMapping(value = "/deleteAllProduits",
 method = RequestMethod.GET)

public Response<Void> deleteAllProduits()
====
L'URL n'a aucun paramètre. Les produits sont supprimés par la
 méthode [deleteAllEntities] de la couche [DaoProduit].

@RequestMapping(value = "/deleteProduitsById",
 method = RequestMethod.POST, consumes =
 "application/json; charset=UTF-8")

public Response<Void> deleteProduitsById
(HttpServletRequest request)
====
La méthode reçoit par un POST les clés primaires des produits
 à supprimer. Celles-ci sont accessibles dans l'objet
 [HttpServletRequest request]. Les produits sont supprimés
 par la méthode [deleteEntitiesById] de la couche 
[DaoProduit].

@RequestMapping(value = "/deleteProduitsByName",
 method = RequestMethod.POST, consumes =
 "application/json; charset=UTF-8")

public Response<Void> deleteProduitsByName
(HttpServletRequest request)
====
La méthode reçoit par un POST les noms des produits à
 supprimer. Ceux-ci sont accessibles dans l'objet
 [HttpServletRequest request]. Les produits sont supprimés
 par la méthode [deleteEntitiesByname] de la couche
 [DaoProduit].

@RequestMapping(value = "/getAllShortProduits",
 method = RequestMethod.GET)

public Response<List<Produit>> getAllShortProduits()
====
L'URL n'a aucun paramètre. Les catégories courtes sont
 obtenues par la méthode [getAllShortEntities] de la couche
 [DaoProduit].

@RequestMapping(value = "/getAllLongProduits",
 method = RequestMethod.GET)

public Response<List<Produit>> getAllLongProduits()
====
L'URL n'a aucun paramètre. Les produits longs sont obtenus
 par la méthode [getAllLongEntities] de la couche
 [DaoProduit].

@RequestMapping(value = "/getLongProduitsById",
 method = RequestMethod.POST)

public Response<List<Produit>> getLongProduitsById
(HttpServletRequest request)
====
La méthode reçoit par un POST les clés primaires des produits
 désirés. Celles-ci sont accessibles dans l'objet
 [HttpServletRequest request]. Les produits longs sont 
obtenus par la méthode [getLongEntitiesById] de la couche

 [DaoProduit].

@RequestMapping(value = "/getLongProduitsByName",
 method = RequestMethod.POST)

public Response<List<Produit>> getLongProduitsByName
(HttpServletRequest request)
====
La méthode reçoit par un POST les noms des produits désirés.
 Ceux-ci sont accessibles dans l'objet [HttpServletRequest
 request]. Les produits longs sont obtenus par la méthode
 [getLongEntitiesByName] de la couche [DaoProduit].

@RequestMapping(value = "/getShortProduitsByName",
 method = RequestMethod.POST)

public Response<List<Produit>> getShortProduitsByName
(HttpServletRequest request)
====
La méthode reçoit par un POST les noms des produits désirés.
 Ceux-ci sont accessibles dans l'objet [HttpServletRequest
 request]. Les produits courts sont obtenus par la méthode 
[getShortEntitiesByName] de la couche [DaoProduit].

@RequestMapping(value = "/getShortProduitsById",
 method = RequestMethod.POST)

public Response<List<Produit>> getShortProduitsById
(HttpServletRequest request)
====
La méthode reçoit par un POST les clés primaires des produits
 désirés. Celles-ci sont accessibles dans l'objet 
[HttpServletRequest request]. Les produits courts sont 
obtenus par la méthode [getShortEntitiesById] de la couche
 [DaoProduit].

17.3.5. Implementazione generica del servizio web

  

L'elenco dei URL esposti dal servizio web mostra che vengono offerti gli stessi tipi di URL per gestire le categorie e i prodotti. Anziché scrivere due controller molto simili, li faremo derivare da una classe che si occuperà di tutte le operazioni comuni ai due controller. Si tratterà della classe [AbstractController] sopra indicata. Questa classe implementerà la seguente interfaccia [Iws]:


package spring.webjson.server.service;

import java.util.List;

import javax.servlet.http.HttpServletRequest;

import spring.jdbc.entities.AbstractCoreEntity;

public interface Iws<T extends AbstractCoreEntity> {

    // elenco di tutte le entità T
    public Response<List<T>> getAllShortEntities();

    public Response<List<T>> getAllLongEntities();

    // di entità particolari - versione breve
    public Response<List<T>> getShortEntitiesById(HttpServletRequest request);

    public Response<List<T>> getShortEntitiesByName(HttpServletRequest request);

    // di entità specifiche - versione estesa
    public Response<List<T>> getLongEntitiesById(HttpServletRequest request);

    public Response<List<T>> getLongEntitiesByName(HttpServletRequest request);

    // aggiornamento di più entità
    public Response<List<T>> saveEntities(HttpServletRequest request);

    // eliminazione di tutte le entità
    public  Response<Void> deleteAllEntities();

    // eliminazione di più entità
    public  Response<Void> deleteEntitiesById(HttpServletRequest request);

    public  Response<Void> deleteEntitiesByName(HttpServletRequest request);
}

Questa interfaccia riprende i metodi dell’interfaccia del livello [DAO] che verrà utilizzata:


package spring.jdbc.dao;

import java.util.List;

import spring.jdbc.entities.AbstractCoreEntity;

public interface IDao<T extends AbstractCoreEntity> {

    // elenco di tutte le entità T
    public List<T> getAllShortEntities();

    public List<T> getAllLongEntities();

    // di entità specifiche - versione breve
    public List<T> getShortEntitiesById(Iterable<Long> ids);

    public List<T> getShortEntitiesById(Long... ids);

    public List<T> getShortEntitiesByName(Iterable<String> names);

    public List<T> getShortEntitiesByName(String... names);

    // di entità specifiche - versione estesa
    public List<T> getLongEntitiesById(Iterable<Long> ids);

    public List<T> getLongEntitiesById(Long... ids);

    public List<T> getLongEntitiesByName(Iterable<String> names);

    public List<T> getLongEntitiesByName(String... names);

    // aggiornamento di più entità
    public List<T> saveEntities(Iterable<T> entities);

    public List<T> saveEntities(@SuppressWarnings("unchecked") T... entities);

    // eliminazione di tutte le entità
    public void deleteAllEntities();

    // eliminazione di più entità
    public void deleteEntitiesById(Iterable<Long> ids);

    public void deleteEntitiesById(Long... ids);

    public void deleteEntitiesByName(Iterable<String> names);

    public void deleteEntitiesByName(String... names);

    public void deleteEntitiesByEntity(Iterable<T> entities);

    public void deleteEntitiesByEntity(@SuppressWarnings("unchecked") T... entities);
}

Il passaggio dall’interfaccia [IDao<T>] del livello [DAO] all’interfaccia [Iws<T>] del servizio web ha rispettato le seguenti regole:

  • i metodi dell’interfaccia [Iws<T>] non genereranno eccezioni. Se dovesse verificarsi un’eccezione, questa verrà incapsulata nell’oggetto [Response];
  • le varianti dei parametri, come quelle alle righe 45 e 47 di [Iterable<String> names, String... names], vengono eliminate. I metodi ottengono i propri parametri dalla richiesta HTTP del client di tipo [HttpServletRequest request];

Tutte le risposte del servizio web saranno incapsulate nel seguente oggetto [Response]:

  

package spring.webjson.server.service;


public class Response<T> {

    // ----------------- proprietà
    // stato dell'operazione
    private int status;
    // un messaggio di errore
    private String exception;
    // il corpo della risposta
    private T body;

    // costruttori
    public Response() {

    }

    public Response(int status, String exception, T body) {
        this.status = status;
        this.exception = exception;
        this.body = body;
    }

    // getter e setter
...
}
  • riga 4: la risposta incapsula un tipo T;
  • riga 12: la risposta di tipo T;
  • righe 7-10: è possibile che un metodo generi un'eccezione. In tal caso, restituirà una risposta con:
    • riga 8: status!=0;
    • riga 10: un messaggio di errore;

La classe [AbstractController] è la seguente:


package spring.webjson.server.service;

import java.util.List;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;

import spring.jdbc.dao.IDao;
import spring.jdbc.entities.AbstractCoreEntity;
import spring.jdbc.infrastructure.DaoException;
import spring.webjson.server.infrastructure.ServerException;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.io.CharStreams;

public abstract class AbstractController<T extends AbstractCoreEntity> implements Iws<T> {

    @Autowired
    protected ApplicationContext context;

    // livello DAO
    private IDao<T> dao;

    abstract protected IDao<T> getDao();

    // locale
    private String simpleClassName = getClass().getSimpleName();

    @PostConstruct
    public void init(){
        dao=getDao();
    }
    
    @Override
    public Response<List<T>> getAllShortEntities() {
        try {
            // risposta
            return new Response<List<T>>(0, null, dao.getAllShortEntities());
        } catch (DaoException e) {
            return new Response<List<T>>(1, e.toString(), null);
        } catch (Exception e) {
            return new Response<List<T>>(2, new ServerException(1007, e, simpleClassName).toString(), null);
        }
    }

    @Override
    public Response<List<T>> getAllLongEntities() {
        try {
            // risposta
            return new Response<List<T>>(0, null, dao.getAllLongEntities());
        } catch (DaoException e) {
            return new Response<List<T>>(1, e.toString(), null);
        } catch (Exception e) {
            return new Response<List<T>>(2, new ServerException(1008, e, simpleClassName).toString(), null);
        }
    }

    @Override
    public Response<List<T>> getShortEntitiesById(HttpServletRequest request) {
    ...
    }

    @Override
    public Response<List<T>> getShortEntitiesByName(HttpServletRequest request) {
    ...
    }

    @Override
    public Response<List<T>> getLongEntitiesById(HttpServletRequest request) {
    ...
    }

    @Override
    public Response<List<T>> getLongEntitiesByName(HttpServletRequest request) {
    ...
    }

        @Override
    public Response<List<T>> saveEntities(HttpServletRequest request) {
        return new Response<List<T>>(2, new ServerException(1013, new RuntimeException("[saveEntities] not implemented"), simpleClassName).toString(), null);
    }


    @Override
    public Response<Void> deleteAllEntities() {
        try {
            // si elimina
            dao.deleteAllEntities();
            // risposta
            return new Response<Void>(0, null, null);
        } catch (DaoException e) {
            return new Response<Void>(1, e.toString(), null);
        } catch (Exception e) {
            return new Response<Void>(2, new ServerException(1014, e, simpleClassName).toString(), null);
        }
    }

    @Override
    public Response<Void> deleteEntitiesById(HttpServletRequest request) {
        ...
    }

    @Override
    public Response<Void> deleteEntitiesByName(HttpServletRequest request) {
    ...
    }

}

I metodi sono tutti implementati allo stesso modo:

  1. se richiedono informazioni, le recuperano dall'oggetto [HttpServletRequest request];
  2. richiamano il metodo del livello [DAO] che porta lo stesso nome;
  3. gestiscono le eccezioni che possono verificarsi, sia nell’operazione 1 di recupero dei parametri, sia nell’operazione 2 di chiamata al livello [DAO];

Esaminiamo innanzitutto come viene inserito il livello [DAO] nella classe [AbstractController]:


public abstract class AbstractController<T extends AbstractCoreEntity> implements Iws<T> {

    @Autowired
    protected ApplicationContext context;

    // livello DAO
    private IDao<T> dao;

    abstract protected IDao<T> getDao();

    @PostConstruct
    public void init(){
        dao=getDao();
}

  • riga 1: la classe è astratta e implementa l'interfaccia generica [Iws<T>];
  • righe 3-4: iniezione del contesto Spring;
  • riga 7: il riferimento, ancora sconosciuto, del livello [DAO] da utilizzare;
  • riga 9: il metodo astratto [getDao] che restituirà il riferimento al livello [DAO] da utilizzare. Questo metodo verrà ridefinito dalla classe figlia ed è quindi la classe figlia che indicherà quale strato [DAO] utilizzare (DaoProduit o DaoCategorie);
  • riga 11: l'annotazione [@PostConstruct] indica un metodo da eseguire al termine dell'istanziazione dell'oggetto. Una volta completata l'istanziazione, le iniezioni Spring sono state effettuate. La classe figlia avrà quindi ottenuto il riferimento al proprio livello [DAO] e potrà quindi trasmetterlo alla propria classe padre;

Il metodo [getShortEntitiesById] è il seguente:


    @Override
    public Response<List<T>> getShortEntitiesById(HttpServletRequest request) {
        try {
            // si recupera il valore inviato
            String body = CharStreams.toString(request.getReader());
            // lo deserializza
            ObjectMapper mapper = context.getBean("jsonMapper", ObjectMapper.class);
            List<Long> ids = mapper.readValue(body, new TypeReference<List<Long>>() {
            });
            // risposta
            return new Response<List<T>>(0, null, dao.getShortEntitiesById(ids));
        } catch (DaoException e) {
            return new Response<List<T>>(1, e.toString(), null);
        } catch (Exception e) {
            return new Response<List<T>>(2, new ServerException(1009, e, simpleClassName).toString(), null);
        }
}
  • riga 5: il valore inviato dal client sarà una stringa jSON. È qui che viene recuperato;
  • righe 7-9: la stringa jSON contiene l’elenco delle chiavi primarie delle entità di cui si desidera la versione abbreviata;
  • riga 11: si chiama il metodo [DAO] con lo stesso nome. La risposta di tipo [List<T>] è incapsulata in un oggetto [Response];
  • riga 13: caso in cui il livello [DAO] abbia generato un'eccezione;
  • riga 15: caso delle altre eccezioni, in particolare la possibile eccezione alla deserializzazione del parametro jSON, riga 8;

Il metodo [getShortEntitiesByName] è analogo:


@Override
    public Response<List<T>> getShortEntitiesByName(HttpServletRequest request) {
        try {
            // si recupera il valore inviato
            String body = CharStreams.toString(request.getReader());
            // lo deserializziamo
            ObjectMapper mapper = context.getBean("jsonMapper", ObjectMapper.class);
            List<String> noms = mapper.readValue(body, new TypeReference<List<String>>() {
            });
            // risposta
            return new Response<List<T>>(0, null, dao.getShortEntitiesByName(noms));
        } catch (DaoException e) {
            return new Response<List<T>>(1, e.toString(), null);
        } catch (Exception e) {
            return new Response<List<T>>(2, new ServerException(1010, e, simpleClassName).toString(), null);
        }
}
  • righe 4-9: qui il parametro jSON è l'elenco dei nomi delle categorie di cui si desidera la versione abbreviata;

Il metodo [saveEntities] non è stato implementato perché dipende in larga misura dalla natura dell’entità da salvare, [Categorie] o [Produit]. C’è poco codice da fattorizzare. Questo lavoro è quindi lasciato alle classi figlie.


        @Override
    public Response<List<T>> saveEntities(HttpServletRequest request) {
        return new Response<List<T>>(2, new ServerException(1013, new RuntimeException("[saveEntities] not implemented"), simpleClassName).toString(), null);
    }

17.3.6. Il controller [CategorieController]

  

Il controller [CategorieController] gestisce l’elaborazione dei URL relativi alle categorie:


package spring.webjson.server.service;

import java.util.ArrayList;
import java.util.List;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import spring.jdbc.dao.IDao;
import spring.jdbc.entities.Categorie;
import spring.jdbc.entities.Produit;
import spring.jdbc.infrastructure.DaoException;
import spring.webjson.server.entities.CoreCategorie;
import spring.webjson.server.entities.CoreProduit;
import spring.webjson.server.infrastructure.ServerException;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.io.CharStreams;

@RestController
public class CategorieController extends AbstractController<Categorie> {

    @Autowired
    private IDao<Categorie> daoCategorie;

    @Override
    protected IDao<Categorie> getDao() {
        return daoCategorie;
    }

    // locale
    private String simpleClassName = getClass().getSimpleName();

    @RequestMapping(value = "/getAllShortCategories", method = RequestMethod.GET)
    public Response<List<Categorie>> getAllShortCategories() {
        // genitore
        Response<List<Categorie>> response = super.getAllShortEntities();
        // filtri di serializzazione jSON
        context.getBean("jsonMapperShortCategorie", ObjectMapper.class);
        // risposta
        return response;
    }

    @RequestMapping(value = "/getAllLongCategories", method = RequestMethod.GET)
    public Response<List<Categorie>> getAllLongCategories() {
        // genitore
        Response<List<Categorie>> response = super.getAllLongEntities();
        // filtri di serializzazione jSON
        context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
        // risposta
        return response;
    }

    @RequestMapping(value = "/getShortCategoriesById", method = RequestMethod.POST)
    public Response<List<Categorie>> getShortCategoriesById(HttpServletRequest request) {
        // genitore
        Response<List<Categorie>> response = super.getShortEntitiesById(request);
        // filtri di serializzazione jSON
        context.getBean("jsonMapperShortCategorie", ObjectMapper.class);
        // risposta
        return response;
    }

    @RequestMapping(value = "/getShortCategoriesByName", method = RequestMethod.POST)
    public Response<List<Categorie>> getShortCategoriesByName(HttpServletRequest request) {
        // genitore
        Response<List<Categorie>> response = super.getShortEntitiesByName(request);
        // filtri di serializzazione jSON
        context.getBean("jsonMapperShortCategorie", ObjectMapper.class);
        // risposta
        return response;
    }

    @RequestMapping(value = "/getLongCategoriesById", method = RequestMethod.POST)
    public Response<List<Categorie>> getLongCategoriesById(HttpServletRequest request) {
        // genitore
        Response<List<Categorie>> response = super.getLongEntitiesById(request);
        // filtri di serializzazione jSON
        context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
        // risposta
        return response;
    }

    @RequestMapping(value = "/getLongCategoriesByName", method = RequestMethod.POST)
    public Response<List<Categorie>> getLongCategoriesByName(HttpServletRequest request) {
        // genitore
        Response<List<Categorie>> response = super.getLongEntitiesByName(request);
        // filtri di serializzazione jSON
        context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
        // risposta
        return response;
    }

    @RequestMapping(value = "/saveCategories", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<List<CoreCategorie>> saveCategories(HttpServletRequest request) {
    ...
    }

    @RequestMapping(value = "/deleteAllCategories", method = RequestMethod.GET)
    public Response<Void> deleteAllCategories() {
        return super.deleteAllEntities();
    }

    @RequestMapping(value = "/deleteCategoriesById", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<Void> deleteCategoriesById(HttpServletRequest request) {
        return super.deleteEntitiesById(request);
    }

    @RequestMapping(value = "/deleteCategoriesByName", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<Void> deleteCategoriesByName(HttpServletRequest request) {
        return super.deleteEntitiesByName(request);
    }

}
  • riga 26: la classe [CategorieController] estende la classe [AbstractController];
  • riga 25: l'annotazione [@RestController] rende la classe un componente Spring. Questa annotazione indica inoltre che la classe è un servizio web i cui metodi inviano direttamente la risposta al client nel formato jSON;
  • righe 28-29: qui viene iniettato il riferimento al livello [DAO];
  • righe 31-34: ridefinizione del metodo [getDao], dichiarato astratto nella classe padre, il cui scopo è restituire un riferimento al livello [DAO] da utilizzare;

I metodi sono tutti strutturati secondo lo stesso modello:

  • delega dell'elaborazione alla classe padre;
  • inizializzazione del mappatore jSON che provvederà alla serializzazione della risposta;
  • invio della risposta;

Esaminiamo la firma di alcuni URL:


@RequestMapping(value = "/getAllShortCategories", method
 = RequestMethod.GET)
- il URL [/getAllShortCategories] viene chiamato con un GET

@RequestMapping(value = "/getShortCategoriesById",
 method = RequestMethod.POST, consumes =
 "application/json; charset=UTF-8")
- la funzione URL [/getShortCategoriesById] viene chiamata con un POST. Il valore inviato è la stringa jSON delle chiavi primarie delle
categorie desiderate;

@RequestMapping(value = "/getLongCategoriesByName",
 method = RequestMethod.POST, consumes =
 "application/json; charset=UTF-8")
- URL [/getLongCategoriesByName] viene chiamata con un
POST. Il valore inviato è la stringa jSON contenente i nomi delle
categorie desiderate;

Ora analizziamo in dettaglio il metodo [saveCategories], che non segue il formato degli altri metodi:


    @RequestMapping(value = "/saveCategories", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<List<CoreCategorie>> saveCategories(HttpServletRequest request) {
        // si mantengono le categorie
        try {
            // si recupera il valore inviato
            String body = CharStreams.toString(request.getReader());
            // lo deserializziamo
            ObjectMapper mapper = context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
            List<Categorie> categories = mapper.readValue(body, new TypeReference<List<Categorie>>() {
            });
            // si salvano le categorie
            categories = daoCategorie.saveEntities(categories);
            // si restituisce il risultato
            List<CoreCategorie> coreCategories = new ArrayList<CoreCategorie>();
            for (Categorie categorie : categories) {
                CoreCategorie coreCategorie = new CoreCategorie(categorie.getId());
                coreCategories.add(coreCategorie);
                List<Produit> produits = categorie.getProduits();
                if (produits != null) {
                    List<CoreProduit> coreProduits = new ArrayList<CoreProduit>();
                    for (Produit produit : categorie.getProduits()) {
                        coreProduits.add(new CoreProduit(produit.getId()));
                    }
                    coreCategorie.setCoreProduits(coreProduits);
                }
            }
            // risultato
            return new Response<List<CoreCategorie>>(0, null, coreCategories);
        } catch (DaoException e) {
            return new Response<List<CoreCategorie>>(1, e.toString(), null);
        } catch (Exception e) {
            return new Response<List<CoreCategorie>>(2, new ServerException(1020, e, simpleClassName).toString(), null);
        }
}
  • riga 1: la stringa URL [/saveCategories] è accompagnata da un valore inviato. Si tratta della stringa jSON contenente le versioni estese delle categorie da conservare;
  • righe 5-10: le categorie da conservare vengono ricreate a partire dalla stringa jSON. Il collegamento [produit.categorie] che collega un [Produit] al suo [Catégorie] ha il valore null poiché, nella versione lunga di un [Categorie], ogni [Produit] è a sua volta nella sua versione breve senza il campo [categorie]. Ciò non costituisce un problema, poiché il livello [DAO] implementato con JDBC non necessita di questa informazione;
  • riga 12: le categorie vengono salvate. L’elenco delle categorie ricevuto è stato arricchito con le chiavi primarie degli elementi salvati, categorie e prodotti. Nient’altro è cambiato. Anziché restituire l’intero elenco ricevuto, operazione che comporta un costo, restituiremo solo le chiavi primarie degli elementi di tale elenco. A tal fine, si utilizzano le seguenti classi [CoreCategorie] e [CoreProduit]:
  

package spring.webjson.server.entities;

import java.util.List;

public class CoreCategorie {

    // chiave primaria
    private Long id;
    
    // produttori
    public CoreCategorie() {

    }

    public CoreCategorie(Long id) {
        this.id=id;
    }

    // elenco dei prodotti
    private List<CoreProduit> coreProduits;

    // getter e setter
    ...
}
  • riga 8: la chiave primaria di un prodotto;
  • riga 20: le chiavi primarie dei suoi prodotti;

package spring.webjson.server.entities;

public class CoreProduit {

    // chiave primaria
    private Long id;

    // costruttori
    public CoreProduit() {

    }

    public CoreProduit(Long id) {
        this.id = id;
    }

    // getter e setter
...
}
  • riga 6: la chiave primaria di un prodotto;

Torniamo al codice del metodo [saveCategories]:


...            
// si salvano le categorie
            categories = daoCategorie.saveEntities(categories);
            // si restituisce il risultato
            List<CoreCategorie> coreCategories = new ArrayList<CoreCategorie>();
            for (Categorie categorie : categories) {
                CoreCategorie coreCategorie = new CoreCategorie(categorie.getId());
                coreCategories.add(coreCategorie);
                List<Produit> produits = categorie.getProduits();
                if (produits != null) {
                    List<CoreProduit> coreProduits = new ArrayList<CoreProduit>();
                    for (Produit produit : categorie.getProduits()) {
                        coreProduits.add(new CoreProduit(produit.getId()));
                    }
                    coreCategorie.setCoreProduits(coreProduits);
                }
            }
            // risultato
            return new Response<List<CoreCategorie>>(0, null, coreCategories);
...
  • righe 5-17: si costruisce l'elenco dei [CoreCategorie] che verrà restituito al client remoto;
  • riga 19: la risposta viene restituita e serializzata in jSON;

17.3.7. Gestione dei filtri jSON

Per ogni metodo di un controller, ci sono due momenti per la serializzazione/deserializzazione jSON:

  • la deserializzazione del valore inserito: in questo caso viene gestita in modo esplicito;
  • la serializzazione del risultato: in questo caso viene gestita implicitamente;

Iniziamo con la deserializzazione del valore inserito in [CategorieController.saveCategories]:


    @RequestMapping(value = "/saveCategories", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<List<CoreCategorie>> saveCategories(HttpServletRequest request) {
        // si salvano le categorie
        try {
            // si recupera il valore inviato
            String body = CharStreams.toString(request.getReader());
            // lo deserializziamo
            ObjectMapper mapper = context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
            List<Categorie> categories = mapper.readValue(body, new TypeReference<List<Categorie>>() {
});
  • riga 8: nel contesto Spring si recupera un mappatore configurato per gestire il filtro jSON [jsonMapperLongCategorie]. Torniamo alla definizione di questo mappatore nella classe di configurazione [WebConfig]:

// -------------------------------- configurazione dei filtri [json]
    // mappatura jSON
    @Bean
    public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
        final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        final ObjectMapper objectMapper = new ObjectMapper();
        converter.setObjectMapper(objectMapper);
        return converter;
    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(mappingJackson2HttpMessageConverter());
        super.configureMessageConverters(converters);
    }

    // filtri jSON
    @Bean
    public ObjectMapper jsonMapper(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
        return mappingJackson2HttpMessageConverter.getObjectMapper();
    }

    @Bean
    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    ObjectMapper jsonMapperShortCategorie(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
        ObjectMapper jsonMapper = jsonMapper(mappingJackson2HttpMessageConverter);
        jsonMapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterCategorie",
                SimpleBeanPropertyFilter.serializeAllExcept("produits")));
        return jsonMapper;
    }

    @Bean
    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    ObjectMapper jsonMapperLongCategorie(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
        ObjectMapper jsonMapper = jsonMapper(mappingJackson2HttpMessageConverter);
        jsonMapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterCategorie",
                SimpleBeanPropertyFilter.serializeAllExcept()).addFilter("jsonFilterProduit",
                SimpleBeanPropertyFilter.serializeAllExcept("categorie")));
        return jsonMapper;
    }

  • righe 32-40: il mappatore jSON [jsonMapperLongCategorie] recuperato dalla classe [CategorieController];
  • riga 35: questo mappatore viene restituito dal metodo [jsonMapper] delle righe 18-21;
  • righe 18-21: il metodo [jsonMapper] restituisce il mappatore jSON del convertitore [MappingJackson2HttpMessageConverter] delle righe 3-9;

In altre parole, il mappatore jSON recuperato dalla riga 4 qui sotto in [CategorieController.saveCategories]:


            // si recupera il valore inviato
            String body = CharStreams.toString(request.getReader());
            // lo deserializziamo
            ObjectMapper mapper = context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
            List<Categorie> categories = mapper.readValue(body, new TypeReference<List<Categorie>>() {
});

è il convertitore utilizzato di default da Spring MVC per deserializzare il valore inviato dal client e serializzare il risultato che gli viene inviato. Nelle righe precedenti non c'è stata alcuna deserializzazione implicita del valore inviato. Per farlo, sarebbe stato necessario scrivere:


    @RequestMapping(value = "/saveCategories", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<List<CoreCategorie>> saveCategories(@RequestBody List<Categorie> categories) {

In questo caso, ci sarebbe stata una deserializzazione automatica del valore inviato nel parametro [categories]. Tuttavia, si presentava il problema del filtro [jsonFilterCategorie] presente nelle entità [Categorie]. È necessario configurarlo. Per questo motivo abbiamo optato per una deserializzazione esplicita (righe 4-5). Il secondo punto da considerare è che il mappatore della riga 4 (che è quello utilizzato di default da Spring MVC) è adatto anche per la serializzazione del risultato [Response<List<CoreCategorie>]. Infatti, l’entità [CoreCategorie] non dispone di un filtro jSON. Non è quindi necessario configurare il mappatore jSON ottenuto con un filtro aggiuntivo. In questo caso, avrà luogo una serializzazione implicita della risposta inviata al cliente.

17.3.8. Il controller [ProduitController]

  

Il controller [ProduitController] gestisce l’elaborazione dei URL relativi ai prodotti. Il suo codice è analogo a quello del controller [CategorieController]:


package spring.webjson.server.service;

import java.util.ArrayList;
import java.util.List;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import spring.jdbc.dao.IDao;
import spring.jdbc.entities.Produit;
import spring.jdbc.infrastructure.DaoException;
import spring.webjson.server.entities.CoreProduit;
import spring.webjson.server.infrastructure.ServerException;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.io.CharStreams;

@RestController
public class ProduitController extends AbstractController<Produit> {

    @Autowired
    private IDao<Produit> daoProduit;

    @Override
    protected IDao<Produit> getDao() {
        return daoProduit;
    }

    // locale
    private String simpleClassName = getClass().getSimpleName();

    @RequestMapping(value = "/getAllShortProduits", method = RequestMethod.GET)
    public Response<List<Produit>> getAllShortProduits() {
        // genitore
        Response<List<Produit>> response = super.getAllShortEntities();
        // filtri di serializzazione jSON
        context.getBean("jsonMapperShortProduit", ObjectMapper.class);
        // risposta
        return response;
    }

    @RequestMapping(value = "/getAllLongProduits", method = RequestMethod.GET)
    public Response<List<Produit>> getAllLongProduits() {
        // genitore
        Response<List<Produit>> response = super.getAllLongEntities();
        // filtri di serializzazione jSON
        context.getBean("jsonMapperLongProduit", ObjectMapper.class);
        // risposta
        return response;
    }

    @RequestMapping(value = "/getShortProduitsById", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<List<Produit>> getShortProduitsById(HttpServletRequest request) {
        // genitore
        Response<List<Produit>> response = super.getShortEntitiesById(request);
        // filtri di serializzazione jSON
        context.getBean("jsonMapperShortProduit", ObjectMapper.class);
        // risposta
        return response;
    }

    @RequestMapping(value = "/getShortProduitsByName", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<List<Produit>> getShortProduitsByName(HttpServletRequest request) {
        // genitore
        Response<List<Produit>> response = super.getShortEntitiesByName(request);
        // filtri di serializzazione jSON
        context.getBean("jsonMapperShortProduit", ObjectMapper.class);
        // risposta
        return response;
    }

    @RequestMapping(value = "/getLongProduitsById", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<List<Produit>> getLongProduitsById(HttpServletRequest request) {
        // genitore
        Response<List<Produit>> response = super.getLongEntitiesById(request);
        // filtri di serializzazione jSON
        context.getBean("jsonMapperLongProduit", ObjectMapper.class);
        // risposta
        return response;
    }

    @RequestMapping(value = "/getLongProduitsByName", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<List<Produit>> getLongProduitsByName(HttpServletRequest request) {
        // genitore
        Response<List<Produit>> response = super.getLongEntitiesByName(request);
        // filtri di serializzazione jSON
        context.getBean("jsonMapperLongProduit", ObjectMapper.class);
        // risposta
        return response;
    }

    @RequestMapping(value = "/saveProduits", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<List<CoreProduit>> saveProduits(HttpServletRequest request) {
        ...
    }

    @RequestMapping(value = "/deleteAllProduits", method = RequestMethod.GET)
    public Response<Void> deleteAllProduits() {
        return super.deleteAllEntities();
    }

    @RequestMapping(value = "/deleteProduitsById", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<Void> deleteProduitsById(HttpServletRequest request) {
        return super.deleteEntitiesById(request);
    }

    @RequestMapping(value = "/deleteProduitsByName", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<Void> deleteProduitsByName(HttpServletRequest request) {
        return super.deleteEntitiesByName(request);
    }

}

Solo il metodo [saveProduits] presenta una struttura diversa rispetto agli altri metodi:


@RequestMapping(value = "/saveProduits", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<List<CoreProduit>> saveProduits(HttpServletRequest request) {
        try {
            // si recupera il valore inviato
            String body = CharStreams.toString(request.getReader());
            // lo deserializziamo
            ObjectMapper mapper = context.getBean("jsonMapperShortProduit", ObjectMapper.class);
            List<Produit> produits = mapper.readValue(body, new TypeReference<List<Produit>>() {
            });
            // si salvano i prodotti
            produits = daoProduit.saveEntities(produits);
            List<CoreProduit> coreProduits = new ArrayList<CoreProduit>();
            for (Produit produit : produits) {
                coreProduits.add(new CoreProduit(produit.getId()));
            }
            // si restituisce la risposta
            return new Response<List<CoreProduit>>(0, null, coreProduits);
        } catch (DaoException e) {
            return new Response<List<CoreProduit>>(1, e.toString(), null);
        } catch (Exception e) {
            return new Response<List<CoreProduit>>(2, new ServerException(1021, e, simpleClassName).toString(), null);
        }
}
  • righe 4-9: a partire dalla stringa jSON ricevuta, si ricostruisce l'elenco dei [Produit] da conservare. Poiché la stringa jSON ricevuta corrisponde alle versioni brevi dei prodotti, il campo [categorie] di questi ultimi è pari a null. Ancora una volta, il livello DAO / JDBC non necessita di questa informazione;
  • riga 11: i prodotti vengono salvati;
  • righe 12-15: viene costruito l’elenco dei [CoreProduit] da restituire;
  • riga 18: viene restituita la risposta che verrà serializzata (serializzazione implicita eseguita da Spring MVC) dal mappatore della riga 7 prima di essere inviata al client remoto (cfr. discussione al paragrafo 17.3.7);

17.3.9. La classe di esecuzione del servizio web / jSON

  

La classe [Boot] è la classe eseguibile del progetto:


package spring.webjson.server.boot;

import org.springframework.boot.SpringApplication;

import spring.webjson.server.config.AppConfig;

public class Boot {

    public static void main(String[] args) {
        SpringApplication.run(AppConfig.class, args);
    }
}
  • riga 10: viene eseguito il metodo statico [SpringApplication.run]. La classe [SpringApplication] è una classe del progetto [spring Boot] (riga 3). Le vengono passati due parametri:
    • [AppConfig.class]: la classe che configura l’intera applicazione;
    • [args]: eventuali argomenti passati al metodo [main] alla riga 9. Questo parametro non viene utilizzato in questo caso;

Quando si esegue questa classe, si ottengono i seguenti log:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.2.3.RELEASE)

11:34:08.661 [main] INFO  spring.webjson.server.boot.Boot - Starting Boot on Gportpers3 with PID 6796 (started by ST in D:\data\istia-1415\spring data\dvp\dvp-spring-database-05\spring-database-generic\spring-webjson\spring-webjson-server-jdbc-generic)
11:34:08.700 [main] INFO  o.s.b.c.e.AnnotationConfigEmbeddedWebApplicationContext - Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@2df32bf7: startup date [Mon Jun 08 11:34:08 CEST 2015]; root of context hierarchy
11:34:08.916 [main] INFO  o.s.b.f.s.DefaultListableBeanFactory - Overriding bean definition for bean 'jsonMapper': replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=generic.jdbc.config.ConfigJdbc; factoryMethodName=jsonMapper; initMethodName=null; destroyMethodName=(inferred); defined in class generic.jdbc.config.ConfigJdbc] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=spring.webjson.server.config.WebConfig; factoryMethodName=jsonMapper; initMethodName=null; destroyMethodName=(inferred); defined in class spring.webjson.server.config.WebConfig]
11:34:08.917 [main] INFO  o.s.b.f.s.DefaultListableBeanFactory - Overriding bean definition for bean 'jsonMapperLongCategorie': replacing [Root bean: class [null]; scope=prototype; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=generic.jdbc.config.ConfigJdbc; factoryMethodName=jsonMapperLongCategorie; initMethodName=null; destroyMethodName=(inferred); defined in class generic.jdbc.config.ConfigJdbc] with [Root bean: class [null]; scope=prototype; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=spring.webjson.server.config.WebConfig; factoryMethodName=jsonMapperLongCategorie; initMethodName=null; destroyMethodName=(inferred); defined in class spring.webjson.server.config.WebConfig]
11:34:08.918 [main] INFO  o.s.b.f.s.DefaultListableBeanFactory - Overriding bean definition for bean 'jsonMapperShortProduit': replacing [Root bean: class [null]; scope=prototype; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=generic.jdbc.config.ConfigJdbc; factoryMethodName=jsonMapperShortProduit; initMethodName=null; destroyMethodName=(inferred); defined in class generic.jdbc.config.ConfigJdbc] with [Root bean: class [null]; scope=prototype; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=spring.webjson.server.config.WebConfig; factoryMethodName=jsonMapperShortProduit; initMethodName=null; destroyMethodName=(inferred); defined in class spring.webjson.server.config.WebConfig]
11:34:08.919 [main] INFO  o.s.b.f.s.DefaultListableBeanFactory - Overriding bean definition for bean 'jsonMapperShortCategorie': replacing [Root bean: class [null]; scope=prototype; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=generic.jdbc.config.ConfigJdbc; factoryMethodName=jsonMapperShortCategorie; initMethodName=null; destroyMethodName=(inferred); defined in class generic.jdbc.config.ConfigJdbc] with [Root bean: class [null]; scope=prototype; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=spring.webjson.server.config.WebConfig; factoryMethodName=jsonMapperShortCategorie; initMethodName=null; destroyMethodName=(inferred); defined in class spring.webjson.server.config.WebConfig]
11:34:08.919 [main] INFO  o.s.b.f.s.DefaultListableBeanFactory - Overriding bean definition for bean 'jsonMapperLongProduit': replacing [Root bean: class [null]; scope=prototype; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=generic.jdbc.config.ConfigJdbc; factoryMethodName=jsonMapperLongProduit; initMethodName=null; destroyMethodName=(inferred); defined in class generic.jdbc.config.ConfigJdbc] with [Root bean: class [null]; scope=prototype; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=spring.webjson.server.config.WebConfig; factoryMethodName=jsonMapperLongProduit; initMethodName=null; destroyMethodName=(inferred); defined in class spring.webjson.server.config.WebConfig]
11:34:09.409 [main] INFO  o.s.b.c.e.t.TomcatEmbeddedServletContainer - Tomcat initialized with port(s): 8081 (http)
11:34:09.641 [main] INFO  o.a.catalina.core.StandardService - Starting service Tomcat
11:34:09.642 [main] INFO  o.a.catalina.core.StandardEngine - Starting Servlet Engine: Apache Tomcat/8.0.20
11:34:09.778 [localhost-startStop-1] INFO  o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring embedded WebApplicationContext
11:34:09.778 [localhost-startStop-1] INFO  o.s.web.context.ContextLoader - Root WebApplicationContext: initialization completed in 1081 ms
11:34:09.839 [localhost-startStop-1] INFO  o.s.b.c.e.ServletRegistrationBean - Mapping servlet: 'dispatcherServlet' to [/*]
11:34:10.558 [main] INFO  o.h.validator.internal.util.Version - HV000001: Hibernate Validator 5.1.3.Final
11:34:10.654 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerAdapter - Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@2df32bf7: startup date [Mon Jun 08 11:34:08 CEST 2015]; root of context hierarchy
11:34:10.745 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/saveCategories],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.util.List<spring.webjson.server.entities.CoreCategorie>> spring.webjson.server.service.CategorieController.saveCategories(javax.servlet.http.HttpServletRequest)
11:34:10.745 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/deleteCategoriesById],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.lang.Void> spring.webjson.server.service.CategorieController.deleteCategoriesById(javax.servlet.http.HttpServletRequest)
11:34:10.745 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/getShortCategoriesByName],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.util.List<spring.jdbc.entities.Categorie>> spring.webjson.server.service.CategorieController.getShortCategoriesByName(javax.servlet.http.HttpServletRequest)
11:34:10.746 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/getAllLongCategories],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.util.List<spring.jdbc.entities.Categorie>> spring.webjson.server.service.CategorieController.getAllLongCategories()
11:34:10.746 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/getLongCategoriesById],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.util.List<spring.jdbc.entities.Categorie>> spring.webjson.server.service.CategorieController.getLongCategoriesById(javax.servlet.http.HttpServletRequest)
11:34:10.746 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/deleteCategoriesByName],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.lang.Void> spring.webjson.server.service.CategorieController.deleteCategoriesByName(javax.servlet.http.HttpServletRequest)
11:34:10.746 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/getShortCategoriesById],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.util.List<spring.jdbc.entities.Categorie>> spring.webjson.server.service.CategorieController.getShortCategoriesById(javax.servlet.http.HttpServletRequest)
11:34:10.746 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/getAllShortCategories],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.util.List<spring.jdbc.entities.Categorie>> spring.webjson.server.service.CategorieController.getAllShortCategories()
11:34:10.747 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/getLongCategoriesByName],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.util.List<spring.jdbc.entities.Categorie>> spring.webjson.server.service.CategorieController.getLongCategoriesByName(javax.servlet.http.HttpServletRequest)
11:34:10.747 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/deleteAllCategories],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.lang.Void> spring.webjson.server.service.CategorieController.deleteAllCategories()
11:34:10.748 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/saveProduits],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.util.List<spring.webjson.server.entities.CoreProduit>> spring.webjson.server.service.ProduitController.saveProduits(javax.servlet.http.HttpServletRequest)
11:34:10.749 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/getShortProduitsById],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.util.List<spring.jdbc.entities.Produit>> spring.webjson.server.service.ProduitController.getShortProduitsById(javax.servlet.http.HttpServletRequest)
11:34:10.749 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/getAllLongProduits],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.util.List<spring.jdbc.entities.Produit>> spring.webjson.server.service.ProduitController.getAllLongProduits()
11:34:10.749 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/getShortProduitsByName],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.util.List<spring.jdbc.entities.Produit>> spring.webjson.server.service.ProduitController.getShortProduitsByName(javax.servlet.http.HttpServletRequest)
11:34:10.749 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/getAllShortProduits],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.util.List<spring.jdbc.entities.Produit>> spring.webjson.server.service.ProduitController.getAllShortProduits()
11:34:10.749 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/deleteProduitsByName],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.lang.Void> spring.webjson.server.service.ProduitController.deleteProduitsByName(javax.servlet.http.HttpServletRequest)
11:34:10.750 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/getLongProduitsByName],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.util.List<spring.jdbc.entities.Produit>> spring.webjson.server.service.ProduitController.getLongProduitsByName(javax.servlet.http.HttpServletRequest)
11:34:10.750 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/deleteProduitsById],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.lang.Void> spring.webjson.server.service.ProduitController.deleteProduitsById(javax.servlet.http.HttpServletRequest)
11:34:10.750 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/deleteAllProduits],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.lang.Void> spring.webjson.server.service.ProduitController.deleteAllProduits()
11:34:10.750 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/getLongProduitsById],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.server.service.Response<java.util.List<spring.jdbc.entities.Produit>> spring.webjson.server.service.ProduitController.getLongProduitsById(javax.servlet.http.HttpServletRequest)
11:34:10.809 [main] INFO  o.a.coyote.http11.Http11NioProtocol - Initializing ProtocolHandler ["http-nio-8081"]
11:34:10.826 [main] INFO  o.a.coyote.http11.Http11NioProtocol - Starting ProtocolHandler ["http-nio-8081"]
11:34:10.860 [main] INFO  o.a.tomcat.util.net.NioSelectorPool - Using a shared selector for servlet write/read
11:34:11.733 [main] INFO  o.s.b.c.e.t.TomcatEmbeddedServletContainer - Tomcat started on port(s): 8081 (http)
11:34:11.934 [main] INFO  spring.webjson.server.boot.Boot - Started Boot in 3.533 seconds (JVM running for 4.137)
11:34:20.382 [http-nio-8081-exec-1] INFO  o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring FrameworkServlet 'dispatcherServlet'
11:34:20.384 [http-nio-8081-exec-1] INFO  o.s.web.servlet.DispatcherServlet - FrameworkServlet 'dispatcherServlet': initialization started
11:34:20.410 [http-nio-8081-exec-1] INFO  o.s.web.servlet.DispatcherServlet - FrameworkServlet 'dispatcherServlet': initialization completed in 26 ms
11:34:33.103 [http-nio-8081-exec-8] INFO  o.s.b.f.xml.XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [org/springframework/jdbc/support/sql-error-codes.xml]
11:34:33.168 [http-nio-8081-exec-8] INFO  o.s.j.support.SQLErrorCodesFactory - SQLErrorCodes loaded: [DB2, Derby, H2, HSQL, Informix, MS-SQL, MySQL, Oracle, PostgreSQL, Sybase, Hana]
  • righe 11-15: vengono individuati i bean che definiscono i filtri jSON. Essi ridefiniscono i bean con lo stesso nome individuati nel progetto di configurazione del livello JDBC;
  • righe 17-18: avvio del server Tomcat che eseguirà il servizio web / jSON;
  • righe 19-21: viene inizializzato il contesto Spring MVC;
  • righe 24-43: vengono individuati i URL esposti;

17.3.10. Test del servizio web / jSON

Per eseguire i test, utilizziamo il client [Advanced Rest Client] (cfr. paragrafo 23.11) per interrogare le URL esposte dal servizio web / jSON (ovviamente il servizio web / jSON deve essere avviato, così come il SGBD). Per avere un database popolato, eseguiamo la configurazione di esecuzione denominata [spring-jdbc-generic-04-fillDataBase] che popola il database con 5 categorie e 10 prodotti:

 
  • in [1-3], richiediamo URL e [/getAllLongCategories] tramite un comando HTTP e GET;

Otteniamo la seguente risposta:

  • in [1], la richiesta HTTP del client;
  • in [2], la risposta HTTP del server;
  • in [3], lo stato [200 OK] indica che il server ha elaborato correttamente la richiesta;
  • in [4], la risposta jSON del server;

La risposta completa jSON è la seguente:


{"status":0,"exception":null,"body":[{"id":1880,"version":1,"nom":"categorie[0]","produits":[{"id":9072,"version":1,"nom":"produit[0,0]","idCategorie":1880,"prix":100.0,"description":"desc[0,0]"},{"id":9073,"version":1,"nom":"produit[0,1]","idCategorie":1880,"prix":101.0,"description":"desc[0,1]"},{"id":9074,"version":1,"nom":"produit[0,2]","idCategorie":1880,"prix":102.0,"description":"desc[0,2]"},{"id":9075,"version":1,"nom":"produit[0,3]","idCategorie":1880,"prix":103.0,"description":"desc[0,3]"},{"id":9076,"version":1,"nom":"produit[0,4]","idCategorie":1880,"prix":104.0,"description":"desc[0,4]"}]},{"id":1881,"version":1,"nom":"categorie[1]","produits":[{"id":9077,"version":1,"nom":"produit[1,0]","idCategorie":1881,"prix":110.00000000000001,"description":"desc[1,0]"},{"id":9078,"version":1,"nom":"produit[1,1]","idCategorie":1881,"prix":111.00000000000001,"description":"desc[1,1]"},{"id":9079,"version":1,"nom":"produit[1,2]","idCategorie":1881,"prix":112.00000000000001,"description":"desc[1,2]"},{"id":9080,"version":1,"nom":"produit[1,3]","idCategorie":1881,"prix":112.99999999999999,"description":"desc[1,3]"},{"id":9081,"version":1,"nom":"produit[1,4]","idCategorie":1881,"prix":114.00000000000001,"description":"desc[1,4]"}]}]}
  • status:0 significa che non si sono verificati errori lato server;
  • exception: null significa che non vi è alcun messaggio di errore;
  • body: è il corpo della risposta, in questo caso l'elenco delle categorie con i relativi prodotti. Ci sono due categorie, ciascuna con 5 prodotti;

Aggiungeremo alla categoria [categorie1] il prodotto [produit15]. A tal fine utilizzeremo il codice URL [/saveProduits], che attende la stringa jSON dei prodotti da salvare (inserimento/modifica). Tale stringa sarà la seguente:

[{"id":null,"version":null,"nom":"produit15","idCategorie":1881,"prix":111.0,"description":"desc15"}]}]

La richiesta al servizio web / jSON viene effettuata nel modo seguente:

  • in [1], l'operazione URL richiesta;
  • in [2], viene richiesta tramite un'operazione POST;
  • in [3], la stringa jSON inviata;
  • in [4], si comunica al server che gli verrà inviato jSON;

La risposta del server è la seguente:

  • in [1], si è ottenuto un elenco di [CoreProduit] con le relative chiavi primarie. In questo caso, si è ottenuto un elenco di un elemento con la chiave primaria del prodotto appena inserito nel database;

Ora richiediamo la versione estesa della categoria denominata [categorie[1]:

  • in [1], la URL richiesta;
  • in [2], si crea un POST;
  • in [3,4], il valore inviato è una stringa jSON. Questa rappresenta l'elenco dei nomi delle categorie di cui si desidera la versione estesa;

Otteniamo il seguente risultato:

  • in [5], la categoria [categorie[1]] ora ha un sesto prodotto;

Eliminiamo ora questo prodotto:

  • in [1], il URL richiesto;
  • in [2], si crea un POST;
  • in [3-4], si invia una stringa jSON che rappresenta l'elenco delle chiavi primarie dei prodotti che si desidera eliminare;

Il risultato ottenuto è il seguente:

 
  • [status:0] indica che l’eliminazione è avvenuta correttamente;

Ora, richiediamo il prodotto [produit[1,5]] per verificare che sia stato effettivamente eliminato:

 

Otteniamo il seguente risultato:

 
  • [status:0] indica che l'operazione si è svolta senza eccezioni;
  • [body:[0]] indica che [body] è un elenco di 0 elementi. L'entità [produit[1,5]] è stata quindi effettivamente eliminata;

Tutte le operazioni [GET] possono essere eseguite in un semplice browser:

Si invita il lettore a provare le altre operazioni URL del servizio web / jSON.