Brief review of EclipseLink JPA-RS new features

At the end of this year planned a release of EclipseLink 2.6.0. EclipseLink is an open source ORM framework developed by Eclipse Foundation. Here I would like to briefly introduce some new features of JPA-RS. JPA-RS is a part of EclipseLink. It’s a service allowing automatic generation of RESTful web service based on provided JPA entity model.

1. Protocol semantics version

Protocol semantics may change from version to version. A JSON schema of resources in JPA-RS 2.0 differs from the version 1.0 of the service. To make the service backwards compatible we added a version number to the URL.

The URL template looks like this:

http(s)://{server:port}/{app}/persistence/{version}/{persistent-unit}/...

Where:

  • server:port – server IP address (DNS name) + port\
  • app – your application name.
  • persistence – JPARS application name. Constant.
  • version – JPARS version. Currently only values “v1.0”, “v2.0” and “latest” are supported.
  • persistent-unit – Your persistent unit name. Defined in persistece.xml file.

To make the URL format compatible with the previous version of the service it is possible to omit version parameter from the URL. In this case value “v1.0” is used by the server.

It is possible to use latest keyword instead of the version number. In this case the latest protocol semantic version will be used. It is “v2.0” at the moment.

Examples:

This request gets a Car entity with id = 1 using car-pu persistent unit from the localhost. It returns data in JPARS 1.0 format.

http(s)://localhost:8080/app/persistence/car-pu/entity/Car/1

it’s the same as:

http(s)://localhost:8080/app/persistence/v1.0/car-pu/entity/Car/1

This request does the same but uses JPARS 2.0 protocol semantic:

http://localhost:8080/app/persistence/v2.0/car-pu/entity/Car/1

This request does absolutely the same as the previous one:

http://localhost:8080/app/persistence/latest/car-pu/entity/Basket/1

Later in the article I will reference this URL http://localhost:8080/app/persistence/latest/car-pu as {root}.

2. Pagination

Pagination is not available for all collection resources by default. It’s configured on the server side using annotations. If a request with pagination parameters comes to the resource which doesn’t support it exception is thrown.

Resources supporting pagination:

  • Named query resources: {root}/query/{query-name}
  • Single attributes of collection type {root}/entity/{entity-name}/{primary-key}/{attribute-name}

The server side configuration is done by annotating your entity with special paging annotations:

@RestPageableQueries

This is a container for a list of @RestPageableQuery annotations. It’s placed on entity class. It works the similar way as @NamedQueries annotation.

@RestPageableQuery(queryName=, limit=)

This annotation must be placed inside @RestPageableQueries annotation. It says that named query with name *named_query_name* is pageable and the default number of items per page is *limit*. If limit is not defined the default value of 100 used. Named query with *named_query_name* must exist.

Sample:

@Entity
@Table(name = "CAR")
@NamedQueries({
@NamedQuery(
    name = "Car.findAll",
    query = "SELECT c FROM Car c ORDER BY c.name"),
@NamedQuery(
    name = "Car.findAllPageable",
    query = "SELECT c FROM Car c ORDER BY c.name"),
})
@RestPageableQueries({
    @RestPageableQuery(queryName = "Car.findAllPageable", limit = 20)
})
public class Car {
    @Id
    @Column(name = "CAR_ID")
    private Integer id;

    @Column(name = "CAR_NAME")
    private String name;

    @Column(name = "CAR_SHORT_DESCR")
    private String shortDescr;

    @Column(name = "CAR_LONG_DESCR")
    private String longDescr;

    @ManyToOne
    @JoinColumn(name = "CAR_ID")
    private Owner owner;

    // Getters and setters are skipped
}

@Entity
@Table(name = "OWNER")
public class Owner {
    @Id
    @Column(name = "OWNER_ID")
    private Integer id;

    @Column(name = "OWNER_NAME")
    private String name;

    @OneToMany(mappedBy = "owner")
    @RestPageable(limit = 20)
    List cars;

    // Getters and setters are skipped
}

@RestPageable(limit=items_per_page)

This annotation defines a pageable collection attribute. It must be placed on the collection field. The number of items per page is defined by the optional limit parameter. Default limit value is 100.

Sample:

@OneToMany(mappedBy = "owner")
@RestPageable(limit = 20)
List cars;

If pagination is supported for a resource the following two optional request parameters can be used to control it.

  • limit: sets the page size and controls how many rows to return. If this parameter is not used then the default value is used. The default value is set in the corresponding @RestPageableQuery or @RestPageable annotation.
  • offset: sets the start record of the returning data from the beginning of the list. If not present the default value of 0 is used.

For example these requests return the first page (20 records) of Cars.findAllPageable query:

{root}/query/Car.findAllPageable
{root}/query/Car.findAllPageable?limit=20

This is the second page:

{root}/query/Car.findAllPageable?offset=20
{root}/query/Car.findAllPageable?limit=20&offset=20

Third page with 10 records per page:

{root}/query/Car.findAllPageable?limit=10&offset=20

7 records starting from the third one:

{root}/query/Car.findAllPageable?limit=7&offset=3

Server calculates the actual limit as minimum from limit query parameter and default limit specified in the annotation.

For example, the limit=20 will be used for the following query:

{root}/query/Car.findAllPageable?limit=100

