Skip to content

15. [TD]: Creating a client for the web service

Keywords: multi-layer architecture, Spring, dependency injection, web service / JSON, client / server

15.1. Support

  

The projects for this chapter can be found in the [support / chap-15] folder.

15.2. The client/server architecture

We want to create the following client/server architecture:

The [ui] layer will be the one already developed in sections 9 and 10. This will be possible because the [business] layer above will implement the same interface [IElectionsMetier] as the [business] layer in section 8:


package elections.client.business;

import elections.client.entities.VoterList;

public interface IElectionsMetier {

    // Get the lists of candidates
    public List[] getElectionLists();

    // the number of seats to be filled
    public int getSeatsToBeFilled();

    // the electoral threshold
    public double getElectionThreshold();

    // recording the results
    public void recordResults(VoterList[] voterLists);

    // calculating seats
    public VoterList[] calculateSeats(VoterList[] voterLists);

}

In Section 7, the [DAO] layer exchanged data with a DBMS. Here, the [DAO] layer exchanges data with a web server / JSON.

Initially, we will focus on the following architecture:

15.3. The Eclipse Project

The Eclipse project is as follows:

This structure mirrors that of the example project in Section 13.6.1. We will follow the same approach.

15.4. Maven Configuration

This is the one described in Section 13.6.2.

15.5. Implementation of the [DAO] layer

  
  • The [elections.client.config] package contains the Spring configuration for the [DAO] layer;
  • The [elections.client.dao] package contains the implementation of the [DAO] layer;
  • the [elections.client.entities] package contains the objects exchanged with the web service / JSON;
  • The [elections.client.business] package contains the [business] layer
  • The [elections.client.ui] package contains the [UI] layer

15.5.1. Configuration of the [business] layer

  

The [MetierConfig] class handles the Spring configuration of the [business] layer. Its code is as follows:


package elections.client.config;

import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Scope;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

import com.fasterxml.jackson.databind.ObjectMapper;

@ComponentScan({ "elections.client.dao", "elections.client.business" })
public class MetierConfig {

    // constants
    static private final int TIMEOUT = 1000;
    static private final String URL_WEBJSON = "http://localhost:8080";

    @Bean
    public RestTemplate restTemplate(int timeout) {
        // Create the RestTemplate component
        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
        RestTemplate restTemplate = new RestTemplate(factory);
        // Set the request timeout
        factory.setConnectTimeout(timeout);
        factory.setReadTimeout(timeout);
        // result
        return restTemplate;
    }

    @Bean
    public int timeout() {
        return TIMEOUT;
    }

    @Bean
    public String urlWebJson() {
        return URL_WEBJSON;
    }

    // JSON mapper
    @Bean
    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public ObjectMapper jsonMapper() {
        return new ObjectMapper();
    }
}

This code was explained in Section 13.6.3.1. It is simpler because there are no JSON filters to manage here.

15.5.2. The entities

  

The entities handled by the [DAO] and [business] layers are the ones they exchange with the web service / JSON. These are objects of type [ElectionsConfig] and [VoterList]. On the server side, these entities had JPA persistence annotations. Here, those annotations have been removed. We’re including the entity code again for reference:

[AbstractEntity]


package spring.webjson.client.entities;


public abstract class AbstractEntity {
    // properties
    protected Long id;
    protected Long version;

    // constructors
    public AbstractEntity() {

    }

    public AbstractEntity(Long id, Long version) {
        this.id = id;
        this.version = version;
    }

    // override [equals] and [hashCode]
    @Override
    public int hashCode() {
        return (id != null ? id.hashCode() : 0);
    }

    @Override
    public boolean equals(Object entity) {
        if (!(entity instanceof AbstractEntity)) {
            return false;
        }
        String class1 = this.getClass().getName();
        String class2 = entity.getClass().getName();
        if (!class2.equals(class1)) {
            return false;
        }
        AbstractEntity other = (AbstractEntity) entity;
        return id != null && this.id == other.id.longValue();
    }

