Skip to content

17. [TD]: protezione del server web / jSON delle elezioni

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

Applichiamo ora quanto appreso nel capitolo precedente al TD relativo alle elezioni. L’architettura sarà la seguente:

Il processo si svolgerà nel modo seguente:

  • scrittura del server;
  • sviluppo del client senza il livello [ui] ma con un test JUnit del livello [métier];
  • scrittura del client con il livello [ui];

17.1. Support

 

I progetti di questo capitolo si trovano nella cartella [support / chap-17]. Lo script SQL serve a generare il database necessario per i test.

17.2. Il database

Il database del server sicuro deve ora contenere le tabelle [USERS], [ROLES] e [USERS_ROLES]:

 

Lo script SQL per la generazione del database è disponibile nella documentazione di supporto.

17.3. Il server sicuro

Per configurare il server sicuro per le elezioni, è sufficiente copiare e incollare il progetto di esempio [intro-spring-security-server-01]:


Operazione da eseguire: rinominare i pacchetti come indicato in [1].


Fatto ciò, modifichiamo il file [pom.xml] nel modo 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>istia.st.elections</groupId>
    <artifactId>elections-security-webjson-metier-dao-spring-data</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <name>elections-security-webjson-metier-dao-spring-data</name>
    <description>elections spring security</description>

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

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

    <dependencies>
        <dependency>
            <groupId>istia.st.elections</groupId>
            <artifactId>elections-webjson-metier-dao-spring-data</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <!-- Spring Security -->
        ...
    </dependencies>
    <!-- plugin -->
    <build>
        ...
    </build>

</project>
  • alle righe 23-27, sostituire la dipendenza dal progetto [intro-server-webjson-01] con quella dal progetto [elections-webjson-metier-dao-spring-data] descritto al paragrafo 12;
  • alle righe 4-7, inserire le caratteristiche del nuovo progetto;

Resta da modificare le classi di configurazione del progetto:

[DaoConfig]


package elections.security.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@EnableJpaRepositories(basePackages = { "elections.security.repositories" })
@ComponentScan(basePackages = { "elections.security.dao" })
@Import({ elections.dao.config.DaoConfig.class })
public class DaoConfig {

    // costanti
    final static private String[] ENTITIES_PACKAGES = { "elections.dao.entities", "elections.security.entities" };


    @Bean
    public String[] packagesToScan() {
        return ENTITIES_PACKAGES;
    }

}

Le modifiche riguardano le seguenti righe:

  • righe 8, 9, 14: inserire i nomi corretti dei pacchetti;
  • riga 10: la classe importata è ora [elections.dao.config.DaoConfig] del progetto [elections-webjson-metier-dao-spring-data];

Nota: come già detto, questa configurazione funziona solo se la classe [elections.dao.config.DaoConfig] presenta l’annotazione [@Configuration]. Verificare questo punto.

[SecurityConfig]


package elections.security.config;

...

@EnableWebSecurity
@ComponentScan(basePackages = { "elections.security.service" })
@Import({ WebConfig.class, DaoConfig.class })
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    ...
}

Le modifiche riguardano le seguenti righe:

  • riga 6: modificate il nome del pacchetto;

Questo è tutto. Eseguite un primo test lanciando il test JUnit []:

  

Eseguite la classe di avvio [Boot], quindi con l'estensione Chrome [Advanced Rest Client] effettuate il seguente test:

In [1], la richiesta. In [3], la risposta. In [2], l'intestazione HTTP è l'intestazione di autenticazione dell'utente [admin, admin]: Authorization:Basic YWRtaW46YWRtaW4=

Ora richiedete gli elenchi in gara:

17.4. Il client del server sicuro senza il livello [ui]

Esegui un copia/incolla del progetto [elections-ui-metier-dao-webjson] nel progetto [elections-metier-dao-security-webjson].


Operazioni da eseguire: nel progetto [1], rinominare i pacchetti se necessario ed eliminare quelli del livello [ui] e delle classi di avvio [boot].


Procederemo ora come indicato nel paragrafo 16.5.

17.4.1. Configurazione Maven

Cambia solo la parte relativa all’identificativo del progetto:


