Skip to content

Latest commit

 

History

History
240 lines (170 loc) · 12.8 KB

providers.asciidoc

File metadata and controls

240 lines (170 loc) · 12.8 KB

MicroProfile Rest Client Provider Registration

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:

ClientResponseFilter

Filters of type ClientResponseFilter are invoked in order when a response is received from a remote service.

ClientRequestFilter

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.

MessageBodyReader

The MessageBodyReader interface defined by JAX-RS allows the entity to be read from the API response after invocation.

MessageBodyWriter

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.

ParamConverter

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.

ReaderInterceptor

The ReaderInterceptor interface is a listener for when a read occurs against the response received from a remote service call.

WriterInterceptor

The WriterInterceptor interface is a listener for when a write occurs to the stream to be sent on the remote service invocation.

ResponseExceptionMapper

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.

How to Implement ResponseExceptionMapper

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.

Provider Declaration

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.

CDI Managed Providers

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.

Provider Priority

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.

Feature Registration

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).

Automatic Provider Registration

Implementations may provide any number of providers registered automatically, but the following providers must be registered by the runtime.

JSON-P and JSON-B Providers

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 {
    //...
}

Default Message Body Readers and Writers

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

Values supported with text/plain

The following types are supported for automatic conversion, only when the media type is text/plain.

  • Number

  • Character and char

  • Long and long

  • Integer and int

  • Double and double

  • Float and float

  • Boolean and boolean (literal value of true and false only)

Default ResponseExceptionMapper

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)