The RestClientBuilder interface extends the Configurable
interface from JAX-RS, allowing a user to register custom providers while its being built. The behavior of the providers supported is defined by the JAX-RS Client API specification. Below is a list of provider types expected to be supported by an implementation:
Filters of type ClientResponseFilter
are invoked in order when a response is received from a remote service.
Filters of type ClientRequestFilter
are invoked in order when a request is made to a remote service.
Both the ClientRequestFilter
and ClientResponseFilter
interfaces contains methods that pass an instance of ClientRequestContext
. The Rest Client implementation must provide a property via that ClientRequestContext
called org.eclipse.microprofile.rest.client.invokedMethod
- the value of this property should be the java.lang.reflect.Method
object representing the Rest Client interface method currently being invoked.
The MessageBodyReader
interface defined by JAX-RS allows the entity to be read from the API response after invocation.
The MessageBodyWriter
interface defined by JAX-RS allows a request body to be written in the request for @POST
, @PUT
operations, as well as other HTTP methods that support bodies.
The ParamConverter
interface defined by JAX-RS allows a parameter in a resource method to be converted to a format to be used in a request or a response.
The ReaderInterceptor
interface is a listener for when a read occurs against the response received from a remote service call.
The WriterInterceptor
interface is a listener for when a write occurs to the stream to be sent on the remote service invocation.
The ResponseExceptionMapper
is specific to MicroProfile Rest Client. This mapper will take a Response
object retrieved via an invocation of a client and convert it to a Throwable
, if applicable. The runtime should scan all of the registered mappers, sort them ascending based on getPriority()
, find the ones that can handle the given status code and response headers, and invoke them. The first one discovered where toThrowable
returns a non-null Throwable
that can be thrown given the client method’s signature will be thrown by the runtime.
The specification provides default methods for getPriority()
and handles(int status, MultivaluedMap<String,Object> headers)
methods. Priority is meant to be derived via a @Priority
annotation added to the ResponseExceptionMapper
implementation. The runtime will sort ascending, taking the one with the lowest numeric value first to check if it can handle the Response
object based on it’s status code and headers. The usage of ascending sorting is done to be consistent with JAX-RS behavior.
Likewise, the handles
method by default will handle any response status code >= 400. You may override this behavior if you so choose to handle other response codes (both a smaller ranger and a larger range are expected) or base the decision on the response headers.
The toThrowable(Response)
method actually does the conversion work. This method should not raise any Throwable
, instead just return a Throwable
if it can. This method may return null
if no throwable should be raised. If this method returns a non-null throwable that is a sub-class of RuntimeException or Error (i.e. unchecked throwables), then this exception will be thrown to the client. Otherwise, the (checked) exception will only be thrown to the client if the client method declares that it throws that type of exception (or a super-class). For example, assume there is a client interface like this:
@Path("/")
public interface SomeService {
@GET
public String get() throws SomeException;
@PUT
public String put(String someValue);
}
and assume that the following ResponseExceptionMapper has been registered:
public class MyResponseExceptionMapper implements ResponseExceptionMapper<SomeException> {
@Override
public SomeException toThrowable(Response response) {
return new SomeException();
}
}
In this case, if the get
method results in an exception (response status code of 400 or higher), SomeException will be thrown. If the put
method results in an exception, SomeException will not be thrown because the method does not declare that it throws SomeException. If another ResponseExceptionMapper (such as the default mapper, see below) is registered that returns a subclass of RuntimeException or Error, then that exception will be thrown.
Any methods that read the response body as a stream must ensure that they reset the stream.
In addition to defining providers via the client definition, interfaces may use the @RegisterProvider
annotation to define classes to be registered as providers in addition to providers registered via the RestClientBuilder
.
Providers may also be registered by implementing the RestClientBuilderListener
or RestClientListener
interfaces. These interfaces are intended as SPIs to allow global provider registration. The implementation of these interface must be specified in a META-INF/services/org.eclipse.microprofile.rest.client.spi.RestClientBuilderListener
or META-INF/services/org.eclipse.microprofile.rest.client.spi.RestClientListener
file, respectively, following the ServiceLoader
pattern.
If CDI is available in the implementation’s runtime environment, and CDI is managing the lifecycle of a registered provider class, the implementation must use the CDI-managed instance of the provider. This does not apply when an instance is registered, nor does it apply when a class is registered but no instance of that class is managed by CDI.
The following example shows cases where a CDI-managed provider instance must be used:
@ApplicationScoped // makes it a CDI bean
public class MyFilter implements ClientRequestFilter {
@Inject SomeOtherCdiObject obj;
// ...
}
@RegisterRestClient
@RegisterProvider(MyFilter.class)
public interface MyRestClient1 { /* ... */ }
@RegisterRestClient
// MP Config property: com.mycompany.MyRestClient2/mp-rest/providers=com.mycompany.MyFilter
public interface MyRestClient2 { /* ... */ }
public interface MyRestClient3 { /* ... */ }
public class Client3Builder {
public MyClient3 createClient3() {
return RestClientBuilder.baseUri(someUri).register(MyFilter.class).build(MyClient3.class);
}
}
When registering Features
, it should not matter whether the feature itself is managed by CDI or not, but the
implementation should use CDI-managed instances of classes registered by the feature. For example:
public class MyFeature implements Feature {
@Override
public boolean configure(FeatureContext context) {
context.register(MyFilter.class); // will be managed by CDI
context.register(new MyOtherFilter()); // will not be managed by CDI
return true;
}
}
@RegisterRestClient
@RegisterProvider(MyFeature.class)
public interface MyRestClient4 { /* ... */ }
For more information on integration with CDI, see cdi.asciidoc for more details.
Providers may be registered via both annotations and the builder pattern. Providers registered via a builder will take precedence over the @RegisterProvider
annotation. The @RegisterProvider
annotation takes precedence over the @Priority
annotation on the class.
Provider priorities can be overridden using the various register
methods on Configurable
, which can take a provider class, provider instance as well as priority and mappings of those priorities.
If the type of provider registered is a Feature
, then the priority set by that Feature
will be a part of the builder as well. Implementations must maintain the overall priority of registered providers, regardless of how they are registered. A Feature
will be used to register additional providers at runtime, and may be registered via @RegisterProvider
, configuration or via RestClientBuilder
. A Feature
will be executed immediately, as a result its priority is not taken into account (features are always executed).
Implementations may provide any number of providers registered automatically, but the following providers must be registered by the runtime.
Implementations of the MicroProfile Rest Client should behave similar to JAX-RS implementations with regard to built-in JSON-P and JSON-B providers. Implementations must provide a built-in JSON-P entity provider. If the implementation supports JSON-B, then it must also provide a built-in JSON-B entity provider. Note that the JSON-B provider should take precedence over the JSON-P provider unless the client interface method’s entity parameter or return type is a JSON-P object type (javax.json.JsonObject
, javax.json.JsonArray
, etc.).
When an interface is registered that contains:
-
@Produces("*/json")
or -
@Consumes("*/json")
or -
a method that declares input or output of type
javax.json.JsonValue
or any subclass therein (JSON-P only) or -
no
@Produces
or@Consumes
Then a JSON-B or JSON-P MessageBodyReader
and MessageBodyWriter
will be registered automatically by the implementation. This is in alignment with the JAX-RS 2.1 specification. The provider registered will have a priority of Integer.MAX_VALUE
, allowing a user to register a custom provider to be used instead.
Users may configure how JSON-B serializes a request entity or deserializes a response entity by registering a class or instance of ContextResolver<Jsonb>
. For example, the following code would enable the JSON-B provider implementation to deserialize private fields (without needing getters/setters):
public class MyJsonbContextResolver implements ContextResolver<Jsonb> {
@Override
public Jsonb getContext(Class<?> type) {
JsonbConfig config = new JsonbConfig().
withPropertyVisibilityStrategy(new PropertyVisibilityStrategy(){
@Override
public boolean isVisible(Field f) {
return true;
}
@Override
public boolean isVisible(Method m) {
return false;
}
});
return JsonbBuilder.newBuilder().
withConfig(config).
build();
}
}
@RegisterRestClient
@RegisterProvider(MyJsonbContextResolver.class)
public interface JsonBClient {
//...
}
For the following types, and any media type, the runtime must support `MessageBodyReader`s and `MessageBodyWriter`s being automatically registered.
-
byte[]
-
String
-
InputStream
-
Reader
-
File
Each implementation will provide out of the box a ResponseExceptionMapper
implementation that will map the response into a WebApplicationException
whenever the response status code is >= 400. It has a priority of Integer.MAX_VALUE
. It is meant to be used as a fall back whenever an error is encountered. This mapper will be registered by default to all client interfaces.
This behavior can be disabled by adding a configuration property microprofile.rest.client.disable.default.mapper
with value true
that will be resolved as a boolean
via MicroProfile Config.
It can also be disabled on a per client basis by using the same property when building the client, RestClientBuilder.newBuilder().property("microprofile.rest.client.disable.default.mapper",true)