Eclipse RCP and REST – JAX-RS Extensions

This is a continuation of a series of blog posts demonstrating the use of the ECF Remote Services JAX-RS Jersey Client within an Eclipse RCP application. The previous posts are:

In this post I’ll demonstrate how to use standard JAX-RS extensions in this environment.

Introduction to JAX-RS extensions

JAX-RS extensions can be used to customize many different aspects of REST service requests and responses. The current extensions supported by the ECF Remote Services JAX-RS Jersey Client are:

  • ClientRequestFilter and ClientResponseFilter
  • ContextResolver
  • ExceptionMapper
  • Feature
  • MessageBodyReader and MessageBodyWriter
  • ReaderInterceptor and WriterInterceptor

The specific uses of these extensions is beyond the scope of this article, but I will show a few examples below that should provide a starting point.

Extending JAX-RS using Declarative Services

As the ECF client (and Eclipse RCP for that matter) is built on OSGi, the best way to register JAX-RS extensions is by contributing them as OSGi services. It is fairly trivial to create and register an extension using Declarative Services annotations. For example, here is the code for a ClientRequestFilter.

@Component(service = ClientRequestFilter.class)
public class LaunchServiceClientRequestFilter implements ClientRequestFilter {

	@Override
	public void filter(ClientRequestContext context) throws IOException {
		MultivaluedMap<String, Object> headers = context.getHeaders();
		Map<String, Cookie> cookies = context.getCookies();
		
		/* Manipulate headers or cookies before request is made */
	}
}

A common use case for this type of filter is to intercept and modify the request headers and cookies being sent to the REST service. For example, you may want to manage cookies that relate to back-end security services.

Contributing a custom ObjectMapper

One of the most common JAX-RS customizations to make is to contribute a service-specific ObjectMapper. A REST service client will often need a custom ObjectMapper to control the way JSON is serialized and/or deserialized into Java POJOs.

In the SpaceX Launch Service that we’ve been using as an example, the Launch POJO is currently configured to map from the SpaceX JSON format to Java camel casing using an annotation on the POJO.

@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) // r/SpaceX uses underscored field names
public class Launch {

	private String flightNumber;
	private String missionName;
	
	public String getFlightNumber() {
		return flightNumber;
	}
	
	public String getMissionName() {
		return missionName;
	}
}

But as the number of POJOs used to define the service increases, it makes more sense to centralize this customization in a contributed ObjectMapper. This contribution can be made with a ContextResolver defined as an OSGi service using DS annotations.

@Component(service = ContextResolver.class)
public class LaunchServiceObjectMapperResolver implements ContextResolver<ObjectMapper> {

	private ObjectMapper objectMapper;
	
	public LaunchServiceObjectMapperResolver() {
	    objectMapper = new ObjectMapper();
	    objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
	    objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
	    
	    /* Add custom serializers or deserializers, etc. if needed */
	}

	@Override
	public ObjectMapper getContext(Class<?> type) {
		return objectMapper;
	}
}

At this point, the JAX-RS annotation can be removed from the Launch POJO.

Wrapping up

JAX-RS extensions give you the power to customize many aspects of REST calls within an Eclipse RCP application, and it’s very simple to contribute these extensions using OSGi Declarative Services annotations. The example code on GitHub has been updated to demonstrate how this works.

Note that if you’ve been using earlier versions of the ECF Remote Services JAX-RS Jersey Client, you’ll need to reload the target to bring in the latest version (1.13.8 or later).

https://github.com/modular-mind/spacex-client