    // getters and setters
    ...
}

[ElectionsConfig]


package elections.webjson.client.entities;


public class ElectionsConfig extends AbstractEntity {

    // fields
    private int numberOfSeatsToBeFilled;
    private double electoralThreshold;

    // constructors
    public ElectionsConfig() {

    }

    public ElectionsConfig(int seatsToBeFilled, double electoralThreshold) {
        this.seatsToBeFilled = seatsToBeFilled;
        this.votingThreshold = votingThreshold;
    }

    // getters and setters
    ...
}

[VoterList]


package elections.webjson.client.entities;


public class VoterList extends AbstractEntity {

    // fields
    private String name;
    private int votes;
    private int seats;
    private boolean eliminated;

    // constructors
    public VoterList() {
    }

    public VoterList(String name, int votes, int seats, boolean eliminated) {
        setName(name);
        setVoice(voice);
        setSeats(seats);
        setEliminate(eliminate);
    }

    // getters and setters
    ...
}

15.5.3. The [DAO] layer interface

  

The [DAO] layer has the following [IClientDao] interface:


package elections.client.dao;

public interface IClientDao {

    // generic request
    String getResponse(String url, String jsonPost);

}

The interface has only one method [getResponse]:

  • the first parameter is the URL of the server to query;
  • the second parameter is the JSON value to be posted, null if there is nothing to post;
  • the result is the JSON string of an [Response<T>] object, where the [Response] class was described in Section 14.7;

15.5.4. Implementation of communication with the web service / JSON

  

The [ClientDao] class implements the [IClientDao] interface as follows:


package elections.client.dao;

import java.net.URI;
import java.net.URISyntaxException;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import elections.client.entities.ElectionsException;

@Component
public class ClientDao implements IClientDao {

    // data
    @Autowired
    protected RestTemplate restTemplate;
    @Autowired
    protected String webServiceJsonUrl;

    // generic request
    @Override
    public String getResponse(String url, String jsonPost) {

        try {
            // url: URL to contact
            // jsonPost: the JSON value to post

            // execute request
            RequestEntity<?> request;
            if (jsonPost != null) {
                // POST request
                request = RequestEntity.post(new URI(String.format("%s%s", urlServiceWebJson, url)))
                        .header("Content-Type", "application/json").accept(MediaType.APPLICATION_JSON).body(jsonPost);
            } else {
                // GET request
                request = RequestEntity.get(new URI(String.format("%s%s", urlServiceWebJson, url)))
                        .accept(MediaType.APPLICATION_JSON).build();
            }
            // execute the request
            return restTemplate.exchange(request, new ParameterizedTypeReference<String>() {
            }).getBody();
        } catch (URISyntaxException e1) {
            throw new ElectionsException(200, e1);
        } catch (RuntimeException e2) {
            throw new ElectionsException(201, e2);
        }
    }
}

This code is described in section 13.6.3.6.

15.6. Implementation of the [business] layer

 

As mentioned, the [business] layer has the same interface [IElectionsMetier] as in section 8.4:


package elections.client.business;

import elections.client.entities.VoterList;

public interface IElectionsMetier {

    // retrieve the competing lists
    public VoterList[] getVoterLists();

    // the number of seats to be filled
    public int getSeatsToBeFilled();

    // the electoral threshold
    public double getElectionThreshold();

    // recording the results
    public void recordResults(VoterList[] voterLists);

    // calculating seats
    public VoterList[] calculateSeats(VoterList[] voterLists);

}

This interface is implemented by the following [ElectionsMetier] class:


package elections.client.business;

import java.util.ArrayList;
import java.util.List;

import javax.annotation.PostConstruct;

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.client.dao.IClientDao;
import elections.client.entities.ElectionsConfig;
import elections.client.entities.ElectionsException;
import elections.client.entities.VoterList;

@Component
public class ElectionsMetier implements IElectionsMetier {

