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.















