Creating a hypermedia-driven RESTful web service

duration 30 minutes

Prerequisites:

You’ll explore how to use Hypermedia As The Engine Of Application State (HATEOAS) to drive your RESTful web service on Open Liberty.

What you’ll learn

You will learn how to use hypermedia to create a specific style of a response JSON, which has contents that you can use to navigate your REST service. You’ll build on top of a simple inventory REST service that you can develop with MicroProfile technologies. You can find the service at the following URL:

http://localhost:9080/inventory/hosts

The service responds with a JSON file that contains an array of registered hosts. Each host has a collection of HATEOAS links:

[
  {
    "hostname": "foo",
    "_links": [
      {
        "href": "http://localhost:9080/inventory/hosts/foo",
        "rel": "self"
      }
    ]
  },
  {
    "hostname": "bar",
    "_links": [
      {
        "href": "http://localhost:9080/inventory/hosts/bar",
        "rel": "self"
      }
    ]
  },
  {
  "hostname": "*",
  "_links": [
    {
      "href": "http://localhost:9080/inventory/hosts/*",
      "rel": "self"
    }
  ]
  }
]

What is HATEOAS?

HATEOAS is a constrained form of REST application architecture. With HATEOAS, the client receives information about the available resources from the REST application. The client does not need to be hardcoded to a fixed set of resources, and the application and client can evolve independently. In other words, the application tells the client where it can go and what it can access by providing it with a simple collection of links to other available resources.

Response JSON

When you build a RESTful web service, consider the style of your response files. Whether they are JSON files, XML files, or in some other format, a good practice is to always have them in a clean and organized form. In the context of HATEOAS, each resource must contain a link reference to itself, commonly referred to as self. Each link also needs a relationship to be associated with it, although no strict rules exist as to how you need to format this relationship. Contain the collection of such links within a _links array, which itself must be a direct property of the resource object. The underscore in the _links property is used so that the property does not collide with any existing fields that are named links. In this guide, you will use the following structure of HATEOAS links:

  "_links": [
    {
      "href": ,
      "rel":
    }
  ]

The following example shows two different links. The first link has a self relationship with the resource object and is generated whenever you register a host. The link points to that host entry in the inventory:

  {
    "href": "http://localhost:9080/inventory/hosts/<hostname>",
    "rel": "self"
  }

The second link has a properties relationship with the resource object and is generated if the host system service is running. The link points to the properties resource on the host:

  {
    "href": "http://<hostname>:9080/system/properties",
    "rel": "properties"
  }

Other formats

Although you should stick to the previous format for the purpose of this guide, another common convention has the link as the value of the relationship:

  "_links": {
      "self": "http://localhost:9080/inventory/hosts/<hostname>",
      "properties": "http://<hostname>:9080/system/properties"
  }

Getting started

The fastest way to work through this guide is to clone the Git repository and use the projects that are provided inside:

git clone https://github.com/openliberty/guide-rest-hateoas.git
cd guide-rest-hateoas

The start directory contains the starting project that you will build upon.

The finish directory contains the finished project that you will build.

Try what you’ll build

The finish directory in the root of this guide contains the finished application. Give it a try before you proceed.

To try out the application, first go to the finish directory and run the following Maven goal to build the application inside Open Liberty:

mvn install

Next, run the Maven liberty:start-server goal to start the application inside Open Liberty:

mvn liberty:start-server

After the server runs, you can find your hypermedia-driven inventory service at the following URL:

After you are done checking out the application, stop the Open Liberty server:

mvn liberty:stop-server

Creating the response JSON

Navigate to the start directory.

Begin by building your response JSON, which is composed of the _links array, as well as the name of the host machine.

Linking to inventory contents

As mentioned before, your starting point is an existing simple inventory REST service.

Take a look at the request handlers in the src/main/java/io/openliberty/guides/microprofile/InventoryResource.java file.

