17. [TD]: Securing the web server / JSON for elections
Keywords: multi-tier architecture, Spring, dependency injection, secure web service / JSON, client / server
We will now apply what we learned in the previous chapter to the elections assignment. The architecture will be as follows:
![]() |
The process will proceed as follows:
- Writing the server;
- writing the client without the [ui] layer but with a JUnit test for the [business] layer;
- writing the client with the [UI] layer;
17.1. Support
![]() | ![]() |
The projects for this chapter can be found in the [support / chap-17] folder. The SQL script is used to generate the database required for testing.
17.2. The Database
The secure server database must now contain the [USERS], [ROLES], and [USERS_ROLES] tables:
![]() | ![]() |
The SQL script for generating the database is available in the document's support section.
17.3. The secure server
To set up the secure election server, simply copy and paste the example project [intro-spring-security-server-01]:
![]() |
Task: Rename the packages as shown in [1].
Once this is done, we modify the [pom.xml] file as follows:
<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>
- On lines 23–27, replace the dependency on the [intro-server-webjson-01] project with the dependency on the [elections-webjson-metier-dao-spring-data] project discussed in paragraph 12;
- On lines 4–7, enter the properties of the new project;
We still need to modify the project's configuration classes:
[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;
}
}
The changes are in the following lines:
- lines 8, 9, 14: enter the correct package names;
- line 10: the imported class is now [elections.dao.config.DaoConfig] from the [elections-webjson-metier-dao-spring-data] project;
Note: As mentioned, this configuration only works if the class [elections.dao.config.DaoConfig] has the [@Configuration] annotation. Please verify this.
[SecurityConfig]
package elections.security.config;
...
@EnableWebSecurity
@ComponentScan(basePackages = { "elections.security.service" })
@Import({ WebConfig.class, DaoConfig.class })
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
}
The changes are on the following lines:
- line 6: change the package name;
That's it. Run an initial test by executing the JUnit test []:
![]() |
Run the [Boot] class, then use the Chrome extension [Advanced Rest Client] to perform the following test:
![]() |
In [1], the request. In [3], the response. In [2], the HTTP header is the user authentication header [admin, admin]: Authorization:Basic YWRtaW46YWRtaW4=
Now request the competing lists:
![]() |
17.4. The secure server client without the [ui] layer
![]() |
Copy and paste the [elections-ui-metier-dao-webjson] project into the [elections-metier-dao-security-webjson] project.
![]() |
Task: In [1], rename the packages if necessary and remove those from the [ui] layer and the [boot] startup classes.
We will now proceed as described in section 16.5.
17.4.1. Maven Configuration
Only the project identification section changes:
<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 of the [business] layer
![]() |
The [IElectionsMetier] interface evolves as follows:
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);
}
All methods have as their first parameter the user who wants to use the secure server's resources.
The [ElectionsMetier] implementation evolves as follows:
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);
}
}
Task: Complete the code above.
17.4.3. Spring Configuration
![]() |
The [MetierConfig] class evolves as follows:
package elections.security.client.config;
...
@ComponentScan({ "elections.security.client.dao", "elections.security.client.metier" })
public class MetierConfig {
Line 5: The packages to be scanned must be updated.
17.4.4. The JUnit test for the [business] layer
![]() |
The [Test01] test class changes as follows:
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());
}
}
Task: Understand this test and verify that it passes.
17.5. The client of the secure server with a [console] layer
![]() |
In NetBeans, we open the Maven projects [elections-business-dao-security-webjson] and [elections-ui-business-dao-webjson], then create a new project [elections-console-business-dao-security-webjson] by copying and pasting the [elections-ui-business-dao-webjson] project:
![]() |
Most of the classes in the new project [3] come from the [elections-ui-metier-dao-webjson] project [2], which was the client for the unsecured web/JSON server:
![]() |
Follow these steps to build the new Maven project:
- Copy and paste the [elections-ui-metier-dao-webjson] project into the [elections-ui-metier-dao-security-webjson] project;
- In the new project:
- delete the packages [elections.client.dao, elections.client.metier, elections.client.entities];
- in the [elections.client.ui] package, keep only the console class [ElectionsConsole] and its interface [IElectionsUI];
- rename the packages as indicated in [3];
- fix import issues in the various classes by [right-clicking / Fix Imports];
At this stage, the new project contains numerous errors.
17.5.1. Maven Configuration
The [pom.xml] file for the new project is as follows:
<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>
- lines 4–8: the Maven ID of the new project;
- lines 22–26: the dependency on the [elections-metier-dao-security-webjson] project that we just built in Section 17.4, which provides the [DAO] and [business] layers;
17.5.2. Spring Configuration
![]() |
The [UiConfig] class is as follows:
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;
}
}
- Line 8: We import the class [elections.security.client.config.MetierConfig] from the project [elections-metier-dao-security-webjson] discussed in Section 17.4;
- line 9: we declare the packages where the Spring beans are located;
- lines 12–18: the [admin] bean is the [admin, admin] user that will be used by the console application. Recall that this is the only user authorized to query the secure web/JSON server;
17.5.3. The console boot application
![]() |
The [ElectionsConsole] class evolves as follows:
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;
}
}
}
}
The ElectionsConsole class changes very little. Just remember that now the methods in the [business] layer require a user as their first parameter (lines 31, 49, 51). This is provided by the injection in lines 21–22. The injected user is the one authorized to query the web server / jSON.
Task: Test the console application (remember to start the secure server first).
17.6. The client of the secure server with a [swing] layer
![]() |
We create a new NetBeans project [elections-swing-business-dao-security-webjson] by copying and pasting the [elections-ui-business-dao-webjson] project:
![]() |
In the new project [2]:
- delete the packages [elections.client.dao, elections.client.metier, elections.client.entities];
- in the [elections.client.ui] package, delete the classes [ElectionsConsole, IElectionsUI];
- rename the packages as indicated in [2];
- in the [elections.security.client.swing] package, rename the [AbstractElectionsSwing] class, which implements the graphical user interface, to [AbstractElectionsMainForm], and the [ElectionsSwing] class, which implements the graphical user interface event handlers, to [ElectionsMainForm];
- fix the import issues in the various classes by right-clicking and selecting [Fix Imports];
At this stage, there are numerous errors.
17.6.1. Maven Configuration
The [pom.xml] file changes as follows:
<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>
- lines 4–8: the Maven ID of the new project;
- lines 22–26: a dependency on the console project we just examined in Section 17.5;
17.6.2. Spring Configuration
This project uses the Spring configuration of the console project it imports (see Section 17.5.2).
17.6.3. The Swing Application Views
![]() |
This project will use two views:
![]() |
- The [1] login view is new. It is used to authenticate the user who wants to use the application. It uses the following classes:
- [AbstractElectionsConnectForm], which implements the view;
- [ElectionsConnectForm], which handles the view’s events;
- View [2] is familiar. It is the one used so far. It uses the following classes:
- [AbstractElectionsMainForm], which implements the view;
- [ElectionsMainForm], which handles the view's events;
17.6.4. The session
![]() |
When a Swing application has multiple views, a mechanism is needed to allow one view to pass information to another view. We use a concept from web development: the session, which allows views associated with a specific user to share information. This concept will be implemented here using a Spring singleton that will be injected into the two classes that handle events for both views:
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
...
}
- line 6: the [UiSession] class is a Spring component;
- line 10: it serves only one purpose: to store the user who logs in with view [1]. View [2], which needs to know this user, will retrieve it from there;
17.6.5. The boot class
![]() |
The boot class for the graphical application is as follows:
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);
}
}
- line 5: the [BootElectionsSwing] class extends the [AbstractBootElections] class defined in the console project included in the project dependencies;
- lines 10–13: the [BootElectionsSwing] class will display the login view [ElectionsConnectForm];
- lines 6–8: the [run] method of this class will be executed;
17.6.6. The [ElectionsMainForm] class
The [ElectionsMainForm] class is the one that was previously called [ElectionsSwing]. It handles events for the [AbstractElectionsMainForm] view. Its code evolves as follows:
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);
}
}
- lines 32-33: inject the application session;
- lines 112–119: the view will be activated as before using its [run] method;
- line 115: the user is retrieved from the session. This user was placed there by the code in the login view [ElectionsConnectForm]. The code is then modified so that the first parameter of calls to the [business] layer is this user (lines 63, 69-70, for example);
17.6.7. The [AbstractElectionsConnectForm] view
![]() |
Use NetBeans to build the following form:
![]() |
The class will not implement the method that handles the click on the [Login] menu option itself. It will call a method [doConnecter] declared as abstract, and the view class will itself be declared abstract. We will remove the static method [main] that was automatically generated.
17.6.8. Event handling for the login view
![]() |
The [ElectionsConnectForm] class implements event handling for the login view as follows:
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) {
...
}
}
- lines 73–82: the [run] method, which will be called by the boot class [BootElectionsSwing];
- lines 26-27: injection of a reference to view #2, which should be displayed if the user is recognized;
- lines 30-31: injection of the application session;
- line 84: the [isPageValid] method does two things:
- it checks that the username is not empty (the password may be empty);
- instantiates the User field from line 23 with the input;
![]() | ![]() |
![]() | ![]() |
Task: Complete the class code.
Task: Test the application.