    @Autowired
    private IClientDao dao;
    @Autowired
    private ApplicationContext context;

    // election configuration
    private ElectionsConfig electionsConfig;

    @PostConstruct
    public void init() {
        // JSON mappers
        ObjectMapper mapperResponse = context.getBean(ObjectMapper.class);
        try {
            // request
            Response<ElectionsConfig> response = mapperResponse.readValue(dao.getResponse("/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();
            }
        } catch (ElectionsException e1) {
            throw e1;
        } catch (Exception e2) {
            throw new ElectionsException(100, getMessagesForException(e2));
        }
    }

    @Override
    public List[] getVoterLists() {
        ...
    }

    @Override
    public int getNumberOfSeatsToBeFilled() {
        return electionsConfig.getNumberOfSeatsToBeFilled();
    }

    @Override
    public double getVotingThreshold() {
        return electionsConfig.getVotingThreshold();
    }

    @Override
    public void recordResults(VoterList[] voterLists) {
    ...
    }

    @Override
    public VoterList[] calculateSeats(VoterList[] voterLists) {
...
    }

    // list of error messages for an exception
    private List<String> getMessagesForException(Exception exception) {
        // retrieve the list of error messages for the exception
        Throwable cause = exception;
        List<String> errors = new ArrayList<String>();
        while (cause != null) {
            // retrieve the message only if it is not null and not empty
            String message = cause.getMessage();
            if (message != null) {
                message = message.trim();
                if (message.length() != 0) {
                    errors.add(message);
                }
            }
            // next cause
            cause = cause.getCause();
        }
        return errors;
    }
}

The [Response] type used on line 37 is the web server/JSON response described in section 14.7;


Task: Following Section 13.6.3.7, complete the [ElectionsMetier] class;


15.7. The JUnit test

Let’s return to the client/server architecture currently under construction:

The [JUnit] layer [1] communicates with the server’s [Business] layer [5] through layers [2–4]. By ensuring that the [Business] layers [2] and [5] have the same interface, we make layers [2–4] transparent. Layer [1] appears to communicate directly with layer [5]. The interesting point is that in [1] we will be able to use the JUnit test that was used to test the [Business] layer [5].

  

Task: Run the project’s JUnit test to verify your implementation of both the server and its client.


15.8. Implementation of the [UI] layer

Let’s return to the architecture we want to build:

Now that the [Business] layer [2] has been built and tested, we can build the [UI] layer [1].

Since the [IElectionsMetier] interface of the [Business] layer is identical to that of the project described in paragraph 8, we can [3] copy the [UI] layer project from paragraph 10. This project was a NetBeans project. Simply copy and paste the relevant Java classes from NetBeans to Eclipse. Once this is done, there are some package and import adjustments to make.

We will do the same for the executable classes in the [elections.client.boot] package [4].

The [AbstractBootElections] class is as follows:


package elections.client.boot;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import elections.client.config.UiConfig;
import elections.client.entities.ElectionsException;
import elections.client.ui.IElectionsUI;

public abstract class AbstractBootElections {

    // Retrieve the Spring context
    protected AnnotationConfigApplicationContext ctx;

    public void run() {
        // instantiate the [ui] layer
        IElectionsUI electionsUI = null;
        try {
            // retrieve the Spring context
            ctx = new AnnotationConfigApplicationContext(UiConfig.class);
            // retrieve the [ui] layer
            electionsUI = getUI();

...
  • Line 19: The Spring context defined by the [UiConfig] configuration class is instantiated. This class is as follows:
  

The [UiConfig] class is as follows:


package elections.client.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;

@Import(MetierConfig.class)
@ComponentScan(basePackages = { "elections.client.ui" })
public class UiConfig {
}
  • Line 6: Imports the beans from the [business] layer;
  • Line 7: We indicate that there are Spring beans in the [elections.client.ui] package;

Task: Verify that the console and Swing versions of the [ui] layer are working.