Usage

Client-side Usage #

This page provides guidance on creation of Java clients that consume the ProCAKE Rest-API. There are two convenient ways of implementation:

  1. Using Open API 3.0 client generation feature
  2. Manual implementation

The following content provides a summarized overview of both possibilities.

Swagger Installation #

Since the API uses the Swagger implementation of the Open API 3.0 specification, clients can be built via Swagger codegen. This can be done either via the online available Swagger Editior or directly via the Swagger Codegen.

Swagger Editor #

When using the Swagger Editor to generate a client, simply access the Rest-API’s swagger.json documentation file and paste it into the editor. This file can be found by navigating to:

http://{hostAddress:port}/api/swagger.json

Next, select Generate cient and choose the desired language (here Java). A compressed zip-archive will then be downloaded, also containing detailed instructions on how to set up the client in the local environment. If using Java 11 or higher, the following dependency has to be manually added to the pom.xml

<dependency>
      <groupId>javax.annotation</groupId>
      <artifactId>javax.annotation-api</artifactId>
      <version>${javax-annotation-api.version}</version>
</dependency>

Swagger Codegen Application #

Alternatively, the code generator used by the online editor can also be addressed directly. To do so, download the Swagger CodeGen CLI JAR and run it in the target directory. The required Jar can also be created by cloning and building the official repository.

-jar swagger-codegen-cli-2.2.1.jar generate -i {location of swagger.json} -l java -o {output target}

Manual Implementation #

Another way of consuming the API is the manual development of a client application. The following simple client is built with the javax.ws.rs.client client library and provides basic blocking interaction with endpoints of the API. A non-blocking variant will be presented later on.

For security and administrative reasons, endpoints are protected by Basic-Authentication. In order for users to gain access, they require a corresponding user role. Modeling requests to these endpoints requires an additional HTTP Basic-Authentication Header, which can be defined using org.jboss.resteasy.client.jaxrs.BasicAuthentication.

String username, password;

Authenticator myAuth = new Authenticator()
        {
            @Override
            protected PasswordAuthentication getPasswordAuthentication()
            {
                return new PasswordAuthentication(username, password.toCharArray());
            }
        };

Then, the newly created Authenticator can be registered when creating the client via the ClientBuilder as follows:

client = ClientBuilder.newBuilder().register(myAuth).build();

For the sake of demonstration, we assume a simple workflow handling the CaseBase-Endpoint, which could look like the following:

  1. Adding a caseBase to the server
  2. Fetching a case from the new caseBase
  3. Modifying the case and writing it back into the caseBase
  4. Performing a retrieval on the caseBase
  5. Deleting the caseBase

Let’s start by adding a caseBase

String caseBaseXML;
String caseBaseID = "cars";

WebTarget target = client.target("http://{hostAddress:port}/api")
                         .register(new BasicAuthentication(user, pass))
                         .path("cases")
                         .path(caseBaseID);

Response response = target.request(MediaType.APPLICATION_XML)
                          .put(Entity.xml(caseBaseXML));

The variable caseBaseXML contains the concerning caseBase in XML-representation. caseBaseID represents the new ID of the caseBase defined by the user. A WebTarget can be defined for targeting the desired endpoint - it is then used to invoke a javax.ws.rs.core.Response object, containing (among others) the HTTP status code and the response entity returned by the server. Since we are performing a PUT-operation, the caseBase has to be embedded into the request body. If the request is successful, the server should return the status code 204 (No content). This can be checked by accessing the returned Response object.

response.getStatus()

Next, our aim is to fetch a case from the newly added caseBase. To do so, we perform a GET-request to the corresponding WebTarget

String caseBaseID = "cars";
String caseID = "cars::23";

target = client.target("http://{hostAddress:port}/api")
               .register(new BasicAuthentication(user, pass))
               .path("cases")
               .path(caseBaseID)
               .path(caseID);

response = target.request(MediaType.APPLICATION)
                 .get();

String requestedCase = response.readEntity(String.class);

