PA165/Lab session Webservices REST

Z FI WIKI
Přejít na: navigace, hledání


Developing RESTful webservices with Jersey

During this lab we will see how to create, and access a REST webservice by using the Jersey framework ([1]). Jersey provides support for JAX-RS APIs, providing further functionalities to simplify the development of RESTful webservices.

Just as a reminder, REpresentational State Transfer (REST) is defined as a set of architectural patterns that design web services based on resources. A RESTful webservice (compliant to REST principles) has the following characteristics:

  • Uses HTTP methods explicitly
  • Is stateless
  • It exposes URIs as directory structures to manage resources
  • Allows the transferral of XML and/or JavaScript Object Notation (JSON) formats

What is needed for the current lab:

Create a new project of type Maven - Web Application, named rest-jersey-server configured to be run under Tomcat. Add to the pom.xml file:

<properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.glassfish.jersey.containers</groupId>
            <!-- if your container implements Servlet API older than 3.0, use "jersey-container-servlet-core"  -->
            <artifactId>jersey-container-servlet</artifactId>
            <version>2.4.1</version>
        </dependency>
     </dependencies>
    <build>
        <plugins>
            <!-- Java language version -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>6</source>
                    <target>6</target>
                </configuration>
            </plugin>
            <!-- Servlet 3.0 without web.xml -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.1.1</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
        </plugins>
    </build>

}

Create a new package called package cz.muni.fi.pa165.rest Add the Application class that will define the entrypoint:

 
package cz.muni.fi.pa165.rest;
 
import java.util.Set;
import javax.ws.rs.core.Application;
 
@javax.ws.rs.ApplicationPath("webresources")
public class ApplicationConfig extends Application {
 
    @Override
    public Set<Class<?>> getClasses() {
        Set<Class<?>> resources = new java.util.HashSet<Class<?>>();
        addRestResourceClasses(resources);
        return resources;
    }
    private void addRestResourceClasses(Set<Class<?>> resources) {
        resources.add(cz.muni.fi.pa165.rest.ServiceResource.class);
    }
}

Create a resource class:

 
package cz.muni.fi.pa165.rest;
 
import javax.ws.rs.core.Context;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.PathParam;
import javax.ws.rs.Consumes;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.GET;
import javax.ws.rs.Produces;
 
@Path("service")
public class ServiceResource {
 
    @Context
    private UriInfo context;
  
    public ServiceResource() {
    }
 
    @GET
    @Produces("text/plain")
    public String getText() {
        return "hello!";
    }
}

Run the application at http://localhost:8084/rest-jersey-server/webresources/service to see that all has been configured correctly. You should receive a “hello!” message. As well, you can test the application by using curl with curl -i http://localhost:8084/rest-jersey-server/webresources/service

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/plain
Content-Length: 6
Date: Mon, 25 Nov 2013 14:05:45 GMT

hello!

Let's add some resources. We will define a container class mapped to “customers” that will manage access to all instances of customer(s). First create a class cz.muni.fi.pa165.rest.CustomersResource.

 
package cz.muni.fi.pa165.rest;
 
import java.util.SortedMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.atomic.AtomicInteger;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.PathParam;
import javax.ws.rs.Path;
import javax.ws.rs.GET;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
 
/**
 * Note: we are instantiating all the customers and adding them to 
 * an in-memory representation
 *
 */
@Path("/customers")
public class CustomersResource {
 
    private static SortedMap<Integer, CustomerResource> customerDB = new ConcurrentSkipListMap<Integer, CustomerResource>();
  
    private static AtomicInteger idCounter = new AtomicInteger(0);
    
    @Context
    private UriInfo context;
 
    public CustomersResource() {
           
        customerDB.put(idCounter.incrementAndGet(), new CustomerResource(Integer.toString(idCounter.get()), 
                "Isaac", "Newton", "Scientist", "Law of Universal Gravitation"));
        customerDB.put(idCounter.incrementAndGet(), new CustomerResource(Integer.toString(idCounter.get()), 
                "Johannes", "Gutenberg", "Inventor", "printing press"));
        customerDB.put(idCounter.incrementAndGet(), new CustomerResource(Integer.toString(idCounter.get()), 
                "Albert", "Einstein", "Scientist", "Theory of Relativity"));
        customerDB.put(idCounter.incrementAndGet(), new CustomerResource(Integer.toString(idCounter.get()), 
                "Enrico", "Fermi", "Physicist", "Development of Quantum Theory"));
        customerDB.put(idCounter.incrementAndGet(), new CustomerResource(Integer.toString(idCounter.get()), 
                "James", "Watt", "Inventor", "Steam Engine"));
        customerDB.put(idCounter.incrementAndGet(), new CustomerResource(Integer.toString(idCounter.get()), 
                "Guglielmo", "Marconi", "Inventor", "Radio"));
        customerDB.put(idCounter.incrementAndGet(), new CustomerResource(Integer.toString(idCounter.get()), 
                "Werner", "Eisenberg", "Physicist", "Quantum Mechanics"));
        
    }
 
    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String getPlain() {
        StringBuilder returnString = new StringBuilder();
 
        for (CustomerResource customerResource : customerDB.values()) {
            returnString.append(customerResource);
            returnString.append(" ");
        }
 
        return returnString.toString();
    }
 