InventoryResource.java

 1// tag::comment[]
 2/*******************************************************************************
 3 * Copyright (c) 2017 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License v1.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-v10.html
 8 *
 9 * Contributors:
10 *     IBM Corporation - Initial implementation
11 *******************************************************************************/
12 // end::comment[]
13package io.openliberty.guides.microprofile;
14
15import javax.enterprise.context.ApplicationScoped;
16import javax.inject.Inject;
17import javax.json.JsonArray;
18import javax.json.JsonObject;
19import javax.ws.rs.GET;
20import javax.ws.rs.Path;
21import javax.ws.rs.PathParam;
22import javax.ws.rs.Produces;
23import javax.ws.rs.core.Context;
24import javax.ws.rs.core.MediaType;
25import javax.ws.rs.core.UriInfo;
26
27@ApplicationScoped
28@Path("hosts")
29// tag::InventoryResource[]
30public class InventoryResource {
31
32    @Inject
33    InventoryManager manager;
34
35    // tag::Context[]
36    @Context
37    // end::Context[]
38    // tag::UriInfo[]
39    UriInfo uriInfo;
40    // end::UriInfo[]
41
42    @GET
43    @Produces(MediaType.APPLICATION_JSON)
44    // tag::handler[]
45    public JsonArray handler() {
46        return manager.getSystems(uriInfo.getAbsolutePath().toString());
47    }
48    // end::handler[]
49
50    @GET
51    @Path("{hostname}")
52    @Produces(MediaType.APPLICATION_JSON)
53    // tag::PropertiesForHost[]
54    public JsonObject getPropertiesForHost(@PathParam("hostname") String hostname) {
55        return (hostname.equals("*")) ? manager.list() : manager.get(hostname);
56    }
57    // end::PropertiesForHost[]
58}
59// end::InventoryResource[]

Since the …​/inventory/hosts/ URL will no longer respond with a JSON representation of the contents of your inventory, you can discard the listContents method and integrate it into the getPropertiesForHost method.

Replace the InventoryResource class.
src/main/java/io/openliberty/guides/microprofile/InventoryResource.java

The contents of your inventory are now under the asterisk (*) wildcard and reside at the following URL:

http://localhost:9080/inventory/hosts/*

The GET request handler is responsible for handling all GET requests that are made to the target URL. This method responds with a JSON that contains HATEOAS links.

The UriInfo object is what is used to build your HATEOAS links.

The @Context annotation is a part of CDI and indicates that the UriInfo will be injected when the resource is instantiated.

Your new InventoryResource class is now replaced. Next, let’s implement the getSystems method and build the response JSON object.

InventoryManager.java

 1// tag::comment[]
 2/*******************************************************************************
 3 * Copyright (c) 2017, 2019 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License v1.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-v10.html
 8 *
 9 * Contributors:
10 *     IBM Corporation - Initial implementation
11 *******************************************************************************/
12 // end::comment[]
13package io.openliberty.guides.microprofile;
14
15import java.util.concurrent.ConcurrentHashMap;
16import java.util.concurrent.ConcurrentMap;
17
18import javax.enterprise.context.ApplicationScoped;
19import javax.json.Json;
20import javax.json.JsonArray;
21import javax.json.JsonArrayBuilder;
22import javax.json.JsonObject;
23import javax.json.JsonObjectBuilder;
24
25import io.openliberty.guides.microprofile.util.ReadyJson;
26import io.openliberty.guides.microprofile.util.InventoryUtil;
27
28@ApplicationScoped
29public class InventoryManager {
30
31    private ConcurrentMap<String, JsonObject> inv = new ConcurrentHashMap<>();
32
33    public JsonObject get(String hostname) {
34        JsonObject properties = inv.get(hostname);
35        if (properties == null) {
36            if (InventoryUtil.responseOk(hostname)) {
37                properties = InventoryUtil.getProperties(hostname);
38                this.add(hostname, properties);
39            } else {
40                return ReadyJson.SERVICE_UNREACHABLE.getJson();
41            }
42        }
43        return properties;
44    }
45
46    public void add(String hostname, JsonObject systemProps) {
47        inv.putIfAbsent(hostname, systemProps);
48    }
49
50    public JsonObject list() {
51        JsonObjectBuilder systems = Json.createObjectBuilder();
52        inv.forEach((host, props) -> {
53            JsonObject systemProps = Json.createObjectBuilder()
54                                         .add("os.name", props.getString("os.name"))
55                                         .add("user.name", props.getString("user.name"))
56                                         .build();
57            systems.add(host, systemProps);
58        });
59        systems.add("hosts", systems);
60        systems.add("total", inv.size());
61        return systems.build();
62    }
63
64    // tag::getSystems[]
65    public JsonArray getSystems(String url) {
66        // inventory content
67        JsonObject content = InventoryUtil.buildHostJson("*", url);
68
69        // collecting systems jsons
70        JsonArrayBuilder jsonArray = inv.keySet().stream().map(host -> {
71            return InventoryUtil.buildHostJson(host, url);
72        }).collect(Json::createArrayBuilder, JsonArrayBuilder::add, JsonArrayBuilder::add);
73
74        jsonArray.add(content);
75
76        return jsonArray.build();
77    }
78    // end::getSystems[]
79
80}

