Skip to content

17. Rendere accessibile un database sul Web

17.1. Architettura del servizio Web/JSON

Implementeremo la seguente architettura:

  • in [1], i livelli [DAO, [JPA], JDBC] sono implementati utilizzando una delle 24 configurazioni presentate nei capitoli precedenti, in particolare nel 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 al livello [4];

Ci affideremo ai seguenti progetti:

  • il progetto [sgbd-config-jdbc], che configura il livello JDBC di uno dei sei DBMS;
  • il progetto [sgbd-config-jpa-*], che configura il livello JPA del DBMS selezionato 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] [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 per tutte le 24 configurazioni di servizi web;

17.2. Configurazione dell'ambiente di sviluppo

Lavoreremo con i seguenti componenti:

  • database MySQL 5.6.25;
  • implementazione JPA di Hibernate;

Importare i seguenti progetti in STS:

  
  • I progetti [spring-webjson-*] si trovano nella cartella [<examples>\spring-database-generic\spring-webjson];
  • Premere [Alt-F5] e quindi rigenerare tutti i progetti sopra indicati;

Per verificare che l'ambiente di sviluppo sia installato correttamente, procedere come segue:

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

quindi:

  • avvia il client per questo servizio web utilizzando la configurazione di runtime [spring-webjson-client-generic], che è un test JUnit:

Il test dovrebbe avere esito positivo:

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

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

Il test dovrebbe avere esito positivo:

 

17.3. Implementazione di servizi web / JSON / JDBC

Ci concentreremo innanzitutto sulla seguente architettura:

in cui il livello [DAO] [1] comunica direttamente con il livello JDBC del DBMS.

17.3.1. Il progetto Eclipse per il servizio web

Il progetto Eclipse per il 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>
        <!-- web layer -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- layer [DAO] -->
        <dependency>
            <groupId>dvp.spring.database</groupId>
            <artifactId>spring-jdbc-generic-04</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>
    <!-- plugins -->
    <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 principale;
  • 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 creare un servizio web / JSON. Include anche librerie non necessarie. Sarebbe quindi necessaria una configurazione più precisa, ma questa configurazione è utile per iniziare.

Le dipendenze introdotte da questa configurazione sono le seguenti:

1
  • In [1], possiamo vedere che Eclipse ha rilevato la dipendenza dall'archivio del progetto [spring-jdbc-generic-04];

Le dipendenze sopra indicate riguardano sia il livello [DAO] che il 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]

