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 all of the registered hosts. Each host has a collection of HATEOAS links:

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

What is HATEOAS?

HATEOAS is a constraint of REST application architectures. 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

In the context of HATEOAS, each resource must contain a link reference to itself, which is commonly referred to as self. In this guide, the JSON structure features a mapping between the hostname and its corresponding list of HATEOAS links:

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

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.

Before you begin, make sure you have all the necessary prerequisites.

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 and deploy it to Open Liberty:

cd finish
mvn liberty:run

After you see the following message, your Liberty instance is ready:

The defaultServer server is ready to run a smarter planet.

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

After you are finished checking out the application, stop the Liberty instance by pressing CTRL+C in the command-line session where you ran Liberty. Alternatively, you can run the liberty:stop goal from the finish directory in another shell session:

mvn liberty:stop

Creating the response JSON

Navigate to the start directory.

When you run Open Liberty in dev mode, dev mode listens for file changes and automatically recompiles and deploys your updates whenever you save a new change. Run the following goal to start Open Liberty in dev mode:

mvn liberty:dev

After you see the following message, your Liberty instance is ready in dev mode:

**************************************************************
*    Liberty is running in dev mode.

Dev mode holds your command-line session to listen for file changes. Open another command-line session to continue, or open the project in your editor.

Begin by building your response JSON, which is composed of the name of the host machine and its list of HATEOAS links.

Linking to inventory contents

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

Look at the request handlers in the InventoryResource.java file.

The …​/inventory/hosts/ URL will no longer respond with a JSON representation of your inventory contents, so 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

InventoryResource.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2017, 2022 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 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12package io.openliberty.guides.microprofile;
13
14import jakarta.enterprise.context.ApplicationScoped;
15import jakarta.inject.Inject;
16import jakarta.json.JsonArray;
17import jakarta.json.JsonObject;
18import jakarta.ws.rs.GET;
19import jakarta.ws.rs.Path;
20import jakarta.ws.rs.PathParam;
21import jakarta.ws.rs.Produces;
22import jakarta.ws.rs.core.Context;
23import jakarta.ws.rs.core.MediaType;
24import jakarta.ws.rs.core.UriInfo;
25
26@ApplicationScoped
27@Path("hosts")
28// tag::InventoryResource[]
29public class InventoryResource {
30
31    @Inject
32    InventoryManager manager;
33
34    // tag::Context[]
35    @Context
36    // end::Context[]
37    // tag::UriInfo[]
38    UriInfo uriInfo;
39    // end::UriInfo[]
40
41    @GET
42    @Produces(MediaType.APPLICATION_JSON)
43    // tag::handler[]
44    public JsonObject handler() {
45        return manager.getSystems(uriInfo.getAbsolutePath().toString());
46    }
47    // end::handler[]
48
49    @GET
50    @Path("{hostname}")
51    @Produces(MediaType.APPLICATION_JSON)
52    // tag::PropertiesForHost[]
53    public JsonObject getPropertiesForHost(@PathParam("hostname") String hostname) {
54        return (hostname.equals("*")) ? manager.list() : manager.get(hostname);
55    }
56    // end::PropertiesForHost[]
57}
58// end::InventoryResource[]

The contents of your inventory are now under the asterisk (*) wildcard and reside at the http://localhost:9080/inventory/hosts/* URL.

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 will be 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, you will implement the getSystems method and build the response JSON object.