    @Path("{id}")
    public CustomerResource getCustomerResource(@PathParam("id") Integer id) {
        return customerDB.get(id);
    }
 
    @GET
    @Path("count")
    @Produces(MediaType.TEXT_PLAIN)
    public String getCount() {
        return String.valueOf(customerDB.size());
    }
}

And the contained class cz.muni.fi.pa165.rest.Customer

 
package cz.muni.fi.pa165.rest;
 
import javax.ws.rs.GET;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
 
/**
 * Note: for this lab this is mixing business entities and REST operations.
 * Normally entities would have been mapped with a persistence mechanism
 *
 */
public class CustomerResource {
 
    private String id;
    private String name;
    private String surname;
    private String occupation;
    private String invention;
 
    public CustomerResource(String id, String name, String surname, String occupation, String invention) {
        this.id         = id;
        this.name       = name;
        this.surname    = surname;
        this.occupation = occupation;
        this.invention  = invention;
    }
 
    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String getPlain() {
        return this.toString();
    }
 
    public String getId() {
        return id;
    }
 
    public String getName() {
        return name;
    }
 
    public String getSurname() {
        return surname;
    }
 
    public String getOccupation() {
        return occupation;
    }
    
    public String getInvention() {
        return invention;
    }
 
    @Override
    public String toString() {
        return this.getId() + " " + this.getName() + " " + this.getSurname() + " " + 
                this.getOccupation() + " " + this.getInvention();
    }
}

Register the resources within the addRestResourceClasses() method. Those classes will be instantiated per request.

 
resources.add(cz.muni.fi.pa165.rest.CustomerResource.class);
resources.add(cz.muni.fi.pa165.rest.CustomersResource.class);

If you want to have singleton resources you can mark them with the @Singleton annotation. CustomersResources becomes:

 
@Path("/customers")
@Singleton
public class CustomersResource {
...
}

Trying one of the addresses with curl -i http://localhost:8084/rest-jersey-server/webresources/customers/ You should get:

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/plain
Content-Length: 320
Date: Mon, 25 Nov 2013 09:28:18 GMT

1 Isaac Newton Scientist Law of Universal Gravitation 2 Johannes Gutenberg Inventor printing press 3 Albert Einstein Scientist Theory of Relativity 4 Enrico Fermi Physicist Development of Quantum Theory 5 James Watt Inventor Steam Engine 6 Guglielmo Marconi Inventor Radio 7 Werner Eisenberg Physicist Quantum Mechanics

As well you can try to get curl -i http://localhost:8084/rest-jersey-server/webresources/customers/1

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/plain
Content-Length: 53
Date: Mon, 25 Nov 2013 09:44:37 GMT

1 Isaac Newton Scientist Law of Universal Gravitation

Let's provide a new method to create a new instance of customer via @POST and JSON. We will use MOXy as a JSON provider (you can have a look at https://jersey.java.net/documentation/latest/media.html for alternative ways). We need to add an additional dependency to our project:

<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-moxy</artifactId>
    <version>2.4.1</version>
</dependency>

We need to modify two aspects of our application. First we need to add @XmlRootElement and a public constructor. As well we need to have all setter methods in our CustomerResource class.

Changes to the class CustomerResource:

 
@XmlRootElement
public class CustomerResource {public CustomerResource(){}public void setId(String id) {
       this.id = id;
   }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public void setSurname(String surname) {
        this.surname = surname;
    }
 
    public void setOccupation(String occupation) {
        this.occupation = occupation;
    }
 
    public void setInvention(String invention) {
        this.invention = invention;
    }
 
….
}

Let's add the following method to the CustomersResource class:

 
    @GET
    @Path("json/{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public CustomerResource getJson(@PathParam("id") Integer id) {
        return customerDB.get(id);
    }

