17. [TD]: Protezione del server web / JSON per le elezioni
Parole chiave: architettura multilivello, Spring, iniezione di dipendenze, servizio web sicuro / JSON, client / server
Ora applicheremo ciò che abbiamo imparato nel capitolo precedente al compito sulle elezioni. L'architettura sarà la seguente:
![]() |
Il processo procederà come segue:
- Scrivere il server;
- scrittura del client senza il livello [ui] ma con un test JUnit per il livello [business];
- scrittura del client con il livello [UI];
17.1. Assistenza
![]() | ![]() |
I progetti relativi a questo capitolo si trovano nella cartella [support / chap-17]. Lo script SQL serve a generare il database necessario per l'esecuzione dei 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 sezione di supporto del documento.
17.3. Il server sicuro
Per configurare il server elettorale sicuro, è sufficiente copiare e incollare il progetto di esempio [intro-spring-security-server-01]:
![]() |
Compito: rinominare i pacchetti come mostrato in [1].
Una volta fatto ciò, modifichiamo il file [pom.xml] 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-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>
<!-- plugins -->
<build>
...
</build>
</project>
- Alle righe 23–27, sostituisci la dipendenza dal progetto [intro-server-webjson-01] con la dipendenza dal progetto [elections-webjson-metier-dao-spring-data] descritto al paragrafo 12;
- Alle righe 4–7, inserisci le proprietà del nuovo progetto;
Dobbiamo ancora 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 {
// constants
final static private String[] ENTITIES_PACKAGES = { "elections.dao.entities", "elections.security.entities" };
@Bean
public String[] packagesToScan() {
return ENTITIES_PACKAGES;
}
}
Le modifiche si trovano nelle seguenti righe:
- righe 8, 9, 14: inserire i nomi corretti dei pacchetti;
- riga 10: la classe importata è ora [elections.dao.config.DaoConfig] dal 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]. Si prega di verificarlo.
[SecurityConfig]
package elections.security.config;
...
@EnableWebSecurity
@ComponentScan(basePackages = { "elections.security.service" })
@Import({ WebConfig.class, DaoConfig.class })
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
}
Le modifiche sono riportate nelle seguenti righe:
- riga 6: modificare il nome del pacchetto;
Questo è tutto. Eseguire un test iniziale eseguendo il test JUnit []:
![]() |
Esegui la classe [Boot], quindi utilizza l'estensione di Chrome [Advanced Rest Client] per eseguire il seguente test:
![]() |
In [1], la richiesta. In [3], la risposta. In [2], l'intestazione HTTP è l'intestazione di autenticazione utente [admin, admin]: Authorization:Basic YWRtaW46YWRtaW4=
Ora richiedi gli elenchi concorrenti:
![]() |
17.4. Il client del server sicuro senza il livello [ui]
![]() |
Copiare e incollare il progetto [elections-ui-metier-dao-webjson] nel progetto [elections-metier-dao-security-webjson].
![]() |
Compito: in [1], rinominare i pacchetti se necessario e rimuovere quelli dal livello [ui] e le classi di avvio [boot].
Procederemo ora come descritto nella sezione 16.5.
17.4.1. Configurazione di Maven
Cambia solo la sezione di identificazione 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 [business]
![]() |
L'interfaccia [IElectionsMetier] si evolve come segue:
package elections.security.client.metier;
import elections.security.client.entities.ListeElectorale;
import elections.security.client.entities.User;
public interface IElectionsMetier {
// authentication
public void authenticate(User user);
// get the lists in competition
public ListeElectorale[] getListesElectorales(User user);
// the number of seats to be filled
public int getNbSiegesAPourvoir(User user);
// the electoral threshold
public double getSeuilElectoral(User user);
// recording results
public void recordResultats(User user, ListeElectorale[] listesElectorales);
// calculating seats
public ListeElectorale[] calculerSieges(User user, ListeElectorale[] listesElectorales);
}
Tutti i metodi hanno come primo parametro l'utente che desidera utilizzare le risorse del server sicuro.
L'implementazione di [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;
// election configuration
private ElectionsConfig electionsConfig;
private ElectionsConfig getElectionsConfig(User user) {
if(electionsConfig!=null){
return electionsConfig;
}
// mappers jSON
ObjectMapper mapperResponse = context.getBean(ObjectMapper.class);
try {
// request
Response<ElectionsConfig> response = mapperResponse.readValue(dao.getResponse(user, "/getElectionsConfig", null),
new TypeReference<Response<ElectionsConfig>>() {
});
// mistake?
if (response.getStatus() != 0) {
// 1 exception is thrown
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: Completa il codice sopra riportato.
17.4.3. Configurazione Spring
![]() |
La classe [MetierConfig] si evolve come segue:
package elections.security.client.config;
...
@ComponentScan({ "elections.security.client.dao", "elections.security.client.metier" })
public class MetierConfig {
Riga 5: I pacchetti da scansionare devono essere aggiornati.
17.4.4. Il test JUnit per il livello [business]
![]() |
La classe di test [Test01] viene modificata 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 {
// layer [electionsMetier]
@Autowired
private IElectionsMetier electionsMetier;
// mapper jSON
private final ObjectMapper mapper = new ObjectMapper();
// users
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() {
// create the table of 7 candidate lists
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);
// the seats for each list are calculated
listes = electionsMetier.calculerSieges(admin,listes);
// check results
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() {
// create the table of 7 candidate lists
ListeElectorale[] listes = electionsMetier.getListesElectorales(admin);
// the voices are hard-fixed
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);
// the seats obtained by each list are calculated
listes = electionsMetier.calculerSieges(admin,listes);
// check results
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() {
// we create a table of 24 candidate lists, each with 1 vote
ListeElectorale[] listes = new ListeElectorale[25];
// all 25 lists will have the same number of votes (4%)
for (int i = 0; i < listes.length; i++) {
listes[i] = new ListeElectorale("Liste" + (i + 1), 1, 0, false);
}
// calculation of seats - normally there should be a ElectionsException
// with an electoral threshold of 5%
electionsMetier.calculerSieges(admin,listes);
}
/**
* enregistrement des résultats de l'élection
*
* @throws JsonProcessingException
*/
@Test
public void ecritureResultatsElections() throws JsonProcessingException {
// create the table of 7 candidate lists
ListeElectorale[] listes = electionsMetier.getListesElectorales(admin);
// the voices are hard-fixed
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);
// the seats obtained by each list are calculated
listes = electionsMetier.calculerSieges(admin,listes);
// display results
for (int i = 0; i < listes.length; i++) {
System.out.println(mapper.writeValueAsString(listes[i]));
}
// results are entered into the database
electionsMetier.recordResultats(admin,listes);
// check results
listes = electionsMetier.getListesElectorales(admin);
// display results
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: Comprendere questo test e verificare che venga superato.
17.5. Il client del server sicuro con un livello [console]
![]() |
In NetBeans, apriamo i progetti Maven [elections-business-dao-security-webjson] e [elections-ui-business-dao-webjson], quindi creiamo un nuovo progetto [elections-console-business-dao-security-webjson] copiando e incollando il progetto [elections-ui-business-dao-webjson]:
![]() |
La maggior parte delle classi nel nuovo progetto [3] proviene dal progetto [elections-ui-metier-dao-webjson] [2], che era il client per il server web/JSON non protetto:
![]() |
Segui questi passaggi per compilare il nuovo progetto Maven:
- Copia e incolla il progetto [elections-ui-metier-dao-webjson] nel progetto [elections-ui-metier-dao-security-webjson];
- Nel nuovo progetto:
- elimina i pacchetti [elections.client.dao, elections.client.metier, elections.client.entities];
- nel pacchetto [elections.client.ui], mantieni solo la classe console [ElectionsConsole] e la sua interfaccia [IElectionsUI];
- rinominare i pacchetti come indicato in [3];
- correggere i problemi di importazione nelle varie classi tramite [clic destro / Correggi importazioni];
A questo punto, il nuovo progetto contiene numerosi errori.
17.5.1. Configurazione di Maven
Il file [pom.xml] per il 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'ID Maven del nuovo progetto;
- righe 22–26: la dipendenza dal progetto [elections-metier-dao-security-webjson] che abbiamo appena creato nella Sezione 17.4, che fornisce i livelli [DAO] e [business];
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 {
// director
private final User ADMIN = new User("admin", "admin");
@Bean
private User admin() {
return ADMIN;
}
}
- Riga 8: importiamo la classe [elections.security.client.config.MetierConfig] dal progetto [elections-metier-dao-security-webjson] descritto nella Sezione 17.4;
- riga 9: dichiariamo i pacchetti in cui si trovano i bean Spring;
- righe 12–18: il bean [admin] è l'utente [admin, admin] che verrà utilizzato dall'applicazione console. Ricordiamo che questo è l'unico utente autorizzato a interrogare il server web/JSON protetto;
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() {
// competing lists
ListeElectorale[] listes;
// data entry
try (Scanner clavier = new Scanner(System.in)) {
// lists in competition are requested from the [metier] layer
listes = electionsMetier.getListesElectorales(admin);
// we enter the votes
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");
}
}
}
}
// we calculate the number of seats
listes=electionsMetier.calculerSieges(admin,listes);
// we record the results
electionsMetier.recordResultats(admin,listes);
// lists sorted in descending order of votes
Arrays.sort(listes, new CompareListesElectorales());
// we display them
System.out.println("\nRésultats de l'élection\n");
for (int i = 0; i < listes.length; i++) {
System.out.println(listes[i]);
}
}
// electoral list comparison class
class CompareListesElectorales implements Comparator<ListeElectorale> {
// comparison of two candidate lists by number of votes
@Override
public int compare(ListeElectorale listeElectorale1, ListeElectorale listeElectorale2) {
// we compare the votes of these two lists
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 cambia molto poco. Basta ricordare che ora i metodi nel livello [business] richiedono un utente come primo parametro (righe 31, 49, 51). Questo viene fornito dall'iniezione alle righe 21–22. L'utente iniettato è quello autorizzato a interrogare il server web / jSON.
Compito: testare l'applicazione 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-business-dao-security-webjson] copiando e incollando il progetto [elections-ui-business-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 utente grafica, in [AbstractElectionsMainForm], e la classe [ElectionsSwing], che implementa i gestori di eventi dell'interfaccia utente grafica, in [ElectionsMainForm];
- correggere i problemi di importazione nelle varie classi facendo clic con il tasto destro del mouse e selezionando [Fix Imports];
A questo punto, ci sono numerosi errori.
17.6.1. Configurazione di Maven
Il file [pom.xml] cambia 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'ID Maven del nuovo progetto;
- righe 22–26: una dipendenza dal progetto console che abbiamo appena esaminato nella Sezione 17.5;
17.6.2. Configurazione Spring
Questo progetto utilizza la configurazione Spring del progetto console che importa (vedere la Sezione 17.5.2).
17.6.3. Le viste dell'applicazione Swing
![]() |
Questo progetto utilizzerà due viste:
![]() |
- La vista di accesso [1] è nuova. Viene utilizzata per autenticare l'utente che desidera utilizzare l'applicazione. Utilizza le seguenti classi:
- [AbstractElectionsConnectForm], che implementa la vista;
- [ElectionsConnectForm], che gestisce gli eventi della vista;
- La vista [2] è familiare. È quella utilizzata finora. Utilizza le seguenti 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 un meccanismo che consenta a una vista di trasmettere informazioni a un'altra vista. Utilizziamo un concetto derivante dallo sviluppo web: la sessione, che consente alle viste associate a un utente specifico di condividere informazioni. Questo concetto verrà implementato qui utilizzando un singleton Spring che verrà iniettato nelle due classi che gestiscono gli eventi per entrambe le viste:
package elections.security.client.swing;
import elections.security.client.entities.User;
import org.springframework.stereotype.Component;
@Component
public class UiSession {
// the connected user
private User user;
// getters and setters
...
}
- riga 6: la classe [UiSession] è un componente Spring;
- riga 10: ha un unico scopo: memorizzare l'utente che effettua l'accesso con la vista [1]. La vista [2], che ha bisogno di conoscere questo utente, 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 incluso nelle dipendenze del progetto;
- righe 10–13: la classe [BootElectionsSwing] visualizzerà la vista di accesso [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 era denominata [ElectionsSwing]. Gestisce gli eventi per la 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;
// reference on the [business] layer
@Autowired
private IElectionsMetier metier;
// session UI
@Autowired
private UiSession uiSession;
// logged-in user
private User user;
// list templates JList
private DefaultListModel<String> modèleNomsVoix = null;
private DefaultListModel<String> modèleRésultats = null;
// competing lists
private ListeElectorale[] listes;
// user-entered lists
private final List<ListeElectorale> listesSaisies = new ArrayList<>();
private ListeElectorale[] tListesSaisies;
// initializations
@Override
protected void init() {
// generation of components by the parent class
super.init();
// local initializations
modèleNomsVoix = new DefaultListModel<>();
jListNomsVoix.setModel(modèleNomsVoix);
modèleRésultats = new DefaultListModel<>();
jListResultats.setModel(modèleRésultats);
String info;
boolean erreur = false;
try {
// lists are requested from the [business] layer
listes = metier.getListesElectorales(user);
// associate list names with the jComboBoxNomsListes combo
for (int i = 0; i < listes.length; i++) {
jComboBoxNomsListes.addItem(String.format("%s - %s", listes[i].getId(), listes[i].getNom()));
}
// and election parameters
int nbSiegesAPourvoir = metier.getNbSiegesAPourvoir(user);
double seuilElectoral = metier.getSeuilElectoral(user);
// we initialize the labels linked to these two pieces of information
jLabelSAP.setText(jLabelSAP.getText() + nbSiegesAPourvoir);
jLabelSE.setText(jLabelSE.getText() + seuilElectoral);
// message of success
info = "Source de données lue avec succès";
} catch (ElectionsException ex1) {
// we note the error
info = getInfoForException("Les erreurs suivantes se sont produites :", ex1);
erreur = true;
} catch (RuntimeException ex2) {
// we note the error
info = getInfoForException("Les erreurs suivantes se sont produites :", ex2);
erreur = true;
}
// mistake?
if (erreur) {
// display info
jTextPaneMessages.setText(info);
jTextPaneMessages.setCaretPosition(0);
return;
} else {
jTextPaneMessages.setText("");
}
// form status
Utilitaires.setEnabled(new JLabel[]{jLabelAjouter, jLabelCalculer, jLabelEnregistrer, jLabelSupprimer}, false);
Utilitaires.setEnabled(
new JMenuItem[]{jMenuItemAjouter, jMenuItemCalculer, jMenuItemEnregistrer, jMenuItemSupprimer}, false);
// center window
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() {
// user memory
user = uiSession.getUser();
// window initialization
init();
setVisible(true);
}
}
- righe 32-33: inserisci la sessione dell'applicazione;
- righe 112–119: la vista verrà attivata come prima utilizzando il suo metodo [run];
- riga 115: l'utente viene recuperato dalla sessione. Questo utente è stato inserito lì dal codice nella vista di accesso [ElectionsConnectForm]. Il codice viene quindi modificato in modo che il primo parametro delle chiamate al livello [business] sia questo utente (righe 63, 69-70, ad esempio);
17.6.7. La vista [AbstractElectionsConnectForm]
![]() |
Utilizza NetBeans per creare il seguente modulo:
![]() |
La classe non implementerà direttamente il metodo che gestisce il clic sull'opzione di menu [Login]. Chiamerà un metodo [doConnecter] dichiarato come astratto, e la classe della vista sarà a sua volta dichiarata astratta. Rimuoveremo il metodo statico [main] che è stato generato automaticamente.
17.6.8. Gestione degli eventi per la vista di login
![]() |
La classe [ElectionsConnectForm] implementa la gestione degli eventi per la vista di accesso come segue:
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;
// reference on the [business] layer
@Autowired
private IElectionsMetier metier;
// logged-in user
private User user;
// main form
@Autowired
private ElectionsMainForm electionsMainForm;
// session UI
@Autowired
private UiSession uiSession;
@Override
protected void doConnect() {
String info = null;
try {
if (isPageValid()) {
// user authentication
metier.authenticate(user);
// the user is saved in the session
uiSession.setUser(user);
// connection view is hidden
setVisible(false);
// the main view is displayed
electionsMainForm.run();
}
} catch (ElectionsException ex1) {
// we note the error
info = getInfoForException("Les erreurs suivantes se sont produites :", ex1);
} catch (RuntimeException ex2) {
// we note the error
info = getInfoForException("Les erreurs suivantes se sont produites :", ex2);
}
// mistake?
if (info != null) {
// display info
jTextPaneErreurs.setText(info);
jTextPaneErreurs.setCaretPosition(0);
}
}
// initializations
@Override
protected void init() {
// generation of components by the parent class
super.init();
// local initializations
...
// center window
...
}
@Override
public void run() {
// the graphical interface is displayed
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 dovrebbe 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 il nome utente non sia vuoto (la password può essere vuota);
- istanzia il campo User della riga 23 con l'input;
![]() | ![]() |
![]() | ![]() |
Compito: Completa il codice della classe.
Compito: Testare l'applicazione.































