A simple, model-mapping, link-traversing Java client library for consuming a JSON+HAL REST API.

Overview

Bowman is a Java library for accessing a JSON+HAL REST API, supporting the mapping of a client-side model to HTTP resources with automatic link traversal into associated resources.

Philosophy

The original motivation for this library was to make it easier to write clients for Spring Data REST-exposed JPA repositories, supporting lazy-loading of associations in a similar style to JPA.

Bowman mandates the use of a client-side model — comprised of entities, though separate from the server-side model — which maps to an API’s JSON+HAL representation via annotations. It then enhances the model by creating proxies using Javassist, so that invoking an accessor or query method can transparently retrieve a linked remote resource and add it to the client-side object graph. This can make client code much easier to write and understand.

See the blog post Simpler Spring Data REST Clients with Bowman, for the thinking that led to the creation of this library.

Getting Started

Add to Your Project

Add the Maven dependency:

<dependency>
  <groupId>uk.co.blackpepper.bowman</groupId>
  <artifactId>bowman-client</artifactId>
  <version>0.9.0</version>
</dependency>

Usage Example

Given the following annotated model objects:

import uk.co.blackpepper.bowman.annotation.RemoteResource;
import uk.co.blackpepper.bowman.annotation.ResourceId;

@RemoteResource("/people")
public class Person {

  private URI id;
  private String name;

  public Person() {}
  public Person(String name) { this.name = name; }

  @ResourceId public URI getId() { return id; }
  public String getName() { return name; }
}

and

import uk.co.blackpepper.bowman.annotation.LinkedResource;
import uk.co.blackpepper.bowman.annotation.RemoteResource;
import uk.co.blackpepper.bowman.annotation.ResourceId;

@RemoteResource("/greetings")
public class Greeting {

  private URI id;
  private Person recipient;
  private String message;

  public Greeting() {}
  public Greeting(String message, Person recipient)
    { this.message = message; this.recipient = recipient; }

  @ResourceId public URI getId() { return id; }
  @LinkedResource public Person getRecipient() { return recipient; }
  public String getMessage() { return message; }
}

Client instances can be constructed and used as demonstrated below.

The HTTP requests/responses corresponding to each instruction are shown in a comment beneath.
import uk.co.blackpepper.bowman.Client;
import uk.co.blackpepper.bowman.ClientFactory;
import uk.co.blackpepper.bowman.Configuration;

...

ClientFactory factory = Configuration.builder().setBaseUri("http://...").build()
  .buildClientFactory();

Client<Person> people = factory.create(Person.class);
Client<Greeting> greetings = factory.create(Greeting.class);

URI id = people.post(new Person("Bob"));
// POST /people {"name": "Bob"}
//  -> Location: http://.../people/1

Person recipient = people.get(id);
// GET /people/1
//  -> {"name": "Bob", "_links": {"self": {"href": "http://.../people/1"}}}

assertThat(recipient.getName(), is("Bob"));

id = greetings.post(new Greeting("hello", recipient));
// POST /greetings {"message": "hello", "recipient": "http://.../people/1"}}
//  -> Location: http://.../greetings/1

Greeting greeting = greetings.get(id);
// GET /greetings/1
//  -> {"message": "hello", "_links": {"self": {"href": "http://.../greetings/1"},
// 			"recipient": {"href": "http://.../people/1"}}}

assertThat(greeting.getMessage(), is("hello"));

recipient = greeting.getRecipient();
// GET /people/1
//  -> {"name": "Bob", "_links": {"self": {"href": {"http://.../people/1"}}}

assertThat(recipient.getName(), is("Bob"));

API Usage

Using Bowman requires:

  1. An annotated client model

  2. A configured ClientFactory

  3. A Client created from that factory

A Client corresponds to a specific entity type; you will create one Client for each entity type that requires direct (without following HAL links) retrieval or manipulation.

Factory Instantiation

ClientFactory instances are created through Configuration.builder().getClientFactory().

Clients are then created from the factory using ClientFactory.create(clazz).

Both ClientFactory and Client instances maintain no mutable state and may safely be shared across threads.