Using curl -i http://localhost:8084/rest-jersey-server/webresources/customers/json/1

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/json
Content-Length: 112
Date: Mon, 25 Nov 2013 11:49:32 GMT

{"id":"1","invention":"Law of Universal Gravitation","name":"Isaac","occupation":"Scientist","surname":"Newton"}

Note that we can have multiple media types supported. If we modify the method with:

 @GET
    @Path("json/{id}")
    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML} )
    public CustomerResource getJson(@PathParam("id") Integer id) {
        return customerDB.get(id);
    }

We can call it by giving the accept:application/xml header. Using curl -i -H “Accept: application/xml” http://localhost:8084/rest-jersey-server/webresources/customers/json/1

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/xml
Content-Length: 230
Date: Mon, 25 Nov 2013 13:10:22 GMT

<?xml version="1.0" encoding="UTF-8" standalone="yes"?><customerResource><id>1</id><invention>Law of Universal Gravitation</invention><name>Isaac</name><occupation>Scientist</occupation>

Let's add the creation of a new Customer:

 
@POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Response postJson(CustomerResource customerResource) {
        customerDB.put(Integer.parseInt(customerResource.getId()), customerResource);
        System.out.println("Created customer " + customerResource.getId());
        return Response.created(URI.create(context.getAbsolutePath() + "/"+ customerResource.getId())).build();
    }

we can test it by creating a new file with the JSON entity:

{"id":"99","invention":"mass production","name":"Henry","occupation":"Industrialist","surname":"Ford"} 

you can then call it directly as:

curl -i -H "Content-Type: application/json" --data '{"id":"99","invention":"mass production","name":"Henry","occupation":"Industrialist","surname":"Ford"}' http://localhost:8084/rest-jersey-server/webresources/customers

or by using the file as

curl -i -H "Content-Type: application/json" --data @/home/data.json http://localhost:8084/rest-jersey-server/webresources/customers

Jersey Client API