InventoryManager.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2017, 2022 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 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12package io.openliberty.guides.microprofile;
13
14import java.util.concurrent.ConcurrentHashMap;
15import java.util.concurrent.ConcurrentMap;
16
17import jakarta.enterprise.context.ApplicationScoped;
18import jakarta.json.Json;
19import jakarta.json.JsonArray;
20import jakarta.json.JsonArrayBuilder;
21import jakarta.json.JsonObject;
22import jakarta.json.JsonObjectBuilder;
23
24import io.openliberty.guides.microprofile.util.ReadyJson;
25import io.openliberty.guides.microprofile.util.InventoryUtil;
26
27@ApplicationScoped
28public class InventoryManager {
29
30    private ConcurrentMap<String, JsonObject> inv = new ConcurrentHashMap<>();
31
32    public JsonObject get(String hostname) {
33        JsonObject properties = inv.get(hostname);
34        if (properties == null) {
35            if (InventoryUtil.responseOk(hostname)) {
36                properties = InventoryUtil.getProperties(hostname);
37                this.add(hostname, properties);
38            } else {
39                return ReadyJson.SERVICE_UNREACHABLE.getJson();
40            }
41        }
42        return properties;
43    }
44
45    public void add(String hostname, JsonObject systemProps) {
46        inv.putIfAbsent(hostname, systemProps);
47    }
48
49    public JsonObject list() {
50        JsonObjectBuilder systems = Json.createObjectBuilder();
51        inv.forEach((host, props) -> {
52            JsonObject systemProps = Json.createObjectBuilder()
53                                         .add("os.name", props.getString("os.name"))
54                                         .add("user.name", props.getString("user.name"))
55                                         .build();
56            systems.add(host, systemProps);
57        });
58        systems.add("hosts", systems);
59        systems.add("total", inv.size());
60        return systems.build();
61    }
62
63    // tag::getSystems[]
64    public JsonObject getSystems(String url) {
65        // inventory content
66        JsonObjectBuilder systems = Json.createObjectBuilder();
67        systems.add("*", InventoryUtil.buildLinksForHost("*", url));
68
69        // collecting systems jsons
70        for (String host : inv.keySet()) {
71            systems.add(host, InventoryUtil.buildLinksForHost(host, url));
72        }
73
74        return systems.build();
75    }
76    // end::getSystems[]
77
78}

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::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2017, 2022 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 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12package io.openliberty.guides.microprofile;
13
14import java.util.concurrent.ConcurrentHashMap;
15import java.util.concurrent.ConcurrentMap;
16
17import jakarta.enterprise.context.ApplicationScoped;
18import jakarta.json.Json;
19import jakarta.json.JsonArray;
20import jakarta.json.JsonArrayBuilder;
21import jakarta.json.JsonObject;
22import jakarta.json.JsonObjectBuilder;
23
24import io.openliberty.guides.microprofile.util.ReadyJson;
25import io.openliberty.guides.microprofile.util.InventoryUtil;
26
27@ApplicationScoped
28public class InventoryManager {
29
30    private ConcurrentMap<String, JsonObject> inv = new ConcurrentHashMap<>();
31
32    public JsonObject get(String hostname) {
33        JsonObject properties = inv.get(hostname);
34        if (properties == null) {
35            if (InventoryUtil.responseOk(hostname)) {
36                properties = InventoryUtil.getProperties(hostname);
37                this.add(hostname, properties);
38            } else {
39                return ReadyJson.SERVICE_UNREACHABLE.getJson();
40            }
41        }
42        return properties;
43    }
44
45    public void add(String hostname, JsonObject systemProps) {
46        inv.putIfAbsent(hostname, systemProps);
47    }
48
49    public JsonObject list() {
50        JsonObjectBuilder systems = Json.createObjectBuilder();
51        inv.forEach((host, props) -> {
52            JsonObject systemProps = Json.createObjectBuilder()
53                                         .add("os.name", props.getString("os.name"))
54                                         .add("user.name", props.getString("user.name"))
55                                         .build();
56            systems.add(host, systemProps);
57        });
58        systems.add("hosts", systems);
59        systems.add("total", inv.size());
60        return systems.build();
61    }
62
63    // tag::getSystems[]
64    public JsonObject getSystems(String url) {
65        // inventory content
66        JsonObjectBuilder systems = Json.createObjectBuilder();
67        systems.add("*", InventoryUtil.buildLinksForHost("*", url));
68
69        // collecting systems jsons
70        for (String host : inv.keySet()) {
71            systems.add(host, InventoryUtil.buildLinksForHost(host, url));
72        }
73
74        return systems.build();
75    }
76    // end::getSystems[]
77
78}

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::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2017, 2022 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 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12package io.openliberty.guides.microprofile.util;
13
14import java.net.HttpURLConnection;
15import java.net.URI;
16import java.net.URL;
17
18import jakarta.json.Json;
19import jakarta.json.JsonArray;
20import jakarta.json.JsonArrayBuilder;
21import jakarta.json.JsonObject;
22import jakarta.ws.rs.client.Client;
23import jakarta.ws.rs.client.ClientBuilder;
24import jakarta.ws.rs.core.MediaType;
25import jakarta.ws.rs.core.UriBuilder;
26
27import org.apache.commons.lang3.StringUtils;
28
29public class InventoryUtil {
30
31    private static final int PORT = 9080;
32    private static final String PROTOCOL = "http";
33    private static final String SYSTEM_PROPERTIES = "/system/properties";
34
35    public static JsonObject getProperties(String hostname) {
36        Client client = ClientBuilder.newClient();
37        URI propURI = InventoryUtil.buildUri(hostname);
38        return client.target(propURI)
39                     .request(MediaType.APPLICATION_JSON)
40                     .get(JsonObject.class);
41    }
42
43    // tag::buildLinksForHost[]
44    public static JsonArray buildLinksForHost(String hostname, String invUri) {
45
46        JsonArrayBuilder links = Json.createArrayBuilder();
47
48        links.add(Json.createObjectBuilder()
49                      .add("href", StringUtils.appendIfMissing(invUri, "/") + hostname)
50                      // tag::self[]
51                      .add("rel", "self"));
52                      // end::self[]
53
54        if (!hostname.equals("*")) {
55            links.add(Json.createObjectBuilder()
56                 .add("href", InventoryUtil.buildUri(hostname).toString())
57                 // tag::properties[]
58                 .add("rel", "properties"));
59                 // end::properties[]
60        }
61
62        return links.build();
63    }
64    // end::buildLinksForHost[]
65
66    public static boolean responseOk(String hostname) {
67        try {
68            URL target = new URL(buildUri(hostname).toString());
69            HttpURLConnection http = (HttpURLConnection) target.openConnection();
70            http.setConnectTimeout(50);
71            int response = http.getResponseCode();
72            return (response != 200) ? false : true;
73        } catch (Exception e) {
74            return false;
75        }
76    }
77
78    private static URI buildUri(String hostname) {
79        return UriBuilder.fromUri(SYSTEM_PROPERTIES)
80                .host(hostname)
81                .port(PORT)
82                .scheme(PROTOCOL)
83                .build();
84    }
85
86}

