Skip to content

14. [TD]: Esposizione sul web del livello [metier]

Parole chiave: architettura multistrato, Spring, iniezione di dipendenze, servizio web / jSON, client / server.

Torniamo all’architettura attuale dell’applicazione TD:

Evolveremo questa architettura verso la seguente:

al fine di esporre sul web l’interfaccia [IMetier] del livello business. A tal fine seguiremo la metodologia descritta al paragrafo 13.5.

14.1. Support

  

I progetti di questo capitolo si trovano nella cartella [support / chap-14].

14.2. Il progetto Eclipse del livello [métier]

  

14.2.1. Configurazione Maven

Il progetto del livello [métier] è un progetto Maven configurato dal seguente file [pom.xml]:


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <modelVersion>4.0.0</modelVersion>
    <groupId>istia.st.elections</groupId>
    <artifactId>elections-metier-dao-spring-data</artifactId>
    <version>0.1.0</version>

    <!-- dipendenze -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.7.RELEASE</version>
    </parent>
    <dependencies>
        <!-- livello [DAO] -->
        <dependency>
            <groupId>istia.st.elections</groupId>
            <artifactId>elections-dao-spring-data-01</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <!-- Spring Boot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- Test di Spring Boot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <properties>
        <!-- utilizza UTF-8 per tutto -->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
    </build>
</project>
  • righe 18-22: la dipendenza dal livello [DAO] creato al paragrafo 12;
  • righe 23-34: le dipendenze necessarie per i test;

14.2.2. Configurazione Spring

  

Il progetto del livello [métier] è un progetto Spring configurato dal seguente file [MetierConfig]:


package elections.metier.config;

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

import elections.dao.config.DaoConfig;

@Import({ DaoConfig.class })
@ComponentScan({ "elections.metier.service" })
public class MetierConfig {
}
  • Qui non utilizziamo la notazione [@Configuration] che rende la classe una classe di configurazione Spring. La presenza delle annotazioni [@Import, @ComponentScan] la rende automaticamente una classe di configurazione;
  • riga 8: si importa il file di configurazione del livello [DAO]. A questo punto sono disponibili tutti i bean definiti da questo file;
  • riga 9: altri bean Spring sono disponibili nella cartella [elections.metier.service];

14.2.3. Implementazione del livello [métier]

  

L'implementazione del livello [métier] è quella definita al paragrafo 8.5.

14.2.4. Test del livello [métier]

  

La classe di test è quella descritta al paragrafo 8.6.


Compito da svolgere: implementare il progetto del livello [métier] e superare il relativo test unitario. Generare l’archivio del livello nel repository Maven locale (run as/ Maven / install).


14.3. Il progetto Eclipse del livello [web]

Il livello web è un livello Spring MVC:

Il progetto Eclipse presenta la seguente struttura:

  • [Boot.java] è la classe che avvia il servizio web;
  • [WebConfig.java] è la classe di configurazione del servizio web;
  • [Response.java] è la risposta generata dalle diverse istanze di URL del servizio web;
  • [ElectionsController] è la classe di implementazione del servizio web;

14.4. Configurazione Maven

Il progetto è un progetto Maven configurato dal seguente file [pom.xml]:


<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>istia.st.elections</groupId>
    <artifactId>elections-webjson-metier-dao-spring-data</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <name>elections-webjson-metier-dao-spring-data</name>
    <description>couche métier exposée comme un service web / jSON</description>

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

    <dependencies>
        <!-- livello business -->
        <dependency>
            <groupId>istia.st.elections</groupId>
            <artifactId>elections-metier-dao-spring-data</artifactId>
            <version>0.1.0</version>
        </dependency>
        <!-- livello MVC -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
    </build>

</project>
  • righe 19-23: la dipendenza dall’archivio del livello [métier]. È quella che abbiamo creato al paragrafo 14;
  • righe 25-28: la dipendenza per avere un'applicazione Spring MVC;