Linking to each available resource

Take a look at your InventoryManager and InventoryUtil files.

Replace the InventoryManager class.
src/main/java/io/openliberty/guides/microprofile/InventoryManager.java

InventoryManager.java

 1// tag::comment[]
 2/*******************************************************************************
 3 * Copyright (c) 2017, 2019 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License v1.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-v10.html
 8 *
 9 * Contributors:
10 *     IBM Corporation - Initial implementation
11 *******************************************************************************/
12 // end::comment[]
13package io.openliberty.guides.microprofile;
14
15import java.util.concurrent.ConcurrentHashMap;
16import java.util.concurrent.ConcurrentMap;
17
18import javax.enterprise.context.ApplicationScoped;
19import javax.json.Json;
20import javax.json.JsonArray;
21import javax.json.JsonArrayBuilder;
22import javax.json.JsonObject;
23import javax.json.JsonObjectBuilder;
24
25import io.openliberty.guides.microprofile.util.ReadyJson;
26import io.openliberty.guides.microprofile.util.InventoryUtil;
27
28@ApplicationScoped
29public class InventoryManager {
30
31    private ConcurrentMap<String, JsonObject> inv = new ConcurrentHashMap<>();
32
33    public JsonObject get(String hostname) {
34        JsonObject properties = inv.get(hostname);
35        if (properties == null) {
36            if (InventoryUtil.responseOk(hostname)) {
37                properties = InventoryUtil.getProperties(hostname);
38                this.add(hostname, properties);
39            } else {
40                return ReadyJson.SERVICE_UNREACHABLE.getJson();
41            }
42        }
43        return properties;
44    }
45
46    public void add(String hostname, JsonObject systemProps) {
47        inv.putIfAbsent(hostname, systemProps);
48    }
49
50    public JsonObject list() {
51        JsonObjectBuilder systems = Json.createObjectBuilder();
52        inv.forEach((host, props) -> {
53            JsonObject systemProps = Json.createObjectBuilder()
54                                         .add("os.name", props.getString("os.name"))
55                                         .add("user.name", props.getString("user.name"))
56                                         .build();
57            systems.add(host, systemProps);
58        });
59        systems.add("hosts", systems);
60        systems.add("total", inv.size());
61        return systems.build();
62    }
63
64    // tag::getSystems[]
65    public JsonArray getSystems(String url) {
66        // inventory content
67        JsonObject content = InventoryUtil.buildHostJson("*", url);
68
69        // collecting systems jsons
70        JsonArrayBuilder jsonArray = inv.keySet().stream().map(host -> {
71            return InventoryUtil.buildHostJson(host, url);
72        }).collect(Json::createArrayBuilder, JsonArrayBuilder::add, JsonArrayBuilder::add);
73
74        jsonArray.add(content);
75
76        return jsonArray.build();
77    }
78    // end::getSystems[]
79
80}

The getSystems method accepts a target URL as an argument and returns a JSON object that contains HATEOAS links.

Replace the InventoryUtil class.
src/main/java/io/openliberty/guides/microprofile/util/InventoryUtil.java