Il ruolo principale della classe [WebConfig] è quello di configurare:

  • il server Tomcat su cui verrà distribuito il servizio web;
  • i filtri JSON per la serializzazione e la deserializzazione degli oggetti [Product] e [Category]:

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 {
 
    // -------------------------------- layer configuration [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);
    }
 
    // -------------------------------- configuration filters [json]
    ...
}
  • riga 25: la classe è una classe di configurazione Spring;
  • riga 26: l'annotazione [@EnableWebMvc] indica che il livello web è implementato utilizzando Spring MVC. Ciò attiverà configurazioni implicite che non dovremo impostare;
  • righe 30-31: iniezione del contesto Spring dell'applicazione;
  • righe 33–37: definizione del bean [dispatcherServlet], che, nelle applicazioni Spring MVC, funge da [FrontController], il cui ruolo è quello di instradare le richieste dei client al controller in grado di gestirle;
  • righe 39–42: il servlet del servizio web viene registrato insieme agli URL che gestisce. Qui abbiamo scritto [/*], che significa tutti gli URL;
  • Righe 44–47: definizione del bean [embeddedServletContainerFactory], che specifica il server web da utilizzare. In questo caso, sarà il server web Tomcat [http://tomcat.apache.org/]. È possibile utilizzare anche il server Jetty [http://www.eclipse.org/jetty/]. Entrambi sono server incorporati inclusi nelle dipendenze di Maven. Quando [Spring Boot] avvia il progetto, avvia automaticamente il server web specificato nella configurazione e distribuisce il servizio o l'applicazione web su di esso;

I filtri JSON sono configurati come segue:


package spring.webjson.server.config;
 
import java.util.List;
...
 
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
 
    // -------------------------------- layer configuration [web]
...
    // -------------------------------- configuration filters [json]
    // mapping 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);
    }
 
    // filters 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 sovrascrivere determinati metodi di questa classe. In questo caso, vogliamo sovrascrivere il metodo [configureMessageConverters] nelle righe [22–26] (notare 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 da una riga di testo ricevuta (deserializzazione) e di creare una riga di testo da un oggetto (serializzazione). In questo caso, le righe di testo saranno stringhe JSON. Faremo quindi riferimento alla serializzazione/deserializzazione JSON;
  • riga 23: il metodo [configureMessageConverters] accetta 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 [MappingJackson2HttpMessageConverter];
  • righe [29-32]: definiscono il mappatore JSON creato alla riga 17 come bean Spring. Questo 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)] garantisce che il bean qui definito non sia un singleton. Ogni volta che viene richiesto dal contesto, il metodo [jsonMapperCategoryWithoutProducts] verrà rieseguito. Ciò è necessario in questo caso perché stiamo definendo quattro filtri JSON. Tuttavia, solo uno dovrebbe essere attivo in un dato momento. Assegnando al bean l'ambito [ConfigurableBeanFactory.SCOPE_PROTOTYPE], ci assicuriamo che il metodo venga rieseguito e che il filtro precedente venga sostituito da quello nuovo;
  • Per comprendere questi filtri, ricordiamo che nel livello [DAO]:
    • l'entità [Product] è stata annotata con l'annotazione [jsonFilterProduct];
    • l'entità [Category] è stata annotata con [jsonFilterCategory];

Dobbiamo quindi definire filtri con questi nomi.

  • righe [34-41]: definiscono un filtro chiamato [jsonMapperShortCategorie] che fornisce la rappresentazione JSON di una categoria senza i suoi prodotti;
  • righe [43-51]: definiscono un filtro chiamato [jsonMapperLongCategory] che fornisce la rappresentazione JSON di una categoria insieme ai suoi prodotti;
  • righe [53-60]: definiscono un filtro chiamato [jsonMapperShortProduct] che fornisce la rappresentazione JSON di un prodotto senza la sua categoria;
  • righe [62-70]: definiscono un filtro chiamato [jsonMapperLongProduct] che fornisce la rappresentazione JSON di un prodotto con la sua 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: importiamo i bean dal livello [DAO / JDBC] e quelli definiti dalla classe [WebConfig]. Tutti i bean del livello [DAO] saranno quindi disponibili nell'applicazione web/JSON;
  • riga 8: specifica i pacchetti in cui si trovano altri bean Spring;

17.3.3. L'[ServerException]

  

Proprio come nei capitoli precedenti, in cui il livello [DAO] generava un'eccezione [DaoException] non gestita, il livello [web] genererà un'eccezione [ServerException] non gestita:


package spring.webjson.server.infrastructure;
 
import generic.jdbc.infrastructure.UncheckedException;
 
public class ServerException extends UncheckedException {
 
    private static final long serialVersionUID = 1L;
 
    // manufacturers
    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 controller

  

Qui avremo due controller:

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

Gli URL esposti dai controller corrispondono in modo biunivoco ai metodi delle interfacce [DaoCategorie] e [DaoProduit] nel livello [DAO]:

Quindi, come mostrato sopra:

  • il metodo web [deleteAllCategories] chiamerà 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. Gli URL esposti dal [CategoryController]

URL
Metodo

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

public Response<List<CoreCategory>>
 saveCategories
(HttpServletRequest richiesta)
====
Il metodo riceve le categorie da salvare tramite una richiesta POST.
Queste sono accessibili nella [HttpServletRequest
 ]. Le categorie vengono salvate dal
metodo [saveEntities] del livello [DaoCategorie]. Vengono salvati solo
 degli oggetti salvati (categorie / prodotti) vengono
 restituiti al client.

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

public Response<Void> deleteAllCategories()
====
L'URL non ha parametri. Le categorie vengono eliminate dal
 il metodo [deleteAllEntities] nel livello [DaoCategorie].

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

public Response<Void> deleteCategoriesById
(HttpServletRequest request)
====
Il metodo riceve le chiavi primarie delle
 categorie da eliminare. Queste sono accessibili nell'
 oggetto [HttpServletRequest request]. Le categorie vengono
 eliminate utilizzando il metodo [deleteEntitiesById] del
 livello [DaoCategorie].

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

public Response<Void> deleteCategoriesByName
(HttpServletRequest request)
====
Il metodo riceve i nomi delle categorie da
eliminate. Questi sono accessibili nella
 [HttpServletRequest request]. Le categorie vengono eliminate
 utilizzando il metodo [deleteEntitiesByname] dal
livello [DaoCategorie].

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

public Response<List<Category>> getAllShortCategories()
====
L'URL non ha parametri. Le categorie abbreviate vengono
recuperate utilizzando il metodo [getAllShortEntities] dal
 [DaoCategorie].

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

public Response<List<Category>> getAllLongCategories()
====
L'URL non ha parametri. Le categorie estese vengono
 recuperate utilizzando il metodo [getAllLongEntities] dal
[DaoCategorie].

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

public Response<List<Category>> getLongCategoriesById(HttpServletRequest request)
====
Il metodo riceve le chiavi primarie delle
 categorie desiderate tramite una richiesta POST. Queste sono accessibili in
 [HttpServletRequest request]. Le categorie complete vengono
 recuperate utilizzando il metodo [getLongEntitiesById] del
 [DaoCategorie].

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

public Response<List<Category>> getLongCategoriesByName
(HttpServletRequest request)
====
Il metodo riceve i nomi delle categorie desiderate tramite una richiesta POST
 . Questi sono accessibili in
 [HttpServletRequest request]. Le categorie complete vengono
recuperate utilizzando il metodo [getLongEntitiesByName] del
 [DaoCategorie].

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

public Response<List<Category>> getShortCategoriesByName
(HttpServletRequest request)
====
Il metodo riceve i nomi delle categorie desiderate tramite una richiesta POST
 . Questi sono accessibili in
 [HttpServletRequest request]. Le categorie abbreviate vengono
 recuperate dal metodo [getShortEntitiesByName] del
 livello [DaoCategorie].

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

public Response<List<Category>> getShortCategoriesById
(HttpServletRequest request)
====
Il metodo riceve le chiavi primarie delle
 categorie desiderate tramite una richiesta POST. Queste sono accessibili in
 [HttpServletRequest request]. I nomi abbreviati delle categorie sono
 recuperati tramite il metodo [getShortEntitiesById] della
 [DaoCategorie].

17.3.4.2. Gli URL esposti dal [ProductController]

URL
Metodo

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

public Response<List<CoreProduct>> saveProducts
(HttpServletRequest request)
====
Il metodo riceve i prodotti da salvare tramite una richiesta POST. Questi
sono accessibili nella [HttpServletRequest
 ]. I prodotti vengono salvati dal
 metodo [saveEntities] nel livello [DaoProduit]. Solo i
 vengono restituiti al client.

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

public Response<Void> deleteAllProducts()
====
L'URL non ha parametri. I prodotti vengono eliminati dal
 metodo [deleteAllEntities] nel livello [DaoProduit] di .

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

public Response<Void> deleteProductsById
(HttpServletRequest request)
====
Il metodo riceve le chiavi primarie dei prodotti
 da eliminare tramite una richiesta POST. Queste sono accessibili in
 [HttpServletRequest request]. I prodotti vengono eliminati
 utilizzando il metodo [deleteEntitiesById] del
[DaoProduit].

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

public Response<Void> deleteProductsByName
(HttpServletRequest request)
====
Il metodo riceve i nomi dei prodotti da
 eliminati. Questi sono accessibili nella
 [HttpServletRequest request]. I prodotti vengono eliminati
 utilizzando il metodo [deleteEntitiesByname] del
 livello [DaoProduit].

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

public Response<List<Product>> getAllShortProducts()
====
L'URL non contiene parametri. Le categorie abbreviate vengono
 recuperate utilizzando il metodo [getAllShortEntities] dal
 livello [DaoProduct].

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

public Response<List<Product>> getAllLongProducts()
====
L'URL non ha parametri. I prodotti lunghi vengono recuperati
 utilizzando il metodo [getAllLongEntities] dal
 livello [DaoProduct].

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

public Response<List<Product>> getLongProductsById
(HttpServletRequest request)
====
Il metodo riceve le chiavi primarie dei prodotti desiderati tramite una richiesta POST
 . Queste sono accessibili in
 [HttpServletRequest request]. I prodotti long vengono
recuperati utilizzando il metodo [getLongEntitiesById] del

 [DaoProduit].

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

public Response<List<Product>> getLongProductsByName
(HttpServletRequest request)
====
Il metodo riceve i nomi dei prodotti desiderati tramite una richiesta POST.
 Questi sono accessibili nella [HttpServletRequest
 ]. I prodotti lunghi vengono recuperati dal
 metodo [getLongEntitiesByName] nel livello [DaoProduit].

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

public Response<List<Product>> getShortProductsByName
(HttpServletRequest request)
====
Il metodo riceve i nomi dei prodotti desiderati tramite una richiesta POST.
 Questi sono accessibili nella [HttpServletRequest
 ]. I prodotti abbreviati vengono ottenuti tramite il
metodo [getShortEntitiesByName] nel livello [DaoProduit].

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

public Response<List<Product>> getShortProductsById
(HttpServletRequest request)
====
Il metodo riceve le chiavi primarie dei prodotti desiderati tramite una richiesta POST
 . Queste sono accessibili in
[HttpServletRequest request]. I prodotti abbreviati vengono
recuperati utilizzando il metodo [getShortEntitiesById] del
 [DaoProduit].

17.3.5. Implementazione generica del servizio web

  

L'elenco degli URL esposti dal servizio web mostra che offriamo gli stessi tipi di URL per la gestione delle categorie e dei prodotti. Anziché scrivere due controller molto simili, li faremo derivare da una classe che gestirà tutto il lavoro comune a entrambi i controller. Si tratterà della classe [AbstractController] sopra riportata. 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> {
 
    // list of all T entities
    public Response<List<T>> getAllShortEntities();
 
    public Response<List<T>> getAllLongEntities();
 
    // special entities - short version
    public Response<List<T>> getShortEntitiesById(HttpServletRequest request);
 
    public Response<List<T>> getShortEntitiesByName(HttpServletRequest request);
 
    // special entities - long version
    public Response<List<T>> getLongEntitiesById(HttpServletRequest request);
 
    public Response<List<T>> getLongEntitiesByName(HttpServletRequest request);
 
    // update of several entities
    public Response<List<T>> saveEntities(HttpServletRequest request);
 
    // delete all entities
    public  Response<Void> deleteAllEntities();
 
    // deletion of multiple entities
    public  Response<Void> deleteEntitiesById(HttpServletRequest request);
 
    public  Response<Void> deleteEntitiesByName(HttpServletRequest request);
}

Questa interfaccia implementa 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> {
 
    // list of all T entities
    public List<T> getAllShortEntities();
 
    public List<T> getAllLongEntities();
 
    // special entities - short version
    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);
 
    // special entities - long version
    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);
 
    // update of several entities
    public List<T> saveEntities(Iterable<T> entities);
 
    public List<T> saveEntities(@SuppressWarnings("unchecked") T... entities);
 
    // delete all entities
    public void deleteAllEntities();
 
    // deletion of multiple entities
    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>] nel livello [DAO] all'interfaccia [Iws<T>] nel servizio web ha seguito queste regole:

  • i metodi dell'interfaccia [Iws<T>] non genereranno eccezioni. Se si verifica un'eccezione, questa verrà incapsulata nell'oggetto [Response];
  • le variazioni dei parametri come quelle alle righe 45 e 47 [Iterable<String> names, String... names] vengono rimosse. 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> {
 
    // ----------------- properties
    // operation status
    private int status;
    // an error message
    private String exception;
    // the body of the reply
    private T body;
 
    // manufacturers
    public Response() {
 
    }
 
    public Response(int status, String exception, T body) {
        this.status = status;
        this.exception = exception;
        this.body = body;
    }
 
    // getters and setters
...
}
  • riga 4: la risposta incapsula un tipo T;
  • riga 12: la risposta di tipo T;
  • righe 7–10: un metodo può incontrare un'eccezione. In questo 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;
 
    // layer DAO
    private IDao<T> dao;
 
    abstract protected IDao<T> getDao();
 
    // local
    private String simpleClassName = getClass().getSimpleName();
 
    @PostConstruct
    public void init(){
        dao=getDao();
    }
    
    @Override
    public Response<List<T>> getAllShortEntities() {
        try {
            // answer
            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 {
            // answer
            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 {
            // we delete
            dao.deleteAllEntities();
            // answer
            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) {
    ...
    }
 
}

Tutti i metodi sono implementati allo stesso modo:

  1. se hanno bisogno di informazioni, le recuperano dall'oggetto [HttpServletRequest request];
  2. chiamano il metodo nel livello [DAO] che ha lo stesso nome del loro;
  3. gestiscono eventuali eccezioni che potrebbero verificarsi, sia nell'operazione 1 (recupero dei parametri) che nell'operazione 2 (chiamata del livello [DAO]);

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


public abstract class AbstractController<T extends AbstractCoreEntity> implements Iws<T> {
 
    @Autowired
    protected ApplicationContext context;
 
    // layer 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 al livello [DAO] da utilizzare;
  • riga 9: il metodo astratto [getDao] che restituirà il riferimento al livello [DAO] da utilizzare. Questo metodo verrà sovrascritto dalla classe figlia, quindi sarà la classe figlia a specificare quale livello [DAO] utilizzare (DaoProduct o DaoCategory);
  • riga 11: l'annotazione [@PostConstruct] contrassegna un metodo da eseguire una volta completata l'istanziazione dell'oggetto. Una volta completata l'istanziazione, le iniezioni Spring sono state eseguite. La classe figlia avrà quindi ottenuto il riferimento al proprio livello [DAO] e potrà quindi passarlo alla propria classe padre;

Il metodo [getShortEntitiesById] è il seguente:


    @Override
    public Response<List<T>> getShortEntitiesById(HttpServletRequest request) {
        try {
            // retrieve the posted value
            String body = CharStreams.toString(request.getReader());
            // we deserialize it
            ObjectMapper mapper = context.getBean("jsonMapper", ObjectMapper.class);
            List<Long> ids = mapper.readValue(body, new TypeReference<List<Long>>() {
            });
            // answer
            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: chiamiamo 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] ha generato un'eccezione;
  • riga 15: caso per altre eccezioni, in particolare la possibile eccezione durante la deserializzazione del parametro JSON, riga 8;

Il metodo [getShortEntitiesByName] è simile:


@Override
    public Response<List<T>> getShortEntitiesByName(HttpServletRequest request) {
        try {
            // retrieve the posted value
            String body = CharStreams.toString(request.getReader());
            // we deserialize it
            ObjectMapper mapper = context.getBean("jsonMapper", ObjectMapper.class);
            List<String> noms = mapper.readValue(body, new TypeReference<List<String>>() {
            });
            // answer
            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 per cui vogliamo la versione breve;

Il metodo [saveEntities] non è stato implementato perché dipende fortemente dal tipo di entità da salvare, [Category] o [Product]. C'è poco codice da rifattorizzare. Questo compito è 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 [CategoryController]

  

Il controller [CategorieController] gestisce gli 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;
    }
 
    // local
    private String simpleClassName = getClass().getSimpleName();
 
    @RequestMapping(value = "/getAllShortCategories", method = RequestMethod.GET)
    public Response<List<Categorie>> getAllShortCategories() {
        // parent
        Response<List<Categorie>> response = super.getAllShortEntities();
        // serialization filters jSON
        context.getBean("jsonMapperShortCategorie", ObjectMapper.class);
        // answer
        return response;
    }
 
    @RequestMapping(value = "/getAllLongCategories", method = RequestMethod.GET)
    public Response<List<Categorie>> getAllLongCategories() {
        // parent
        Response<List<Categorie>> response = super.getAllLongEntities();
        // serialization filters jSON
        context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
        // answer
        return response;
    }
 
    @RequestMapping(value = "/getShortCategoriesById", method = RequestMethod.POST)
    public Response<List<Categorie>> getShortCategoriesById(HttpServletRequest request) {
        // parent
        Response<List<Categorie>> response = super.getShortEntitiesById(request);
        // serialization filters jSON
        context.getBean("jsonMapperShortCategorie", ObjectMapper.class);
        // answer
        return response;
    }
 
    @RequestMapping(value = "/getShortCategoriesByName", method = RequestMethod.POST)
    public Response<List<Categorie>> getShortCategoriesByName(HttpServletRequest request) {
        // parent
        Response<List<Categorie>> response = super.getShortEntitiesByName(request);
        // serialization filters jSON
        context.getBean("jsonMapperShortCategorie", ObjectMapper.class);
        // answer
        return response;
    }
 
    @RequestMapping(value = "/getLongCategoriesById", method = RequestMethod.POST)
    public Response<List<Categorie>> getLongCategoriesById(HttpServletRequest request) {
        // parent
        Response<List<Categorie>> response = super.getLongEntitiesById(request);
        // serialization filters jSON
        context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
        // answer
        return response;
    }
 
    @RequestMapping(value = "/getLongCategoriesByName", method = RequestMethod.POST)
    public Response<List<Categorie>> getLongCategoriesByName(HttpServletRequest request) {
        // parent
        Response<List<Categorie>> response = super.getLongEntitiesByName(request);
        // serialization filters jSON
        context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
        // answer
        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 anche che la classe è un servizio web i cui metodi inviano le loro risposte direttamente al client in 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 costruiti sullo stesso modello:

  • delega dell'elaborazione alla classe padre;
  • inizializzazione del mapper JSON che serializzerà la risposta;
  • invio della risposta;

Diamo un'occhiata alle firme di alcuni URL:


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

@RequestMapping(value = "/getShortCategoriesById",
 method = RequestMethod.POST, consumes =
 "application/json; charset=UTF-8")
- L'URL [/getShortCategoriesById] viene chiamato con una richiesta POST. Il valore inviato è la stringa JSON contenente le chiavi primarie delle
categorie desiderate;

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

Ora, diamo un'occhiata più da vicino al 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) {
        // we persist categories
        try {
            // retrieve the posted value
            String body = CharStreams.toString(request.getReader());
            // we deserialize it
            ObjectMapper mapper = context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
            List<Categorie> categories = mapper.readValue(body, new TypeReference<List<Categorie>>() {
            });
            // we persist categories
            categories = daoCategorie.saveEntities(categories);
            // we return the result
            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);
                }
            }
            // result
            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: l'URL [/saveCategories] è accompagnato da un valore inviato tramite POST. Si tratta della stringa JSON contenente le versioni complete delle categorie da salvare;
  • righe 5–10: le categorie da salvare vengono ricreate dalla stringa JSON. Il collegamento [product.category] che collega un [Product] alla sua [Category] è nullo perché nella versione lunga di una [Category], ogni [Product] è nella sua versione breve senza il campo [category]. Questo non è un problema, poiché il livello [DAO] implementato con JDBC non richiede questa informazione;
  • riga 12: le categorie vengono salvate. L'elenco delle categorie ricevuto è stato arricchito con le chiavi primarie degli elementi salvati, delle categorie e dei prodotti. Nient'altro è cambiato. Anziché restituire l'intero elenco ricevuto, operazione costosa, restituiremo solo le chiavi primarie degli elementi presenti in questo elenco. Per farlo, utilizziamo le seguenti classi [CoreCategory] e [CoreProduct]:
  

package spring.webjson.server.entities;
 
import java.util.List;
 
public class CoreCategorie {
 
    // primary key
    private Long id;
    
    // manufacturers
    public CoreCategorie() {
 
    }
 
    public CoreCategorie(Long id) {
        this.id=id;
    }
 
    // list of products
    private List<CoreProduit> coreProduits;
 
    // getters and setters
    ...
}
  • riga 8: la chiave primaria di un prodotto;
  • riga 20: le chiavi primarie dei suoi prodotti;

package spring.webjson.server.entities;
 
public class CoreProduit {
 
    // primary key
    private Long id;
 
    // manufacturers
    public CoreProduit() {
 
    }

    public CoreProduit(Long id) {
        this.id = id;
    }
 
    // getters and setters
...
}
  • riga 6: la chiave primaria di un prodotto;

Torniamo al codice del metodo [saveCategories]:


...            
// on persiste les catégories
            categories = daoCategorie.saveEntities(categories);
            // on rend le résultat
            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);
                }
            }
            // résultat
            return new Response<List<CoreCategorie>>(0, null, coreCategories);
...
  • righe 5–17: creiamo l'elenco di oggetti [CoreCategory] che restituiremo 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 punti per la serializzazione/deserializzazione JSON:

  • deserializzazione del valore inviato: qui viene gestita in modo esplicito;
  • serializzazione del risultato: qui viene gestita implicitamente;

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


    @RequestMapping(value = "/saveCategories", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<List<CoreCategorie>> saveCategories(HttpServletRequest request) {
        // we persist categories
        try {
            // retrieve the posted value
            String body = CharStreams.toString(request.getReader());
            // we deserialize it
            ObjectMapper mapper = context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
            List<Categorie> categories = mapper.readValue(body, new TypeReference<List<Categorie>>() {
});
  • Riga 8: Recuperiamo dal contesto Spring un mapper configurato per gestire il filtro JSON [jsonMapperLongCategorie]. Torniamo alla definizione di questo mapper nella classe di configurazione [WebConfig]:

// -------------------------------- configuration filters [json]
    // mapping 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);
    }
 
    // filters 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] nelle righe 18–21;
  • righe 18–21: il metodo [jsonMapper] restituisce il mappatore JSON dal [MappingJackson2HttpMessageConverter] nelle righe 3–9;

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


            // on récupère la valeur postée
            String body = CharStreams.toString(request.getReader());
            // on la désérialise
            ObjectMapper mapper = context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
            List<Categorie> categories = mapper.readValue(body, new TypeReference<List<Categorie>>() {
});

è il convertitore predefinito utilizzato da Spring MVC per deserializzare il valore inviato dal client e serializzare il risultato che gli viene restituito. Nelle righe precedenti non c'era alcuna deserializzazione implicita del valore inviato. Per farlo, avresti dovuto 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]. Ma c'era il problema del filtro [jsonFilterCategorie] che le entità [Categorie] possiedono. Deve essere configurato. Ecco perché abbiamo scelto la deserializzazione esplicita (righe 4–5). Il secondo punto da notare è che il mapper alla riga 4 (che è quello utilizzato di default da Spring MVC) è adatto anche per serializzare il risultato [Response<List<CoreCategory>]. Infatti, l'entità [CoreCategorie] non ha un filtro JSON. Pertanto, non è necessario configurare il mapper JSON risultante con un filtro aggiuntivo. In questo caso, la risposta inviata al client verrà serializzata implicitamente.

17.3.8. Il [ProductController]

  

Il controller [ProduitController] gestisce gli URL relativi ai prodotti. Il suo codice è simile 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;
    }
 
    // local
    private String simpleClassName = getClass().getSimpleName();
 
    @RequestMapping(value = "/getAllShortProduits", method = RequestMethod.GET)
    public Response<List<Produit>> getAllShortProduits() {
        // parent
        Response<List<Produit>> response = super.getAllShortEntities();
        // serialization filters jSON
        context.getBean("jsonMapperShortProduit", ObjectMapper.class);
        // answer
        return response;
    }
 
    @RequestMapping(value = "/getAllLongProduits", method = RequestMethod.GET)
    public Response<List<Produit>> getAllLongProduits() {
        // parent
        Response<List<Produit>> response = super.getAllLongEntities();
        // serialization filters jSON
        context.getBean("jsonMapperLongProduit", ObjectMapper.class);
        // answer
        return response;
    }
 
    @RequestMapping(value = "/getShortProduitsById", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<List<Produit>> getShortProduitsById(HttpServletRequest request) {
        // parent
        Response<List<Produit>> response = super.getShortEntitiesById(request);
        // serialization filters jSON
        context.getBean("jsonMapperShortProduit", ObjectMapper.class);
        // answer
        return response;
    }
 
    @RequestMapping(value = "/getShortProduitsByName", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<List<Produit>> getShortProduitsByName(HttpServletRequest request) {
        // parent
        Response<List<Produit>> response = super.getShortEntitiesByName(request);
        // serialization filters jSON
        context.getBean("jsonMapperShortProduit", ObjectMapper.class);
        // answer
        return response;
    }
 
    @RequestMapping(value = "/getLongProduitsById", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<List<Produit>> getLongProduitsById(HttpServletRequest request) {
        // parent
        Response<List<Produit>> response = super.getLongEntitiesById(request);
        // serialization filters jSON
        context.getBean("jsonMapperLongProduit", ObjectMapper.class);
        // answer
        return response;
    }
 
    @RequestMapping(value = "/getLongProduitsByName", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<List<Produit>> getLongProduitsByName(HttpServletRequest request) {
        // parent
        Response<List<Produit>> response = super.getLongEntitiesByName(request);
        // serialization filters jSON
        context.getBean("jsonMapperLongProduit", ObjectMapper.class);
        // answer
        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 [saveProducts] ha una struttura diversa dagli altri metodi:


@RequestMapping(value = "/saveProduits", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
    public Response<List<CoreProduit>> saveProduits(HttpServletRequest request) {
        try {
            // retrieve the posted value
            String body = CharStreams.toString(request.getReader());
            // we deserialize it
            ObjectMapper mapper = context.getBean("jsonMapperShortProduit", ObjectMapper.class);
            List<Produit> produits = mapper.readValue(body, new TypeReference<List<Produit>>() {
            });
            // we persist products
            produits = daoProduit.saveEntities(produits);
            List<CoreProduit> coreProduits = new ArrayList<CoreProduit>();
            for (Produit produit : produits) {
                coreProduits.add(new CoreProduit(produit.getId()));
            }
            // we return the answer
            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: dalla stringa JSON ricevuta, ricostruiamo l'elenco degli oggetti [Product] da salvare. Poiché la stringa JSON ricevuta contiene le versioni abbreviate dei prodotti, il loro campo [category] è nullo. Ancora una volta, il livello DAO/JDBC non ha bisogno di questa informazione;
  • riga 11: i prodotti vengono salvati;
  • righe 12–15: viene costruito l'elenco degli oggetti [CoreProduct] da restituire;
  • riga 18: viene restituita la risposta, che verrà serializzata (serializzazione implicita eseguita da Spring MVC) dal mapper della riga 7 prima di essere inviata al client remoto (vedi la discussione nella sezione 17.3.7);

17.3.9. Il servizio web / classe di esecuzione 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 questa classe viene eseguita, vengono generati 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. Questi sovrascrivono i bean con lo stesso nome individuati nel progetto di configurazione del livello JDBC;
  • righe 17-18: viene avviato il server Tomcat per eseguire il servizio web/JSON;
  • Righe 19–21: viene inizializzato il contesto Spring MVC;
  • righe 24–43: vengono individuati gli URL esposti;

17.3.10. Test del servizio web /jSON

Per eseguire i test, utilizziamo [Advanced Rest Client] (vedere la sezione 23.11) per interrogare gli URL esposti dal servizio web /jSON (ovviamente, il servizio web /jSON deve essere in esecuzione, così come il DBMS). Per popolare il database, 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 l'URL [/getAllLongCategories] tramite una richiesta HTTP GET;

Riceviamo 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 con successo la richiesta;
  • in [4], la risposta JSON del server;

La risposta JSON completa è 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 ci sono stati errori lato server;
  • exception: null significa che non c'è 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 il prodotto [product15] alla categoria [category1]. Per farlo, useremo l'URL [/saveProducts], che richiede una stringa JSON dei prodotti da salvare (inserimento/aggiornamento). Questa 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 come segue:

  • in [1], l'URL richiesto;
  • in [2], viene richiesto tramite un'operazione POST;
  • in [3], la stringa JSON inviata;
  • in [4], il server viene informato che verranno inviati dati JSON;

La risposta del server è la seguente:

  • in [1], abbiamo ricevuto un elenco di oggetti [CoreProduct] con le loro chiavi primarie. Qui, abbiamo ricevuto un elenco contenente un singolo elemento con la chiave primaria del prodotto che abbiamo appena inserito nel database;

Ora, richiediamo la versione completa della categoria denominata [category[1]:

  • In [1], l'URL richiesto;
  • in [2], effettuiamo una richiesta POST;
  • in [3,4], il valore inviato è una stringa JSON. Questa rappresenta l'elenco dei nomi delle categorie per le quali vogliamo la versione estesa;

Otteniamo il seguente risultato:

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

Ora eliminiamo questo prodotto:

  • In [1], l'URL richiesto;
  • In [2], inviamo una richiesta POST;
  • in [3-4], inviamo una stringa JSON che rappresenta l'elenco delle chiavi primarie dei prodotti che vogliamo eliminare;

Il risultato è il seguente:

 
  • [status:0] indica che l'eliminazione è andata a buon fine;

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

 

Otteniamo il seguente risultato:

 
  • [status:0] indica che l'operazione è stata completata senza eccezioni;
  • [body:[0]] indica che [body] è una lista vuota. L'entità [product[1,5]] è stata quindi eliminata con successo;

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

I lettori sono invitati a provare gli altri URL del servizio web / JSON.