14.5. Configurazione Spring

 

La classe [WebConfig] configura il servizio web:


package elections.webjson.config;

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.ComponentScan;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Scope;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

import com.fasterxml.jackson.databind.ObjectMapper;

import elections.metier.config.MetierConfig;

@EnableWebMvc
@Import({ MetierConfig.class })
@ComponentScan({ "elections.webjson.service" })
public class WebConfig {
    // -------------------------------- 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("", 8080);
    }
    // mappatore jSON
    @Bean
    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public ObjectMapper jsonMapper() {
        return new ObjectMapper();
    }

}
  • il significato di questa configurazione è stato spiegato nel paragrafo 13.5.3.1. Ci limitiamo a illustrare le novità:
  • riga 22: si importa il file di configurazione del livello [métier] per poter utilizzare tutti i suoi bean;
  • riga 23: si indica che altri bean si trovano nella cartella [elections.webjson.server.service];

14.6. La classe di avvio del servizio web

 

La classe [Boot] avvia il servizio web nel modo seguente:


package elections.webjson.boot;

import org.springframework.boot.SpringApplication;

import elections.webjson.config.WebConfig;

public class Boot {

    public static void main(String[] args) {
        SpringApplication.run(WebConfig.class, args);
    }
}
  • riga 10: il metodo statico [SpringApplication.run] utilizzerà il file di configurazione [WebConfig]. Grazie all’annotazione [@EnableAutoConfiguration], Spring Boot avvierà il server Tomcat e distribuirà il servizio web su di esso;

14.7. La risposta del servizio web URL

 

Tutti i URL del servizio web / jSON inviano lo stesso tipo di risposta:


package elections.webjson.service;

import java.util.List;

public class Response<T> {

    // ----------------- proprietà
    // stato dell'operazione
    private int status;
    // eventuali messaggi di errore
    private List<String> messages;
    // il corpo della risposta
    private T body;

    // costruttori
    public Response() {

    }

    public Response(int status, List<String> messages, T body) {
        this.status = status;
        this.messages = messages;
        this.body = body;
    }

    // getter e setter
...
}

Questa classe è stata presentata e analizzata nel paragrafo 13.5.5.3.

14.8. L'implementazione del servizio web / jSON

 

Il servizio web / jSON è implementato dalla seguente classe [ElectionsController]:


package elections.webjson.service;

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

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import elections.dao.entities.ElectionsConfig;
import elections.dao.entities.ElectionsException;
import elections.metier.service.IElectionsMetier;

@Controller
public class ElectionsController {

    // dipendenze Spring
    @Autowired
    private ObjectMapper jsonMapper;

    @Autowired
    private IElectionsMetier metier;

    @RequestMapping(value = "/getElectionsConfig", method = RequestMethod.GET, produces = "application/json; charset=UTF-8")
    @ResponseBody
    public String getElectionsConfig() throws JsonProcessingException {
        // risposta
        Response<ElectionsConfig> response;
        try {
            response = new Response<>(0, null,
                    new ElectionsConfig(metier.getNbSiegesAPourvoir(), metier.getSeuilElectoral()));
        } catch (ElectionsException e1) {
            response = new Response<>(e1.getCode(), e1.getErreurs(), null);
        } catch (RuntimeException e2) {
            response = new Response<>(1000, getErreursForException(e2), null);
        }
        // risposta
        return jsonMapper.writeValueAsString(response);
    }

    @RequestMapping(value = "/getListesElectorales", method = RequestMethod.GET, produces = "application/json; charset=UTF-8")
    @ResponseBody
    public String getListesElectorales() throws JsonProcessingException {
        throw new UnsupportedOperationException("Not supported yet");
    }