InventoryUtil.java

 1// tag::comment[]
 2/*******************************************************************************
 3 * Copyright (c) 2017, 2019 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License v1.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-v10.html
 8 *
 9 * Contributors:
10 *     IBM Corporation - Initial implementation
11 *******************************************************************************/
12 // end::comment[]
13package io.openliberty.guides.microprofile.util;
14
15import java.net.HttpURLConnection;
16import java.net.URI;
17import java.net.URL;
18
19import javax.json.Json;
20import javax.json.JsonArray;
21import javax.json.JsonArrayBuilder;
22import javax.json.JsonObject;
23import javax.ws.rs.client.Client;
24import javax.ws.rs.client.ClientBuilder;
25import javax.ws.rs.core.MediaType;
26import javax.ws.rs.core.UriBuilder;
27
28import org.apache.commons.lang3.StringUtils;
29
30public class InventoryUtil {
31
32    private static final int PORT = 9080;
33    private static final String PROTOCOL = "http";
34    private static final String SYSTEM_PROPERTIES = "/system/properties";
35
36    public static JsonObject getProperties(String hostname) {
37        Client client = ClientBuilder.newClient();
38        URI propURI = InventoryUtil.buildUri(hostname);
39        return client.target(propURI)
40                     .request(MediaType.APPLICATION_JSON)
41                     .get(JsonObject.class);
42    }
43
44    // tag::buildHostJson[]
45    public static JsonObject buildHostJson(String hostname, String url) {
46        return Json.createObjectBuilder()
47                   // tag::hostname[]
48                   .add("hostname", hostname)
49                   // end::hostname[]
50                   // tag::links[]
51                   .add("_links", InventoryUtil.buildLinksForHost(hostname, url))
52                   // end::links[]
53                   .build();
54    }
55    // end::buildHostJson[]
56
57    // tag::buildLinksForHost[]
58    public static JsonArray buildLinksForHost(String hostname, String invUri) {
59
60        JsonArrayBuilder links = Json.createArrayBuilder();
61
62        links.add(Json.createObjectBuilder()
63                      .add("href", StringUtils.appendIfMissing(invUri, "/") + hostname)
64                      // tag::self[]
65                      .add("rel", "self"));
66                      // end::self[]
67
68        links.add(Json.createObjectBuilder()
69                .add("href", InventoryUtil.buildUri(hostname).toString())
70                // tag::properties[]
71                .add("rel", "properties"));
72                // end::properties[]
73
74        return links.build();
75    }
76    // end::buildLinksForHost[]
77
78    public static boolean responseOk(String hostname) {
79        try {
80            URL target = new URL(buildUri(hostname).toString());
81            HttpURLConnection http = (HttpURLConnection) target.openConnection();
82            http.setConnectTimeout(50);
83            int response = http.getResponseCode();
84            return (response != 200) ? false : true;
85        } catch (Exception e) {
86            return false;
87        }
88    }
89
90    private static URI buildUri(String hostname) {
91        return UriBuilder.fromUri(SYSTEM_PROPERTIES)
92                .host(hostname)
93                .port(PORT)
94                .scheme(PROTOCOL)
95                .build();
96    }
97
98}

The buildHostJson helper method builds in the InventoryUtil class. It creates a JSON object that contains the hostname of your system and the _links array, which is generated separately in the buildLinksForHost helper method.

This helper accepts a hostname and a target URL as arguments. The helper builds a link that points to the inventory entry with a self relationship and also builds a link that points to the system service with a properties relationship:

  • http://localhost:9080/inventory/hosts/<hostname>

  • http://<hostname>:9080/system/properties

Linking to inactive services or unavailable resources

