Common use-case in web-application development is aggregating data from multiple resources, combining them together and returning them to the used as XML/JSON or as a web page. In Java world these (external) resources can be approached via standardized Clients from JAX-RS 2.0. Jersey 2 application can use so-called managed client mechanism that brings a convenient way to create JAX-RS clients and web targets for such resources.
In this article I’ll demonstrate the concepts on a simple application that displays ratings of movies obtained from different sources:
- IMDb via omdbapi.com
- search & movie detail – http://www.omdbapi.com/?r={format}&t={title}
- CSFD (clone of IMDb; only in Czech) via csfdapi.cz
- search – http://csfdapi.cz/movie?search={title}
- movie detail – http://csfdapi.cz/movie/{id}
You can try the application at
jersey-managed-jaxrs-client.herokuapp.com (deployed on Heroku)
for example: jersey-managed-jaxrs-client.herokuapp.com/rating/fargo
and browse the code
github.com/mgajdos/jersey-managed-jaxrs-client
Injecting WebTarget via @Uri
Injecting a resource target pointing at a resource identified by the resolved URI can be done via new annotation @Uri. The annotation can be placed on WebTarget that represents
a method parameter, i.e. FormResource:
@Path("/{greeting: .*}") public class FormResource { @GET @Produces("text/html") @Template(name = "/form") public String getForm( @Uri("internal/greeting/{greeting}") final WebTarget greeting) { return greeting.request().get(String.class); } }
a class field for example in resources or providers (filters, interceptors, ..), i.e. RatingsResource:
@Path("/rating") @Produces("text/html") public class RatingsResource { @CsfdClient @Uri("movie/{id}") private WebTarget csfdMovie; @ImdbClient @Uri("http://omdbapi.com/?r={format}&t={search}") private WebTarget imdbMovie; ... }
or a bean property.
As you probably noticed the value of @Uri can be either absolute or relative URI. The relative ones are resolved into absolute URIs in the context of application (context path) or in a context of enclosing resource class. Web target from the first example (internal/greeting/{greeting}
) is pointing at an internal resource (present in the same application) but web target from the second example (movie/{id}
) is not relative to our application as you’ll see in the next section.
Provided URIs can also contain template parameters (internal/greeting/{greeting}
, r={format}&t={search}
) which are resolved automatically (internal/greeting/{greeting}
), if they are represented as path params (see @Path):
@Path("/{greeting: .*}")
public class FormResource {
@GET
@Produces("text/html")
@Template(name = "/form")
public String getForm(
@Uri("internal/greeting/{greeting}") final WebTarget greeting) {
return greeting.request().get(String.class);
}
}
The
greeting
parameter is used on the front page in the header (it’s enough to set it, i.e. jersey-managed-jaxrs-client/Greetings, Commander, you don’t need to resolve it by hand).
or you can resolve them manually (r={format}&t={search}
):
final Response response = imdbMovie
.resolveTemplate("search", search)
.resolveTemplate("format", format)
.request("application/" + format)
.get();
Configuring
By default, injected clients are configured with features and providers from the configuration of an application (i.e. MyApplication) that are also applicable to clients. In our case only JacksonFeature would be taken into consideration.
In case this is not enough for you need to take a look at @ClientBinding.
@ClientBinding
Meta-annotation that provides a facility for creating bindings between an @Uri-injectable web target instances and clients (and their configurations) that are used to create the injected web target instances.
So, basically, the first step is to create a custom client-binding annotation and put @ClientBinding on top of it, i.e. CsfdClient:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@ClientBinding(
baseUri = "http://csfdapi.cz/",
inheritServerProviders = false,
configClass = ManagedClientConfig.class
)
public @interface CsfdClient {
}
Second step is to annotate WebTarget with custom client-binding annotation along with @Uri, i.e. RatingsResource:
@Path("/rating")
@Produces("text/html")
public class RatingsResource {
@CsfdClient
@Uri("movie")
private WebTarget csfdSearch;
@CsfdClient
@Uri("movie/{id}")
private WebTarget csfdMovie;
...
}
Notice that one client (created for @CsfdClient) is used to create two WebTarget instances that are used separately.
Third step is to use the injected web target.
Custom client-binding annotation can be configured programmatically (see CsfdClient) via parameters of @ClientBinding which are:
- baseUri – base URI which is used to absolutize relative URI provided in @Uri (this is the reason why
csfdMovie
/csfdSearch
web targets aren’t pointing at internal resource). - inheritServerProviders – determines whether server providers ought to be registered in the client (and web targets) as well.
- configClass – configuration class to configure client (and web targets), in our case it’s ManagedClientConfig (that extends ClientConfig) where we’re registering LoggingFilter and JacksonFeature.
web.xml
Parameters of @ClientBinding for particular client-binding annotation can be set declaratively in web.xml
via init-param
-eters. You need to remember to prefix parameter name with fully-qualified name of custom client-binding annotation:
<servlet>
<servlet-name>sk.dejavu.jersey.sample.MyApplication</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>sk.dejavu.jersey.sample.MyApplication</param-value>
</init-param>
<!-- Imdb client configuration -->
<init-param>
<param-name>sk.dejavu.jersey.sample.ImdbClient.configClass</param-name>
<param-value>sk.dejavu.jersey.sample.ManagedClientConfig</param-value>
</init-param>
<init-param>
<param-name>sk.dejavu.jersey.sample.ImdbClient.inheritServerProviders</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>sk.dejavu.jersey.sample.ImdbClient.property.format</param-name>
<param-value>xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
The last highlighted init-parameter (sk.dejavu.jersey.sample.ImdbClient.property.format
) shows special way how to define a property for the newly created client. In this case we can obtain value of the property from configuration of our web target and set the format of entities received from imdbMovie
:
final String format = imdbMovie.getConfiguration().getProperty("format").toString();
final Response response = imdbMovie
.resolveTemplate("search", search)
.resolveTemplate("format", format)
.request("application/" + format)
.get();
Examples
Besides the example I’ve been using in this article there are few others available in the Jersey workspace:
- managed-client
- managed-client-webapp (+ GF4 project)
- managed-client-simple-webapp (+ GF4 project)