You can use the Jersey Client API (https://jersey.java.net/documentation/latest/client.html) by creating a new Maven Java project. Call the project rest-jersey-client, and add a package cz.muni.fi.pa165.

Add as a maven dependency:

   <dependency>
    <groupId>org.glassfish.jersey.core</groupId>
    <artifactId>jersey-client</artifactId>
    <version>2.4.1</version>
</dependency>

Create a RESTClient class and add the following:

package cz.muni.fi.pa165.rest;

import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

public class RESTClient {
    
    public static void main(String[] args) {
        Client client = ClientBuilder.newClient();
        WebTarget webTarget = client.target("http://localhost:8084/rest-jersey-server/webresources");
        WebTarget resourceWebTarget = webTarget.path("customers/json/1");
        
        Invocation.Builder invocationBuilder =
        resourceWebTarget.request(MediaType.APPLICATION_JSON);
        invocationBuilder.header("accept", "application/json");
        
        Response response = invocationBuilder.get();
        System.out.println(response.getStatus());
        System.out.println(response.readEntity(String.class));
   }   
}

You can use it to test your webservice in similar way as with the curl command.

Update Jersey exposes a WADL file for each application using the resource application.wadl for each application (e.g. http://localhost:8084/jersey-rest-server/webresources/application.wadl) that describes the service in similar way as the WSDL file for SOAP (although the WADL format is not yet a recognized standard). Support for generation of stubs from the WADL specification is far from perfect. An example of such a tool is: https://wadl.java.net/wadl2java.html - a detailed discussion about the usage within Apache CXF is available at https://wadl.java.net/wadl2java.html

For example, you can generate the client files by using:

wadl2java -o test/ -s jaxrs20 -p cz.muni http://localhost:8084/jersey-rest-server/webresources/application.wadl

Closing Exercise

  1. provide the implementation of the @PUT method to update a customer instance;
  2. provide an implementation of the @DELETE operation for a customer instance;
  3. add the management of exceptions to all the REST methods;

For managing exceptions, there are several API classes/methods that can help. You can manage errors to a client both by returning a Response object with the appropriate error code or by throwing an exception. You can use javax.ws.rs.core.Response.ResponseBuilder to return appropriate HTTP codes, for example:

 
....
ResponseBuilder builder = Response.ok(object);
builder.header("header-name", "value");  // if you want to set some header value
return builder.build();
....

you can use the enum javax.ws.rs.core.Response.Status to manage error codes, example:

 
return Response.status(Status.GONE).build();

By throwing exceptions you have two ways, either use javax.ws.rs.WebApplicationException that will be handled by JAX-RS runtime, or provide an exception mapper to return codes. Example:

 
...
if (object == null) {
  throw new WebApplicationException(Response.Status.NOT_FOUND);
}
...

The alternative way is to use an exception mapper by implementing and registering instances of javax.ws.rs.ext.ExceptionMapper:

 
  @Provider
  public class EntityNotFoundMapper implements ExceptionMapper<EntityNotFoundException> {
     public Response toResponse(EntityNotFoundException e) {
        return Response.status(Response.Status.NOT_FOUND).build();
     }
  }

See https://jersey.java.net/documentation/latest/representations.html for more examples and details.

Successful HTTP response code numbers go from 200 to 399. The creation will return 200, “OK” if the object returned is not null. 204, “No Content” is returned when a null object was retrieved. As well as if the return is of type void 204, “No Content” is returned.

HTTP error response code numbers are from 400 to 599. A 404 “Not Found” response code will be sent back to the client if the resource requested is not found. A bad request "400" is sent back in case of bad parameters. All the codes in the range 5xx indicate internal errors of the application.

See this link for an example of a list of common return messages. See http://www.w3.org/Protocols/rfc2616/rfc2616.html for the expected behaviour of GET, POST, PUT and DELETE.

Sample solutions

  • implementation of the PUT method:
 
   @PUT
    @Path("{id}")
    @Consumes(MediaType.APPLICATION_JSON)
    public Response putJson(@PathParam("id") Integer id, CustomerResource customerResource) {
    
        System.out.println("----  putting item ");
 
        URI uri = context.getAbsolutePath();
        System.out.println(context.getAbsolutePath());
 
        Response response;
        if (!customerDB.containsKey(id)) {
            response = Response.created(uri).build();
        } else {
            response = Response.noContent().build();
        }
 
        customerDB.put(id, new CustomerResource(customerResource));
 
       return response;
    }

You can try the implementation with

curl -i -X PUT -H "Content-Type: application/json" --data '{"id":"99","invention":"mass production","name":"Henry","occupation":"Industrialist","surname":"Ford"}' http://localhost:8084/jersey-rest-server/webresources/customers/99


  • implementation of the DELETE method:
 
   @DELETE
    @Path("{id}")
    public void delete(@PathParam("id") Integer id) {
        System.out.println("---- Deleting item nr. " + id);
        
        if (!customerDB.containsKey(id)) {
             throw new WebApplicationException(Response.Status.NOT_FOUND);
        }
        customerDB.remove(id);
    }

that you can try with:

curl -i -X DELETE http://localhost:8084/jersey-rest-server/webresources/customers/1


Securing REST Methods

(continued on 3.12.2013)

First of all, we will use the security-constraints offered by the servlet implementation. We can define 3 different types of authentication:

  • basic type, using just Base64-encoded username and password;
  • digest, that involves the exchange of secure MD5 hashes of the username, password, operation, URI, and optionally the hash of the message body itself;
  • with client certificate;

To have servlet-based authentication with Jersye, we need to add the web.xml file to our deployment, as application-based configuration is only foreseen for simpler scenarios without security configuration.

First we comment out from the maven build configuration:

 <!--
     <configuration>
        <failOnMissingWebXml>false</failOnMissingWebXml>
     </configuration>
 -->

We can then REMOVE the ApplicationConfig class

We add web.xml file with the following configuration:

<web-app version="3.0"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 
    <!-- Servlet declaration can be omitted in which case
         it would be automatically added by Jersey -->
    <servlet>
        <servlet-name>javax.ws.rs.core.Application</servlet-name>
    </servlet>
 

    <servlet-mapping>
        <servlet-name>javax.ws.rs.core.Application</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
</web-app>

(note, we also semplified the url pattern to shorten it)

You can check that the configuration has been picked-up by running the server and using http://localhost:8084/jersey-rest-server/service here you should get the hello response – check also the customers because we will use those for the security enhanced application.

To add security to a REST method, we need to modify our web.xml file. Suppose that we want to secure the GET method for customers, so that only authorized users can access the REST service.

Let's add the context information to the web.xml file:

  <security-constraint>
        <web-resource-collection>
            <web-resource-name>customers creation</web-resource-name>
            <url-pattern>/customers</url-pattern>
            <http-method>GET</http-method>
        </web-resource-collection>
        <auth-constraint>
            <role-name>admin</role-name>
        </auth-constraint>
    </security-constraint>
    <login-config>
        <auth-method>DIGEST</auth-method>
        <realm-name>org.apache.catalina.realm.MemoryRealm</realm-name>
    </login-config>
    <security-role>
        <role-name>admin</role-name>
    </security-role>

We can use the authentication provided within Tomcat by loading the information from the file conf/tomcat-users.xml. In the META-INF/context.xml file we can add:

 <Realm className="org.apache.catalina.realm.MemoryRealm"
   pathname="conf/tomcat-users.xml" />

So that it becomes similar to:

 <?xml version="1.0" encoding="UTF-8"?>
   <Context antiJARLocking="true" path="/jersey-rest-server">
        <Realm className="org.apache.catalina.realm.MemoryRealm" pathname="conf/tomcat-users.xml" />
   </Context>

You can edit /home/<username>/.netbeans/7.3.1/apache-tomcat-7.0.34.0_base/conf/tomcat-users.xml Add the username and password that you prefer, just for testing!

Now, the resource is secured, if you try to access it you should get HTTP status 403 access denied.

The Basic authentication will send Base64-encoded username and password and as such is the simplest form of authentication. Any third party intercepting communication can use the information within HTTP requests to authenticate. As such, it is obligatory to use secure connection such as HTTPS to enforce security.

To enforce security we can apply HTTPS by adding a <user-data-constraint> block so that that looks as follows:

<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>

Our configuration file will look like:

<security-constraint>
       <web-resource-collection>
           <web-resource-name>customers creation</web-resource-name>
           <url-pattern>/customers</url-pattern>
           <http-method>GET</http-method>
       </web-resource-collection>
       <auth-constraint>
           <role-name>admin</role-name>
       </auth-constraint>

       <user-data-constraint>
           <transport-guarantee>CONFIDENTIAL</transport-guarantee>
       </user-data-constraint>
   </security-constraint>
   <login-config>
       <auth-method>DIGEST</auth-method>
       <realm-name>org.apache.catalina.realm.MemoryRealm</realm-name>
   </login-config>
   <security-role>
       <role-name>admin</role-name>
   </security-role>

This will very likely not run on your configuration, as you need to configure tomcat to run SSL. If you want to configure SSL, you can do as follows:

  • Run keytool -genkey -alias tomcat -keyalg RSA (if not in the path, you will need to run it from the jdk bin folder);
  • edit the file server.xml from within netbeans and replace:
    <!--
    <Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"
               maxThreads="150" scheme="https" secure="true"
               clientAuth="false" sslProtocol="TLS" />
    -->

with

   <Connector SSLEnabled="true" acceptCount="128" clientAuth="false"
    disableUploadTimeout="true" enableLookups="false" maxThreads="25"
    port="8443" keystoreFile="/home/brossi/.keystore" keystorePass="password"
    protocol="org.apache.coyote.http11.Http11NioProtocol" scheme="https"
    secure="true" sslProtocol="TLS" />

with the correct location of the keystore and password. You should now be able to access then https://localhost:8443/jersey-rest-server/customers

We can use SecurityContext to access programmatically security information. You can inject the security context by using @Context SecurityContext, e.g. at the method level:


 
    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String getPlain(@Context SecurityContext sc) {
        StringBuilder returnString = new StringBuilder();
        returnString.append("Authenticated user: ");
        returnString.append( sc.getUserPrincipal().getName() );
        return returnString.toString();
    }
 

Alternative way, using annotation based security

To enable this feature we will need to configure RolesAllowedDynamicFeature. To do this, we need to provide our application class that extends resourceconfig (note, is is possible to use this configuration also without web.xml)

So we add this class:

 
package cz.muni.fi.pa165.rest;
 
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature;
 
public class MyApplication extends ResourceConfig {
    public MyApplication() {
        super(CustomersResource.class, CustomerResource.class, ServiceResource.class);
        register(RolesAllowedDynamicFeature.class);
    }
}
 

But we need to change our web.xml file to use this configuration:

...
 <servlet>
        <servlet-name>cz.muni.fi.pa165.rest.MyApplication</servlet-name>
    </servlet>
     <servlet-mapping>
        <servlet-name>cz.muni.fi.pa165.rest.MyApplication</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
…

You can then use annotations directly at the class level, like @RolesAllowed(...), @PermitAll() (for more details see http://www.oracle.com/technetwork/articles/javaee/security-annotation-142276.html)


We can use for example:

 
    @GET
    @Path("count")
    @RolesAllowed("admin")
    @Produces(MediaType.TEXT_PLAIN)
    public String getCount() {
        return String.valueOf(customerDB.size());
    }
 

Final note: to configure the client for authentication and secure connection, you can refer to the Jersey documentation: https://jersey.java.net/documentation/latest/client.html#d0e3273 You can modify the client we deployed by taking into consideration these parameters.


More security documentation is available at: https://jersey.java.net/documentation/latest/security.html