InventoryUtil.java

 1// tag::comment[]
 2/*******************************************************************************
 3 * Copyright (c) 2017, 2019 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License v1.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-v10.html
 8 *
 9 * Contributors:
10 *     IBM Corporation - Initial implementation
11 *******************************************************************************/
12 // end::comment[]
13package io.openliberty.guides.microprofile.util;
14
15import java.net.HttpURLConnection;
16import java.net.URI;
17import java.net.URL;
18
19import javax.json.Json;
20import javax.json.JsonArray;
21import javax.json.JsonArrayBuilder;
22import javax.json.JsonObject;
23import javax.ws.rs.client.Client;
24import javax.ws.rs.client.ClientBuilder;
25import javax.ws.rs.core.MediaType;
26import javax.ws.rs.core.UriBuilder;
27
28import org.apache.commons.lang3.StringUtils;
29
30public class InventoryUtil {
31
32    private static final int PORT = 9080;
33    private static final String PROTOCOL = "http";
34    private static final String SYSTEM_PROPERTIES = "/system/properties";
35
36    public static JsonObject getProperties(String hostname) {
37        Client client = ClientBuilder.newClient();
38        URI propURI = InventoryUtil.buildUri(hostname);
39        return client.target(propURI)
40                     .request(MediaType.APPLICATION_JSON)
41                     .get(JsonObject.class);
42    }
43
44    // tag::buildHostJson[]
45    public static JsonObject buildHostJson(String hostname, String url) {
46        return Json.createObjectBuilder()
47                   // tag::hostname[]
48                   .add("hostname", hostname)
49                   // end::hostname[]
50                   // tag::links[]
51                   .add("_links", InventoryUtil.buildLinksForHost(hostname, url))
52                   // end::links[]
53                   .build();
54    }
55    // end::buildHostJson[]
56
57    // tag::buildLinksForHost[]
58    public static JsonArray buildLinksForHost(String hostname, String invUri) {
59
60        JsonArrayBuilder links = Json.createArrayBuilder();
61
62        links.add(Json.createObjectBuilder()
63                      .add("href", StringUtils.appendIfMissing(invUri, "/") + hostname)
64                      // tag::self[]
65                      .add("rel", "self"));
66                      // end::self[]
67
68        links.add(Json.createObjectBuilder()
69                .add("href", InventoryUtil.buildUri(hostname).toString())
70                // tag::properties[]
71                .add("rel", "properties"));
72                // end::properties[]
73
74        return links.build();
75    }
76    // end::buildLinksForHost[]
77
78    public static boolean responseOk(String hostname) {
79        try {
80            URL target = new URL(buildUri(hostname).toString());
81            HttpURLConnection http = (HttpURLConnection) target.openConnection();
82            http.setConnectTimeout(50);
83            int response = http.getResponseCode();
84            return (response != 200) ? false : true;
85        } catch (Exception e) {
86            return false;
87        }
88    }
89
90    private static URI buildUri(String hostname) {
91        return UriBuilder.fromUri(SYSTEM_PROPERTIES)
92                .host(hostname)
93                .port(PORT)
94                .scheme(PROTOCOL)
95                .build();
96    }
97
98}

Consider what happens when one of the return links does not work or when a link should be available for one object but not for another. In other words, it is important that a resource or service is available and running before it is linked in the _links array.

Although this guide does not cover this case, always make sure that you receive a good response code from a service before you link that service. Similarly, make sure that it makes sense for a particular object to access a resource it is linked to. For instance, it doesn’t make sense for an account holder to be able to withdraw money from their account when their balance is 0. Hence, the account holder should not be linked to a resource that provides money withdrawal.

Building and running the application

To build the application, run the Maven install phase from the command line in the start directory:

mvn install

This command builds the application and creates a .war file in the target directory. It also configures and installs Open Liberty into the target/liberty/wlp directory.

Next, run the Maven liberty:start-server goal:

mvn liberty:start-server

This goal starts an Open Liberty server instance. Your Maven pom.xml is already configured to start the application in this server instance.

After the server runs, you can find your new hypermedia-driven inventory service at the following URL:

Testing the hypermedia-driven RESTful web service

At the following URLs, access the inventory service that is now driven by hypermedia:

The first URL returns the current contents of the inventory, and the second URL returns the system properties for the host name. If the inventory does not contain an entry for the host name that is specified in the URL, the system service that is running on the requested host name is called instead. The system properties are retrieved from that system service and then stored in the inventory and returned.

If the servers are running, you can point your browser to each of the previous URLs to test the application manually. Nevertheless, you should rely on automated tests since they are more reliable and trigger a failure if a change introduces a defect.