<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-metier-dao-security-webjson</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <description>Client jUnit du serveur web / jSON</description>
    <name>elections-metier-dao-security-webjson</name>
...

17.4.2. Rifattorizzazione del livello [métier]

  

L'interfaccia [IElectionsMetier] viene modificata come segue:


package elections.security.client.metier;

import elections.security.client.entities.ListeElectorale;
import elections.security.client.entities.User;

public interface IElectionsMetier {

    // autenticazione
    public void authenticate(User user);

    // ottenere gli elenchi in gara
    public ListeElectorale[] getListesElectorales(User user);

    // il numero di seggi da assegnare
    public int getNbSiegesAPourvoir(User user);

    // soglia elettorale
    public double getSeuilElectoral(User user);

    // registrazione dei risultati
    public void recordResultats(User user, ListeElectorale[] listesElectorales);

    // il calcolo dei seggi
    public ListeElectorale[] calculerSieges(User user, ListeElectorale[] listesElectorales);

}

Tutti i metodi hanno come primo parametro l'utente che desidera utilizzare le risorse del server protetto.

L'implementazione [ElectionsMetier] si evolve come segue:


package elections.security.client.metier;

import java.io.IOException;

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

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

import elections.security.client.dao.IClientDao;
import elections.security.client.entities.ElectionsConfig;
import elections.security.client.entities.ElectionsException;
import elections.security.client.entities.ListeElectorale;
import elections.security.client.entities.User;

@Component
public class ElectionsMetier implements IElectionsMetier {

  @Autowired
  private IClientDao dao;
  @Autowired
  private ApplicationContext context;

  // configurazione delle elezioni
  private ElectionsConfig electionsConfig;

  private ElectionsConfig getElectionsConfig(User user) {
    if(electionsConfig!=null){
      return electionsConfig;
    }
    // mappatori jSON
    ObjectMapper mapperResponse = context.getBean(ObjectMapper.class);
    try {
      // richiesta
      Response<ElectionsConfig> response = mapperResponse.readValue(dao.getResponse(user, "/getElectionsConfig", null),
              new TypeReference<Response<ElectionsConfig>>() {
      });
      // errore?
      if (response.getStatus() != 0) {
        // viene generata 1 eccezione
        throw new ElectionsException(response.getStatus(), response.getMessages());
      } else {
        electionsConfig = response.getBody();
        return electionsConfig;
      }
    } catch (ElectionsException e1) {
      throw e1;
    } catch (IOException | RuntimeException e2) {
      throw new ElectionsException(100, e2);
    }
  }

  @Override
  public ListeElectorale[] getListesElectorales(User user) {
   ...
  }

  @Override
  public int getNbSiegesAPourvoir(User user) {
    return getElectionsConfig(user).getNbSiegesAPourvoir();
  }

  @Override
  public double getSeuilElectoral(User user) {
    return getElectionsConfig(user).getSeuilElectoral();
  }

  @Override
  public void recordResultats(User user, ListeElectorale[] listesElectorales) {
    ...
  }

  @Override
  public ListeElectorale[] calculerSieges(User user, ListeElectorale[] listesElectorales) {
    ...
  }

  @Override
  public void authenticate(User user) {
    dao.getResponse(user, "/authenticate", null);
  }
}

Compito da svolgere: completare il codice sopra riportato.


17.4.3. Configurazione Spring

  

La classe [MetierConfig] viene modificata come segue:


package elections.security.client.config;

...

