17. [TD]: Absicherung des Webservers / jSON für Wahlen
Stichworte: mehrschichtige Architektur, Spring, Dependency Injection, Webdienst / jSON gesichert, Client / Server
Wir wenden nun das im vorherigen Kapitel Gelernte auf das Wahlsystem TD an. Die Architektur sieht wie folgt aus:
![]() |
Der Ablauf wird wie folgt sein:
- Erstellung des Servers;
- Programmierung des Clients ohne die [ui]-Schicht, jedoch mit einem Test der JUnit-Schicht für die [métier]-Schicht;
- Schreiben vom Client mit der Schicht [ui];
17.1. Support
![]() | ![]() |
Die Projekte dieses Kapitels befinden sich im Ordner [support / chap-17]. Das Skript SQL dient dazu, die für die Tests erforderliche Datenbank zu erstellen.
17.2. Die Datenbank
Die Datenbank des sicheren Servers muss nun die Tabellen [USERS], [ROLES] und [USERS_ROLES] enthalten:
![]() | ![]() |
Das Skript SQL zur Erstellung der Datenbank ist im Dokumenten-Support verfügbar.
17.3. Der sichere Server
Um den sicheren Wahlserver einzurichten, kopieren Sie einfach das Beispielprojekt [intro-spring-security-server-01] und fügen Sie es ein:
![]() |
Zu erledigende Aufgabe: Benennen Sie die Pakete wie in [1] gezeigt um.
Anschließend ändern wir die Datei [pom.xml] wie folgt:
<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>
- Ersetzen Sie in den Zeilen 23–27 die Abhängigkeit vom Projekt [intro-server-webjson-01] durch die vom Projekt [elections-webjson-metier-dao-spring-data], das in Abschnitt 12 behandelt wurde;
- Geben Sie in den Zeilen 4–7 die Merkmale des neuen Projekts ein;
Nun müssen wir noch die Konfigurationsklassen des Projekts ändern:
[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 {
// Konstanten
final static private String[] ENTITIES_PACKAGES = { "elections.dao.entities", "elections.security.entities" };
@Bean
public String[] packagesToScan() {
return ENTITIES_PACKAGES;
}
}
Die Änderungen befinden sich in den folgenden Zeilen:
- Zeilen 8, 9, 14: Geben Sie die richtigen Paketnamen ein;
- Zeile 10: Die importierte Klasse ist nun [elections.dao.config.DaoConfig] aus dem Projekt [elections-webjson-metier-dao-spring-data];
Hinweis: Wie bereits erwähnt, funktioniert diese Konfiguration nur, wenn die Klasse [elections.dao.config.DaoConfig] die Annotation [@Configuration] hat. Bitte überprüfen Sie diesen Punkt.
[SecurityConfig]
package elections.security.config;
...
@EnableWebSecurity
@ComponentScan(basePackages = { "elections.security.service" })
@Import({ WebConfig.class, DaoConfig.class })
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
}
Die Änderungen befinden sich in den folgenden Zeilen:
- Zeile 6: Ändern Sie den Namen des Pakets;
Das war’s. Führen Sie einen ersten Test durch, indem Sie den Test JUnit [] ausführen:
![]() |
Führen Sie die Boot-Klasse [Boot] aus und führen Sie anschließend mit der Chrome-Erweiterung [Advanced Rest Client] den folgenden Test durch:
![]() |
In [1] die Anfrage. In [3] die Antwort. In [2] ist der Header HTTP der Authentifizierungsheader des Benutzers [admin, admin]: Authorization:Basic YWRtaW46YWRtaW4=
Fragen Sie jetzt nach den konkurrierenden Listen:
![]() |
17.4. Der Client des sicheren Servers ohne die Schicht [ui]
![]() |
Führen Sie ein Kopieren/Einfügen des Projekts [elections-ui-metier-dao-webjson] in das Projekt [elections-metier-dao-security-webjson] durch.
![]() |
Zu erledigende Aufgabe: Benennen Sie in [1] die Pakete bei Bedarf um und löschen Sie diejenigen aus der Ebene [ui] sowie aus den Startklassen [boot].
Wir gehen nun genauso vor wie in Abschnitt 16.5 beschrieben.
17.4.1. Maven-Konfiguration
Nur der Teil zur Projektidentifikation ändert sich:
<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. Refactoring der Schicht [métier]
![]() |
Die Schnittstelle [IElectionsMetier] entwickelt sich wie folgt weiter:
package elections.security.client.metier;
import elections.security.client.entities.ListeElectorale;
import elections.security.client.entities.User;
public interface IElectionsMetier {
// Authentifizierung
public void authenticate(User user);
// Wettbewerbslisten abrufen
public ListeElectorale[] getListesElectorales(User user);
// Anzahl der zu vergebenden Sitze
public int getNbSiegesAPourvoir(User user);
// Wahlhürde
public double getSeuilElectoral(User user);
// Erfassung der Ergebnisse
public void recordResultats(User user, ListeElectorale[] listesElectorales);
// Berechnung der Sitze
public ListeElectorale[] calculerSieges(User user, ListeElectorale[] listesElectorales);
}
Alle Methoden haben als ersten Parameter den Benutzer, der die Ressourcen des sicheren Servers nutzen möchte.
Die Implementierung [ElectionsMetier] entwickelt sich wie folgt:
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;
// Konfiguration der Wahl
private ElectionsConfig electionsConfig;
private ElectionsConfig getElectionsConfig(User user) {
if(electionsConfig!=null){
return electionsConfig;
}
// Zuordnungsfunktionen jSON
ObjectMapper mapperResponse = context.getBean(ObjectMapper.class);
try {
// Abfrage
Response<ElectionsConfig> response = mapperResponse.readValue(dao.getResponse(user, "/getElectionsConfig", null),
new TypeReference<Response<ElectionsConfig>>() {
});
// Fehler?
if (response.getStatus() != 0) {
// Es wird 1 Ausnahme ausgelöst
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);
}
}
Aufgabe: Vervollständigen Sie den obigen Code.
17.4.3. Spring-Konfiguration
![]() |
Die Klasse [MetierConfig] entwickelt sich wie folgt:
package elections.security.client.config;
...
@ComponentScan({ "elections.security.client.dao", "elections.security.client.metier" })
public class MetierConfig {
Zeile 5: Die zu durchsuchenden Pakete müssen aktualisiert werden.
17.4.4. Der Test JUnit der Schicht [métier]
![]() |
Die Testklasse [Test01] entwickelt sich wie folgt:
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 {
// Ebene [electionsMetier]
@Autowired
private IElectionsMetier electionsMetier;
// Mapper jSON
private final ObjectMapper mapper = new ObjectMapper();
// Benutzer
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() {
// Es wird die Tabelle mit den 7 Kandidatenlisten erstellt
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);
// die Sitze jeder einzelnen Liste werden berechnet
listes = electionsMetier.calculerSieges(admin,listes);
// die Ergebnisse werden überprüft
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() {
// Es wird eine Tabelle mit den 7 Kandidatenlisten erstellt
ListeElectorale[] listes = electionsMetier.getListesElectorales(admin);
// Die Stimmen werden fest vorgegeben
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);
// Die von jeder Liste erzielten Sitze werden berechnet
listes = electionsMetier.calculerSieges(admin,listes);
// Die Ergebnisse werden überprüft
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() {
// Es wird eine Tabelle mit 24 Kandidatenlisten erstellt, wobei jede Liste 1 Stimme erhält
ListeElectorale[] listes = new ListeElectorale[25];
// Alle 25 Listen erhalten die gleiche Anzahl an Stimmen (4 %)
for (int i = 0; i < listes.length; i++) {
listes[i] = new ListeElectorale("Liste" + (i + 1), 1, 0, false);
}
// Sitzberechnung – normalerweise müsste es ElectionsException lauten
// bei einer Wahlhürde von 5 %
electionsMetier.calculerSieges(admin,listes);
}
/**
* enregistrement des résultats de l'élection
*
* @throws JsonProcessingException
*/
@Test
public void ecritureResultatsElections() throws JsonProcessingException {
// Es wird eine Tabelle mit den 7 Kandidatenlisten erstellt
ListeElectorale[] listes = electionsMetier.getListesElectorales(admin);
// Die Stimmen werden fest vorgegeben
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);
// die von jeder Liste erzielten Sitze werden berechnet
listes = electionsMetier.calculerSieges(admin,listes);
// Die Ergebnisse werden angezeigt
for (int i = 0; i < listes.length; i++) {
System.out.println(mapper.writeValueAsString(listes[i]));
}
// Die Ergebnisse werden in der Datenbank gespeichert
electionsMetier.recordResultats(admin,listes);
// Die Ergebnisse werden überprüft
listes = electionsMetier.getListesElectorales(admin);
// Die Ergebnisse werden angezeigt
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());
}
}
Aufgabe: Machen Sie sich mit diesem Test vertraut und stellen Sie sicher, dass er erfolgreich durchläuft.
17.5. Der Client des sicheren Servers mit der Schicht [console]
![]() |
Mit NetBeans öffnen wir die Maven-Projekte [elections-metier-dao-security-webjson] und [elections-ui-metier-dao-webjson] und erstellen dann ein neues Projekt [elections-console-metier-dao-security-webjson] durch Kopieren und Einfügen des Projekts [elections-ui-metier-dao-webjson]:
![]() |
Die meisten Klassen des neuen Projekts [3] stammen aus den Projekten [elections-ui-metier-dao-webjson] und [2], die als Clients des ungesicherten Webservers jSON fungierten:
![]() |
Gehen Sie wie folgt vor, um das neue Maven-Projekt zu erstellen:
- Kopieren Sie das Projekt [elections-ui-metier-dao-webjson] und fügen Sie es in das Projekt [elections-ui-metier-dao-security-webjson] ein;
- im neuen Projekt:
- Löschen Sie die Pakete [elections.client.dao, elections.client.metier, elections.client.entities];
- Behalten Sie im Paket [elections.client.ui] nur die Konsolenklasse [ElectionsConsole] und deren Schnittstelle [IElectionsUI] bei;
- benennen Sie die Pakete wie angegeben in [3] um;
- Beheben Sie die Importprobleme in den verschiedenen Klassen mit [clic droit / Fix Imports];
Zu diesem Zeitpunkt weist das neue Projekt zahlreiche Fehler auf.
17.5.1. Maven-Konfiguration
Die Datei [pom.xml] des neuen Projekts lautet wie folgt:
<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>
- Zeilen 4–8: die Maven-ID des neuen Projekts;
- Zeilen 22–26: die Abhängigkeit vom Projekt [elections-metier-dao-security-webjson], das wir gerade in Abschnitt 17.4 erstellt haben und das die Schichten [DAO] und [métier] bereitstellt;
17.5.2. Spring-Konfiguration
![]() |
Die Klasse [UiConfig] sieht wie folgt aus:
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 {
// Administrator
private final User ADMIN = new User("admin", "admin");
@Bean
private User admin() {
return ADMIN;
}
}
- Zeile 8: Die Klasse [elections.security.client.config.MetierConfig] aus dem in Abschnitt 17.4 behandelten Projekt [elections-metier-dao-security-webjson] wird importiert;
- Zeile 9: Hier werden die Pakete deklariert, in denen sich die Spring-Beans befinden;
- Zeilen 12–18: Die Bean [admin] ist der Benutzer [admin, admin], der von der Konsolenanwendung verwendet wird. Zur Erinnerung: Dies ist der einzige Benutzer, der berechtigt ist, den gesicherten Webserver /jSON abzufragen;
17.5.3. Die Boot-Anwendung der Konsole
![]() |
Die Klasse [ElectionsConsole] entwickelt sich wie folgt:
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() {
// die Teilnehmerlisten
ListeElectorale[] listes;
// Dateneingabe
try (Scanner clavier = new Scanner(System.in)) {
// Anforderung der Wahllisten von der Ebene [metier]
listes = electionsMetier.getListesElectorales(admin);
// Die Stimmen werden erfasst
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");
}
}
}
}
// Die Sitze werden berechnet
listes=electionsMetier.calculerSieges(admin,listes);
// Die Ergebnisse werden gespeichert
electionsMetier.recordResultats(admin,listes);
// Sortierung der Listen in absteigender Reihenfolge der Stimmen
Arrays.sort(listes, new CompareListesElectorales());
// Anzeige der Listen
System.out.println("\nRésultats de l'élection\n");
for (int i = 0; i < listes.length; i++) {
System.out.println(listes[i]);
}
}
// Vergleichsklasse für Wahllisten
class CompareListesElectorales implements Comparator<ListeElectorale> {
// Vergleich zweier Kandidatenlisten nach der Anzahl der Stimmen
@Override
public int compare(ListeElectorale listeElectorale1, ListeElectorale listeElectorale2) {
// Die Stimmen dieser beiden Listen werden verglichen
int nbVoix1 = listeElectorale1.getVoix();
int nbVoix2 = listeElectorale2.getVoix();
if (nbVoix1 < nbVoix2) {
return +1;
} else {
if (nbVoix1 > nbVoix2)
return -1;
else
return 0;
}
}
}
}
Die Klasse ElectionsConsole ändert sich kaum. Man muss lediglich beachten, dass die Methoden der Schicht [métier] nun als ersten Parameter einen Benutzer verlangen (Zeilen 31, 49, 51). Dieser wird durch die Injektion in den Zeilen 21–22 bereitgestellt. Der injizierte Benutzer ist derjenige, der berechtigt ist, den Webserver /jSON abzufragen.
Aufgabe: Testen Sie die Konsolenanwendung (denken Sie daran, zuvor den sicheren Server zu starten).
17.6. Der Client des sicheren Servers mit einer [swing]-Schicht
![]() |
Wir erstellen ein neues NetBeans-Projekt [elections-swing-metier-dao-security-webjson] durch Kopieren und Einfügen des Projekts [elections-ui-metier-dao-webjson]:
![]() |
Im neuen Projekt [2]:
- Löschen Sie die Pakete [elections.client.dao, elections.client.metier, elections.client.entities];
- im Paket [elections.client.ui] die Klassen [ElectionsConsole, IElectionsUI] löschen;
- benennen Sie die Pakete wie in [2] angegeben um;
- Im Paket [elections.security.client.swing] benennen Sie die Klasse [AbstractElectionsSwing], die die grafische Benutzeroberfläche implementiert, in [AbstractElectionsMainForm] um, und die Klasse [ElectionsSwing], die die Ereignisbehandler dergrafischen Benutzeroberflächen in „[ElectionsMainForm]“ um;
- beheben Sie die Importprobleme in den verschiedenen Klassen mithilfe von [clic droit / Fix Imports];
Derzeit gibt es zahlreiche Fehler.
17.6.1. Maven-Konfiguration
Die Datei [pom.xml] entwickelt sich wie folgt:
<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>
- Zeilen 4–8: die Maven-Identität des neuen Projekts;
- Zeilen 22–26: eine Abhängigkeit vom Konsolenprojekt, das wir gerade in Abschnitt 17.5 behandelt haben;
17.6.2. Spring-Konfiguration
Dieses Projekt verwendet die Spring-Konfiguration des Konsolenprojekts, das es importiert (siehe Abschnitt 17.5.2).
17.6.3. Die Ansichten der Swing-Anwendung
![]() |
In diesem Projekt werden zwei Ansichten verwendet:
![]() |
- Die Anmeldesicht [1] ist neu. Sie dient dazu, den Benutzer zu authentifizieren, der die Anwendung nutzen möchte. Sie verwendet die folgenden Klassen:
- [AbstractElectionsConnectForm], die die Ansicht implementiert;
- [ElectionsConnectForm], die die Ereignisse der Ansicht verwaltet;
- Die Ansicht [2] ist bekannt. Sie wurde bisher verwendet. Sie nutzt die folgenden Klassen:
- [AbstractElectionsMainForm], die die Ansicht implementiert;
- [ElectionsMainForm], das die Ereignisse der Ansicht verwaltet;
17.6.4. Die Sitzung
![]() |
Wenn eine Swing-Anwendung mehrere Ansichten hat, muss ein Weg gefunden werden, damit eine Ansicht Informationen an eine andere Ansicht weitergeben kann. Wir nutzen ein aus der Webentwicklung bekanntes Konzept: die Sitzung, die es den mit einem bestimmten Benutzer verbundenen Ansichten ermöglicht, Informationen auszutauschen. Dieses Konzept wird hier mithilfe eines Spring-Singletons implementiert, das in die beiden Klassen injiziert wird, die die Ereignisse der beiden Ansichten verwalten:
package elections.security.client.swing;
import elections.security.client.entities.User;
import org.springframework.stereotype.Component;
@Component
public class UiSession {
// der angemeldete Benutzer
private User user;
// Getter und Setter
...
}
- Zeile 6: Die Klasse [UiSession] ist eine Spring-Komponente;
- Zeile 10: Sie dient nur einem Zweck: dem Speichern des Benutzers, der sich über die Ansicht [1] anmeldet. Die Ansicht [2], die diesen Benutzernamen benötigt, ruft ihn dort ab;
17.6.5. Die Boot-Klasse
![]() |
Die Boot-Klasse der grafischen Anwendung lautet wie folgt:
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);
}
}
- Zeile 5: Die Klasse [BootElectionsSwing] erweitert die Klasse [AbstractBootElections], die im Konsolenprojekt definiert ist, das zu den Projektabhängigkeiten gehört;
- Zeilen 10–13: Die Klasse [BootElectionsSwing] zeigt die Anmeldemaske [ElectionsConnectForm] an;
- Zeilen 6–8: Die Methode [run] dieser Klasse wird ausgeführt;
17.6.6. Die Klasse [ElectionsMainForm]
Die Klasse [ElectionsMainForm] hieß zuvor [ElectionsSwing]. Sie verwaltet die Ereignisse der Ansicht [AbstractElectionsMainForm]. Ihr Code entwickelt sich wie folgt:
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;
// Referenz zur Schicht [métier]
@Autowired
private IElectionsMetier metier;
// Sitzung UI
@Autowired
private UiSession uiSession;
// angemeldeter Benutzer
private User user;
// Listenvorlagen JList
private DefaultListModel<String> modèleNomsVoix = null;
private DefaultListModel<String> modèleRésultats = null;
// die Listen im Wettbewerb
private ListeElectorale[] listes;
// Vom Benutzer eingegebene Listen
private final List<ListeElectorale> listesSaisies = new ArrayList<>();
private ListeElectorale[] tListesSaisies;
// Initialisierungen
@Override
protected void init() {
// Generierung der Komponenten durch die übergeordnete Klasse
super.init();
// lokale Initialisierungen
modèleNomsVoix = new DefaultListModel<>();
jListNomsVoix.setModel(modèleNomsVoix);
modèleRésultats = new DefaultListModel<>();
jListResultats.setModel(modèleRésultats);
String info;
boolean erreur = false;
try {
// Die Listen werden von der Schicht [métier] angefordert
listes = metier.getListesElectorales(user);
// Die Namen der Listen werden dem Kombinationsfeld zugeordnet jComboBoxNomsListes
for (int i = 0; i < listes.length; i++) {
jComboBoxNomsListes.addItem(String.format("%s - %s", listes[i].getId(), listes[i].getNom()));
}
// sowie die Wahlparameter
int nbSiegesAPourvoir = metier.getNbSiegesAPourvoir(user);
double seuilElectoral = metier.getSeuilElectoral(user);
// werden die mit diesen beiden Informationen verbundenen Beschriftungen initialisiert
jLabelSAP.setText(jLabelSAP.getText() + nbSiegesAPourvoir);
jLabelSE.setText(jLabelSE.getText() + seuilElectoral);
// Erfolgsmeldung
info = "Source de données lue avec succès";
} catch (ElectionsException ex1) {
// der Fehler wird protokolliert
info = getInfoForException("Les erreurs suivantes se sont produites :", ex1);
erreur = true;
} catch (RuntimeException ex2) {
// Der Fehler wird protokolliert
info = getInfoForException("Les erreurs suivantes se sont produites :", ex2);
erreur = true;
}
// Fehler?
if (erreur) {
// Information wird angezeigt
jTextPaneMessages.setText(info);
jTextPaneMessages.setCaretPosition(0);
return;
} else {
jTextPaneMessages.setText("");
}
// Formularstatus
Utilitaires.setEnabled(new JLabel[]{jLabelAjouter, jLabelCalculer, jLabelEnregistrer, jLabelSupprimer}, false);
Utilitaires.setEnabled(
new JMenuItem[]{jMenuItemAjouter, jMenuItemCalculer, jMenuItemEnregistrer, jMenuItemSupprimer}, false);
// Fenster zentrieren
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() {
// Benutzerdaten speichern
user = uiSession.getUser();
// Fenster initialisieren
init();
setVisible(true);
}
}
- Zeilen 32–33: Einbindung der Anwendungssitzung;
- Zeilen 112–119: Die Ansicht wird wie zuvor über ihre Methode [run] aktiviert;
- Zeile 115: Der Benutzer wird aus der Sitzung abgerufen. Dieser wurde zuvor durch den Code der Anmeldesicht [ElectionsConnectForm] dort hinterlegt. Anschließend wird der Code so geändert, dass der erste Parameter der Aufrufe der Schicht [métier] dieser Benutzer ist (beispielsweise Zeilen 63, 69–70);
17.6.7. Die Ansicht [AbstractElectionsConnectForm]
![]() |
Erstellen Sie mit NetBeans das folgende Formular:
![]() |
Die Klasse wird die Methode zur Verarbeitung des Klicks auf die Menüoption [Connexion] nicht selbst implementieren. Sie wird eine als abstrakt deklarierte Methode [doConnecter] aufrufen, und die Ansichtsklasse wird ihrerseits als abstrakt deklariert. Die automatisch generierte statische Methode [main] wird entfernt.
17.6.8. Die Ereignisbehandlung der Anmeldesicht
![]() |
Die Klasse [ElectionsConnectForm] implementiert die Ereignisbehandlung der Anmeldesicht wie folgt:
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;
// Referenz auf die Ebene [métier]
@Autowired
private IElectionsMetier metier;
// Benutzer angemeldet
private User user;
// Hauptformular
@Autowired
private ElectionsMainForm electionsMainForm;
// Sitzung UI
@Autowired
private UiSession uiSession;
@Override
protected void doConnect() {
String info = null;
try {
if (isPageValid()) {
// Benutzerauthentifizierung
metier.authenticate(user);
// Der Benutzer wird in der Sitzung gespeichert
uiSession.setUser(user);
// Die Anmeldeseite wird ausgeblendet
setVisible(false);
// Die Hauptansicht wird angezeigt
electionsMainForm.run();
}
} catch (ElectionsException ex1) {
// Der Fehler wird protokolliert
info = getInfoForException("Les erreurs suivantes se sont produites :", ex1);
} catch (RuntimeException ex2) {
// Der Fehler wird protokolliert
info = getInfoForException("Les erreurs suivantes se sont produites :", ex2);
}
// Fehler?
if (info != null) {
// Die Information wird angezeigt
jTextPaneErreurs.setText(info);
jTextPaneErreurs.setCaretPosition(0);
}
}
// Initialisierungen
@Override
protected void init() {
// Generierung der Komponenten durch die übergeordnete Klasse
super.init();
// Lokale Initialisierungen
...
// Fenster zentrieren
...
}
@Override
public void run() {
// Die grafische Benutzeroberfläche wird angezeigt
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) {
...
}
}
- Zeilen 73–82: Die Methode [run], die von der Boot-Klasse [BootElectionsSwing] aufgerufen wird;
- Zeilen 26–27: Einfügen einer Referenz auf die Ansicht Nr. 2, die angezeigt werden soll, wenn der Benutzer erkannt wird;
- Zeilen 30–31: Einfügen der Anwendungssitzung;
- Zeile 84: Die Methode [isPageValid] führt zwei Schritte aus:
- Sie überprüft, ob die ID nicht leer ist (das Passwort darf hingegen leer sein);
- sie initialisiert das Feld „User“ in Zeile 23 mit den Eingaben;
![]() | ![]() |
![]() | ![]() |
Aufgabe: Vervollständigen Sie den Code der Klasse.
Aufgabe: Testen Sie die Anwendung.