Factory Configuration

Base URI

Set the base URI for the API with setBaseUri. Entities' base resources, specified by @RemoteResource, will be resolved relative to this.

ClientFactory factory = Configuration.builder()
  .setBaseUri("http://www.example.com/my-api-root")
  .build();

This is often all the ClientFactory configuration you need.

Framework Components

Bowman uses Spring’s RestTemplate and Jackson’s ObjectMapper under the hood. It may often be necessary to customise these, to support custom serialisation, add authentication headers to the request etc.

ClientFactory factory = Configuration.builder()
  .setBaseUri(...)
  .setRestTemplateConfigurer(...) (1)
  .setObjectMapperConfigurer(...) (2)
  .setClientHttpRequestFactory(...) (3)
  .build();
1 Provide custom configuration of the RestTemplate via an implementation of uk.co.blackpepper.bowman.RestTemplateConfigurer
2 Provide custom configuration of the ObjectMapper via an implementation of uk.co.blackpepper.bowman.ObjectMapperConfigurer
3 Provide an implementation of org.springframework.http.client.ClientHttpRequestFactory. The default HttpComponentsClientHttpRequestFactory is often sufficient, but you may wish to provide a buffering request factory to allow logging of the response body in an interceptor prior to deserialisation.
Provided implementations of ClientHttpRequestFactory must throw an org.springframework.web.client.HttpClientErrorException when an HTTP 404 is returned accessing the remote resource.

Client Instantiation

Then from your ClientFactory you can create a Client of the desired type. The base resource of the Client's API is then determined by its @RemoteResource annotation.

Client<Customer> customers = factory.create(Customer.class);

Client API Methods

Clients support:

  • get() - GET the single entity from the base resource

  • get(URI id) - GET the entity with the given ID

  • getAll() - GET a collection of entities from the base resource

  • getAll(URI location) - GET a collection of entities from the given resource

  • post(T object) - POST the entity to the base resource

  • put(T object) - PUT the entity to its resource

  • patch(URI id, P patch) - PATCH the entity with the given ID with a set of changes

  • delete(URI id) - DELETE the entity with the given ID

PUT/PATCH are supported with caveats: there is currently a whole category of Spring Data REST limitations interacting via PUT/PATCH with JPA repositories due to attempts to replace persistent collections and state merge occurring outside of a transaction.

The Client Model

Bowman requires you to define a client-side model, consisting of entities, and enhanced with annotations to define the remote resources these entities map to and the relationships between them.

Entity Types

Bowman supports abstract class and interface entity types as well as concrete classes.

Concrete Classes

Entity classes have a HAL representation containing JSON properties. Their corresponding classes are assumed to obey Java Bean conventions — i.e. have zero-arg constructors and private fields with public getters (get*/is*) and setters (set*).

JSON+HAL Representation
{
  "property": "value",
  "_links": {
    "self": "http://www.example.com/entities/1"
  }
}
Client Model
class Entity {
  private URI id;
  private String property = "value";

  @ResourceId URI getId() { return id; }

  public String getProperty() { return property; }
  public void setProperty(String property) { this.property = property; }
}

Abstract Classes

It may be more convenient to map read-only linked resources using abstract methods.

JSON+HAL Representation
{
  "property": "value",
  "_links": {
    "self": "http://www.example.com/entities/1",
    "nextEntity": "http://www.example.com/entitites/1/next"
  }
}
abstract class Entity {
  private URI id;
  private String property = "value";

  @ResourceId URI getId() { return id; }

  public String getProperty() { return property; }
  public void setProperty(String property) { this.property = property; }

  @LinkedResource public abstract Entity nextEntity();
}

Interfaces

If an entity’s HAL representation comprises read-only links only, it’s probably more convenient to map it using an interface type.

JSON+HAL Representation
{
  "_links": {
    "findByName": "http://www.example.com/related/findByName{?name}"
  }
}
Client Model
interface Entity {
  @LinkedResource Related findByName(String name);
}

Remote Resources

Annotate your entities with @RemoteResource(path), where path is the location of the entity’s base resource, relative to the base URI set when building the ClientFactory.