The helper builds a link that points to the inventory entry with a self relationship. The helper 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::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2017, 2022 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 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12package io.openliberty.guides.microprofile.util;
13
14import java.net.HttpURLConnection;
15import java.net.URI;
16import java.net.URL;
17
18import jakarta.json.Json;
19import jakarta.json.JsonArray;
20import jakarta.json.JsonArrayBuilder;
21import jakarta.json.JsonObject;
22import jakarta.ws.rs.client.Client;
23import jakarta.ws.rs.client.ClientBuilder;
24import jakarta.ws.rs.core.MediaType;
25import jakarta.ws.rs.core.UriBuilder;
26
27import org.apache.commons.lang3.StringUtils;
28
29public class InventoryUtil {
30
31    private static final int PORT = 9080;
32    private static final String PROTOCOL = "http";
33    private static final String SYSTEM_PROPERTIES = "/system/properties";
34
35    public static JsonObject getProperties(String hostname) {
36        Client client = ClientBuilder.newClient();
37        URI propURI = InventoryUtil.buildUri(hostname);
38        return client.target(propURI)
39                     .request(MediaType.APPLICATION_JSON)
40                     .get(JsonObject.class);
41    }
42
43    // tag::buildLinksForHost[]
44    public static JsonArray buildLinksForHost(String hostname, String invUri) {
45
46        JsonArrayBuilder links = Json.createArrayBuilder();
47
48        links.add(Json.createObjectBuilder()
49                      .add("href", StringUtils.appendIfMissing(invUri, "/") + hostname)
50                      // tag::self[]
51                      .add("rel", "self"));
52                      // end::self[]
53
54        if (!hostname.equals("*")) {
55            links.add(Json.createObjectBuilder()
56                 .add("href", InventoryUtil.buildUri(hostname).toString())
57                 // tag::properties[]
58                 .add("rel", "properties"));
59                 // end::properties[]
60        }
61
62        return links.build();
63    }
64    // end::buildLinksForHost[]
65
66    public static boolean responseOk(String hostname) {
67        try {
68            URL target = new URL(buildUri(hostname).toString());
69            HttpURLConnection http = (HttpURLConnection) target.openConnection();
70            http.setConnectTimeout(50);
71            int response = http.getResponseCode();
72            return (response != 200) ? false : true;
73        } catch (Exception e) {
74            return false;
75        }
76    }
77
78    private static URI buildUri(String hostname) {
79        return UriBuilder.fromUri(SYSTEM_PROPERTIES)
80                .host(hostname)
81                .port(PORT)
82                .scheme(PROTOCOL)
83                .build();
84    }
85
86}

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 added in the HATEOAS links array of the hostname.

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.

