17. [TD]: proteção do servidor web / jSON das eleições
Palavras-chave: arquitetura multicamadas, Spring, injeção de dependências, serviço web / jSON seguro, cliente / servidor
Agora aplicamos o que aprendemos no capítulo anterior ao TD das eleições. A arquitetura será a seguinte:
![]() |
O processo se dará da seguinte maneira:
- programação do servidor;
- programação do cliente sem a camada [ui], mas com um teste JUnit da camada [métier];
- gravação do cliente com a camada [ui];
17.1. Support
![]() | ![]() |
Os projetos deste capítulo estão na pasta [support / chap-17]. O script SQL serve para gerar o banco de dados necessário para os testes.
17.2. O banco de dados
O banco de dados do servidor seguro deve agora conter as tabelas [USERS], [ROLES] e [USERS_ROLES]:
![]() | ![]() |
O script SQL para a geração do banco de dados está disponível na documentação de suporte.
17.3. O servidor seguro
Para configurar o servidor seguro das eleições, basta copiar e colar o projeto de exemplo [intro-spring-security-server-01]:
![]() |
Tarefa a ser realizada: renomeie os pacotes conforme mostrado em [1].
Feito isso, modificamos o arquivo [pom.xml] da seguinte maneira:
<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>
<!-- plug-ins -->
<build>
...
</build>
</project>
- nas linhas 23 a 27, substitua a dependência do projeto [intro-server-webjson-01] pela dependência do projeto [elections-webjson-metier-dao-spring-data], abordado no parágrafo 12;
- nas linhas 4 a 7, insira as características do novo projeto;
Resta-nos modificar as classes de configuração do projeto:
[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 {
// constantes
final static private String[] ENTITIES_PACKAGES = { "elections.dao.entities", "elections.security.entities" };
@Bean
public String[] packagesToScan() {
return ENTITIES_PACKAGES;
}
}
As alterações estão nas seguintes linhas:
- linhas 8, 9, 14: insira os nomes corretos dos pacotes;
- linha 10: a classe importada agora é [elections.dao.config.DaoConfig] do projeto [elections-webjson-metier-dao-spring-data];
Observação: como já foi mencionado, essa configuração só funciona se a classe [elections.dao.config.DaoConfig] tiver a anotação [@Configuration]. Verifique esse ponto.
[SecurityConfig]
package elections.security.config;
...
@EnableWebSecurity
@ComponentScan(basePackages = { "elections.security.service" })
@Import({ WebConfig.class, DaoConfig.class })
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
}
As alterações estão nas seguintes linhas:
- linha 6: altere o nome do pacote;
É tudo. Faça um primeiro teste executando o teste JUnit []:
![]() |
Execute a classe de inicialização [Boot] e, em seguida, com a extensão do Chrome [Advanced Rest Client], faça o seguinte teste:
![]() |
Em [1], a solicitação. Em [3], a resposta. Em [2], o cabeçalho HTTP é o cabeçalho de autenticação do usuário [admin, admin]: Authorization:Basic YWRtaW46YWRtaW4=
Agora, veja as listas em disputa:
![]() |
17.4. O cliente do servidor seguro sem a camada [ui]
![]() |
Faça um copiar/colar do projeto [elections-ui-metier-dao-webjson] no projeto [elections-metier-dao-security-webjson].
![]() |
Tarefa a ser realizada: no [1], renomeie os pacotes, se necessário, e exclua aqueles da camada [ui] e das classes de inicialização [boot].
Vamos prosseguir agora da mesma forma que foi feito no parágrafo 16.5.
17.4.1. Configuração do Maven
Apenas a parte de identificação do projeto muda:
<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. Reestruturação da camada [métier]
![]() |
A interface [IElectionsMetier] sofre as seguintes alterações:
package elections.security.client.metier;
import elections.security.client.entities.ListeElectorale;
import elections.security.client.entities.User;
public interface IElectionsMetier {
// autenticação
public void authenticate(User user);
// obter as listas em disputa
public ListeElectorale[] getListesElectorales(User user);
// o número de vagas a serem preenchidas
public int getNbSiegesAPourvoir(User user);
// o limite eleitoral
public double getSeuilElectoral(User user);
// registro dos resultados
public void recordResultats(User user, ListeElectorale[] listesElectorales);
// cálculo dos assentos
public ListeElectorale[] calculerSieges(User user, ListeElectorale[] listesElectorales);
}
Todos os métodos têm como primeiro parâmetro o usuário que deseja utilizar os recursos do servidor seguro.
A implementação [ElectionsMetier] evolui da seguinte forma:
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;
// configuração da eleição
private ElectionsConfig electionsConfig;
private ElectionsConfig getElectionsConfig(User user) {
if(electionsConfig!=null){
return electionsConfig;
}
// mapadores jSON
ObjectMapper mapperResponse = context.getBean(ObjectMapper.class);
try {
// consulta
Response<ElectionsConfig> response = mapperResponse.readValue(dao.getResponse(user, "/getElectionsConfig", null),
new TypeReference<Response<ElectionsConfig>>() {
});
// erro?
if (response.getStatus() != 0) {
// lançando 1 exceção
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);
}
}
Tarefa: complete o código acima.
17.4.3. Configuração do Spring
![]() |
A classe [MetierConfig] sofre as seguintes alterações:
package elections.security.client.config;
...
@ComponentScan({ "elections.security.client.dao", "elections.security.client.metier" })
public class MetierConfig {
Na linha 5, os pacotes a serem explorados devem ser atualizados.
17.4.4. O teste JUnit da camada [métier]
![]() |
A classe de teste [Test01] evolui da seguinte forma:
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 {
// camada [electionsMetier]
@Autowired
private IElectionsMetier electionsMetier;
// mapeador jSON
private final ObjectMapper mapper = new ObjectMapper();
// usuários
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() {
// cria-se a tabela das 7 listas de candidatos
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);
// calculam-se os assentos de cada uma das listas
listes = electionsMetier.calculerSieges(admin,listes);
// verifica-se os resultados
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() {
// cria-se a tabela das 7 listas de candidatos
ListeElectorale[] listes = electionsMetier.getListesElectorales(admin);
// fixa-se o número de votos
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);
// calcula-se o número de cadeiras obtidas por cada uma das listas
listes = electionsMetier.calculerSieges(admin,listes);
// verifica-se os resultados
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() {
// cria-se uma tabela com 24 listas de candidatos, cada uma com 1 voto
ListeElectorale[] listes = new ListeElectorale[25];
// as 25 listas terão o mesmo número de votos (4%)
for (int i = 0; i < listes.length; i++) {
listes[i] = new ListeElectorale("Liste" + (i + 1), 1, 0, false);
}
// cálculo de cadeiras — normalmente, deveria ser ElectionsException
// com um limite eleitoral de 5%
electionsMetier.calculerSieges(admin,listes);
}
/**
* enregistrement des résultats de l'élection
*
* @throws JsonProcessingException
*/
@Test
public void ecritureResultatsElections() throws JsonProcessingException {
// cria-se a tabela das 7 listas candidatas
ListeElectorale[] listes = electionsMetier.getListesElectorales(admin);
// definimos os votos de forma fixa
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);
// calcula-se o número de cadeiras obtidas por cada uma das listas
listes = electionsMetier.calculerSieges(admin,listes);
// exibe-se os resultados
for (int i = 0; i < listes.length; i++) {
System.out.println(mapper.writeValueAsString(listes[i]));
}
// os resultados são gravados no banco de dados
electionsMetier.recordResultats(admin,listes);
// verifica-se os resultados
listes = electionsMetier.getListesElectorales(admin);
// exibe-se os resultados
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());
}
}
Tarefa a ser realizada: compreenda este teste e verifique se ele é aprovado.
17.5. O cliente do servidor seguro com a camada [console]
![]() |
Com o NetBeans, abrimos os projetos Maven [elections-metier-dao-security-webjson] e [elections-ui-metier-dao-webjson] e, em seguida, criamos um novo projeto [elections-console-metier-dao-security-webjson] por meio de copiar/colar do projeto [elections-ui-metier-dao-webjson]:
![]() |
A maioria das classes do novo projeto [3] provém dos projetos [elections-ui-metier-dao-webjson] e [2], que eram os clientes do servidor web / jSON não seguro:
![]() |
Siga estas etapas para compilar o novo projeto Maven:
- faça um copiar/colar do projeto [elections-ui-metier-dao-webjson] no projeto [elections-ui-metier-dao-security-webjson];
- no novo projeto:
- exclua os pacotes [elections.client.dao, elections.client.metier, elections.client.entities];
- no pacote [elections.client.ui], mantenha apenas a classe console [ElectionsConsole] e sua interface [IElectionsUI];
- renomeie os pacotes conforme indicado em [3];
- corrija os problemas de importação nas diversas classes por meio de [clic droit / Fix Imports];
Nesta fase, o novo projeto apresenta vários erros.
17.5.1. Configuração do Maven
O arquivo [pom.xml] do novo projeto é o seguinte:
<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>
- linhas 4-8: a identidade Maven do novo projeto;
- linhas 22-26: a dependência do projeto [elections-metier-dao-security-webjson] que acabamos de construir no parágrafo 17.4, e que fornece as camadas [DAO] e [métier];
17.5.2. Configuração do Spring
![]() |
A classe [UiConfig] é a seguinte:
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 {
// administrador
private final User ADMIN = new User("admin", "admin");
@Bean
private User admin() {
return ADMIN;
}
}
- linha 8: importa-se a classe [elections.security.client.config.MetierConfig] do projeto [elections-metier-dao-security-webjson], abordado no parágrafo 17.4;
- linha 9: declaram-se os pacotes onde se encontram os beans do Spring;
- linhas 12-18: o bean [admin] é o usuário [admin, admin] que será utilizado pelo aplicativo de console. Vale lembrar que este é o único usuário autorizado a consultar o servidor web seguro /jSON;
17.5.3. O aplicativo de inicialização do console
![]() |
A classe [ElectionsConsole] evolui da seguinte forma:
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() {
// as listas em disputa
ListeElectorale[] listes;
// entrada de dados
try (Scanner clavier = new Scanner(System.in)) {
// solicita-se as listas em disputa à camada [metier]
listes = electionsMetier.getListesElectorales(admin);
// realiza-se a contagem dos votos
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");
}
}
}
}
// realiza-se o cálculo dos assentos
listes=electionsMetier.calculerSieges(admin,listes);
// registra-se os resultados
electionsMetier.recordResultats(admin,listes);
// classificação das listas em ordem decrescente de votos
Arrays.sort(listes, new CompareListesElectorales());
// elas são exibidas
System.out.println("\nRésultats de l'élection\n");
for (int i = 0; i < listes.length; i++) {
System.out.println(listes[i]);
}
}
// classe de comparação de listas eleitorais
class CompareListesElectorales implements Comparator<ListeElectorale> {
// comparação de duas listas de candidatos com base no número de votos
@Override
public int compare(ListeElectorale listeElectorale1, ListeElectorale listeElectorale2) {
// compara-se o número de votos dessas duas listas
int nbVoix1 = listeElectorale1.getVoix();
int nbVoix2 = listeElectorale2.getVoix();
if (nbVoix1 < nbVoix2) {
return +1;
} else {
if (nbVoix1 > nbVoix2)
return -1;
else
return 0;
}
}
}
}
A classe ElectionsConsole sofre pouquíssimas alterações. Basta lembrar que, agora, os métodos da camada [métier] exigem, como primeiro parâmetro, um usuário (linhas 31, 49, 51). Esse usuário é fornecido pela injeção nas linhas 21-22. O usuário injetado é aquele autorizado a consultar o servidor web / jSON.
Tarefa a ser realizada: teste o aplicativo de console (lembre-se de iniciar o servidor seguro antes).
17.6. O cliente do servidor seguro com uma camada [swing]
![]() |
Criamos um novo projeto NetBeans [elections-swing-metier-dao-security-webjson] por meio de copiar/colar do projeto [elections-ui-metier-dao-webjson]:
![]() |
No novo projeto [2]:
- exclua os pacotes [elections.client.dao, elections.client.metier, elections.client.entities];
- no pacote [elections.client.ui], exclua as classes [ElectionsConsole, IElectionsUI];
- renomeie os pacotes conforme indicado em [2];
- no pacote [elections.security.client.swing], renomeie a classe [AbstractElectionsSwing], que implementa a interface gráfica, para [AbstractElectionsMainForm], e a classe [ElectionsSwing], que implementa os manipuladores de eventos dainterface gráfica para [ElectionsMainForm];
- corrija os problemas de importação nas diversas classes por meio de [clic droit / Fix Imports];
Nesta fase, há muitos erros.
17.6.1. Configuração do Maven
O arquivo [pom.xml] sofre as seguintes alterações:
<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>
- linhas 4-8: a identidade Maven do novo projeto;
- linhas 22-26: uma dependência do projeto console que acabamos de analisar no parágrafo 17.5;
17.6.2. Configuração do Spring
Este projeto utiliza a configuração Spring do projeto console que ele importa (ver parágrafo 17.5.2).
17.6.3. As telas do aplicativo Swing
![]() |
Este projeto utilizará duas visualizações:
![]() |
- a vista [1] de login é nova. Ela serve para autenticar o usuário que deseja utilizar a aplicação. Ela utiliza as classes:
- [AbstractElectionsConnectForm], que implementa a visualização;
- [ElectionsConnectForm], que gerencia os eventos da visualização;
- a vista [2] já é conhecida. É a que tem sido utilizada até agora. Ela utiliza as classes:
- [AbstractElectionsMainForm], que implementa a visualização;
- [ElectionsMainForm], que gerencia os eventos da visualização;
17.6.4. A sessão
![]() |
Quando um aplicativo Swing possui várias visualizações, é necessário encontrar uma maneira de uma visualização passar informações para outra. Utilizamos um conceito empregado no desenvolvimento web: a sessão, que permite que as visualizações associadas a um usuário específico compartilhem informações. Esse conceito será implementado aqui por meio de um singleton do Spring, que será injetado nas duas classes que gerenciam os eventos das duas visualizações:
package elections.security.client.swing;
import elections.security.client.entities.User;
import org.springframework.stereotype.Component;
@Component
public class UiSession {
// o usuário conectado
private User user;
// getters e setters
...
}
- linha 6: a classe [UiSession] é um componente Spring;
- linha 10: ela serve apenas para uma coisa: armazenar o usuário que se conecta à visualização [1]. A visualização [2], que precisa saber quem é esse usuário, irá buscá-lo ali;
17.6.5. A classe de inicialização
![]() |
A classe de inicialização do aplicativo gráfico é a seguinte:
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);
}
}
- linha 5: a classe [BootElectionsSwing] estende a classe [AbstractBootElections] definida no projeto de console presente nas dependências do projeto;
- linhas 10-13: a classe [BootElectionsSwing] exibirá a tela de login [ElectionsConnectForm];
- linhas 6-8: é o método [run] dessa classe que será executado;
17.6.6. A classe [ElectionsMainForm]
A classe [ElectionsMainForm] é aquela que antes se chamava [ElectionsSwing]. Ela gerencia os eventos da visualização [AbstractElectionsMainForm]. Seu código evolui da seguinte forma:
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;
// referência na camada [métier]
@Autowired
private IElectionsMetier metier;
// sessão UI
@Autowired
private UiSession uiSession;
// usuário conectado
private User user;
// modelos das listas JList
private DefaultListModel<String> modèleNomsVoix = null;
private DefaultListModel<String> modèleRésultats = null;
// listas em competição
private ListeElectorale[] listes;
// listas inseridas pelo usuário
private final List<ListeElectorale> listesSaisies = new ArrayList<>();
private ListeElectorale[] tListesSaisies;
// inicializações
@Override
protected void init() {
// geração de componentes pela classe pai
super.init();
// inicializações locais
modèleNomsVoix = new DefaultListModel<>();
jListNomsVoix.setModel(modèleNomsVoix);
modèleRésultats = new DefaultListModel<>();
jListResultats.setModel(modèleRésultats);
String info;
boolean erreur = false;
try {
// as listas são solicitadas à camada [métier]
listes = metier.getListesElectorales(user);
// associa-se os nomes das listas ao combo jComboBoxNomsListes
for (int i = 0; i < listes.length; i++) {
jComboBoxNomsListes.addItem(String.format("%s - %s", listes[i].getId(), listes[i].getNom()));
}
// assim como os parâmetros da eleição
int nbSiegesAPourvoir = metier.getNbSiegesAPourvoir(user);
double seuilElectoral = metier.getSeuilElectoral(user);
// inicializam-se os rótulos associados a essas duas informações
jLabelSAP.setText(jLabelSAP.getText() + nbSiegesAPourvoir);
jLabelSE.setText(jLabelSE.getText() + seuilElectoral);
// mensagem de sucesso
info = "Source de données lue avec succès";
} catch (ElectionsException ex1) {
// registra-se o erro
info = getInfoForException("Les erreurs suivantes se sont produites :", ex1);
erreur = true;
} catch (RuntimeException ex2) {
// registra-se o erro
info = getInfoForException("Les erreurs suivantes se sont produites :", ex2);
erreur = true;
}
// erro?
if (erreur) {
// exibe a informação
jTextPaneMessages.setText(info);
jTextPaneMessages.setCaretPosition(0);
return;
} else {
jTextPaneMessages.setText("");
}
// estado do formulário
Utilitaires.setEnabled(new JLabel[]{jLabelAjouter, jLabelCalculer, jLabelEnregistrer, jLabelSupprimer}, false);
Utilitaires.setEnabled(
new JMenuItem[]{jMenuItemAjouter, jMenuItemCalculer, jMenuItemEnregistrer, jMenuItemSupprimer}, false);
// centralizar a janela
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() {
// memorização do usuário
user = uiSession.getUser();
// inicialização da janela
init();
setVisible(true);
}
}
- linhas 32-33: injeção da sessão do aplicativo;
- linhas 112-119: a visualização será ativada, como anteriormente, por meio de seu método [run];
- linha 115: recupera-se o usuário da sessão. Ele terá sido inserido ali pelo código da visualização de login [ElectionsConnectForm]. Em seguida, o código é modificado para que o primeiro parâmetro das chamadas à camada [métier] seja esse usuário (linhas 63, 69-70, por exemplo);
17.6.7. A visualização [AbstractElectionsConnectForm]
![]() |
Crie com o NetBeans o seguinte formulário:
![]() |
A classe não implementará por si mesma o método que gerencia o clique na opção de menu [Connexion]. Ela chamará um método [doConnecter] declarado como abstrato, e a classe da visualização será, por sua vez, declarada como abstrata. O método estático [main], que foi gerado automaticamente, será removido.
17.6.8. Gerenciamento de eventos da tela de login
![]() |
A classe [ElectionsConnectForm] implementa o gerenciamento de eventos da visualização de conexão da seguinte maneira:
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;
// referência na camada [métier]
@Autowired
private IElectionsMetier metier;
// usuário conectado
private User user;
// formulário principal
@Autowired
private ElectionsMainForm electionsMainForm;
// sessão UI
@Autowired
private UiSession uiSession;
@Override
protected void doConnect() {
String info = null;
try {
if (isPageValid()) {
// autenticação do usuário
metier.authenticate(user);
// o usuário é armazenado na sessão
uiSession.setUser(user);
// a tela de login é ocultada
setVisible(false);
// a tela principal é exibida
electionsMainForm.run();
}
} catch (ElectionsException ex1) {
// registra-se o erro
info = getInfoForException("Les erreurs suivantes se sont produites :", ex1);
} catch (RuntimeException ex2) {
// o erro é registrado
info = getInfoForException("Les erreurs suivantes se sont produites :", ex2);
}
// erro?
if (info != null) {
// a informação é exibida
jTextPaneErreurs.setText(info);
jTextPaneErreurs.setCaretPosition(0);
}
}
// inicializações
@Override
protected void init() {
// geração dos componentes pela classe pai
super.init();
// inicializações locais
...
// centralizar a janela
...
}
@Override
public void run() {
// exibe a interface gráfica
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) {
...
}
}
- linhas 73-82: o método [run], que será chamado pela classe de inicialização [BootElectionsSwing];
- linhas 26-27: injeção de uma referência à visualização nº 2, que deverá ser exibida caso o usuário seja reconhecido;
- linhas 30-31: injeção da sessão do aplicativo;
- linha 84: o método [isPageValid] realiza duas ações:
- verifica se o identificador não está vazio (a senha, por sua vez, pode estar);
- instancia o campo User da linha 23 com os dados inseridos;
![]() | ![]() |
![]() | ![]() |
Tarefa: complete o código da classe.
Tarefa: Teste o aplicativo.