@RemoteResource("/things")
public class Thing { ... }

ID Property

Use @ResourceId to mark a java.net.URI accessor as the resource ID. This is the canonical URI for the resource - its 'self' link.

private URI id;

@ResourceId public URI getId() { return id; }

Value Properties

Simple properties (Strings, primitives) will be mapped to JSON automatically.

Linked Resources

Mark a resource as linked with @LinkedResource on its accessor. Invoking this accessor will automatically query its associated linked remote resource to populate the model.

Concrete Class (Java Bean)
class Entity {
  private Related related;
  private Set<Related> relatedSet = new HashSet<>();

  @LinkedResource public Related getRelated() { return related; }
  @LinkedResource public Set<Related> getRelatedSet() { return relatedSet; }
}
Interface
interface Entity {
  @LinkedResource public Related findByName(String name)
}

Links in an interface type are assumed to be templated with stringifiable query string parameters of the same names and in the same order as those in the interface method signatures.

The HAL rel of a linked resource is assumed to be the same as the method name. You can customise this by setting the rel attribute of the @LinkedResource annotation:

@LinkedResource(rel = "my-rel") public Related getRelated() { ... }

By default, the rel for each @LinkedResource is required to be present in the consumed HAL’s _links property. If you want to tolerate the link being absent completely, you can set the optionalLink attribute:

@LinkedResource(optionalLink = true) public Related getRelated() { ... }

Inline Resources

Mark a resource as inline with the InlineAssociationDeserializer Jackson deserializer. Invoking this accessor will create and return a proxy that is aware of the inline object’s links, and so is able to resolve nested linked resources.

private Related related;
private Set<Related> relatedSet = new HashSet<>();

@JsonDeserialize(using = InlineAssociationDeserializer.class)
public Related getRelated() { return related; }

@JsonDeserialize(contentUsing = InlineAssociationDeserializer.class)
public Set<Related> getRelatedSet() { return relatedSet; }

Embedded Resources

Subresources are loaded from the _embedded property of a HAL response when querying a collection resource. For single-valued resources, embedded resources are currently disregarded: PRs welcome!

Polymorphism

Use @ResourceTypeInfo to declare a type’s subtypes. On deserialization, the type of the resource will be determined using the self link of the resource.

@ResourceTypeInfo(subtypes = {OneThing.class, AnotherThing.class})
class Thing { }

class OneThing extends Thing { }
class AnotherThing extends Thing { }

Alternatively you can register your own TypeResolver to provide custom subtype resolution, perhaps using alternative resource links.

@ResourceTypeInfo(typeResolver = MyTypeResolver.class)
class Thing { }

class MyTypeResolver implements TypeResolver {
  Class<?> resolveType(Class<?> declaredType, Links resourceLinks, Configuration configuration) {
    // own type resolution code here...
  }
}

FAQs

How can I access an API that requires authentication?

For example, you could define an interceptor to send the Authorization header with your OAuth access token on each request:

ClientFactory clientFactory = Configuration.builder()
  .setRestTemplateConfigurer(new RestTemplateConfigurer() {

    public void configure(RestTemplate restTemplate) {

      restTemplate.getInterceptors().add(new ClientHttpRequestInterceptor() {

        public ClientHttpResponse intercept(HttpRequest request, byte[] body,
            ClientHttpRequestExecution execution) throws IOException {

          request.getHeaders().add("Authorization", "Bearer youraccesstoken");
          return execution.execute(request, body);
        }
      });
    }
  })
  .buildClientFactory();

You should be able to use Bowman or any other HTTP client library to first get the token from an OAuth access token response following authorisation.

Glossary

Base resource

The remote resource identified as the entrypoint for an entity’s API, identified by the entity’s @RemoteResource annotation. An entity’s base resource may be single-valued or collection-valued.

Entity

An artifact within the client-side model. This may correspond to a full or embedded remote resource. Entity types may be concrete classes (Java Beans - when their representation contains data properties) or abstract classes or interfaces (useful when their representation contains read-only links).