@ComponentScan({ "elections.security.client.dao", "elections.security.client.metier" })
public class MetierConfig {

Riga 5: i pacchetti da esplorare devono essere aggiornati.

17.4.4. Il test JUnit del livello [métier]

  

La classe di test [Test01] si evolve come segue:


package elections.security.client.metier.junit;

import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

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

import elections.security.client.config.MetierConfig;
import elections.security.client.entities.ElectionsException;
import elections.security.client.entities.ListeElectorale;
import elections.security.client.entities.User;
import elections.security.client.metier.IElectionsMetier;

@SpringApplicationConfiguration(classes = MetierConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class Test01 {

    // livello [electionsMetier]
    @Autowired
    private IElectionsMetier electionsMetier;

    // mappatore jSON
    private final ObjectMapper mapper = new ObjectMapper();

    // utenti
    static private User admin;
    static private User user;
    static private User unknown;

    @BeforeClass
    public static void initTest() {
        admin = new User("admin", "admin");
        user = new User("user", "user");
        unknown = new User("x", "y");
    }

    @Test()
    public void checkUserUser() {
        ElectionsException se = null;
        try {
            electionsMetier.authenticate(user);
        } catch (ElectionsException e) {
            se = e;
        }
        Assert.assertNotNull(se);
        Assert.assertEquals("403 Forbidden", se.getErreurs().get(0));
    }

    @Test()
    public void checkUserUnknown() {
        ElectionsException se = null;
        try {
            electionsMetier.authenticate(unknown);
        } catch (ElectionsException e) {
            se = e;
        }
        Assert.assertNotNull(se);
        Assert.assertEquals("401 Unauthorized", se.getErreurs().get(0));
    }

    @Test()
    public void checkUserAdmin() {
        ElectionsException se = null;
        try {
            electionsMetier.authenticate(admin);
        } catch (ElectionsException e) {
            se = e;
        }
        Assert.assertNull(se);
    }
    
    /**
     * vérification 1 : méthode de calcul des sièges on fixe en dur les listes
     */
    @Test
    public void calculSieges1() {
        // si crea la tabella delle 7 liste candidate
        ListeElectorale[] listes = new ListeElectorale[7];
        listes[0] = new ListeElectorale("A", 32000, 0, false);
        listes[1] = new ListeElectorale("B", 25000, 0, false);
        listes[2] = new ListeElectorale("C", 16000, 0, false);
        listes[3] = new ListeElectorale("D", 12000, 0, false);
        listes[4] = new ListeElectorale("E", 8000, 0, false);
        listes[5] = new ListeElectorale("F", 4500, 0, false);
        listes[6] = new ListeElectorale("G", 2500, 0, false);
        // si calcolano i seggi di ciascuna lista
        listes = electionsMetier.calculerSieges(admin,listes);
        // si verificano i risultati
        Assert.assertEquals(2, listes[0].getSieges());
        Assert.assertFalse(listes[0].isElimine());
        Assert.assertEquals(2, listes[1].getSieges());
        Assert.assertFalse(listes[1].isElimine());
        Assert.assertEquals(1, listes[2].getSieges());
        Assert.assertFalse(listes[2].isElimine());
        Assert.assertEquals(1, listes[3].getSieges());
        Assert.assertFalse(listes[3].isElimine());
        Assert.assertEquals(0, listes[4].getSieges());
        Assert.assertFalse(listes[4].isElimine());
        Assert.assertEquals(0, listes[5].getSieges());
        Assert.assertTrue(listes[5].isElimine());
        Assert.assertEquals(0, listes[6].getSieges());
        Assert.assertTrue(listes[6].isElimine());
    }

    /**
     * vérification 2 : méthode de calcul des sièges on demande les listes à la couche [metier] puis on fixe en dur les
     * voix
     */
    @Test
    public void calculSieges2() {
        // si crea la tabella delle 7 liste candidate
        ListeElectorale[] listes = electionsMetier.getListesElectorales(admin);
        // si fissano in modo definitivo i voti
        listes[0].setVoix(32000);
        listes[1].setVoix(25000);
        listes[2].setVoix(16000);
        listes[3].setVoix(12000);
        listes[4].setVoix(8000);
        listes[5].setVoix(4500);
        listes[6].setVoix(2500);
        // si calcolano i seggi ottenuti da ciascuna delle liste
        listes = electionsMetier.calculerSieges(admin,listes);
        // si verificano i risultati
        Assert.assertEquals(2, listes[0].getSieges());
        Assert.assertFalse(listes[0].isElimine());
        Assert.assertEquals(2, listes[1].getSieges());
        Assert.assertFalse(listes[1].isElimine());
        Assert.assertEquals(1, listes[2].getSieges());
        Assert.assertFalse(listes[2].isElimine());
        Assert.assertEquals(1, listes[3].getSieges());
        Assert.assertFalse(listes[3].isElimine());
        Assert.assertEquals(0, listes[4].getSieges());
        Assert.assertFalse(listes[4].isElimine());
        Assert.assertEquals(0, listes[5].getSieges());
        Assert.assertTrue(listes[5].isElimine());
        Assert.assertEquals(0, listes[6].getSieges());
        Assert.assertTrue(listes[6].isElimine());
    }

    /**
     * vérification 3 méthode de calcul des sièges on provoque une exception
     */
    @Test(expected = ElectionsException.class)
    public void calculSieges3() {
        // si crea una tabella di 24 liste candidate con 1 voto ciascuna
        ListeElectorale[] listes = new ListeElectorale[25];
        // le 25 liste avranno lo stesso numero di voti (4%)
        for (int i = 0; i < listes.length; i++) {
            listes[i] = new ListeElectorale("Liste" + (i + 1), 1, 0, false);
        }
        // calcolo dei seggi - normalmente si dovrebbe ottenere un ElectionsException
        // con una soglia elettorale del 5%
        electionsMetier.calculerSieges(admin,listes);
    }

    /**
     * enregistrement des résultats de l'élection
     * 
     * @throws JsonProcessingException
     */
    @Test
    public void ecritureResultatsElections() throws JsonProcessingException {
        // si crea la tabella delle 7 liste candidate
        ListeElectorale[] listes = electionsMetier.getListesElectorales(admin);
        // si fissano in modo definitivo i voti
        listes[0].setVoix(32000);
        listes[1].setVoix(25000);
        listes[2].setVoix(16000);
        listes[3].setVoix(12000);
        listes[4].setVoix(8000);
        listes[5].setVoix(4500);
        listes[6].setVoix(2500);
        // si calcolano i seggi ottenuti da ciascuna lista
        listes = electionsMetier.calculerSieges(admin,listes);
        // si visualizzano i risultati
        for (int i = 0; i < listes.length; i++) {
            System.out.println(mapper.writeValueAsString(listes[i]));
        }
        // si salvano i risultati nel database
        electionsMetier.recordResultats(admin,listes);
        // si verificano i risultati
        listes = electionsMetier.getListesElectorales(admin);
        // si visualizzano i risultati
        for (int i = 0; i < listes.length; i++) {
            System.out.println(mapper.writeValueAsString(listes[i]));
        }
        Assert.assertEquals(2, listes[0].getSieges());
        Assert.assertFalse(listes[0].isElimine());
        Assert.assertEquals(2, listes[1].getSieges());
        Assert.assertFalse(listes[1].isElimine());
        Assert.assertEquals(1, listes[2].getSieges());
        Assert.assertFalse(listes[2].isElimine());
        Assert.assertEquals(1, listes[3].getSieges());
        Assert.assertFalse(listes[3].isElimine());
        Assert.assertEquals(0, listes[4].getSieges());
        Assert.assertFalse(listes[4].isElimine());
        Assert.assertEquals(0, listes[5].getSieges());
        Assert.assertTrue(listes[5].isElimine());
        Assert.assertEquals(0, listes[6].getSieges());
        Assert.assertTrue(listes[6].isElimine());
    }
}

Compito da svolgere: comprendere questo test e verificare che venga superato.


17.5. Il client del server protetto con un livello [console]

Con NetBeans, apriamo i progetti Maven [elections-metier-dao-security-webjson] e [elections-ui-metier-dao-webjson], quindi creiamo un nuovo progetto [elections-console-metier-dao-security-webjson] copiando e incollando il progetto [elections-ui-metier-dao-webjson]:

La maggior parte delle classi del nuovo progetto [3] proviene dal progetto [elections-ui-metier-dao-webjson] [2], che era il client del server web / jSON non protetto:

Procedete come segue per creare il nuovo progetto Maven:

  • copiare e incollare il progetto [elections-ui-metier-dao-webjson] nel progetto [elections-ui-metier-dao-security-webjson];
  • nel nuovo progetto:
    • eliminare i pacchetti [elections.client.dao, elections.client.metier, elections.client.entities];
    • nel pacchetto [elections.client.ui], conservate solo la classe console [ElectionsConsole] e la sua interfaccia [IElectionsUI];
    • rinominare i pacchetti come indicato in [3];
    • risolvere i problemi di importazione nelle varie classi tramite [clic droit / Fix Imports];

A questo punto, il nuovo progetto presenta numerosi errori.

17.5.1. Configurazione Maven

Il file [pom.xml] del nuovo progetto è 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>istia.st.elections</groupId>
    <artifactId>elections-console-metier-dao-security-webjson</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>elections-console-metier-dao-security-webjson</name>
    <description>couche console du client web / jSON</description>

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

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

    <dependencies>
        <dependency>
            <groupId>istia.st.elections</groupId>
            <artifactId>elections-metier-dao-security-webjson</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>

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

</project>
  • righe 4-8: l'identità Maven del nuovo progetto;
  • righe 22-26: la dipendenza dal progetto [elections-metier-dao-security-webjson] che abbiamo appena creato al paragrafo 17.4 e che fornisce i livelli [DAO] e [métier];

17.5.2. Configurazione Spring

  

La classe [UiConfig] è la seguente:


package elections.security.client.config;

import elections.security.client.entities.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;

@Import(MetierConfig.class)
@ComponentScan(basePackages = {"elections.security.client.console","elections.security.client.swing"})
public class UiConfig {

  // amministratore
  private final User ADMIN = new User("admin", "admin");

  @Bean
  private User admin() {
    return ADMIN;
  }

}
  • riga 8: si importa la classe [elections.security.client.config.MetierConfig] dal progetto [elections-metier-dao-security-webjson] esaminato al paragrafo 17.4;
  • riga 9: si dichiarano i pacchetti in cui si trovano i bean Spring;
  • righe 12-18: il bean [admin] rappresenta l’utente [admin, admin] che verrà utilizzato dall’applicazione console. Ricordiamo che si tratta dell’unico utente autorizzato a interrogare il server web protetto /jSON;

17.5.3. L'applicazione di avvio della console

  

La classe [ElectionsConsole] si evolve come segue:


package elections.security.client.console;

import java.util.Arrays;
import java.util.Comparator;
import java.util.Scanner;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import elections.security.client.entities.ElectionsException;
import elections.security.client.entities.ListeElectorale;
import elections.security.client.entities.User;
import elections.security.client.metier.IElectionsMetier;

@Component
public class ElectionsConsole implements IElectionsUI {

    @Autowired
    private IElectionsMetier electionsMetier;

  @Autowired
  private User admin;
  
    @Override
    public void run() {
        // le liste in competizione
        ListeElectorale[] listes;
        // inserimento dei dati
        try (Scanner clavier = new Scanner(System.in)) {
            // si richiedono le liste in competizione al livello [metier]
            listes = electionsMetier.getListesElectorales(admin);
            // si procede all'inserimento dei voti
            System.out.println("Il y a " + listes.length
                    + " listes en compétition. Veuillez indiquer le nombre de voix de chacune d'elles :");
            for (int i = 0; i < listes.length; i++) {
                boolean saisieOK = false;
                while (!saisieOK) {
                    System.out.print("Nombre de voix de la liste [" + listes[i].getNom() + "] : ");
                    try {
                        listes[i].setVoix(Integer.parseInt(clavier.nextLine()));
                        saisieOK = true;
                    } catch (ElectionsException | NumberFormatException ex) {
                        System.out.println("Nombre de voix incorrect. Veuillez recommencer");
                    }
                }
            }
        }
        // si procede al calcolo dei seggi
        listes=electionsMetier.calculerSieges(admin,listes);
        // si registrano i risultati
        electionsMetier.recordResultats(admin,listes);
        // ordinamento delle liste in ordine decrescente di voti
        Arrays.sort(listes, new CompareListesElectorales());
        // vengono visualizzate
        System.out.println("\nRésultats de l'élection\n");
        for (int i = 0; i < listes.length; i++) {
            System.out.println(listes[i]);
        }
    }

    // classe di confronto delle liste elettorali
    class CompareListesElectorales implements Comparator<ListeElectorale> {

        // confronto tra due liste candidate in base al numero di voti
        @Override
        public int compare(ListeElectorale listeElectorale1, ListeElectorale listeElectorale2) {
            // si confrontano i voti di queste due liste
            int nbVoix1 = listeElectorale1.getVoix();
            int nbVoix2 = listeElectorale2.getVoix();
            if (nbVoix1 < nbVoix2) {
                return +1;
            } else {
                if (nbVoix1 > nbVoix2)
                    return -1;
                else
                    return 0;
            }
        }
    }

}

La classe ElectionsConsole subisce modifiche minime. È sufficiente ricordare che ora i metodi del livello [métier] richiedono come primo parametro un utente (righe 31, 49, 51). Questo viene fornito dall’iniezione delle righe 21-22. L'utente iniettato è quello autorizzato a interrogare il server web / jSON.


Compito da svolgere: testare l’applicazione da console (ricordarsi di avviare prima il server sicuro).


17.6. Il client del server sicuro con un livello [swing]

Creiamo un nuovo progetto NetBeans [elections-swing-metier-dao-security-webjson] tramite copia/incolla dal progetto [elections-ui-metier-dao-webjson]:

Nel nuovo progetto [2]:

  • eliminare i pacchetti [elections.client.dao, elections.client.metier, elections.client.entities];
  • nel pacchetto [elections.client.ui], eliminare le classi [ElectionsConsole, IElectionsUI];
  • rinominare i pacchetti come indicato in [2];
  • nel pacchetto [elections.security.client.swing], rinominare la classe [AbstractElectionsSwing] che implementa l'interfaccia grafica in [AbstractElectionsMainForm] e la classe [ElectionsSwing] che implementa i gestori degli eventi dell'interfaccia grafica in [ElectionsMainForm];
  • risolvere i problemi di importazione nelle varie classi tramite [clic droit / Fix Imports];

A questo punto, sono presenti numerosi errori.

17.6.1. Configurazione Maven

Il file [pom.xml] si evolve come segue:


<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-swing-metier-dao-security-webjson</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>elections-swing-metier-dao-security-webjson</name>
    <description>couche swing du client web / jSON</description>

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

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

    <dependencies>
        <dependency>
            <groupId>istia.st.elections</groupId>
            <artifactId>elections-console-metier-dao-security-webjson</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>

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

</project>
  • righe 4-8: l'identità Maven del nuovo progetto;
  • righe 22-26: una dipendenza dal progetto console che abbiamo appena esaminato nel paragrafo 17.5;

17.6.2. Configurazione Spring

Questo progetto utilizza la configurazione Spring del progetto console che importa (cfr. paragrafo 17.5.2).

17.6.3. Le viste dell'applicazione Swing

 

Questo progetto utilizzerà due viste:

  • la vista di accesso [1] è nuova. Serve ad autenticare l'utente che desidera utilizzare l'applicazione. Utilizza le classi:
    • [AbstractElectionsConnectForm], che implementa la vista;
    • [ElectionsConnectForm] che gestisce gli eventi della vista;
  • la vista [2] è già nota. È quella utilizzata finora. Utilizza le classi:
    • [AbstractElectionsMainForm], che implementa la vista;
    • [ElectionsMainForm], che gestisce gli eventi della vista;

17.6.4. La sessione

  

Quando un’applicazione Swing presenta più viste, è necessario trovare un modo affinché una vista possa trasmettere informazioni a un’altra vista. Utilizziamo un concetto impiegato nello sviluppo web: la sessione, che consente alle viste associate a un utente specifico di condividere informazioni. Questo concetto verrà implementato qui tramite un singleton Spring che verrà iniettato nelle due classi che gestiscono gli eventi delle due viste:


package elections.security.client.swing;

import elections.security.client.entities.User;
import org.springframework.stereotype.Component;

@Component
public class UiSession {
  
    // l'utente connesso
  private User user;
  
  // getter e setter
... 
}
  • riga 6: la classe [UiSession] è un componente Spring;
  • riga 10: serve solo a una cosa: memorizzare l’utente che si connette alla vista [1]. La vista [2], che ha bisogno di conoscerlo, lo recupererà da lì;

17.6.5. La classe di avvio

  

La classe di avvio dell’applicazione grafica è la seguente:


package elections.security.client.boot;

import elections.security.client.console.IElectionsUI;

public class BootElectionsSwing extends AbstractBootElections {
    public static void main(String[] arguments) {
        new BootElectionsSwing().run();
    }

    @Override
    protected IElectionsUI getUI() {
        return ctx.getBean("electionsConnectForm", IElectionsUI.class);
    }
}
  • riga 5: la classe [BootElectionsSwing] estende la classe [AbstractBootElections] definita nel progetto console presente nelle dipendenze del progetto;
  • righe 10-13: la classe [BootElectionsSwing] visualizzerà la vista di connessione [ElectionsConnectForm];
  • righe 6-8: verrà eseguito il metodo [run] di questa classe;

17.6.6. La classe [ElectionsMainForm]

La classe [ElectionsMainForm] è quella che in precedenza si chiamava [ElectionsSwing]. Gestisce gli eventi della vista [AbstractElectionsMainForm]. Il suo codice si evolve come segue:


package elections.security.client.swing;

import elections.security.client.console.IElectionsUI;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.util.ArrayList;
import java.util.List;

import javax.swing.DefaultListModel;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import elections.security.client.entities.ElectionsException;
import elections.security.client.entities.ListeElectorale;
import elections.security.client.entities.User;
import elections.security.client.metier.IElectionsMetier;

@Component
public class ElectionsMainForm extends AbstractElectionsMainForm implements IElectionsUI {

  private static final long serialVersionUID = 1L;

  // riferimento al livello [métier]
  @Autowired
  private IElectionsMetier metier;

  // sessione UI
  @Autowired
  private UiSession uiSession;

  // utente connesso
  private User user;

  // modelli delle liste JList
  private DefaultListModel<String> modèleNomsVoix = null;
  private DefaultListModel<String> modèleRésultats = null;

  // le liste in competizione
  private ListeElectorale[] listes;

  // elenchi inseriti dall'utente
  private final List<ListeElectorale> listesSaisies = new ArrayList<>();
  private ListeElectorale[] tListesSaisies;

  // inizializzazioni
  @Override
  protected void init() {
    // generazione dei componenti da parte della classe padre
    super.init();
    // inizializzazioni locali
    modèleNomsVoix = new DefaultListModel<>();
    jListNomsVoix.setModel(modèleNomsVoix);
    modèleRésultats = new DefaultListModel<>();
    jListResultats.setModel(modèleRésultats);
    String info;
    boolean erreur = false;
    try {
      // si richiedono le liste al livello [métier]
      listes = metier.getListesElectorales(user);
      // si associano i nomi delle liste alla casella combinata jComboBoxNomsListes
      for (int i = 0; i < listes.length; i++) {
        jComboBoxNomsListes.addItem(String.format("%s - %s", listes[i].getId(), listes[i].getNom()));
      }
      // insieme ai parametri dell'elezione
      int nbSiegesAPourvoir = metier.getNbSiegesAPourvoir(user);
      double seuilElectoral = metier.getSeuilElectoral(user);
      // si inizializzano le etichette relative a queste due informazioni
      jLabelSAP.setText(jLabelSAP.getText() + nbSiegesAPourvoir);
      jLabelSE.setText(jLabelSE.getText() + seuilElectoral);
      // messaggio di successo
      info = "Source de données lue avec succès";
    } catch (ElectionsException ex1) {
      // viene segnalato l'errore
      info = getInfoForException("Les erreurs suivantes se sont produites :", ex1);
      erreur = true;
    } catch (RuntimeException ex2) {
      // si registra l'errore
      info = getInfoForException("Les erreurs suivantes se sont produites :", ex2);
      erreur = true;
    }
    // errore?
    if (erreur) {
      // viene visualizzata l'informazione
      jTextPaneMessages.setText(info);
      jTextPaneMessages.setCaretPosition(0);
      return;
    } else {
      jTextPaneMessages.setText("");
    }
    // stato del modulo
    Utilitaires.setEnabled(new JLabel[]{jLabelAjouter, jLabelCalculer, jLabelEnregistrer, jLabelSupprimer}, false);
    Utilitaires.setEnabled(
            new JMenuItem[]{jMenuItemAjouter, jMenuItemCalculer, jMenuItemEnregistrer, jMenuItemSupprimer}, false);
    // centrare la finestra
    Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
    Dimension frameSize = getSize();
    if (frameSize.height > screenSize.height) {
      frameSize.height = screenSize.height;
    }
    if (frameSize.width > screenSize.width) {
      frameSize.width = screenSize.width;
    }
    setLocation((screenSize.width - frameSize.width) / 2, (screenSize.height - frameSize.height) / 2);

  }
...

  @Override
  public void run() {
    // memorizzazione utente
    user = uiSession.getUser();
    // inizializzazione della finestra
    init();
    setVisible(true);
  }
}
  • righe 32-33: inserimento della sessione dell'applicazione;
  • righe 112-119: la vista verrà attivata come in precedenza tramite il suo metodo [run];
  • riga 115: si recupera l'utente dalla sessione. Quest'ultimo sarà stato inserito nella sessione dal codice della vista di accesso [ElectionsConnectForm]. Il codice viene quindi modificato in modo che il primo parametro delle chiamate al livello [métier] sia questo utente (righe 63, 69-70 ad esempio);

17.6.7. La vista [AbstractElectionsConnectForm]

  

Creare con NetBeans il seguente modulo:

 

La classe non implementerà direttamente il metodo che gestisce il clic sull'opzione di menu [Connexion]. Richiamerà invece un metodo [doConnecter] dichiarato astratto e la classe della vista sarà a sua volta dichiarata astratta. Verrà eliminato il metodo statico [main] che è stato generato automaticamente.

17.6.8. Gestione degli eventi della vista di connessione

  

La classe [ElectionsConnectForm] implementa la gestione degli eventi della vista di connessione nel modo seguente:


package elections.security.client.swing;

import elections.security.client.console.IElectionsUI;
import elections.security.client.entities.ElectionsException;
import elections.security.client.entities.User;
import elections.security.client.metier.IElectionsMetier;
import java.awt.Dimension;
import java.awt.Toolkit;
import javax.swing.SwingUtilities;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class ElectionsConnectForm extends AbstractElectionsConnectForm implements IElectionsUI {

    private static final long serialVersionUID = 1L;

    // riferimento al livello [métier]
  @Autowired
  private IElectionsMetier metier;

  // utente connesso
  private User user;

  // modulo principale
  @Autowired
  private ElectionsMainForm electionsMainForm;
  
  // sessione UI
  @Autowired
  private UiSession uiSession;
  
  @Override
  protected void doConnect() {
    String info = null;
    try {
      if (isPageValid()) {
        // autenticazione dell'utente
        metier.authenticate(user);
        // l'utente viene memorizzato nella sessione
        uiSession.setUser(user);
        // la pagina di accesso viene nascosta
        setVisible(false);
        // viene visualizzata la pagina principale
        electionsMainForm.run();
      }
    } catch (ElectionsException ex1) {
      // si registra l'errore
      info = getInfoForException("Les erreurs suivantes se sont produites :", ex1);
    } catch (RuntimeException ex2) {
      // si registra l'errore
      info = getInfoForException("Les erreurs suivantes se sont produites :", ex2);
    }
    // errore?
    if (info != null) {
      // viene visualizzata l'informazione
      jTextPaneErreurs.setText(info);
      jTextPaneErreurs.setCaretPosition(0);
    }
  }

  // inizializzazioni
  @Override
  protected void init() {
    // generazione dei componenti da parte della classe padre
    super.init();
    // inizializzazioni locali
...
    // centrare la finestra
...
  }

  @Override
  public void run() {
    // visualizzazione dell'interfaccia grafica
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        init();
        setVisible(true);
      }
    });
  }

  private boolean isPageValid() {
    ...
  }

  private String getInfoForException(String message, ElectionsException ex) {
...
  }

  private String getInfoForException(String message, RuntimeException ex) {
...
  }

}
  • righe 73-82: il metodo [run] che verrà chiamato dalla classe di avvio [BootElectionsSwing];
  • righe 26-27: inserimento di un riferimento alla vista n. 2 che dovrà essere visualizzata se l'utente viene riconosciuto;
  • righe 30-31: inserimento della sessione dell'applicazione;
  • riga 84: il metodo [isPageValid] esegue due operazioni:
    • verifica che l'identificativo non sia vuoto (la password invece può esserlo);
    • popola il campo User della riga 23 con i dati inseriti;

Compito da svolgere: completate il codice della classe.



Compito da svolgere: testare l'applicazione.