Setting up your tests

EndpointTest.java

  1// tag::comment[]
  2/*******************************************************************************
  3 * Copyright (c) 2017 IBM Corporation and others.
  4 * All rights reserved. This program and the accompanying materials
  5 * are made available under the terms of the Eclipse Public License v1.0
  6 * which accompanies this distribution, and is available at
  7 * http://www.eclipse.org/legal/epl-v10.html
  8 *
  9 * Contributors:
 10 *     IBM Corporation - Initial implementation
 11 *******************************************************************************/
 12 // end::comment[]
 13package it.io.openliberty.guides.hateoas;
 14
 15import static org.junit.Assert.assertEquals;
 16
 17import org.junit.After;
 18import org.junit.Before;
 19import org.junit.Test;
 20
 21import javax.json.JsonArray;
 22import javax.ws.rs.client.Client;
 23import javax.ws.rs.client.ClientBuilder;
 24import javax.ws.rs.core.Response;
 25
 26import org.apache.cxf.jaxrs.provider.jsrjsonp.JsrJsonpProvider;
 27
 28// tag::class[]
 29public class EndpointTest {
 30    // tag::class-contents[]
 31    // tag::setup[]
 32    private String port;
 33    private String baseUrl;
 34
 35    private Client client;
 36
 37    private final String SYSTEM_PROPERTIES = "system/properties";
 38    private final String INVENTORY_HOSTS = "inventory/hosts";
 39
 40    // tag::Before[]
 41    @Before
 42    // end::Before[]
 43    public void setup() {
 44        // tag::urlCreation[]
 45        port = System.getProperty("liberty.test.port");
 46        baseUrl = "http://localhost:" + port + "/";
 47        // end::urlCreation[]
 48
 49        // tag::clientInit[]
 50        client = ClientBuilder.newClient();
 51        client.register(JsrJsonpProvider.class);
 52        // end::clientInit[]
 53    }
 54
 55    // tag::After[]
 56    @After
 57    // end::After[]
 58    public void teardown() {
 59        client.close();
 60    }
 61    // end::setup[]
 62
 63    // tag::Test[]
 64    @Test
 65    // end::Test[]
 66    // tag::testSuite[]
 67    public void testSuite() {
 68        this.testLinkForInventoryContents();
 69        this.testLinksForSystem();
 70    }
 71    // end::testSuite[]
 72
 73    /**
 74     * Checks if the HATEOAS link for the inventory contents (hostname=*) is as expected.
 75     */
 76    // tag::testLinkForInventoryContents[]
 77    public void testLinkForInventoryContents() {
 78        Response response = this.getResponse(baseUrl + INVENTORY_HOSTS);
 79        this.assertResponse(baseUrl, response);
 80
 81        // tag::jsonobj[]
 82        JsonArray sysArray = response.readEntity(JsonArray.class);
 83        // end::jsonobj[]
 84
 85        // tag::assertAndClose[]
 86        String expected, actual;
 87
 88        JsonArray links = sysArray.getJsonObject(0).getJsonArray("_links");
 89
 90        expected = baseUrl + INVENTORY_HOSTS + "/*";
 91        actual = links.getJsonObject(0).getString("href");
 92        assertEquals("Incorrect href", expected, actual);
 93
 94        // asserting that rel was correct
 95        expected = "self";
 96        actual = links.getJsonObject(0).getString("rel");
 97        assertEquals("Incorrect rel", expected, actual);
 98
 99        response.close();
100        // end::assertAndClose[]
101    }
102    // end::testLinkForInventoryContents[]
103
104    /**
105     * Checks that the HATEOAS links, with relationships 'self' and 'properties' for a simple
106     * localhost system is as expected.
107     */
108    // tag::testLinksForSystem[]
109    public void testLinksForSystem() {
110        this.visitLocalhost();
111
112        Response response = this.getResponse(baseUrl + INVENTORY_HOSTS);
113        this.assertResponse(baseUrl, response);
114
115        JsonArray sysArray = response.readEntity(JsonArray.class);
116
117        String expected, actual;
118
119        JsonArray links = sysArray.getJsonObject(0).getJsonArray("_links");
120
121        // testing the 'self' link
122
123        expected = baseUrl + INVENTORY_HOSTS + "/localhost";
124        actual = links.getJsonObject(0).getString("href");
125        assertEquals("Incorrect href", expected, actual);
126
127        expected = "self";
128        actual = links.getJsonObject(0).getString("rel");
129        assertEquals("Incorrect rel", expected, actual);
130
131        // testing the 'properties' link
132
133        expected = baseUrl + SYSTEM_PROPERTIES;
134        actual = links.getJsonObject(1).getString("href");
135        assertEquals("Incorrect href", expected, actual);
136
137        expected = "properties";
138        actual = links.getJsonObject(1).getString("rel");
139        assertEquals("Incorrect rel", expected, actual);
140    }
141    // end::testLinksForSystem[]
142
143    /**
144     * Returns a Response object for the specified URL.
145     */
146    // tag::getResponse[]
147    private Response getResponse(String url) {
148        return client.target(url).request().get();
149    }
150    // end::getResponse[]
151
152    /**
153     * Asserts that the given URL has the correct (200) response code.
154     */
155    // tag::assertResponse[]
156    private void assertResponse(String url, Response response) {
157        assertEquals("Incorrect response code from " + url, 200, response.getStatus());;
158    }
159    // end::assertResponse[]
160
161    /**
162     * Makes a GET request to localhost at the Inventory service.
163     */
164    // tag::visitLocalhost[]
165    private void visitLocalhost() {
166        Response response = this.getResponse(baseUrl + SYSTEM_PROPERTIES);
167        this.assertResponse(baseUrl, response);
168        response.close();
169        // tag::targetResponse[]
170        Response targetResponse = client.target(baseUrl + INVENTORY_HOSTS + "/localhost")
171                                        .request()
172                                        .get();
173        // end::targetResponse[]
174        targetResponse.close();
175    }
176    // end::visitLocalhost[]
177    // end::class-contents[]
178}
179// end::class[]
Create the EndpointTest class.
src/test/java/it/io/openliberty/guides/hateoas/EndpointTest.java