Lets take a look at the response of {root}/query/Car.findAllPageable?offset=20 query:

{
  "items": [
    {
      "id": 21,
      "name": "Mazda MX-5",
      ...
    },
    ... <19 more records> ...
  ],
  "hasMore": true,
  "limit": 20,
  "offset": 20,
  "count": 20,
  "links": [
    {
      "rel": "self",
      "href": "/query/Car.findAllPageable?offset=20"
    },
    {
      "rel": "canonical",
      "href": "/query/Car.findAllPageable?offset=20"
    },
    {
      "rel": "next",
      "href": "/query/Car.findAllPageable?offset=40"
    },
    {
      "rel": "prev",
      "href": "/query/Car.findAllPageable?offset=0"
    }
  ]
}

The response contains some additional properties and links:

  • ‘next’ link – link to the next page if available.
  • ‘prev’ link – link to the previous page if available.
  • limit: the actual paging size used by the server.
  • offset: the actual index from which the records are returned.
  • count: the total number of records in the current response.
  • hasMore: this is a Boolean property to indicate whether there are more pages that satisfy the request. Always returned.

 3. Fields Filtering

This feature allows user to select fields which should be returned. It’s done with help of two query parameters fields and excludeFields:

  • fields parameter defines a list of returned fields.
  • excludeFields parameter defines a list of excluded fields.

NOTE
fields and excludeFields parameters are mutual exclusive. If both of them are present in the request the error is returned.

For example:

GET {root}/entity/Car/1?fields=id,name,shortDescr

Returns only fields *id*, *name* and *shortDescr* of class Car:

{
"id": 1,
"name": "Mazda MX-5",
"shortDescr": "lightweight two-seater roadster",
...
}

This request returns the same as above:

GET /entity/Car/1?excludeFields=longDescr,owner

4. Resources metadata

Resource metadata is information about the resource that is not specific to a particular representation. It contains a link to a resource schema, canonical link to resource and other data.

Metadata for entity resource is provided at:

{root}/metadata-catalog/entity/{entityName}

For example, metadata for Car entity is provided at:

{root}/metadata-catalog/entity/Car

Metadata for query resource is provided at:

{root}/metadata-catalog/query/queryName

Metadata for Car.findAll query is provided at:

{root}/query/Car.findAll

Another way of getting resource metadata is using OPTIONS method against the resource URL.

For example, metadata for Car entity can be retrieved this way:

OPTIONS {root}/entity/Basket

The response header contains a link to the resource metadata URL with “describedby” rel.

Link: <{root}/metadata-catalog/entity/Car>; rel="describedby"

There are two media types that can be used to describe a resource: application/json and application/schema+json. The default value is the former, which returns a very simple description of the resource, including the resource name, a link to the resource instance, and a link to the JSON schema of the resource.

For example if application/json is used

GET {root}/metadata-catalog/entity/Car

returns

{
  "name": "Car",
  "links": [
    {
      "rel": "alternate",
      "href": "{root}/metadata-catalog/entity/Car",
      "mediaType": "application/schema+json"
    },
    {
      "rel": "canonical",
      "href": "{root}/metadata-catalog/entity/Car",
      "mediaType": "application/json"
    },
    {
      "rel": "describes",
      "href": "{root}/entity/Car"
    }
  ]
}

If application/schema+json media type is used (header ‘accept’ value ‘application/schema+json’) to access this resource metadata URI, then a JSON schema for the resource is returned.

{
  "$schema": "{root}/metadata-catalog/entity/Car#",
  "allOf": [
    {
      "$ref": "rest-schemas/#/singularResource"
    }
  ],
  "title": "Car",
  "properties": {
    "id": {
      "type": "number"
    },
    "name": {
      "type": "string"
    },
    "shortDescr": {
      "type": "string"
    },
    "longDescr": {
      "type": "string"
    },
    "owner": {
      "$ref": "{root}/entity/Owner#"
    }
  },
  "links": [
    {
      "rel": "describedby",
      "href": "{root}/entity/Car"
    },
    {
      "rel": "find",
      "href": "{root}/entity/Car/{primaryKey}",
      "method": "GET"
    },
    {
      "rel": "create",
      "href": "{root}/entity/Car",
      "method": "PUT"
    },
    {
      "rel": "update",
      "href": "{root}/entity/Car",
      "method": "POST"
    },
    {
      "rel": "delete",
      "href": "{root}/entity/Car/{primaryKey}",
      "method": "DELETE"
    }
  ]
}

5. Metadata catalog

Metadata catalog is a catalog of all available entities and queries metadata. Metadata catalog is accesible at:

GET {root}/metadata-catalog

For example, a metadata catalog for car-pu persistent unit is provided here:

http://localhost:7001/app/persistence/v2.0/car-pu/metadata-catalog

Response:

{
  "items": [
    {
      "name": "Car",
      ...
    },
    {
      "name": "Owner",
      ...
    },
    {
      "name": "Car.findAll",
      ...
    },
    {
      "name": "Car.findAllPageable",
      ...
    }
  ],
  "links": [
    {
      "rel": "canonical",
      "href": "{root}/metadata-catalog"
    }
  ]
}

That is it. I hope you liked the article.
If you want to try JPA-RS 2.0, EclipseLink nightly builds can be downloaded here.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s