Again, we set our WebTarget to point to the desired endpoint with caseBaseID and caseID defining the requested object IDs. Since a GET-request is performed, the returned response-entity contains the requested case in XML-representation and status code 200.

Now, let’s pretend we modified the requested case and want to replace it in the caseBase. For this purpose, we address the same WebTarget, but altering the request method. The PUT-operation should look as follows

String modifiedCaseXML;

String caseBaseID = "cars";
String caseID = "cars::23";

target = client.target("http://{hostAddress:port}/api")
               .register(new BasicAuthentication(user, pass))
               .path("cases")
               .path(caseBaseID)
               .path(caseID);

response = target.request(MediaType.APPLICATION_XML)
                 .put(Entity.xml(modifiedCaseXML));

Since the case cars:23 already existed in the caseBase, it is replaced by the modified case. This can be prevented by passing a new unique object ID that does not yet exist in the target caseBase. On success, the response returned by the server should contain status code 204.

Now, let’s perform a textual retrieval using the ParallelLinearRetriever. The API is designed to accept retriever configuration via QueryParameters. The WebTarget should therefore be built accordingly. For the sake of simplicity, the following retrieval will use the previous case cars::23 as a query object.

String queryObjectXML;

String retrieverID = "ParallelLinearRetriever";
String caseBaseID = "cars";
int numberOfResults = 5;
boolean setSorting = true;
int taskSize = 10;

target = client.target("http://{hostAddress:port}/api")
               .register(new BasicAuthentication(user, pass))
               .path("textual")
               .path("retrieval")
               .path(retrieverID)
               .path(caseBaseID)
               .queryParam("numberOfResults", numberOfResults)
               .queryParam("setSorting", setSorting)
               .queryParam("taskSize", taskSize);

response = target.request(MediaType.APPLICATION_XML)
                 .put(Enitity.xml(queryObjectXML));

String resultList = response.readEntity(String.class);

If any of the above displayed QueryParameters are not set within the request, ProCAKE internal default values for given retriever will be used for performing the retrieval.

In case we would like to have access to retrieval-specific information besides the actual retrieved cases, the object-based retrieval approach can be used, which returns a full RetrievalResultlist.

Query query;

target = client.target("http://{hostAddress:port}/api")
               .register(new BasicAuthentication(user, pass))
               .path("retrieval")
               .path(retrieverID)
               .path(caseBaseID)
               .queryParam("addQueryToResults", addQueryToResults)
               .queryParam("setSorting", setSorting)
               .queryParam("taskSize", taskSize);

response = target.request(MediaType.APPLICATION_XML)
                 .put(Entity.xml(query.toXML()));

RetrievalResultListImpl rrl = new RetrievalResultListImpl().fromXML(response.readEntity(String.class));

Finally, the previously loaded casebase can also be deleted again. When deleting objects, the server response contains the deleted object in XML-representation and completes with status code 200.

String caseBaseID = "cars";

target = client.target(BASE_URI)
               .register(new BasicAuthentication(user, pass))
               .path("cases")
               .path(caseBaseID);

response = target.request(MediaType.APPLICATION_XML)
                 .delete();

Non-Blocking Client #

The previously shown client implementation follows a synchronous, sequential programming style where the client is blocked waiting for the server to respond. By slight modification of the client and the use of InvocationCallbacks, all the above logic can be implemented in a non-blocking fashion. In the example below, we fetch case cars::12 from the caseBase cars

client = ClientBuilder.newBuilder().build();

target = client.target("http://{hostAddress:port}/api")
               .register(new BasicAuthentication(user, pass))
               .path("cases")
               .path("cars")
               .path("cars::12);

Future<Response> future = target.request().async().get(new InvocationCallback<Response>() {
            @Override
            public void completed(Response response) {
                String caseXML = response.readEntity(String.class);
                client.close();
            }

            @Override
            public void failed(Throwable throwable) {
                System.out.println(throwable.getCause());
                client.close();
            }
        });

By using a Response-Entity wrapped in a Future object and overriding the required methods of the InvocationCallback we ensure that all computation can be performed asynchronously. The status of the request can be checked by calling future.isDone().