You can use the @Before and @After annotations to perform any setup and teardown tasks for each of your individual tests.

Writing the tests

Each test method must be marked with the @Test annotation. Typically, the execution order of test methods is not controlled, but if control is required, you can place methods in a single container test method. This container method is then the only one marked with the @Test annotation. The following testSuite() contains two test methods, which run in the order they appear:

The test testLinkForInventoryContents is responsible for asserting that the correct HATEOAS link is created for the inventory contents.

The getResponse helper method reuses the same line of code for retrieving a response from a specific URL. This technique helps keep your code neat and organized.

The assertResponse method ensures that the response code that you receive is valid (200).

The testLinksForSystem test is responsible for asserting that the correct HATEOAS links are created for the localhost system. This method checks for both the self link that points to the inventory service and the properties link that points to the system service that is running on the localhost system.

Finally, the method visitLocalhost creates a GET request to the system service, registering the localhost system.

Running the tests

If the server is still running from the previous steps, stop it using the Maven liberty:stop-server goal from command line in the start directory:

mvn liberty:stop-server

Then, verify that the tests pass using the Maven verify goal:

mvn verify

It may take some time before build is complete. If the tests pass, you will see a similar output to the following:

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running it.guide-rest-hateoas.EndpointTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.086 sec - in it.guide-rest-hateoas.EndpointTest

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

Great work! You’re done!

You’ve just built and tested a hypermedia-driven RESTful web service on top of Open Liberty.

Guide Attribution

Creating a hypermedia-driven RESTful web service by Open Liberty is licensed under CC BY-ND 4.0

Copied to clipboard
Copy code block
Copy file contents

Prerequisites:

Nice work! Where to next?

What did you think of this guide?

Extreme Dislike Dislike Like Extreme Like

What could make this guide better?

Raise an issue to share feedback

Create a pull request to contribute to this guide

Need help?

Ask a question on Stack Overflow

Like Open Liberty? Star our repo on GitHub.

Star