Running the application

You started the Open Liberty in dev mode at the beginning of the guide, so all the changes were automatically picked up.

After the Liberty instance updates, 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:

If the Liberty instances 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 because they are more reliable and trigger a failure if a change introduces a defect.

Setting up your tests

Create the EndpointIT class.
src/test/java/it/io/openliberty/guides/hateoas/EndpointIT.java

EndpointIT.java

  1// tag::copyright[]
  2/*******************************************************************************
  3 * Copyright (c) 2017, 2022 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 2.0
  6 * which accompanies this distribution, and is available at
  7 * http://www.eclipse.org/legal/epl-2.0/
  8 *
  9 * SPDX-License-Identifier: EPL-2.0
 10 *******************************************************************************/
 11// end::copyright[]
 12package it.io.openliberty.guides.hateoas;
 13
 14import jakarta.json.JsonArray;
 15import jakarta.json.JsonObject;
 16import jakarta.json.JsonValue;
 17import jakarta.ws.rs.client.Client;
 18import jakarta.ws.rs.client.ClientBuilder;
 19import jakarta.ws.rs.core.Response;
 20
 21import org.junit.jupiter.api.BeforeEach;
 22import org.junit.jupiter.api.AfterEach;
 23import org.junit.jupiter.api.Test;
 24import org.junit.jupiter.api.Order;
 25import org.junit.jupiter.api.TestMethodOrder;
 26import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
 27
 28import static org.junit.jupiter.api.Assertions.assertEquals;
 29import static org.junit.jupiter.api.Assertions.assertTrue;
 30
 31// tag::class[]
 32@TestMethodOrder(OrderAnnotation.class)
 33public class EndpointIT {
 34    // tag::class-contents[]
 35    // tag::setup[]
 36    private String port;
 37    private String baseUrl;
 38
 39    private Client client;
 40
 41    private final String SYSTEM_PROPERTIES = "system/properties";
 42    private final String INVENTORY_HOSTS = "inventory/hosts";
 43
 44    // tag::Before[]
 45    @BeforeEach
 46    // end::Before[]
 47    public void setup() {
 48        // tag::urlCreation[]
 49        port = System.getProperty("http.port");
 50        baseUrl = "http://localhost:" + port + "/";
 51        // end::urlCreation[]
 52
 53        // tag::clientInit[]
 54        client = ClientBuilder.newClient();
 55        // end::clientInit[]
 56    }
 57
 58    // tag::After[]
 59    @AfterEach
 60    // end::After[]
 61    public void teardown() {
 62        client.close();
 63    }
 64    // end::setup[]
 65
 66    /**
 67     * Checks if the HATEOAS link for the inventory contents (hostname=*)
 68     * is as expected.
 69     */
 70    // tag::Test1[]
 71    @Test
 72    // end::Test1[]
 73    // tag::Order1[]
 74    @Order(1)
 75    // end::Order1[]
 76    // tag::testLinkForInventoryContents[]
 77    public void testLinkForInventoryContents() {
 78        Response response = this.getResponse(baseUrl + INVENTORY_HOSTS);
 79        assertEquals(200, response.getStatus(),
 80                    "Incorrect response code from " + baseUrl);
 81
 82        // tag::jsonobj[]
 83        JsonObject systems = response.readEntity(JsonObject.class);
 84        // end::jsonobj[]
 85
 86        // tag::assertAndClose[]
 87        String expected;
 88        String actual;
 89        boolean isFound = false;
 90
 91
 92        if (!systems.isNull("*")) {
 93            // mark that the correct host info was found
 94            isFound = true;
 95            JsonArray links = systems.getJsonArray("*");
 96
 97            expected = baseUrl + INVENTORY_HOSTS + "/*";
 98            actual = links.getJsonObject(0).getString("href");
 99            assertEquals(expected, actual, "Incorrect href");
100
101            // asserting that rel was correct
102            expected = "self";
103            actual = links.getJsonObject(0).getString("rel");
104            assertEquals(expected, actual, "Incorrect rel");
105        }
106
107
108        // If the hostname '*' was not even found, need to fail the testcase
109        assertTrue(isFound, "Could not find system with hostname *");
110
111        response.close();
112        // end::assertAndClose[]
113    }
114    // end::testLinkForInventoryContents[]
115
116    /**
117     * Checks that the HATEOAS links, with relationships 'self' and 'properties' for
118     * a simple localhost system is as expected.
119     */
120    // tag::Test2[]
121    @Test
122    // end::Test2[]
123    // tag::Order2[]
124    @Order(2)
125    // end::Order2[]
126    // tag::testLinksForSystem[]
127    public void testLinksForSystem() {
128        this.visitLocalhost();
129
130        Response response = this.getResponse(baseUrl + INVENTORY_HOSTS);
131        assertEquals(200, response.getStatus(),
132                     "Incorrect response code from " + baseUrl);
133
134        JsonObject systems = response.readEntity(JsonObject.class);
135
136        String expected;
137        String actual;
138        boolean isHostnameFound = false;
139
140
141        // Try to find the JSON object for hostname localhost
142        if (!systems.isNull("localhost")) {
143            isHostnameFound = true;
144            JsonArray links = systems.getJsonArray("localhost");
145
146            // testing the 'self' link
147            expected = baseUrl + INVENTORY_HOSTS + "/localhost";
148            actual = links.getJsonObject(0).getString("href");
149            assertEquals(expected, actual, "Incorrect href");
150
151            expected = "self";
152            actual = links.getJsonObject(0).getString("rel");
153            assertEquals(expected, actual, "Incorrect rel");
154
155            // testing the 'properties' link
156            expected = baseUrl + SYSTEM_PROPERTIES;
157            actual = links.getJsonObject(1).getString("href");
158            assertEquals(expected, actual, "Incorrect href");
159
160            expected = "properties";
161            actual = links.getJsonObject(1).getString("rel");
162
163            assertEquals(expected, actual, "Incorrect rel");
164        }
165
166
167        // If the hostname 'localhost' was not even found, need to fail the testcase
168        assertTrue(isHostnameFound, "Could not find system with hostname localhost");
169        response.close();
170
171    }
172    // end::testLinksForSystem[]
173
174    /**
175     * Returns a Response object for the specified URL.
176     */
177    private Response getResponse(String url) {
178        return client.target(url).request().get();
179    }
180
181    /**
182     * Makes a GET request to localhost at the Inventory service.
183     */
184    private void visitLocalhost() {
185        Response response = this.getResponse(baseUrl + SYSTEM_PROPERTIES);
186        assertEquals(200, response.getStatus(),
187                     "Incorrect response code from " + baseUrl);
188        response.close();
189        // tag::targetResponse[]
190        Response targetResponse =
191        client.target(baseUrl + INVENTORY_HOSTS + "/localhost")
192                                        .request()
193                                        .get();
194        // end::targetResponse[]
195        targetResponse.close();
196    }
197    // end::class-contents[]
198}
199// end::class[]

The @BeforeEach and @AfterEach annotations are placed on setup and teardown tasks that are run for each individual test.

Writing the tests

Each test method must be marked with the @Test annotation. The execution order of test methods is controlled by marking them with the @Order annotation. The value that is passed into the annotation denotes the order in which the methods are run.

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

Finally, 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, which is running on the localhost system.

Running the tests

Because you started Open Liberty in dev mode, you can run the tests by pressing the enter/return key from the command-line session where you started dev mode. You will see the following output:

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running it.io.openliberty.guides.hateoas.EndpointIT
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.951 s - in it.io.openliberty.guides.hateoas.EndpointIT

Results:

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

Integration tests finished.

When you are done checking out the service, exit dev mode by pressing CTRL+C in the command-line session where you ran Liberty.

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

Copy file contents
Copied to clipboard

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