This library implements a subset (very very little) of OData speficication, the Quering Collection.
The primary scope of this library is to implement the filter, order and pagination of JPA Entity using the OData sintax. The library use the language specified in OData Query Collection to create dinamically JPA Criteria Query.
The parameters can be taken directly from an JAX-RS Endpoint using the utility class QueryOptionsParser:
- $skip - How many record skip before the first record returned
- $top - How many record return
- $count - Boolean value used to ask to count how many records are impacted by the filter
- $orderby - Sort the records
- $filter - Filter the records
For more information on OData you can see https://www.odata.org/getting-started/.
Add the library as dependency:
<dependency>
<groupId>it.osys</groupId>
<artifactId>jaxrs-odata</artifactId>
<version>1.2.0</version>
</dependency>
In the code, create an instance of OData class passing the Entity Bean Class. Set the Entity Manager and call method or the method passing the QueryOptions.
OData<Author> odata = new OData<Author>(Author.class);
odata.setEntityManager(em);
...
odata.get(queryOptions);
odata.count(queryOptions);
With this rest Endpoint:
@GET
@Path("authors")
@Produces(MediaType.APPLICATION_JSON)
public Collection<Author> getAllAuthors(UriInfo info) {
QueryOptions queryOptions = QueryOptionsParser.from(info);
OData<Author> odata = new OData<Author>(Author.class);
odata.setEntityManager(em);
return odata.getAll(queryOptions);
}
This is only an example, I advice to use a DTO (Data Transfer Object), don't output JPA Entity Bean directly.
You can get authors, filtered, ordered and paginated using a request like this:
http://<SERVER>/authors?$top=10&$skip=0&$filter=contains(name, 'Adriano')
Or
http://<SERVER>/authors?$top=10&$skip=10&$filter=bod ge '1980-01-01' and not (contains(name, 'Valerio'))&$order=address/city asc
You can see many examples in the test classes.
Use a bean like this to marshialing the output data:
import java.util.Collection;
import javax.json.bind.annotation.JsonbProperty;
public class ResultSet<T> {
@JsonbProperty(value = "@odata.count")
public long count;
public Collection<T> value;
@Override
public String toString() {
return "ResultSet [count=" + count + ", value=" + value + "]";
}
}
The rest method, with the logic to output also the total record count:
@GET
@Path("authors")
@Produces(MediaType.APPLICATION_JSON)
public ResultSet<Author> authors(UriInfo info) {
QueryOptions queryOptions = QueryOptionsParser.from(info);
OData<Author> odata = new OData<Author>(Author.class);
odata.setEntityManager(em);
ResultSet<Author> resultSet = new ResultSet<>();
resultSet.value = odata.getAll(queryOptions);
if (queryOptions.count)
resultSet.count = odata.countAll(queryOptions);
return resultSet;
}
You can use the filter method to enrich the swagger json with OData parameter when the Operation's summary contains "odata" string. Add the following content in file src/main/resources/META-INF/microprofile-config.properties with:
mp.openapi.filter=it.osys.jaxrsodata.ODataParamsFilter
And add this class:
import java.util.ArrayList;
import org.eclipse.microprofile.openapi.OASFactory;
import org.eclipse.microprofile.openapi.OASFilter;
import org.eclipse.microprofile.openapi.models.Operation;
import org.eclipse.microprofile.openapi.models.media.Schema;
import org.eclipse.microprofile.openapi.models.parameters.Parameter;
import org.eclipse.microprofile.openapi.models.parameters.Parameter.In;
import org.eclipse.microprofile.openapi.models.parameters.Parameter.Style;
public class ODataParamsFilter implements OASFilter {
@Override
public Operation filterOperation(Operation operation) {
if (operation.getSummary() != null && operation.getSummary().contains("odata")) {
System.out.println("Enrich " + operation.getSummary() + " with OData Parameter");
Schema sInt = OASFactory.createSchema();
sInt.setType(Schema.SchemaType.INTEGER);
Schema sBool = OASFactory.createSchema();
sBool.setType(Schema.SchemaType.BOOLEAN);
Schema sString = OASFactory.createSchema();
sString.setType(Schema.SchemaType.STRING);
Parameter top = OASFactory.createParameter();
top.setName("$top");
top.setIn(In.QUERY);
top.setStyle(Style.SIMPLE);
top.setSchema(sInt);
top.setDescription("Max num. record returned");
Parameter skip = OASFactory.createParameter();
skip.setName("$skip");
skip.setIn(In.QUERY);
skip.setStyle(Style.SIMPLE);
skip.setSchema(sInt);
skip.setDescription("Skip to record");
Parameter count = OASFactory.createParameter();
count.setName("$count");
count.setIn(In.QUERY);
count.setStyle(Style.SIMPLE);
count.setSchema(sBool);
count.setDescription("Calculate the total record affected by $filter");
Parameter orderby = OASFactory.createParameter();
orderby.setName("$orderby");
orderby.setIn(In.QUERY);
orderby.setStyle(Style.SIMPLE);
orderby.setSchema(sString);
orderby.setDescription("Specify the order of records before $skip and $top");
Parameter filter = OASFactory.createParameter();
filter.setName("$filter");
filter.setIn(In.QUERY);
filter.setStyle(Style.SIMPLE);
filter.setSchema(sString);
filter.setDescription("Specify the filter to apply to record");
Parameter search = OASFactory.createParameter();
search.setName("$search");
search.setIn(In.QUERY);
search.setStyle(Style.SIMPLE);
search.setSchema(sString);
search.setDescription("Specify a custom search string");
if (operation.getParameters() == null)
operation.setParameters(new ArrayList<>());
operation.getParameters().add(top);
operation.getParameters().add(skip);
operation.getParameters().add(count);
operation.getParameters().add(orderby);
operation.getParameters().add(filter);
operation.getParameters().add(search);
}
return OASFilter.super.filterOperation(operation);
}
}
And annotate the method with @Operation. Remember to add "odata" in his summary.
...
import org.eclipse.microprofile.openapi.annotations.Operation;
...
@Operation(summary = "Get Authors - odata")
@GET
@Path("authors")
@Produces(MediaType.APPLICATION_JSON)
public ResultSet<Author> authors(UriInfo info) {
Add this annotation:
package it.osys.jaxrsodata;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
@ApiImplicitParams(value = {
@ApiImplicitParam(paramType = "query", name = "$top", dataType = "int", value = "Max number item to get"),
@ApiImplicitParam(paramType = "query", name = "$skip", dataType = "int", value = "How many record skip before the first item"),
@ApiImplicitParam(paramType = "query", name = "$count", dataType = "boolean", value = "True to get also the total number of item the server has"),
@ApiImplicitParam(paramType = "query", name = "$orderby", dataType = "string", value = "Order by the result", example = "id asc"),
@ApiImplicitParam(paramType = "query", name = "$filter", dataType = "string", value = "Filter the result", example = "id eq 1"),
@ApiImplicitParam(paramType = "query", name = "$search", dataType = "string", value = "String to search in items")
})
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ODataImplicitParams {
}
And add it to Endpoint:
@GET
@Path("authors")
@ODataImplicitParams
@Produces(MediaType.APPLICATION_JSON)
public ResultSet<Author> authors(UriInfo info) \{
- The input parameter reference fields in the Entity Bean, and the fields maybe do not has the same name of the field present in the return recordset.
See https://github.com/dometec/jaxrs-odata-examples