This document describes Rest.li support for implementing servers.
Rest.li supports the following runtimes:
Rest.li servers can be configured with different R2 filters, according
to your use case. How the filters are configured depends on which
dependency injection framework (if any) you are using. For example, see Compression to understand how to configure a server for
compression. Another example is to add a
SimpleLoggingFilter
with Spring, which requires you to do
the following (full file
here):
<!-- Example of how to add filters; here we'll enable logging and snappy compression support -->
<bean id="loggingFilter" class="com.linkedin.r2.filter.logging.SimpleLoggingFilter" />
Other R2 filters can also be configured in a similar way.
The first step in building a Rest.li application is to define your data schema using Pegasus Data Schemas. The Pegasus Data Schema format uses a simple Avro-like syntax to define your data model in a language-independent way. Rest.li provides code generators to create Java classes that implement your data model.
After you have defined your data models, the principle programming task when implementing a Rest.li server is to create resource classes. In Rest.li, resource classes define the RESTful endpoints your server provides. You create a resource class by adding a class level annotation and by implementing or extending a Rest.li interface or base class corresponding to the annotation. The annotations help describe the mapping from your Java code to the REST interface protocol. When possible, the framework uses conventions to help minimize the annotations you need to write.
Steps to define a resource class:
implement
the
necessary Resource interface or extend one of the convenience base
classes that implements the interface.Here is a simple example of a Resource class. It extends a convenience
base class, uses an annotation to define a REST end-point (“fortunes”),
and provides a GET endpoint by overriding the standard signature of the
get()
method of the base class:
/**
* A collection of fortunes, keyed by random number.
*/
@RestLiCollection(name = "fortunes", namespace = "com.example.fortune")
public class FortunesResource extends CollectionResourceTemplate<Long, Fortune>
{
/**
* Gets a fortune for a random number.
*/
@Override
public Fortune get(Long key)
{
// retrieve data and return a Fortune object ...
}
}
This interface implements an HTTP GET:
> GET /fortunes/1
...
< { "fortune": "Your lucky color is purple" }
Note that Rest.li does not automatically use the names of your Java identifiers. Class names, method names, and parameter names have no direct bearing on the interface your resource exposes through annotations.
The above example supports the GET operation by overriding the
CollectionResourceTemplate
, and you can also choose to support other
operations by overriding other methods. However, you can also define any
method of your class as handling operations by using Resource
Annotations, described in detail in the next section.
Rest.li resources are documented in the resource source files using javadoc. When writing resources, developers simply add any documentation as javadoc to their java resource classes, methods, and method params. It is recommended that developers follow the javadoc style guidelines for all formatting so that their documentation is displayed correctly.
Rest.li will automatically extract this javadoc and include it in all generated “interface definitions” (.restspec.json files) and generated client bindings. This approach allows REST API clients and tools to easily gain access to the documentation. For example, Rest.li API Hub is an opensource web UI that displays REST API documentation, including all javadoc, for Rest.li APIs.
Resource annotations are used to mark and register a class as providing as Rest.li resource. One of a number of annotations may be used, depending on the Interface Pattern the resource is intended to implement. Briefly, here are the options:
Resource Type | Annotation | Interface or Base Class |
---|---|---|
Collection | @RestLiCollection | For simple keys, implement CollectionResource or extend CollectionResourceTemplate . For complex key implement ComplexKeyResource , extend ComplexKeyResourceTemplate , or implement KeyValueResource for use cases requiring extensive customization |
Simple | @RestLiSimpleResource | Implement SimpleResource , extend SimpleResourceTemplate or implement SingleObjectResource for use cases requiring extensive customization |
Association | @RestLiAssociation | Implement AssociationResource , extend AssociationResourceTemplate , or implement KeyValueResource for use cases requiring extensive customization |
Actions | @RestLiActions | N/A |
The @RestLiCollection
annotation is applied to classes to mark them as
providing a Rest.li collection resource. Collection resources model a
collection of entities, where each entity is referenced by a key. See
Collection Resource Pattern for more details.
The supported annotation parameters are:
name
- required, defines the name of the resource.namespace
- optional, defines the namespace for the resource.
Default is empty (root namespace). The namespace of the resource
appears in the IDL, and is used as the package name for the
generated client builders.keyName
- optional, defines the key name for the resource. Default
is “<ResourceName>Id”.parent
- optional, defines the parent resource for this resource.
Default is root.Classes annotated with @RestLiCollection
must implement the
CollectionResource
interface. The CollectionResource
interface
requires two generic type parameters:
K
- the key type for the resource.V
- the value type for the resource (also known as, the entity
type).The key type for a collection resource must be one of:
String
Boolean
Integer
Long
.pdl
schema)RecordTemplate
generated from a .pdl
schema)The value type for a collection resource must be a pegasus record, any
subclass of RecordTemplate
generated from a .pdl
schema.
For convenience, collection resources may extend
CollectionResourceTemplate
rather than directly implementing the
CollectionResource
interface.
Example:
@RestLiCollection(name = "fortunes", namespace = "com.example.fortune", keyName = "fortuneId")
public class FortunesResource extends CollectionResourceTemplate<Long, Fortune>
{
...
}
Rest.li allows resources to return results asynchronously through a
ParSeq Task
, or
Callback
. For example, a getter can be declared in either of the
following ways:
@RestMethod.Get
public Task<Greeting> get(Long key)
{
// set up some ParSeq tasks and return the final Task
return Tasks.seq(Tasks.par(...), ...);
}
@RestMethod.Get
public void get(Long key, @CallbackParam Callback<Greeting> callback)
{
// use the callback asynchronously
}
These method signatures can be mixed arbitrarily with the synchronous signatures, including in the same resource class. For instance, simple methods can be implemented synchronously and slow methods can be implemented asynchronously. However, multiple implementations of the same REST method with different signatures may not be provided.
You can also use the asynchronous resource templates in order to implement asynchronous Rest.li resources. The templates are:
AssociationResourceAsyncTemplate
AssociationResourceTaskTemplate
CollectionResourceAsyncTemplate
CollectionResourceTaskTemplate
ComplexKeyResourceAsyncTemplate
ComplexKeyResourceTaskTemplate
SimpleResourceAsyncTemplate
SimpleResourceTaskTemplate
The Rest.li server will automatically start any Task
that is returned
by a Task
based method by running it through a ParSeq engine. This ParSeq engine is launched while the Rest.Li server is launched (for example, NettyStandaloneLauncher), so the user does not run these task using another engine explicitly, instead, Rest.Li framework will the run the method’s returning Task
using the ParSeq engine when the method is invoked. (For details check RestLiMethodInvoker.java). Also refer to this document page regarding the best practice of writing asynchronous client and service logic. Callback
-based methods do not receive special treatment.
Sub-resources may be defined by setting the parent
field on
@RestLiCollection
to the class of the parent resource of
the sub-resource.
For example, a sub-resource of the fortunes resource would have a URI path of the form:
/fortunes/{fortuneId}/subresource
Parent resource keys can be accessed by sub-resources, as shown in the following example:
@RestLiCollection(name = "subresource", namespace = "com.example.fortune", parent = FortunesResource.class)
public class SubResource extends CollectionResourceTemplate<Long, SubResourceEntity>
{
@RestMethod.Get
public Greeting get(Long key, @Keys PathKeys keys) {
Long parentId = keys.getAsLong("fortuneId");
...
}
...
}
Alternatively, if not using free form methods, the path key can
retrieved from the resource context. This approach may be deprecated in
future versions in favor of @Keys
.
public SubResourceEntity get(Long subresourceKey)
{
Long parentId = getContext().getPathKeys().getAsLong("fortuneId");
...
}
For details on how to make requests to sub-resources from a client, see Calling Sub-resources
Classes implementing ComplexKeyResource
can use a record type as key.
This allows for arbitrary complex hierarchical structures to be used to
key a collection resource, unlike CollectionResources, which only
support primitive type keys (or typerefs to primitive types).
ComplexKeyResourceTemplate
is a convenient base class to extend when
implementing a ComplexKeyResource
.
The full interface is:
public interface ComplexKeyResource<K extends RecordTemplate, P extends
RecordTemplate, V extends RecordTemplate> ...
A complex key consists of a Key
and Parameter
part. The Key
should
uniquely identify the entities of the collection while the parameters
may optionally be added to allow additional information that is not used
to lookup an entity, such as a version tag for concurrency control.
Since the parameters are often not needed, an EmptyRecord
may be used
in the generic signature of a ComplexKeyResource
to
indicate that no “Parameters” are used to key the collection.
Example:
@RestLiCollection(name = "widgets", namespace = "com.example.widgets")
public class WidgetResource extends ComplexKeyResourceTemplate<WidgetKey, EmptyRecord, Widget>
{
public Widget get(ComplexResourceKey<WidgetKey, EmptyRecord> ck)
{
WidgetKey key = ck.getKey();
int number = key.getNumber();
String make = key.getThing().getMake();
String model = key.getThing().getModel();
return lookupWidget(number, make, model);
}
}
To use EmptyRecord
, restli-common
must be in
the dataModel
dependencies for the api project where client
bindings are generated, as shown in the following example:
api/build.gradle
:
dependencies {
// ...
dataModel spec.product.pegasus.restliCommon
}
where WidgetKey
is defined by the schema:
namespace com.example.widget
record WidgetKey {
number: string
thing: record Thing {
make: string
model: string
}
}
Example request:
curl "http://<hostname:port>/widgets/number=1&thing.make=adruino&thing.model=uno"
If params are added, they are represented in the URL under the “$params” prefix like this:
curl "http://<hostname:port>/widgets/number=1&thing.make=adruino&thing.model=uno&$params.version=1"
The implementation of complex key collection is identical to the regular
RestLiCollection
with the exception that it extends
ComplexKeyResourceTemplate
(or directly implements
ComplexKeyResource
) and takes three type parameters instead of two:
key type, key parameter type, and value type — each extending
@RecordTemplate.
For details on how a complex key is represented in a request URL, see Rest.li Protocol: Complex Types
The @RestLiSimpleResource
annotation is applied to classes to mark
them as providing a Rest.li simple resource. Simple resources model an
entity which is a singleton in a particular scope. See the description
of the Simple Resource Pattern for more details.
The supported annotation parameters are:
name
- required, defines the name of the resource.namespace
- optional, defines the namespace for the resource.
Default is empty (root namespace). The namespace of the resource
appears in the IDL, and is used as the package name for the
generated client builders.parent
- optional, defines the parent resource for this resource.
Default is root.Classes annotated with @RestLiSimpleResource
must implement the
SimpleResource
interface. The SimpleResource
interface requires a
generic type parameter V
, which is the value type for the resource
(also known as, the entity type). The value type for a simple resource
must be a pegasus record, any subclass of RecordTemplate
generated
from a .pdl
schema.
For convenience, simple resources may extend SimpleResourceTemplate
rather than directly implementing the SimpleResource
interface.
Examples:
@RestLiSimpleResource(name = "todaysPromotedProduct", namespace = "com.example.product")
public class TodaysPromotedProductResource extends SimpleResourceTemplate<Product>
{
...
}
The @RestLiAssociation
annotation is applied to classes to mark them
as providing a Rest.li association resource. Association resources model
a collection of relationships between entities. Each relationship is
referenced by the keys of the entities it relates and may define
attributes on the relation itself. See
Association Resource Pattern
For Example:
@RestLiAssociation(name = "memberships", namespace = "com.example",
assocKeys = {
@Key(name = "memberId", type = Long.class),
@Key(name = "groupId", type = Long.class)
}
)
public class MembershipsAssociation extends AssociationResourceTemplate<Membership>
{
@Override
public Membership get(CompoundKey key)
{
return lookup(key.getPartAsLong("memberId", key.getPartAsLong("groupId"));
}
}
curl http://<hostname:port>/memberships/memberId=1&groupId=10
The supported annotation parameters are:
name
- required, defines the name of the resource.namespace
- optional, defines the namespace for the resource.
Default is empty (root namespace). The namespace of the resource
appears in the IDL, and is used as the package name for the
generated client builders.parent
- optional, defines the parent resource for this resource.
Default is root.assocKeys
- required, defines the list of keys for the association
resource. Each key must declare its name and type.Classes annotated with @RestLiAssociation
must implement the
AssociationResource
interface. The AssociationResource
interface
requires a single generic type parameter:
V
, which is the value type for the resource, a.k.a., the entity
type.The value type for an association resource must be a subclass of
RecordTemplate
generated from a .pdl
schema.
Note that for association resources, they key type is always
CompoundKey
, with key parts as defined in the assocKeys
parameter of
the class’ annotation.
For convenience, Association resources may extend
AssociationResourceTemplate
rather than directly implementing the
AssociationResource
interface.
The @RestLiActions
annotation is applied to classes to mark them as
providing a Rest.li action set resource. Action set resources do not
model any resource pattern. They simply group together a set of custom
actions.
For example:
@RestLiActions(name = "simpleActions", namespace = "com.example")
public class SimpleActionsResource {
@Action(name="echo")
public String echo(@ActionParam("input") String input)
{
return input;
}
}
The supported annotation parameters are:
name
- required, defines the name of the resource.namespace
- optional, defines the namespace for the resource.
Default is empty (root namespace).Action set resources do not have a key or value type, and do not need to
implement
any framework interfaces.
Resource methods are operations a resource can perform. Rest.li defines a standard set of resource methods, each with its own interface pattern and intended semantics.
The set of possible resource methods is constrained by the resource type, as described in the table below:
Resource Type | Collection | Simple | Association | Action Set |
---|---|---|---|---|
GET | x | x | x | |
BATCH_GET / GET_ALL | x | x | ||
FINDER | x | x | ||
BATCH_FINDER | x | x | ||
CREATE / BATCH_CREATE | x | |||
UPDATE / PARTIAL_UPDATE | x | x | x | |
BATCH_UPDATE \ BATCH_PARTIAL_UPDATE | x | x | ||
DELETE | x | x | x | |
BATCH_DELETE | x | x | ||
ACTION | x | x | x | x |
In the section below, K
is used to denote the resource’s key type, and
V
is used to denote the resource’s value type. Remember that for
association resources, K
is always CompoundKey
.
The GET resource method is intended to retrieve a single entity representation based upon its key or without a key from a simple resource. GET should not have any visible side effects. For example, it should be safe to call whenever the client wishes.
Resources providing the GET resource method must override one of the following method signatures.
For collection and association resources:
public V get(K key);
For simple resources:
public V get();
For asynchronous resources:
public Task<V> get(K key);
Get methods can also be annotated if not overriding a base class method. GET supports a method signature with a wrapper return type.
For collection and association resources:
@RestMethod.Get
public GetResult<V> getWithStatus(K key);
For simple resources:
@RestMethod.Get
public GetResult<V> getWithStatus();
For asynchronous resources:
@RestMethod.Get
public Task<GetResult<V>> getWithStatus(K key);
An annotated get method may also have arbitrary query params added:
@RestMethod.Get
public GetResult<V> get(K key, @QueryParam("viewerId") String viewerId);
The return type GetResult<V>
allows users to set an arbitrary HTTP
status code for the response. For more information about the
RestMethod.Get
annotation, see Free-Form
Resources.
The BATCH_GET resource method retrieves multiple entity representations given their keys. BATCH_GET should not have any visible side effects. For example, it should be safe to call whenever the client wishes. However, this is not something enforced by the framework, and it is up to the application developer that there are no side effects.
Resources providing the BATCH_GET resource method must override the following method signature:
public Map<K, V> batchGet(Set<K> ids);
An asynchronous method must override the following method signature:
public Task<Map<K, V>> batchGet(Set<K> ids);
@RestMethod.BatchGet
may be used to indicate a batch get method
instead of overriding the batchGet method of a base class.
Resources may also return BatchResult
, which allows errors to be
returned along with entities that were successfully retrieved.
Example of a batch get:
public BatchResult<Long, Greeting> batchGet(Set<Long> ids)
{
Map<Long, Greeting> batch = new HashMap<Long, Greeting>();
Map<Long, RestLiServiceException> errors = new HashMap<Long, RestLiServiceException>();
for (long id : ids)
{
Greeting g = _db.get(id);
if (g != null)
{
batch.put(id, g);
}
else
{
errors.put(id, new RestLiServiceException(HttpStatus.S_404_NOT_FOUND));
}
}
return new BatchResult<Long, Greeting>(batch, errors);
}
Clients should make requests to a batch resource using buildKV()
(not
build()
, it is deprecated), for example:
new FortunesBuilders().batchGet().ids(...).buildKV();
The batch size of requests for a BATCH_GET method can be limited and validated by configuring a max batch size.
When a GET is requested on a collection or association resource with no key provided (for example, /myResource), the GET_ALL resource method is invoked, if present. The GET_ALL resource method retrieves all entities for the collection and supports the same pagination facilities as a FINDER.
public List<V> getAll(@Context PagingContext pagingContext);
An asynchronous resource would implement the following method signature:
public Task<List<V>> getAll(@Context PagingContext pagingContext);
@RestMethod.GetAll
may be used to indicate a get all method instead of
overriding the getAll method of a base class.
To directly control the total and metadata returned by a get all method,
do not override getAll. Instead, create a new method with the
@RestMethod.GetAll
annotation and return a CollectionResult
rather
than a list, for example:
@RestMethod.GetAll
public CollectionResult<Widgets, WidgetsMetadata> getAllWidgets(@Context PagingContext pagingContext)
{
// ...
return new CollectionResult<Widgets, WidgetsMetadata>(pageOfWidgets, total, metadata);
}
When returning a CollectionResult from GetAll, the behavior is identical to a FINDER. See the FINDER documentation below for additional details about CollectionResult.
FINDER methods model query operations. For example, they retrieve an ordered list of 0 or more entities based on criteria specified in the query parameters. Finder results will automatically be paginated by the Rest.li framework. Like GET methods, FINDER methods should not have side effects.
Resources may provide zero or more FINDER resource methods. Each finder
method must be annotated with the @Finder
annotation.
Pagination default to start=0 and count=10. Clients may set both of these parameters to any desired value.
The @Finder
annotation takes a single required parameter, which
indicates the name of the finder method.
For example:
/*
You can access this FINDER method via
/resources/order?q=findOrder&buyerType=1&buyerId=309&orderId=1208210101
*/
@RestLiCollection(name="order",keyName="orderId")
public class OrderResource extends CollectionResourceTemplate<Integer,Order>
{
@Finder("findOrder")
public List<Order> findOrder(@Context PagingContext context,
@QueryParam("buyerId") Integer buyerId,
@QueryParam("buyerType") Integer buyerType,
@QueryParam("orderId") Integer orderId)
throws InternalException
{
...
}
}
Finder methods must return either:
List<V>
CollectionResult<V, MetaData>
BasicCollectionResult<V>
, a subclass of CollectionResult
Every parameter of a finder method must be annotated with one of:
@Context
- indicates that the parameter provides framework context
to the method. Currently all @Context
parameters must be of type
PagingContext
.@QueryParam
- indicates that the value of the parameter is
obtained from a request query parameter. The value of the annotation
indicates the name of the query parameter. Duplicate names are not
allowed for the same finder method.@AssocKey
- indicates that the value of the parameter is a partial
association key, obtained from the request. The value of the
annotation indicates the name of the association key, which must
match the name of an @Key
provided in the assocKeys
field of the
@RestLiAssociation
annotation.Parameters marked with @QueryParam
, @ActionParam
, and @AssocKey
may also be annotated with @Optional
, which indicates that the
parameter is not required. The @Optional
annotation may specify a
String value, indicating the default value to be used if the parameter
is not provided in the request. If the method parameter is of primitive
type, a default value must be specified in the @Optional
annotation.
Valid types for query parameters are:
String
boolean
/ Boolean
int
/ Integer
long
/ Long
float
/ Float
double
/ Double
ByteString
.pdl
schema)RecordTemplate
generated
from a .pdl
schema)String[]
, long[]
, …@Finder("simpleFinder")
public List<V> simpleFind(`Context PagingContext context);
@Finder("complexFinder")
public CollectionResult<V, MyMetaData> complexFinder(@Context(defaultStart= 10, defaultCount = 100) PagingContext context,
@AssocKey("key1") Long key,
@QueryParam("param1") String requiredParam,
@QueryParam("param2") @Optional String optionalParam);
A Finder method in an asynchronous resource could implement the following method signatures:
@Finder("simpleAsyncFinder")
public Task<List<V>> simpleFind(`Context PagingContext context);
@Finder("complexAsyncFinder")
public Task<CollectionResult<V, MyMetaData>> complexFinder(@Context(defaultStart= 10, defaultCount = 100) PagingContext context,
@AssocKey("key1") Long key,
@QueryParam("param1") String requiredParam,
@QueryParam("param2") @Optional String optionalParam);
Custom types can be any Java type, as long as it has a coercer and a typeref schema, even Java classes from libraries such as Date. To create a query parameter that uses a custom type, you will need to write a coercer and a typeref schema for the type you want to use. See the typeref documentation for details.
First for the coercer, you will need to write an implementation of DirectCoercer that converts between your custom type and some simpler underlying type, like String or Double. By convention, the coercer should be an internal class of the custom type it coerces. Additionally, the custom type should register its own coercer in a static code block.
If this is not possible (for example, if you want to use a Java built-in class like Date or URI as a custom type), then you can write a separate coercer class and register the coercer with the private variable declaration:
private static final Object REGISTER_COERCER = Custom.registerCoercer(new ObjectCoercer(), CustomObject.class);
Typeref Schema
The purpose of the typeref schemas is to keep track of the underlying type of the custom Type and the location of the custom type’s class, and, if necessary, the location of its coercer. The basic appearance of the typeref schema is shown below:
namespace com.linkedin.example // namespace of the typeref
@java.class = "com.linkedin.example.CustomObject" // location of the custom type class
@java.coercerClass = "com.linkedin.example.CustomObjectCoercer" // only needed if the custom type itself cannot contain the coercer as an internal class.
typeref CustomObjectRef = string // underlying type that the coercer converts to/from
This typeref can then be referenced in other schemas:
import com.linkedin.example.CustomObjectRef
record ExampleRecord {
member: CustomObjectRef
}
The generated Java data templates will automatically coerce from CustomObjectRef to CustomObject when accessing the member field:
CustomObject o = exampleRecord.getMember();
Once Java data templates are generated, the typeref may also be used in Keys, query parameters, or action parameters.
Keys:
@RestLiCollection(name="entities",
namespace = "com.example",
keyTyperefClass = CustomObjectRef.class)
public class EntitiesResource extends CollectionResourceTemplate<CustomObject, Urn>
Compound keys:
@RestLiAssociation(name="entities",
namespace="com.example",
assocKeys={@Key(name="o", type=CustomObject.class, typeref=CustomObjectRef.class)})
Query parameters:
@QueryParam(value="o", typeref=CustomObjectRef.class) CustomObject o
@QueryParam(value="oArray", typeref=CustomObjectRef.class) CustomObject[] oArray
The BATCH_FINDER resource method accepts a list of filters set. Instead of callings multiple finders with different filter values, we call 1 BATCH_FINDER method with a list of filters.
Resources may provide zero or more BATCH_FINDER resource methods. Each BATCH_FINDER method must be annotated with the @BatchFinder
annotation.
And this method must return a BatchFinderResult
.
For example:
@BatchFinder(value = "searchGreetings", batchParam = "criteria")
public BatchFinderResult<GreetingCriteria, Greeting, EmptyRecord> searchGreetings(@PagingContextParam PagingContext context,
@QueryParam("criteria") GreetingCriteria[] criteria,
@QueryParam("message") String message)
An asynchronous BATCH_FINDER must return a Task<BatchFinderResult>
. For example:
@BatchFinder(value = "searchGreetings", batchParam = "criteria")
public Task<BatchFinderResult<GreetingCriteria, Greeting, EmptyRecord>> searchGreetings(@PagingContextParam PagingContext context,
@QueryParam("criteria") GreetingCriteria[] criteria,
@QueryParam("message") String message)
Please note that “q” cannot be used as QueryParam name for batch finder, because that will produce ambiguation when construcitng URL.
See more details about BATCH_FINDER resource method api here: BatchFinder Resource API
The batch size of requests for a BATCH_FINDER method can be limited and validated by configuring a max batch size.
CREATE methods model the creation of new entities from their representation. In CREATE, the resource implementation is responsible for assigning a new key to the created entity. CREATE methods are neither safe nor idempotent.
Resources providing the CREATE resource method must override the following method signature:
public CreateResponse create(V entity);
An asynchronous method would override the following method signature:
public Task<CreateResponse> create(V entity);
The returned CreateResponse
object indicates the HTTP status code to
be returned (defaults to 201 CREATED), as well as an optional ID for the
newly created entity. If provided, the ID will be written into the
“X-LinkedIn-Id” header by calling toString()
on the ID object.
@RestMethod.Create
may be used to indicate a create method instead of
overriding the create method of a base class.
By default, the newly created entity is not returned in the CREATE response because the client already has the entity when sending the CREATE request. However, there are use cases where the server will attach additional data to the new entity. Returning the entity in the CREATE response saves the client another GET request.
Starting in Rest.li version 2.10.3, we provide the developer the option to
return the newly created entity. To use this feature, add a @ReturnEntity
annotation to the method that implements CREATE. The return type of the
method must be CreateKVResponse
.
@ReturnEntity
public CreateKVResponse create(V entity);
An example implementation for resource is like below, note that the return type will be CreateKVResponse
:
@ReturnEntity
public CreateKVResponse<Long, Greeting> create(Greeting entity)
{
Long id = 1L;
entity.setId(id);
return new CreateKVResponse<Long, Greeting>(entity.getId(), entity);
}
There may be circumstances in which you want to prevent the server from returning the entity, for example to reduce network traffic.
Here is an example curl request that makes use of the $returnEntity
query parameter to indicate that the entity should not be returned:
curl -X POST 'localhost:/greetings?$returnEntity=false' \
-H 'X-RestLi-Method: CREATE' \
-d '{"message": "Hello, world!", "tone": "FRIENDLY"}'
BATCH_CREATE methods model the creation of a group of new entities from their representations. In BATCH_CREATE, the resource implementation is responsible for assigning a new key to each created entity. BATCH_CREATE methods are neither safe nor idempotent.
Resources providing the BATCH_CREATE resource method must override the following method signature:
public BatchCreateResult<K, V> batchCreate(BatchCreateRequest<K, V> entities);
An asynchronous resource providing the BATCH_CREATE resource method must override the following method signature:
public Task<BatchCreateResult<K, V>> batchCreate(BatchCreateRequest<K, V> entities);
The BatchCreateRequest
object wraps a list of entity representations
of type V
.
The returned BatchCreateResult
object wraps a list of CreateResponse
objects (see CREATE). The CreateResponse
objects are expected to be
returned in the same order and position as the respective input objects.
BatchCreateRequest
and BatchCreateResult
support the generic type
parameter K
to allow for future extension.
@RestMethod.BatchCreate
may be used to indicate a batch create method
instead of overriding the batchCreate method of a base class.
Example of a batch create:
public BatchCreateResult<Long, Greeting> batchCreate(BatchCreateRequest<Long, Greeting> entities)
{
List<CreateResponse> responses = new ArrayList<CreateResponse>(entities.getInput().size());
for (Greeting g : entities.getInput())
{
responses.add(create(g));
}
return new BatchCreateResult<Long, Greeting>(responses);
}
public CreateResponse create(Greeting entity)
{
entity.setId(_idSeq.incrementAndGet());
_db.put(entity.getId(), entity);
return new CreateResponse(entity.getId());
}
Error details can be returned in any CreateResponse by providing a RestLiServiceException, for example:
public BatchCreateResult<Long, Greeting> batchCreate(BatchCreateRequest<Long, Greeting> entities)
{
List<CreateResponse> responses = new ArrayList<CreateResponse>(entities.getInput().size());
...
if (...)
{
RestLiServiceException exception = new RestLiServiceException(HttpStatus.S_406_NOT_ACCEPTABLE, "...");
exception.setServiceErrorCode(...);
exception.setErrorDetails(...);
responses.add(new CreateResponse(exception));
}
...
return new BatchCreateResult<Long, Greeting>(responses);
}
Similar to CREATE, BATCH_CREATE also could return the newly created
entities in the response. To do that, add a @ReturnEntity
annotation to
the method implementing BATCH_CREATE. The return type of the method
must be BatchCreateKVResult
.
@ReturnEntity
public BatchCreateKVResult<K, V> batchCreate(BatchCreateRequest<K, V> entities);
An example implementation for resource is like below, note that the
return type will be BatchCreateKVResult
:
@ReturnEntity
public BatchCreateKVResult<Long, Greeting> batchCreate(BatchCreateRequest<Long, Greeting> entities)
{
List<CreateKVResponse<Long, Greeting>> responses = new ArrayList<CreateKVResponse<Long, Greeting>>(entities.getInput().size());
for (Greeting greeting : entities.getInput())
{
responses.add(create(greeting)); // Create function should return CreateKVResponse
}
return BatchCreateKVResult<Long, Greeting>(responses);
}
There may be circumstances in which you want to prevent the server from returning the entity, for example to reduce network traffic.
Here is an example curl request that makes use of the $returnEntity
query parameter to indicate that the entity should not be returned:
curl -X POST 'localhost:/greetings?$returnEntity=false' \
-H 'X-RestLi-Method: BATCH_CREATE' \
-d '{"elements":[{"message": "Hello, world!", "tone": "FRIENDLY"},{"message": "Again!", "tone": "FRIENDLY"}]}'
The batch size of requests for a BATCH_CREATE method can be limited and validated by configuring a max batch size.
UPDATE methods model updating an entity with a given key by setting its value (overwriting the entire entity). UPDATE has side effects but is idempotent. For example, repeating the same update operation has the same effect as calling it once.
Resources may choose whether to allow an UPDATE of an entity that does not already exist, in which case it should be created. This is different from CREATE because the client specifies the key for the entity to be created. Simple resources use UPDATE as a way to create the singleton entity.
Resources providing the UPDATE resource method must override one of the following method signatures.
For collection and association resources:
public UpdateResponse update(K key, V entity);
For simple resources:
public UpdateResponse update(V entity);
For asynchronous resources:
public Task<UpdateResponse> update(K key, V entity);
The returned UpdateResponse
object indicates the HTTP status code to
be returned.
@RestMethod.Update
may be used to indicate a update method instead of
overriding the update method of a base class.
BATCH_UPDATE methods model updating a set of entities with specified keys by setting their values (overwriting each entity entirely). BATCH_UPDATE has side effects but is idempotent. For example, repeating the same batch update operation has the same effect as calling it once.
Resources may choose whether to allow BATCH_UPDATE for entities that do not already exist, in which case each entity should be created. This is different from BATCH_CREATE because the client specifies the keys for the entities to be created.
Resources providing the BATCH_UPDATE resource method must override the following method signature:
public BatchUpdateResult<K, V> batchUpdate(BatchUpdateRequest<K, V> entities);
An asynchronous resource must override the following method signature:
public Task<BatchUpdateResult<K, V>> batchUpdate(BatchUpdateRequest<K, V> entities);
BatchUpdateRequest
contains a map of entity key to entity value.
The returned BatchUpdateResult
object indicates the UpdateResponse
for each key in the BatchUpdateRequest
. In the case of failures,
RestLiServiceException
objects may be added to the BatchUpdateResult
for the failed keys.
@RestMethod.BatchUpdate
may be used to indicate a batch update method
instead of overriding the batchUpdate method of a base class.
Example of a batch update:
public BatchUpdateResult<Long, Greeting> batchUpdate(BatchUpdateRequest<Long, Greeting> entities)
{
Map<Long, UpdateResponse> responseMap = new HashMap<Long, UpdateResponse>();
for (Map.Entry<Long, Greeting> entry : entities.getData().entrySet())
{
responseMap.put(entry.getKey(), update(entry.getKey(),
entry.getValue()));
}
return new BatchUpdateResult<Long, Greeting>(responseMap);
}
public UpdateResponse update(Long key, Greeting entity)
{
Greeting g = _db.get(key);
if (g == null)
{
return new UpdateResponse(HttpStatus.S_404_NOT_FOUND);
}
_db.put(key, entity);
return new UpdateResponse(HttpStatus.S_204_NO_CONTENT);
}
The batch size of requests for a BATCH_UPDATE method can be limited and validated by configuring a max batch size.
PARTIAL_UPDATE methods model updating part of the entity with a given key. PARTIAL_UPDATE has side effects. In general, it is not guaranteed to be idempotent.
Resources providing the PARTIAL_UPDATE resource method must override the following method signature:
public UpdateResponse update(K key, PatchRequest<V> patch);
An asynchronous resource must override the following method signature:
public Task<UpdateResponse> update(K key, PatchRequest<V> patch);
The returned UpdateResponse
object indicates the HTTP status code to
be returned.
Rest.li provides tools to make it easy to handle partial updates to your resources. A typical update function should look something like this:
@Override
public UpdateResponse update(String key, PatchRequest<YourResource> patch)
{
YourResource resource = _db.get(key); // Retrieve the resource object
from somewhere
if (resource == null)
{
return new UpdateResponse(HttpStatus.S_404_NOT_FOUND);
}
try
{
PatchApplier.applyPatch(resource, patch); // Apply the patch.
// Be sure to save the resource if necessary
}
catch (DataProcessingException e)
{
return new UpdateResponse(HttpStatus.S_400_BAD_REQUEST);
}
return new UpdateResponse(HttpStatus.S_204_NO_CONTENT);
}
The PatchApplier automatically updates resources defined using the Pegasus Data format. The Rest.li client classes provide support for constructing patch requests, but here is an example update request using curl:
curl -X POST 'localhost:/fortunes/1' \
-d '{"patch": {"$set": {"fortune": "you will strike it rich!"}}}'
@RestMethod.PartialUpdate
may be used to indicate a partial update
method instead of overriding the partialUpdate method of a base class.
It is possible to inspect the partial update and selectively write only the changed fields to a store.
For example, to update only the street field of this address entity:
{
"address": {
"street": "10th",
"city": "Sunnyvale"
}
}
The partial update to change just the street field is:
{
"patch": {
"address": {
"$set": {
"street": "9th"
}
}
}
}
For the service code to selectively update just the street field (e.g., UPDATE addresses SET street=:street WHERE key=:key). The partial update can be inspected and the selective update if only the street field is changed:
@Override
public UpdateResponse update(String key, PatchRequest<YourResource> patchRequest)
{
try
{
DataMap patch = patchRequest.getPatchDocument();
boolean selectivePartialUpdateApplied = false;
if(patch.containsKey("address") && patch.size() >= 1)
{
DataMap address = patch.getDataMap("address");
if(address.containsKey("$set") && address.size() 1)
{
DataMap set = address.getDataMap("$set");
if(address.containsKey("street") && address.size() 1)
{
String street = address.getString("street");
selectivePartialUpdateApplied = true;
// update only the street, since its the only thing this patch requests to change
}
}
}
if(selectivePartialUpdateApplied false)
{
// no selective update available, update the whole record with
PatchApplier and return the result
}
}
catch (DataProcessingException e)
{
return new UpdateResponse(HttpStatus.S_400_BAD_REQUEST);
}
return new UpdateResponse(HttpStatus.S_204_NO_CONTENT);
}
To create a request to modify field(s), PatchGenerator can be used, for example:
Fortune fortune = new Fortune().setMessage("Today's your lucky day.");
PatchRequest<Fortune> patch = PatchGenerator.diffEmpty(fortune);
Request<Fortune> request = new FortunesBuilders().partialUpdate().id(1L).input(patch).build();
PatchGenerator.diff(original, revised)
can also be used to create a
minimal partial update.
By default, the patched entity is not returned in the PARTIAL_UPDATE response because the client already has the patch data and possibly has the rest of the entity as well. However, there are use cases where the server will attach additional data to the new entity or the user simply doesn’t have the whole entity. Returning the entity in the PARTIAL_UPDATE response saves the client another GET request.
Starting in Rest.li version 24.0.0, we provide the developer the option to
return the patched entity. To use this feature, add a @ReturnEntity
annotation to the method that implements PARTIAL_UPDATE. The return type of the
method must be UpdateEntityResponse
.
@ReturnEntity
@RestMethod.PartialUpdate
public UpdateEntityResponse<V> partialUpdate(K key, PatchRequest<V> patch);
An example resource method implementation is as follows, note that the return type will be UpdateEntityResponse
:
@ReturnEntity
@RestMethod.PartialUpdate
public UpdateEntityResponse<Greeting> update(Long key, PatchRequest<Greeting> patch)
{
Greeting greeting = _db.get(key);
if (greeting == null)
{
throw new RestLiServiceException(HttpStatus.S_404_NOT_FOUND);
}
try
{
PatchApplier.applyPatch(greeting, patch);
}
catch (DataProcessingException e)
{
throw new RestLiServiceException(HttpStatus.S_400_BAD_REQUEST);
}
return new UpdateEntityResponse<Greeting>(HttpStatus.S_200_OK, greeting);
}
There may be circumstances in which you want to prevent the server from returning the entity, for example to reduce network traffic.
Here is an example curl request that makes use of the $returnEntity
query parameter to indicate that the entity should not be returned:
curl -X POST 'localhost:/greetings/1?$returnEntity=false' \
-d '{"patch": {"$set": {"message": "Hello, world!"}}}'
BATCH_PARTIAL_UPDATE methods model partial updates of multiple entities given their keys. BATCH_PARTIAL_UPDATE has side effects. In general, it is not guaranteed to be idempotent.
Resources providing the BATCH_PARTIAL_UPDATE resource method must override the following method signature:
public BatchUpdateResult<K, V> batchUpdate(BatchPatchRequest<K, V> patches);
An asynchronous resource providing the BATCH_PARTIAL_UPDATE resource method must override the following method signature:
public Task<BatchUpdateResult<K, V>> batchUpdate(BatchPatchRequest<K, V> patches);
The BatchPatchRequest
input contains a map of entity key to
PatchRequest
.
The returned BatchUpdateResult
object indicates the UpdateResponse
for each key in the BatchPatchRequest
. In the case of failures,
RestLiServiceException
objects may be added to the BatchUpdateResult
for the failed keys.
@RestMethod.BatchPartialUpdate
may be used to indicate a batch partial
update method instead of overriding the batchPartialUpdate method of a
base class.
Example of a batch partial update:
public BatchUpdateResult<Long, Greeting> batchUpdate(BatchPatchRequest<Long, Greeting> entityUpdates)
{
Map<Long, UpdateResponse> responseMap = new HashMap<Long,
UpdateResponse>();
for (Map.Entry<Long, PatchRequest<Greeting>> entry :
entityUpdates.getData().entrySet())
{
responseMap.put(entry.getKey(), update(entry.getKey(),
entry.getValue()));
}
return new BatchUpdateResult<Long, Greeting>(responseMap);
}
public UpdateResponse update(Long key, PatchRequest<Greeting> patch)
{
Greeting g = _db.get(key);
if (g == null)
{
return new UpdateResponse(HttpStatus.S_404_NOT_FOUND);
}
try
{
PatchApplier.applyPatch(g, patch);
}
catch (DataProcessingException e)
{
return new UpdateResponse(HttpStatus.S_400_BAD_REQUEST);
}
_db.put(key, g);
return new UpdateResponse(HttpStatus.S_204_NO_CONTENT);
}
By default, the patched entities are not returned in the BATCH_PARTIAL_UPDATE response because the client already has the patch data and possibly has the rest of the entities as well. However, there are use cases where the server will attach additional data to the new entities or the user simply doesn’t have the whole entities. Returning the entities in the BATCH_PARTIAL_UPDATE response saves the client another GET request.
Starting in Rest.li version 25.0.5, we provide the developer the option to
return the patched entities. To use this feature, add a @ReturnEntity
annotation to the method that implements BATCH_PARTIAL_UPDATE. The return type of the
method must be BatchUpdateEntityResult
.
@ReturnEntity
@RestMethod.BatchPartialUpdate
public BatchUpdateEntityResult<K, V> batchPartialUpdate(BatchPatchRequest<Long, Greeting> patches);
An example resource method implementation is as follows, note that the return type will be BatchUpdateEntityResult
:
@ReturnEntity
@RestMethod.BatchPartialUpdate
public BatchUpdateEntityResult<Long, Greeting> batchPartialUpdate(BatchPatchRequest<Long, Greeting> patches)
{
Map<Long, UpdateEntityResponse<Greeting>> responseMap = new HashMap<>();
Map<Long, RestLiServiceException> errorMap = new HashMap<>();
for (Map.Entry<Long, PatchRequest<Greeting>> entry : patches.getData().entrySet())
{
try
{
UpdateEntityResponse<Greeting> updateEntityResponse = partialUpdate(entry.getKey(), entry.getValue());
responseMap.put(entry.getKey(), updateEntityResponse);
}
catch (RestLiServiceException e)
{
errorMap.put(entry.getKey(), e);
}
}
return new BatchUpdateEntityResult<>(responseMap, errorMap);
}
There may be circumstances in which you want to prevent the server from returning the entities, for example to reduce network traffic.
Here is an example curl request that makes use of the $returnEntity
query parameter to indicate that the entity should not be returned:
curl -X POST 'localhost:/greetings?ids=List(1)&$returnEntity=false' \
-d '{"entities":{"1":{"patch": {"$set": {"message": "Hello, world!"}}}}}' \
-H 'X-RestLi-Method: BATCH_PARTIAL_UPDATE' \
-H 'X-RestLi-Protocol-Version: 2.0.0'
The batch size of requests for a BATCH_PARTIAL_UPDATE method can be limited and validated by configuring a max batch size.
DELETE methods model deleting (removing) an entity with a given key on collection and association resources or without a key on simple resources. DELETE has side effects but is idempotent.
Resources providing the DELETE resource method must override one of the following method signatures.
For collection and association resources:
public UpdateResponse delete(K key);
For simple resources:
public UpdateResponse delete();
For asynchronous resources:
public Task<UpdateResponse> delete(K key);
The returned UpdateResponse
object indicates the HTTP status code to
be returned.
@RestMethod.Delete
may be used to indicate a delete method instead of
overriding the delete method of a base class.
BATCH_DELETE methods model deleting (removing) multiple entities given their keys. BATCH_DELETE has side effects but is idempotent.
Resources providing the BATCH_DELETE resource method must override the following method signature:
public BatchUpdateResult<K, V> batchDelete(BatchDeleteRequest<K, V> ids);
Asynchronous resources providing the BATCH_DELETE resource method must override the following method signature:
public Task<BatchUpdateResult<K, V>> batchDelete(BatchDeleteRequest<K, V> ids);
The BatchDeleteRequest
input contains the list of keys to be deleted.
BatchDeleteRequest
accepts a generic type parameter V
for future
extension.
The returned BatchUpdateResult
object indicates the UpdateResponse
for each key in the BatchDeleteRequest
. In the case of failures,
RestLiServiceException
objects may be added to the BatchUpdateResult
for the failed keys.
@RestMethod.BatchDelete
may be used to indicate a batch delete method
instead of overriding the batchDelete method of a base class.
Example of a batch delete:
public BatchUpdateResult<Long, Greeting> batchDelete(BatchDeleteRequest<Long, Greeting> deleteRequest)
{
Map<Long, UpdateResponse> responseMap = new HashMap<Long, UpdateResponse>();
for (Long id : deleteRequest.getKeys())
{
responseMap.put(id, delete(id));
}
return new BatchUpdateResult<Long, Greeting>(responseMap);
}
public UpdateResponse delete(Long key)
{
boolean removed = _db.remove(key) != null;
return new UpdateResponse(removed ? HttpStatus.S_204_NO_CONTENT : HttpStatus.S_404_NOT_FOUND);
}
The batch size of requests for a BATCH_DELETE method can be limited and validated by configuring a max batch size.
ACTION methods are very flexible and do not specify any standard behavior.
Resources may provide zero or more ACTION resource methods. Each action
must be annotated with the @Action
annotation.
The @Action
annotation supports the following parameters:
name
Required, the name of the action resource method.resourceLevel
Optional, defaults to ResourceLevel.ANY
, which
indicates that the action is defined directly on the containing
resource and does not support an entity key as a URI parameter.
ResourceLevel.COLLECTION
indicates that the action is defined on
the containing association or collection resource and does not
support an entity key as a URI parameter. ResourceLevel.ENTITY
indicates that the action is defined on the entity and it requires
an entity key as a URI parameter when the containing resource is an
association or collection resource. If the containing resource is a
simple resource ResourceLevel.ENTITY
indicates that the action is
defined directly on the resource and does not support an entity key
as a URI parameter.returnTyperef
Optional, defaults to no typeref. Indicates a
Typeref to be used in the IDL for the action’s return parameter.
Useful for actions that return primitive types.Each parameter to an action method must be annotated with
@ActionParam
, which takes the following annotation parameters:
value
Required, string name for the action parameter. If this is
the only annotation, parameter, it may be specified without being
explicitly named, for example, @ActionParam("paramName")
.typeref
Optional, Typeref to be used in the IDL for the parameter.Parameters of action methods may also be annotated with @Optional
,
which indicates that the parameter is not required in the request. The
@Optional
annotation may specify a String value, which specifies the
default value to be used if the parameter is not provided in the
request. If the method parameter is of primitive type, a default value
must be specified in the @Optional
annotation.
Valid parameter types and return types for action are:
String
boolean
/ Boolean
int
/ Integer
long
/ Long
float
/ Float
double
/ Double
ByteString
.pdl
schema)RecordTemplate
or a subclass of RecordTemplate
generated from a
record schemaFixedTemplate
or a subclass of FixedTemplate
generated from a
fixed schemaAbstractArrayTemplate
or a subclass of AbstractArrayTemplate
,
for example, StringArray
, LongArray
, and so on.AbstractMapTemplate
or a subclass of AbstractMapTemplate
, for
example, StringMap
, LongMap
, and so on.Similar to GetResult<V>
, since 1.5.8, Rest.li supports an
ActionResult<V>
wrapper return type that allows you to specify an
arbitrary HTTP status code for the response.
Simple example:
@Action(name="action")
public void doAction();
A more complex example, illustrating multiple parameters: (Note this action is defined on the entity level, and the entity Key is accessed from the conxt)
@Action(name="sendTestAnnouncement",resourceLevel= ResourceLevel.ENTITY)
public void sendTestAnnouncement(@ActionParam("subject") String subject,
@ActionParam("message") String message,
@ActionParam("emailAddress") String emailAddress)
{
Long entityKey = getContext().getPathKeys().get(_resourceName + "Id");
// ...
}
@ActionParam
and @QueryParam
are used in different methods.
@ActionParam
is only allowed in Action methods, while @QueryParam
is
allowed in all non-Action methods. Besides, they are also different in
terms of how the parameter data is sent to the server. If a parameter is
annotated with @QueryParam
, the information will be sent in the
request url. If a parameter is annotated with @ActionParam
, the
information will be sent in the request body. Therefore, one advantage
of using @ActionParam
would be that the sent parameter can be encoded.
One disadvantage is that the purpose of the request itself can become
less clear if one only examines the url.
Resource methods should never explicitly return null
. If the Rest.li
framework detects this, it will return an HTTP 500
back to the client
with a message indicating ‘Unexpected null encountered’. The only
exceptions to this rule are ACTION and GET. If an ACTION resource method
returns null
, the rest.li framework will return an HTTP 200
. If a
GET returns null
, the Rest.li framework will return an HTTP 404
.
Also note that the HTTP 500
will also be generated by the Rest.li
framework if subsequent data structures inside of resource method
responses are null or contain null. This applies to any data structure
that is not a RecordTemplate. For example, all of the the following
would cause an HTTP 500
to be returned. Note this list is not
exhaustive:
BatchCreateResult
returning a null
results list.BatchCreateResult
returning a valid list that as a null
element inside of it.CreateResponse
returning a null
for the HttpStatus
.BatchUpdateResult
returning a null
key in the results map.BatchUpdateResult
returning a null
errors map.BatchUpdateResult
returning a valid errors map, but with a
null
key or null
value inside of it.It is good practice to make sure that null
is never returned in any
part of resource method responses, with the exception of RecordTemplate
classes, ACTION methods and GET methods.
ResourceContext
provides access to the context of the current request.
ResourceContext
is injected into resources that implement the
BaseResource
interface, by calling setContext()
.
For resources extending CollectionResourceTemplate
,
AssociationResourceTemplate
, or ResourceContextHolder
, the current
context is available by calling getContext()
.
ResourceContext
provides methods to access the raw request, as well as
parsed values from the request. ResourceContext
also provides some
control over the generated response, such as the ability to set response
headers.
Resource Templates provide convenient methods for implementing resource classes by extending them. Subclasses may selectively override relevant methods and for methods that are not overridden, the framework will recognize that your resource does not support this method and will return a 404 if clients attempt to invoke it. Note that unsupported methods will be omitted from your resources IDL (see Restspec IDL for details).
CollectionResourceTemplate
provides a convenient base class for
collection resources. CollectionResourceTemplate
defines methods for
all of the CRUD operations. Subclasses may also implement FINDER, BATCH_FINDER and
ACTION methods by annotating as described above. The asynchronous collection resource templates are CollectionResourceTaskTemplate
and CollectionResourceAsyncTemplate
.
public CreateResponse create(V entity);
public BatchCreateResult<K, V> batchCreate(BatchCreateRequest<K, V> entities);
public V get(K key);
public Map<K, V>batchGet(Set<K> ids);
public UpdateResponse update(K key, V entity);
public BatchUpdateResult<K, V> batchUpdate(BatchUpdateRequest<K, V> entities);
public UpdateResponse update(K key, PatchRequest<V> patch);
public BatchUpdateResult<K, V> batchUpdate(BatchPatchRequest<K, V> patches);
public UpdateResponse delete(K key);
public BatchUpdateResult<K, V> batchDelete(BatchDeleteRequest<K, V> ids);
SimpleResourceTemplate
provides a convenient base class for simple
resources. SimpleResourceTemplate
defines methods for GET, UPDATE, and
DELETE methods. Subclasses may also implement ACTION methods by
annotating as described above. The asynchronous simple resource templates are SimpleResourceTaskTemplate
and SimpleResourceAsyncTemplate
.
public V get();
public UpdateResponse update(V entity);
public UpdateResponse delete();
AssociationResourceTemplate
provides a convenient base class for
association resources. AssociationResourceTemplate
defines methods for
all of the CRUD operations except CREATE. Association resources should
implement CREATE by providing up-sert semantics on UPDATE. Subclasses
may also implement FINDER, BATCH_FINDER and ACTION methods by annotating as described
above. The asynchronous association resource templates are AssociationResourceTaskTemplate
and AssociationResourceAsyncTemplate
.
public CreateResponse create(V entity);
public BatchCreateResult<CompoundKey, V>batchCreate(BatchCreateRequest<CompoundKey, V> entities);
public V get(CompoundKey key);
public Map<CompoundKey, V> batchGet(Set<CompoundKey> ids);
public UpdateResponse update(CompoundKey key, V entity);
public BatchUpdateResult<CompoundKey, V>batchUpdate(BatchUpdateRequest<CompoundKey, V> entities);
public UpdateResponse update(CompoundKey key, PatchRequest<V> patch);
public BatchUpdateResult<CompoundKey, V> batchUpdate(BatchPatchRequest<CompoundKey, V> patches);
public UpdateResponse delete(CompoundKey key);
public BatchUpdateResult<CompoundKey, V> batchDelete(BatchDeleteRequest<CompoundKey, V> ids);
Resource Templates provide a convenient way to implement the recommended signatures for the basic CRUD operations (CREATE, GET, UPDATE, PARTIAL_UPDATE, DELETE, and respective batch operations). When possible, we recommend using the resource templates to ensure that your interface remains simple and uniform.
However, it is sometimes necessary to add custom parameters to CRUD
operations. In these cases, the fixed signatures of resource templates
are too constraining. The solution is to create a free-form resource by
implementing the corresponding marker interface for your resource and
annotating CRUD methods with @RestMethod.*
annotations.The
KeyValueResource
interface is the marker interface for collection and
association resources where the SingleObjectResource
interface is the
marker interface for simple resources.
public class FreeFormCollectionResource implements KeyValueResource<K, V>
{
@RestMethod.Create
public CreateResponse myCreate(V entity);
@RestMethod.BatchCreate
public BatchCreateResult<K, V> myBatchCreate(BatchCreateRequest<K, V> entities);
@RestMethod.Get
public V myGet(K key);
@RestMethod.GetAll
public CollectionResult<V, M> myGetAll(@Context PagingContext pagingContex);
@RestMethod.BatchGet
public Map<K, V> myBatchGet(Set<K> ids);
@RestMethod.Update
public UpdateResponse myUpdate(K key, V entity);
@RestMethod.BatchUpdate
public BatchUpdateResult<K, V> myBatchUpdate(BatchUpdateRequest<K, V> entities);
@RestMethod.PartialUpdate
public UpdateResponse myUpdate(K key, PatchRequest<V> patch);
@RestMethod.BatchPartialUpdate
public BatchUpdateResult<K, V> myBatchUpdate(BatchPatchRequest<K, V> patches);
@RestMethod.Delete
public UpdateResponse myDelete(K key);
@RestMethod.BatchDelete
public BatchUpdateResult<K, V> myBatchDelete(BatchDeleteRequest<K, V> ids);
}
public class FreeFormSimpleResource implements SingleObjectResource<V>
{
@RestMethod.Get
public V myGet();
@RestMethod.Update
public UpdateResponse myUpdate(V entity);
@RestMethod.Delete
public UpdateResponse myDelete();
}
The advantage of explicitly annotating each resource method is that you
can add custom query parameters (see description of @QueryParam
for
FINDER resource method) and take advantage of wrapper return types.
Custom query parameters must be defined after the fixed parameters
shown above.
@RestMethod.Get
public V myGet(K key, @QueryParam("myParam") String myParam);
@RestMethod.Get
public GetResult<V> getWithStatus(K key);
Note that each resource may only provide one implementation of each CRUD
method (for example, it is invalid to annotate two different methods with
@RestMethod.Get
).
KeyValueResource
and
SingleObjectResource
marker interfaces.RestMethod.*
annotations.There are several mechanisms available for resources to report errors to
be returned to the caller. Regardless of which mechanism is used,
resources should be aware of the resulting HTTP status code and ensure
that meaningful status codes are used. Remember that 4xx
codes should
be used to report client errors (errors that the client may be able to
resolve), and 5xx
codes should be used to report server errors.
null
for GETIf a resource method returns null
for GET, the framework will
automatically generate a 404
response to be sent to the client.
Note that returning null
for resource methods is generally forbidden
with the exception of GET and ACTION. Returning a null
for a GET
returns a 404 and returning a null
for an ACTION returns 200.
Returning a null
for any other type of resource method will cause the
rest.li framework to return an HTTP 500
to be sent back to the client
with a message indicating ‘Unexpected null encountered’. This is
described in detail above at Returning
Nulls
CreateResponse
and UpdateResponse
allow an Http Status
Code to be
provided. Status codes in the 4xx
and 5xx
ranges may be used to
report errors.
The framework defines a special exception class,
RestLiServiceException
, which contains an Http Status
Code field, as
well as other fields that are returned to the client in the body of the
HTTP response. Resources may throw RestLiServiceException
or a
subclass to prompt the framework to return an HTTP error response.
All exceptions originating in application code are caught by the
framework and used to generate an HTTP response. If the exception does
not extend RestLiServiceException
, an HTTP 500
response will be
sent.
BATCH_GET methods may return errors for individual items as part of a
BatchResult
object. Each error is represented as a RestLiServiceException
object. In this case, the overall status will still be an HTTP 200
.
public BatchResult<K, V> batchGet((Set<K> ids)
{
Map<K, V> results = ...
Map<K, RestLiServiceException> errors = ...
...
return new BatchResult(results, errors);
}
If you want to return an error response with an overall status of 4xx
or 5xx
,
then you can do this by throwing a RestLiServiceException
in the resource method.
In this case, the response will be an error response and won’t contain any batch
information.
public BatchResult<K, V> batchGet((Set<K> ids)
{
throw new RestLiServiceException(HttpStatus.S_400_BAD_REQUEST);
}
The same logic applies to BATCH_UPDATE, BATCH_PARTIAL_UPDATE, and BATCH_DELETE.
For BATCH_FINDER method, it may return errors for individual criteria as part of a BatchFinderResult
object.
Each error is represented as a RestLiServiceException object when it cannot find a corresponding
response for that search criteria Or the developer can put the customized RestLiServiceException into the BatchFinderResult .
In this case, the overall status will still be an HTTP 200.
When making requests using RestClient
, a ResponseFuture
is always
returned, as shown in this example:
ResponseFuture<Greeting> future = restClient.sendRequest(new GreetingsBuilders.get().id(1L));
This future might contain an error response. When calling
ResponseFuture.getResponse()
, the default behavior is for a
RestLiResponseException
to be thrown if the response contains an error
response. Error responses are all 400 and 500 series HTTP status code,
as shown in this example:
try
{
Greeting greeting = restClient.sendRequest(new GreetingsBuilders.get().id(1L)).getResponseEntity();
// handle successful response
}
catch (RestLiResponseException e)
{
if(e.getStatus() == 400) {
// handle 400
} else {
// ... handle other status codes or rethrow
}
}
Alternatively, ErrorHandlingBehavior.TREAT_SERVER_ERROR_AS_SUCCESS
can
be set when making a request. If set, .getResponse()
will not
throw RestLiResponseException
even if the response contains a 400 or
500 series HTTP status code, as shown in this example:
Response<Greeting> response = restClient.sendRequest(new GreetingsBuilders.get().id(1L),
ErrorHandlingBehavior.TREAT_SERVER_ERROR_AS_SUCCESS).getResponse();
if(response.getStatus() == 200)
{
// handle successful response
}
else if (response.getStatus() == 404)
{
// handle 404
}
else
{
// ... handle other status codes or rethrow
}
However because error responses do not contain an entity, calling
ResponseFuture.getResponseEntity()
or Response.getEntity()
will
always throw a RestLiResponseException
for 400 or 500 series HTTP
status code, regardless of ErrorHandlingBehavior
.
By default, Rest.li returns an extensive HTTP error response that includes:
X-LinkedIn-Error-Response
header (this will be renamed to
X-RestLi-Error-Response
shortly)The error response format configured to return only a subset of these parts using RestLiConfig, as shown in this example:
restLiConfig.setErrorResponseFormat(ErrorResponseFormat.MESSAGE_AND_DETAILS);
When Rest.li server application code throws an exception, if the exception is of type RestLiServiceException, then the error message provided by the RestLiServiceException is used for the error message in the HTTP response. However if any other Java exception is thrown, Rest.li automatically provides a default error message of “Error in application code” in the error response. This default error message may be customized via RestLiConfig as well, as shown in this example:
restLiConfig.setInternalErrorMessage("Internal error, please try again later.");
See this page for more information on error responses in Rest.li.
Rest.li provides built-in support for field projections, for example the structural filtering of responses. The support includes Java Projection Bindings and a JSON Projection wire protocol. The projection is applied separately to each entity object in the response (i.e., to the value-type of the CollectionResource or AssociationResource). If the invoked method is a FINDER that returns a List, the projection is applied to each element of the list individually. Likewise, if the invoked method is a BATCH_GET that returns a Map<K, V>, the projection is applied to each value in the map individually. Project can also be applied to CREATE and BATCH_CREATE when the newly returned entity or entities are returned.
For resource methods that return CollectionResult, the Rest.li framework also provides the ability to project the Metadata and as well as the Paging that is sent back to the client. More info on Collection Pagination is provided below.
The Rest.li server framework recognizes the “fields”, “metadataFields”,
or “pagingFields” query parameters in the request. If available, the
Rest.li framework then parses each of these as individual MaskTrees
.
The resulting MaskTrees
are available through the ResourceContext (see
above) or directly to the resource methods.
Projection can also be toggled between AUTOMATIC
and MANUAL
. The
latter precludes the Rest.li framework from performing any projection
while the former forces the Rest.li framework to perform the projection.
Additional details are described in How to Use Projections in Java
Rest.li provides helper methods to implement collection pagination, but it requires each resource to implement core pagination logic itself. Rest.li pagination uses positional indices to specify page boundaries.
The Rest.li server framework automatically recognizes the "start"
and
"count"
parameters for pagination, parses the values of these
parameters, and makes them available through a PagingContext
object.
FINDER methods may request the PagingContext
by declaring a method
parameter annotated with @Context
(see above).
FINDER methods are expected to honor the PagingContext
requirements,
for example, to return only the subset of results with logical indices
>= start
and < start+count
.
The Rest.li server framework also includes support for returning CollectionMetadata as part of the response. CollectionMetadata includes pagination info such as:
start
count
total
number of results (before pagination)FINDER methods that can provide the total
number of matching results
should do so by returning an appropriate CollectionResult
or
BasicCollectionResult
object.
In order for the Rest.li framework to automatically construct Link
objects,
certain conditions must be met. For both previous and next links, the count
in the request must be greater than 0
. For links to the previous page,
start
must be greater than 0
. For links to the next page, the sum
of start
and count
must be less than the total
number of results.
It’s possible for the total
property to be unspecified, but in this case
the PageIncrement
property of the CollectionResult
must be RELATIVE
and the amount of results returned by the resource method must match the
count
desired in the request. The reasoning here is that the only way
for Rest.li to know that you’re reaching the end of a collection of
results is if the amount of results returned differs from the amount
requested.
Here is an example request illustrating the use of start & count pagination parameters, and resulting links in CollectionMetadata:
$ curl "http://localhost:1338/greetings?q=search&start=4&count=2"
{
"elements": [ ... ],
"paging": {
"count": 2,
"links": [
"href": "/greetings?count=10&start=10&q=search",
"rel": "next",
"type": "application/json"
],
"start": 4
}
}
Note that “start” and “count” returned in CollectionMetadata is REQUEST start and REQUEST count (that is, the paging parameter passed from incoming REQUEST, not metadata for the returned response). If start and count is not passed in Finder or GetAll request, it will return default 0 for start and 10 for count.The rationale behind this is to make it easier for a client to subsequently construct requests for additional pages without having to track the start and count themselves. Furthermore, there is no point to return a count for number of items returned, since client can easily get that by calling size() for the elements array returned.
The Rest.li server framework controls the lifecycle of instances of Resource classes, instantiating a new Resource object for each request. It is therefore frequently necessary/desirable for resources to use a dependency-injection mechanism to obtain the objects they depend upon, for example, database connections or other resources.
Rest.li includes direct support for the following dependency injection frameworks:
Other dependency injection frameworks can be used as well. Rest.li
provides an extensible dependency-injection mechanism, through the
ResourceFactory
interface.
The most broadly used dependency injection mechanism is based on mapping
JSR-330 annotations to the Spring ApplicationContext, and it is provided
by the InjectResourceFactory
from restli-contrib-spring
. This is the
recommended approach.
Resource classes may annotate fields with @Inject
or @Named
. If only
@Inject
is specified, the field will be bound to a bean from the
Spring ApplicationContext based on the type of the field. If @Named
is
used, the field will be bound to a bean with the same name. All beans
must be in the root Spring context.
Rest.li has an on-line documentation generator that dynamically generates resource IDL and PDL schemas hosted in the server. The documentation is available in both HTML and JSON formats, and there are three ways to access the documentation:
restli/docs/
. For
example, the documentation URI for resource
http://<host>:<port>/<context-path>/<resource>
is
GET http://<host>:<port>/<context-path>/restli/docs/rest/<resource>
(GET
is the HTTP GET
method,
which is the default for the web browser). The root URL, such as
http://<host>:<port>/<context-path>/restli/docs
, displays the list
of all accessible resources and data schemas in the server. Use it
as a starting point for HTML documentation. Remember to remove the
<context-path>
part if there is no context path.format=json
query parameter on any of the HTML pages
above. For example,
GET http://<host>:<port>/<context-path>/restli/docs/rest/<resource>?format=json
for resource documentation and
GET http://<host>:<port>/<context-path>/restli/docs/data/<full_name_of_data_schema>?format=json
for schema documentation. Homepage
GET http://<host>:<port>/<context-path>/restli/docs/?format=json
is also available, which aggregates all resources and data
schemas.format
query parameter.
This approach only works for resources, and there is no need for
the special restli/docs/
path. For example,
OPTIONS http://<host>:<port>/<context-path>/<resource>
.The JSON format is structured as following:
{
"models": {
"<full_name_of_data_schema_1>": { pdsc_of_data_schema_1 },
"<full_name_of_data_schema_2>": { pdsc_of_data_schema_2 }
},
"resources": {
"<resource_1>": { idl_of_resource_1 },
"<resource_2>": { idl_of_resource_2 }
}
}
When accessing the JSON format of data schema, the resources
key
exists but the value is always empty.
documentationRequestHandler
: instance of
RestLiDocumentationRequestHandler
class, default to null. Specify
which implementation of documentation generator is used in the
server. If null, the on-line documentation feature is disabled.serverNodeUri
: URI prefix of the server without trailing slash,
default to empty string (“”). The URI prefix is mainly used in the
HTML documents by DefaultDocumentationRequestHandler
to properly
generate links. Usually, this should be an absolute path.