Skip to content

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.