This Client SDK provides convenient access to the Sequoia RESTful services through a set of Java abstractions. It provides general access methods to services and endpoints, but leaves the construction of endpoint-specific models and criteria helpers to user code.
The central idea is that Client SDK allows Java application code to communicate
with the Sequoia RESTful services by exchanging plain Java objects (POJOs) that
inherit from a common type (Resource
). Users can also search, filter and
select their response collections using a fluent Java API. For example:
results = contentsEndpoint.browse(
where(field("title")
.equalTo("Game Of Thrones")));
This first version supports the machine-to-machine OAuth authorisation mechanism using a client credentials authorisation grant - more authorisation types are planned.
The Client SDK supports both business (plain HTTP REST) and resourceful endpoints using the criteria and resourceful model structure.
The Sequoia RESTful services have an OAuth token-based authorisation model,
meaning that the Client SDK must first acquire a time-limited access token
before making further requests. As such, users much provide their username
and password as part of the ClientConfiguration
class.
To use the API it is needed to instantiate a SequoiaClient object, passing a configuration object which set the credentials and the urls for the services 'identity' and 'registry'.
ClientConfiguration configuration = ClientConfiguration.builder()
.identityComponentCredentials(new ComponentCredentials("user", "password"))
.identityHostConfiguration(new HostConfiguration("http://identity.piksel.com"))
.registryHostConfiguration(new HostConfiguration("http://registry.piksel.com"))
.build();
SequoiaClient client = SequoiaClient.client( configuration );
You can add an HTTP interceptor to the client so you can perform some actions to every response. To do so you just need to specify the class name of your inspector this way:
SequoiaClient client = SequoiaClient.client(configuration.httpResponseInterceptorName(LogResponseInterceptor.class.getName()).build());
An example of interceptor could be:
import com.google.api.client.http.HttpResponseInterceptor;
@Slf4j
public class LogResponseInterceptor implements HttpResponseInterceptor {
private final long startTime = System.nanoTime();
@Override
public void interceptResponse(HttpResponse httpResponse) {
long elapsedNanos = System.nanoTime() - startTime;
logResponse(httpResponse.getRequest(),
httpResponse.getStatusCode(),
TimeUnit.NANOSECONDS.toMillis(elapsedNanos));
}
private void logResponse(HttpRequest httpRequest,
int statusCode,
long elapsedTime) {
log.info("HTTP {} {} status code: {} time: {} ms",
httpRequest.getRequestMethod(),
httpRequest.getUrl().toString(),
statusCode,
elapsedTime);
}
}
Once instantiated the Client, it is needed to wait for the initializing to ensure that a token has been taken, the service list has been retrieved and the Client SDK is ready to be used.
client.awaitInitialised(2, TimeUnit.MINUTES));
This is a blocking call and so can be wrapped into a future, such as CompletableFuture
.
A Resourceful endpoint defines the resource on which to perform the operations.
ResourcefulEndpoint<Brand> brands = client.service("cars").resourcefulEndpoint("brands", Brand.class);
Following are the list of operations or methods which can be performed on a ResourcefulEndpoint.
Retrieves one or more resources given their reference and returns the response retrieved.
- ResourceResponse read(Reference ref)
- ResourceResponse read(Collection ref)
ResourceResponse<Brand> response = brands.read(Reference.fromOwnerAndName("test", "porsche"));
Retrieves the list of resources that matches with the criteria and returns the response.
- ResourceResponse browse(ResourceCriteria criteria)
ResourceResponse<Brand> response = brands.browse(where(field("country").equalTo("Germany"))
.and(field("year").greaterThan("1930"))
.orderByCreatedAt());
Creates one or more resources and returns the response retrieved.
- ResourceResponse store(T resource)
- ResourceResponse store(Collection resources)
ResourceResponse<Brand> response = brands.store(newBrand);
Updates one resource given its reference and returns the response retrieved.
- ResourceResponse update(T resource, Reference reference)
ResourceResponse<Brand> response = brands.update(updatedBrand, Reference.fromOwnerAndName("test", "porsche"));
Deletes one or more resources given their references and returns the response retrieved.
- ResourceResponse delete(Reference reference)
- ResourceResponse delete(Collection references)
ResourceResponse<Brand> response = brands.delete(Reference.fromOwnerAndName("test", "porsche"));
The SDK supports a fluent criteria API to abstract client code from the details of the Sequoia query syntax:
carsEndpoint.browse(where(field("engine").equalTo("diesel"))
.and(field("distribution").equalTo("mechanical"))
.orderByCreatedAt());
Filter over included resources is also supported:
carsEndpoint.browse(where(field("engine").equalTo("diesel"))
.and(field("distribution").equalTo("mechanical"))
.and(include(resource("model")))
.and(field("model.type").equalTo("SUV"))
.orderByCreatedAt());
The following filtering criteria are supported:
where(field("engine").equalTo("diesel"));
Will generate the url encoded criteria expression equivalent to: field=diesel
where(field("engine").notEqualTo("diesel"));
Will generate the url encoded criteria expression equivalent to: engine=!diesel
String[] arguments = new String [] { "diesel", "gasoline" };
where(field("engine").oneOrMoreOf(arguments));
or
where(field("engine").oneOrMoreOf("diesel", "gasoline"));
Will generate the url encoded criteria expression equivalent to: engine=diesel||gasoline
Logical and can be achieved by chaing multiple field and equalTo methods like the following:
where(field("engine").equalsTo("diesel")).and(field("engine").equalsTo("gasoline"))
would generate an expression equivalent to: engine=diesel&engine=gasoline
where(field("engine").startsWith("diesel"));
Will generate the url encoded criteria expression equivalent to: engine=diesel*
where(field("engine").exist());
Will generate the url encoded criteria expression equivalent to: engine=* (any value)
where(field("engine").notExist());
Will generate the url encoded criteria expression equivalent to: engine=!*
where(textSearch("text search"));
Will generate the url encoded criteria expression equivalent to: q=text search
where(...).lang("es");
Will generate the url encoded criteria expression equivalent to: lang=es
where(field("startedAt").between("2014", "2015"));
Will generate the url encoded criteria expression equivalent to: startedAt=2014/2015 (between 2014 and 2015 inclusive)
where(field("startedAt").notBetween("2014", "2015"));
Will generate the url encoded criteria expression equivalent to: startedAt=!2014/2015
where(field("startedAt").lessThan("2014"));
Will generate the url encoded criteria expression equivalent to: startedAt=!2014/
where(field("startedAt").lessThanOrEqualTo("2014"));
Will generate the url encoded criteria expression equivalent to: startedAt=/2014
where(field("startedAt").greaterThan("2014"));
Will generate the url encoded criteria expression equivalent to: startedAt=!/2014
where(field("startedAt").greaterThanOrEqualTo("2014"));
Will generate the url encoded criteria expression equivalent to: startedAt=2014/
The SDK support inclusion of related documents up to 1 level (direct relationships).
where(...).include(resource("offers"), resource("channels"));
Both, direct and indirect relationships, are allowed. In each case resource's reference
are needed to perform the mapping.
@DirectRelationship
MUST de used with a ? extends Resource
or a Collection<? extends Resource>
field.
ref
param is the field name with the reference for the linked resource.
relationship
param is the name of the json block inside linked
block which groups the linked resources.
public class Foo extends Resource {
@Key
String barRef;
@DirectRelationship(ref= "barRef", relationship = "bars")
Bar bar;
}
{
"meta": {
"perPage": 1
},
"foos": [
{
"ref": "test:foo1",
"owner": "test",
"name": "foo1",
"createdAt": "2016-04-29T14:12:34.734Z",
"createdBy": "root:sysadmin",
"updatedAt": "2016-05-23T15:27:16.665Z",
"updatedBy": "root:system-piksel-task-management",
"version": "fa9a2db0f5f1dcb8cf0a6166ef2a3a2c4e9fb254",
"barRef": "owner:bar1"
}
],
"linked": {
"bars": [
{
"ref": "owner:bar1",
"someField": "someValue1"
}
]
}
}
@IndirectRelationship
MUST be used with a LinkedResourceIterable<? extends Resource>
field where
ref
param is the field name with the reference in the linked resource.
relationship
param is the name of the json block inside linked
block which groups the linked resources.
public class Bar extends Resource {
@IndirectRelationship(ref= "barRef", relationship = "foos")
LinkedResourceIterable<Foo> foos;
}
{
"meta": {
"perPage": 1,
},
"bars": [
{
"ref": "test:bar1",
"owner": "test",
"name": "bar1",
"createdAt": "2016-04-29T14:12:34.734Z",
"createdBy": "root:sysadmin",
"updatedAt": "2016-05-23T15:27:16.665Z",
"updatedBy": "root:system-piksel-task-management",
"version": "fa9a2db0f5f1dcb8cf0a6166ef2a3a2c4e9fb254",
}
],
"linked": {
"foos": [
{
"ref": "owner:foo1",
"barRef": "owner:bar1",
"someField": "someValue1"
}
]
}
}
- Note that with the introduction of
@IndirectRelationship
Collection<? extends Resource>
andList<Optional<? extends Resource>>
are both deprecated because these do not support pagination over linked resources
The SDK allows to specify which fields will be present in the response, discarding the rest of them.
where(...).fields("name", "title");
It is also possible to specify the needed fields in the related documents linked by inclusion.
where(...).include(resource("offers").fields("name", "title"));
The SDK supports sort criteria by exposing fluent orderBy query parameters.
Generic fields can be sorted using the orderBy method.
where(...).orderBy("field-name");
Common Resourceful sortable fields are mapped to their corresponding fluent method.
where(...).orderByOwner();
where(...).orderByName();
where(...).orderByCreatedAt();
where(...).orderByCreatedBy();
where(...).orderByUpdatedAt();
where(...).orderByUpdatedBy();
By default the orderBy is in ascending order. Is it possible to change the order to descending/ascending by using the fluent methods asc() and desc().
where(...).orderBy("field-name").asc();
where(...).orderBy("field-name").desc();
It is also possible to specify the needed sort criteria in the related documents linked by inclusion.
where(...).include(resource("offers").orderBy("field-name").asc().orderBy("field-name-1").desc());
For collection responses that span multiple pages, the returned ResourceIterable
will transparently load subsequent pages on demand. This will be performed automatically calling next()
ResourceResponse<Brand> response = brands.browse(where(field("country").equalTo("Germany"))
.and(field("year").greaterThan("1930"))
.orderByCreatedAt());
response.getPayload().get().next();
The SDK supports defining the number of resources prefetched.
ResourceResponse<Brand> response = brands.browse(where(field("year").greaterThan("1930"))
.orderByCreatedAt().perPage(10));
ResourceResponse<Brand> response = brands.browse(where(field("year").greaterThan("1930"))
.orderByCreatedAt().count());
response.getPayload().ifPresent(payload -> {
payload.totalCount().ifPresent(totalCount -> {
log.debug("Total brands retrieved [{}]", totalCount);
});
});
Will return totalCount
on the payload
The cardinality of every value of a given field as provided via the count
query parameter
ResourceResponse<Brand> response = brands.browse(where(field("year").greaterThan("1930"))
.orderByCreatedAt().count("type"));
response.getPayload().ifPresent(payload -> {
payload.totalCount().ifPresent(totalCount -> {
log.debug("Total brands retrieved [{}]", totalCount);
});
payload.facetCount().ifPresent(facetCount -> {
log.debug("Total brands retrieved with type "other" [{}]", facetCount.get("type").get("other"));
});
});
Will return facetCount
on the payload as Map<String, Map<String, Integer>>
furthermore it will return totalCount
on the payload
ResourceResponse<Brand> response = brands.browse(where(field("year").greaterThan("1930"))
.orderByCreatedAt().skipResources());
response.getPayload().get().next(); //throws NoSuchElementException
Will skip the resources on the payload.
ResourceResponse<Brand> response = brands.browse(where(active(true)).orderByCreatedAt());
Will return a list of actives brands, a list of brands that fulfill the filer active=true
ResourceResponse<Brand> response = brands.browse(where(available(true)).orderByCreatedAt());
Will return a list of available brands, a list of brands that fulfill the filer available=true
ResourceResponse<Content> response = contents.browse(where(field("releaseYear").greaterThan("2015"))
.intersect("offer", where(field("type").equalTo("linear")).fields("ref"))
.orderByCreatedAt());
Will return a list of contents released after 2015 and with linear offers and it will retrieve only offer's ref
Important
FacetCount and Intersections are incompatible. If you add intersections, facet count option will be disabled
A Endpoint allows to perform operations over non-resources endpoints without Resourceful restrictions.
It defines the response class, request class and the endpoint on which to perform the operations.
Endpoint<Brand, Model> brands = client.service("cars").endpoint("brands", Brand.class);
Performs a get request over the endpoint.
- Response get()
Response<Brand> response = brands.get();
Performs a get request over the endpoint using the headers added as parameters.
- Response get(RequestHeaders headers)
Response<Brand> response = brands.get(withHeaders("header1", "value1").add("header2", 2));
Performs a get request over the endpoint with parameters added.
- Response get(RequestParams params);
Response<Brand> response = brands.get(withParams("engine", "diesel").add("color", "red"));
Performs a get request over the endpoint with parameters and headers added.
- Response get(RequestParams params, RequestHeaders headers);
Response<Brand> response = brands.get(withParams("engine", "diesel").add("color", "red"), withHeaders("header1", "value1").add("header2", 2));
Performs a post request over the endpoint.
- Response post(K payload, RequestHeaders headers);
Response<Brand> response = brands.post(Model.ModelBuilder.name("carrera").build(), withHeaders("header1", "value1").add("header2", 2));
Performs a post request over the endpoint using the headers added.
- Response post(K payload);
Response<Brand> response = brands.post(Model.ModelBuilder.name("carrera").build());
The client library builds upon the Google OAuth Client Library to support transparent credential management such as acquiring access tokens and refreshing them when needed. The library provides a pluggable architecture for supplying custom JSON implementations and transport layer implementations.