    @RequestMapping(value = "/setListesElectorales", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8", produces = "application/json; charset=UTF-8")
    @ResponseBody
    public String setListesElectorales(HttpServletRequest request) throws JsonProcessingException {
        throw new UnsupportedOperationException("Not supported yet");
    }

    @RequestMapping(value = "/calculerSieges", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8", produces = "application/json; charset=UTF-8")
    @ResponseBody
    public String calculerSieges(HttpServletRequest request) throws JsonProcessingException {
        throw new UnsupportedOperationException("Not supported yet");
    }

    // metodi privati -----------------------------
    // elenco dei messaggi di errore di una RuntimeException
    private List<String> getErreursForException(Exception e) {
        // si recupera l'elenco dei messaggi di errore dell'eccezione
        Throwable cause = e;
        List<String> erreurs = new ArrayList<>();
        while (cause != null) {
            // si recupera il messaggio solo se !=null e non è vuoto
            String message = cause.getMessage();
            if (message != null) {
                message = message.trim();
                if (message.length() != 0) {
                    erreurs.add(message);
                }
            }
            // causa successiva
            cause = cause.getCause();
        }
        return erreurs;
    }

}

Compito da svolgere: seguendo quanto fatto nel paragrafo 13.5.5, completate il codice della classe [ElectionsController].


Note:

  • qui non sono presenti filtri jSON poiché le tabelle [CONF] e [LISTES] non sono collegate tra loro da una relazione di chiave esterna, il che alleggerisce notevolmente il codice del servizio web;
  • non dimenticare le diverse annotazioni Spring necessarie;
  • alle tabelle URL verrà assegnato il nome dei metodi associati;
  • il metodo [setListeElectorales] viene chiamato con un'operazione [POST]. Il valore inviato è l'array delle liste in competizione (di tipo ListeElectorale[]) con i relativi attributi [sieges, voix, elimine] che devono essere salvati nel database. Questo metodo restituisce un tipo [Response<Void>] con un campo [status=0] se non si sono verificati errori, altrimenti restituisce un valore diverso;
  • il metodo [calculerSieges] viene chiamato con un'operazione [POST]. Il valore inviato è l'array delle liste in competizione (di tipo ListeElectorale[]) con i relativi attributi [nom, voix]. Questo metodo restituisce un tipo [Response<ListeElectorale[]>] con come corpo le liste elettorali con i relativi campi [sieges, elimine] inizializzati;

14.9. Tests

Dopo aver avviato il servizio web, eseguirete i seguenti test per verificare il corretto funzionamento del servizio web con l'utilità [Advanced Rest Client]:

 

La risposta jSON alla richiesta precedente è la seguente [1]:

1
2

In [2], copiare la risposta negli appunti, quindi incollarla in un editor di testo qualsiasi [3]:

Isolare il valore del campo [body] e modificare, ad esempio, i voti delle liste. Di seguito, in [4], si impostano a 100 i voti di tutte le liste:

Verificate che la vostra stringa jSON inizi con [ et se termine par ]. Questi caratteri servono a delimitare una tabella jSON. In [5], incollate la stringa jSON riportata sopra. Questo sarà il valore inviato per il successivo URL. A tal fine, è necessario selezionare il metodo HTTP [POST] [7].

  • in [6], richiedere il URL [setListesElectorales]. Questo URL va richiesto con un POST. Il valore inserito è la tabella jSON delle liste in gara di cui occorre registrare i risultati nel database;

Si ottiene il seguente risultato:

 

Il campo [status=0] indica che non si sono verificati errori. Per verificarlo, richiedete nuovamente le liste in gara e controllate che le modifiche apportate alle liste siano state prese in considerazione:

Si esegue nuovamente un [POST] per calcolare i seggi ottenuti dalle liste:

  • in [1]: l'URL del calcolo dei seggi;
  • in [2]: si esegue un [POST];
  • in [3]: le liste in lizza. Al campo [voix] vengono assegnati i valori del TD, tutti i [sieges] sono impostati a 0, tutti i campi [elimine] sono impostati a false;

Il risultato ottenuto è il seguente: