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-business-dao-spring-data</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>elections-security-webjson-business-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-business-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>jUnit client for the web server / 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.business;
import elections.security.client.entities.VoterList;
import elections.security.client.entities.User;
public interface IElectionsMetier {
// authentication
public void authenticate(User user);
// retrieve the candidate lists
public ListElectionLists[] getElectionLists(User user);
// number of seats to be filled
public int getSeatsToBeFilled(User user);
// the electoral threshold
public double getVotingThreshold(User user);
// recording the results
public void recordResults(User user, ElectoralLists[] electoralLists);
// calculating seats
public VoterList[] calculateSeats(User user, VoterList[] voterLists);
}
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.business;
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.VoterList;
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;
}
// JSON mappers
ObjectMapper mapperResponse = context.getBean(ObjectMapper.class);
try {
// request
Response<ElectionsConfig> response = mapperResponse.readValue(dao.getResponse(user, "/getElectionsConfig", null),
new TypeReference<Response<ElectionsConfig>>() {
});
// error?
if (response.getStatus() != 0) {
// throw an exception
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 VoterList[] getVoterLists(User user) {
...
}
@Override
public int getNumberOfSeatsToBeFilled(User user) {
return getElectionsConfig(user).getNumberOfSeatsToBeFilled();
}
@Override
public double getVotingThreshold(User user) {
return getElectionsConfig(user).getElectoralThreshold();
}
@Override
public void recordResults(User user, VoterLists[] voterLists) {
...
}
@Override
public VoterList[] calculateSeats(User user, VoterList[] voterLists) {
...
}
@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.business.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.VoterList;
import elections.security.client.entities.User;
import elections.security.client.business.IElectionsBusiness;
@SpringApplicationConfiguration(classes = MetierConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class Test01 {
// [electionsMetier] layer
@Autowired
private IElectionsMetier electionsMetier;
// JSON mapper
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.getErrors().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.getErrors().get(0));
}
@Test()
public void checkUserAdmin() {
ElectionsException se = null;
try {
electionsMetier.authenticate(admin);
} catch (ElectionsException e) {
se = e;
}
Assert.assertNull(se);
}
/**
* check 1: seat allocation method—the lists are hard-coded
*/
@Test
public void calculateSeats1() {
// create an array of the 7 candidate lists
ElectionList[] lists = new ElectionList[7];
lists[0] = new ElectoralList("A", 32000, 0, false);
lists[1] = new ElectoralList("B", 25000, 0, false);
lists[2] = new VoterList("C", 16000, 0, false);
lists[3] = new VoterList("D", 12000, 0, false);
lists[4] = new VoterList("E", 8000, 0, false);
lists[5] = new VoterList("F", 4500, 0, false);
lists[6] = new VoterList("G", 2500, 0, false);
// calculate the seats for each list
lists = electionsMetier.calculateSeats(admin, lists);
// verify the results
Assert.assertEquals(2, lists[0].getSeats());
Assert.assertFalse(lists[0].isEliminated());
Assert.assertEquals(2, lists[1].getSeats());
Assert.assertFalse(lists[1].isEliminated());
Assert.assertEquals(1, lists[2].getSeats());
Assert.assertFalse(lists[2].isEliminated());
Assert.assertEquals(1, lists[3].getSeats());
Assert.assertFalse(lists[3].isEliminated());
Assert.assertEquals(0, lists[4].getSeats());
Assert.assertFalse(lists[4].isEliminated());
Assert.assertEquals(0, lists[5].getSeats());
Assert.assertTrue(lists[5].isEliminated());
Assert.assertEquals(0, lists[6].getSeats());
Assert.assertTrue(lists[6].isEliminated());
}
/**
* Check 2: seat calculation method—we request the lists from the [business] layer, then hard-code the
* votes
*/
@Test
public void calculateSeats2() {
// create an array of the 7 candidate lists
VoterLists[] lists = businessElections.getVoterLists(admin);
// hard-code the votes
lists[0].setVotes(32000);
lists[1].setVotes(25000);
lists[2].setVotes(16000);
lists[3].setVotes(12000);
lists[4].setSamplingRate(8000);
lists[5].setVoice(4500);
lists[6].setVotes(2500);
// Calculate the number of seats won by each list
lists = electionsMetier.calculateSeats(admin, lists);
// verify the results
Assert.assertEquals(2, lists[0].getSeats());
Assert.assertFalse(lists[0].isEliminated());
Assert.assertEquals(2, lists[1].getSeats());
Assert.assertFalse(lists[1].isEliminated());
Assert.assertEquals(1, lists[2].getSeats());
Assert.assertFalse(lists[2].isEliminated());
Assert.assertEquals(1, lists[3].getSeats());
Assert.assertFalse(lists[3].isEliminated());
Assert.assertEquals(0, lists[4].getSeats());
Assert.assertFalse(lists[4].isEliminated());
Assert.assertEquals(0, lists[5].getSeats());
Assert.assertTrue(lists[5].isEliminated());
Assert.assertEquals(0, lists[6].getSeats());
Assert.assertTrue(lists[6].isEliminated());
}
/**
* Verification 3: seat calculation method throws an exception
*/
@Test(expected = ElectionsException.class)
public void calculateSeats3() {
// Create an array of 24 candidate lists, each with 1 vote
VoterList[] lists = new VoterList[25];
// all 25 lists will have the same number of votes (4%)
for (int i = 0; i < listes.length; i++) {
lists[i] = new ElectoralList("List" + (i + 1), 1, 0, false);
}
// Calculate seats - normally we should get an ElectionsException
// with a 5% electoral threshold
electionsMetier.calculateSeats(admin, lists);
}
/**
* recording election results
*
* @throws JsonProcessingException
*/
@Test
public void writeElectionResults() throws JsonProcessingException {
// Create an array of the 7 candidate lists
ElectionLists[] lists = electionsBusiness.getElectionLists(admin);
// hard-code the votes
lists[0].setVotes(32000);
lists[1].setVotes(25000);
lists[2].setVotes(16000);
lists[3].setVotes(12000);
lists[4].setSamplingRate(8000);
lists[5].setVoice(4500);
lists[6].setVotes(2500);
// Calculate the number of seats won by each list
lists = electionsMetier.calculateSeats(admin, lists);
// display the results
for (int i = 0; i < lists.length; i++) {
System.out.println(mapper.writeValueAsString(lists[i]));
}
// save the results to the database
electionsMetier.recordResults(admin, lists);
// check the results
lists = electionsMetier.getVoterLists(admin);
// display the results
for (int i = 0; i < lists.length; i++) {
System.out.println(mapper.writeValueAsString(lists[i]));
}
Assert.assertEquals(2, lists[0].getSeats());
Assert.assertFalse(lists[0].isEliminated());
Assert.assertEquals(2, lists[1].getSeats());
Assert.assertFalse(lists[1].isEliminated());
Assert.assertEquals(1, lists[2].getSeats());
Assert.assertFalse(lists[2].isEliminated());
Assert.assertEquals(1, lists[3].getSeats());
Assert.assertFalse(lists[3].isEliminated());
Assert.assertEquals(0, lists[4].getSeats());
Assert.assertFalse(lists[4].isEliminated());
Assert.assertEquals(0, lists[5].getSeats());
Assert.assertTrue(lists[5].isEliminated());
Assert.assertEquals(0, lists[6].getSeats());
Assert.assertTrue(lists[6].isEliminated());
}
}
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>Web client console layer / 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 {
// administrator
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.VoterList;
import elections.security.client.entities.User;
import elections.security.client.business.IElectionsBusiness;
@Component
public class ElectionsConsole implements IElectionsUI {
@Autowired
private IElectionsMetier electionsMetier;
@Autowired
private User admin;
@Override
public void run() {
// the competing lists
VoterLists[] lists;
// data entry
try (KeyboardScanner = new Scanner(System.in)) {
// request the competing lists from the [business] layer
lists = businessElections.getVoterLists(admin);
// Enter the votes
System.out.println("There are " + lists.length
+ " lists in the race. Please enter the number of votes for each one:");
for (int i = 0; i < lists.length; i++) {
boolean entryOK = false;
while (!entryOK) {
System.out.print("Number of votes for list [" + lists[i].getName() + "] : ");
try {
lists[i].setVotes(Integer.parseInt(keyboard.nextLine()));
inputOK = true;
} catch (ElectionsException | NumberFormatException ex) {
System.out.println("Invalid number of votes. Please try again");
}
}
}
}
// Calculate the number of seats
lists = electionsMetier.calculateSeats(admin, lists);
// save the results
electionsMetier.recordResults(admin, lists);
// sort the lists in descending order of votes
Arrays.sort(lists, new CompareLists());
// display them
System.out.println("\nElection results\n");
for (int i = 0; i < lists.length; i++) {
System.out.println(lists[i]);
}
}
// class for comparing electoral lists
class CompareVoterLists implements Comparator<VoterList> {
// Comparison of two candidate lists based on the number of votes
@Override
public int compare(VoterList voterList1, VoterList voterList2) {
// compare the votes of these two lists
int votes1 = electoralList1.getVotes();
int votes2 = electoralList2.getVotes();
if (votes1 < votes2) {
return +1;
} else {
if (votes1 > votes2)
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-business-logic-database-security-web-json</name>
<description>Web client Swing layer / 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 logged-in 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.VoterList;
import elections.security.client.entities.User;
import elections.security.client.business.IElectionsBusiness;
@Component
public class ElectionsMainForm extends AbstractElectionsMainForm implements IElectionsUI {
private static final long serialVersionUID = 1L;
// reference to the [business] layer
@Autowired
private IElectionsMetier business;
// UI session
@Autowired
private UiSession uiSession;
// logged-in user
private User user;
// JList models
private DefaultListModel<String> voiceNamesModel = null;
private DefaultListModel<String> resultsModel = null;
// competing lists
private ElectoralList[] lists;
// lists entered by the user
private final List<ElectionList> enteredLists = new ArrayList<>();
private ElectoralList[] enteredLists;
// initializations
@Override
protected void init() {
// generate components via the parent class
super.init();
// local initializations
voiceNamesModel = new DefaultListModel<>();
jListVoiceNames.setModel(voiceNamesModel);
resultsModel = new DefaultListModel<>();
jListResults.setModel(resultsModel);
String info;
boolean error = false;
try {
// request the lists from the [business] layer
lists = business.getVoterLists(user);
// assign the list names to the jComboBoxNomsListes combo box
for (int i = 0; i < lists.length; i++) {
jComboBoxNomsListes.addItem(String.format("%s - %s", lists[i].getId(), lists[i].getName()));
}
// as well as the election parameters
int numSeatsToBeFilled = job.getNumSeatsToBeFilled(user);
double electoralThreshold = job.getElectoralThreshold(user);
// Initialize the labels associated with these two pieces of information
jLabelSAP.setText(jLabelSAP.getText() + nbSiegesAPourvoir);
jLabelSE.setText(jLabelSE.getText() + electoralThreshold);
// success message
info = "Data source successfully read";
} catch (ElectionsException ex1) {
// log the error
info = getInfoForException("The following errors occurred:", ex1);
error = true;
} catch (RuntimeException ex2) {
// log the error
info = getInfoForException("The following errors occurred:", ex2);
error = true;
}
// error?
if (error) {
// display the info
jTextPaneMessages.setText(info);
jTextPaneMessages.setCaretPosition(0);
return;
} else {
jTextPaneMessages.setText("");
}
// form state
Utilities.setEnabled(new JLabel[]{jLabelAdd, jLabelCalculate, jLabelSave, jLabelDelete}, false);
Utilities.setEnabled(
new JMenuItem[]{jMenuItemAdd, jMenuItemCalculate, jMenuItemSave, jMenuItemDelete}, false);
// center the 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 storage
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.business.IElectionsBusiness;
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 business;
// logged-in user
private User user;
// main form
@Autowired
private ElectionsMainForm electionsMainForm;
// UI session
@Autowired
private UiSession uiSession;
@Override
protected void doConnect() {
String info = null;
try {
if (isPageValid()) {
// user authentication
business.authenticate(user);
// store the user in the session
uiSession.setUser(user);
// hide the login view
setVisible(false);
// the main view is displayed
electionsMainForm.run();
}
} catch (ElectionsException ex1) {
// log the error
info = getInfoForException("The following errors occurred:", ex1);
} catch (RuntimeException ex2) {
// log the error
info = getInfoForException("The following errors occurred:", ex2);
}
// error?
if (info != null) {
// display the info
jTextPaneErrors.setText(info);
jTextPaneErrors.setCaretPosition(0);
}
}
// initializations
@Override
protected void init() {
// generate components via the parent class
super.init();
// local initializations
...
// center the window
...
}
@Override
public void run() {
// display the GUI
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.































