GithubHelp home page GithubHelp logo

kumuluz / kumuluzee-rest Goto Github PK

View Code? Open in Web Editor NEW
8.0 14.0 4.0 321 KB

KumuluzEE REST extension for implementation of common, advanced and flexible REST API functionalities and patterns as microservices.

Home Page: https://ee.kumuluz.com

License: Other

Java 100.00%
microservices rest patterns kumuluzee java javaee cloud-native

kumuluzee-rest's Introduction

KumuluzEE REST

KumuluzEE CI

KumuluzEE REST greatly simplifies implementation of common REST patterns, such as paging, sorting and filtering. In conjunction with JPA it supports automated querying and retrieving of entities.

KumuluzEE REST provides support for common patterns and best practices for REST services using JAX-RS 2. It greatly simplifies the implementation of paging, sorting and filtering of REST resources and introduces a common syntax. It provides support for automatic parsing of query parameters. In conjunction with JPA it provides support for automated retrieving of entities, parsing query parameters from the URIs and using these query parameters when building JPA queries.

Usage

You can enable the KumuluzEE REST by adding the following dependency:

<dependency>
    <groupId>com.kumuluz.ee.rest</groupId>
    <artifactId>kumuluzee-rest-core</artifactId>
    <version>${kumuluzee-rest.version}</version>
</dependency>

JAX-RS implementation

When implementing REST services the URI context information is needed. The URI can be obtained by adding UriInfo context to selected Resource:

@RequestScoped
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("customers")
public class CustomerResource {

    @Context
    protected UriInfo uriInfo;

    @Inject
    private CustomerService customerBean;
    
    ...
    
}

Using the URI context information the query parameters can be constructed by using the QueryParameters class:

@GET
public Response getAllCustomers() {
    QueryParameters query = QueryParameters.query(uriInfo.getRequestUri().getQuery()).build();
    List<Customer> customers = customerBean.getCustomers(query);
    return Response.ok(customers).build();
}

CDI implementation

After parsing the query parameters they can be used to query or count entities using the JPAUtils class:

@RequestScoped
public class CustomerService {

    @PersistenceContext
    private EntityManager em;

    public List<Customer> getCustomers(QueryParameters query) {
        List<Customer> customers = JPAUtils.queryEntities(em, Customer.class, query);
        return customers;
    }


    public Long getCustomerCount(QueryParameters query) {
        Long count = JPAUtils.queryEntitiesCount(em, Customer.class, query);
        return count;
    }
}

We can also build the query using QueryStringDefaults class which applies the following defaults (if not specified by the client):

  • max results: maximum number of entities that can be returned
  • limit: default number of entities returned
  • offset: default offset
@Context
private UriInfo uriInfo;

@Inject
private QueryStringDefaults qsd;

@Inject
private EntityManager em;

@GET
public Response getList() {
    QueryParameters query = qsd.builder().queryEncoded(uriInfo.getRequestUri().getRawQuery()).build();

    List<Customer> allCustomers = JPAUtils.queryEntities(em, Customer.class, query);
    Long allCustomersCount = JPAUtils.queryEntitiesCount(em, Customer.class, query);

    return Response.ok(allCustomers).header("X-Total-Count", allCustomersCount).build();
}

Defaults can either be constructed:

private QueryStringDefaults qsd = new QueryStringDefaults().maxLimit(100).defaultLimit(20).defaultOffset(0);

or injected with CDI and a producer class:

public class RestProducer {

    @Produces
    @ApplicationScoped
    public QueryStringDefaults getQueryStringDefaults() {
        return new QueryStringDefaults()
                .maxLimit(100)
                .defaultLimit(20)
                .defaultOffset(0);
    }
}

Examples

After the implementation of Rest resources and CDI beans, the query parameters can be used for pagination, sorting and filtering of JPA entities.

Pagination

The offset parameter indicates the position of the first entity which should be returned and the limit parameter indicates the number of entities.

GET /v1/customers?offset=10
GET /v1/customers?limit=5
GET /v1/customers?offset=10&limit=5

We must also return the number of all entities so the client can display correct number of page buttons. One way of doing this is with a custom HTTP header.

...
QueryParameters query = qsd.builder().queryEncoded(uriInfo.getRequestUri().getRawQuery()).build();

List<Customer> allCustomers = JPAUtils.queryEntities(em, Customer.class, query);
Long allCustomersCount = JPAUtils.queryEntitiesCount(em, Customer.class, query);

return Response.ok(allCustomers).header("X-Total-Count", allCustomersCount).build();

With very large datasets counting all filtered records can be very slow. That is why it's possible to specify the count parameter, which can than be used to decide whether or not to perform the count.

GET /v1/customers?offset=10
GET /v1/customers?count=true&limit=5
GET /v1/customers?count=false&offset=10&limit=5

Parameter count is set to trueby default.

NOTE: When using JpaUtils.getQueried counting is performed (or not performed) automatically, depending on the value of count.

Sorting

Sorting of entities can be specified by providing the field and direction.

GET v1/customers?order=id DESC
GET v1/customers?order=lastName ASC

We can chain several sorts together.

GET v1/customers?order=email ASC,lastname DESC

After the last user specified sort, order by unique ID is automatically appended at the end of the query for deterministic sorting of same-valued columns.

Filtering

The entities can be filtered by using multiple operations:

  • EQ | Equals
  • EQIC | Case-insensitive equals
  • NEQ | Not equal
  • NEQIC | Case-insensitive not equal
  • LIKE | Pattern matching (% replaces characters, _ replaces a single character)
  • LIKEIC | Case-insensitive pattern matching (% replaces characters, _ replaces a single character)
  • NLIKE | Negated pattern matching (% replaces characters, _ replaces a single character)
  • NLIKEIC | Case-insensitive negated pattern matching (% replaces characters, _ replaces a single character)
  • GT | Greater than
  • GTE | Greater than or equal
  • LT | Lower than
  • LTE | Lower than or equal
  • IN | In set
  • INIC | Case-insensitive in set
  • NIN | Not in set
  • NINIC | Case-insensitive not in set
  • ISNULL | Null
  • ISNOTNULL | Not null
GET v1/customers?filter=id:EQ:1
GET v1/customers?filter=lastName:NEQIC:'doe'
GET v1/customers?filter=lastName:LIKE:H%
GET v1/customers?filter=age:GT:10
GET v1/customers?filter=id:IN:[1,2,3]
GET v1/customers?filter=lastName:ISNULL
GET v1/customers?filter=lastName:ISNOTNULL

GET v1/customers?filter=age:GT:10 id:IN:[1,2,3] lastName:ISNOTNULL

By default, filters are chained together with an AND operator (represented by an empty space).

Complex queries

It is possible to write more complex queries by using OR and AND operators and by grouping them together with parentheses. Both OR and AND operator can be written in several different ways:

  • OR | ,, or
  • AND | , ;, and

NOTE: AND has precedence over OR, parantheses have precedence over AND

GET v1/customers?filter=age:GT:10 id:IN:[1,2,3] lastName:ISNOTNULL, firstName:LIKE:'B%'
GET v1/customers?filter=age:GT:10 id:IN:[1,2,3] and lastName:ISNOTNULL or firstName:LIKE:'B%'
GET v1/customers?filter=age:GT:10 id:IN:[1,2,3]; lastName:ISNOTNULL, firstName:LIKE:'B%'
GET v1/customers?filter=age:GT:10 id:IN:[1,2,3] and (lastName:ISNOTNULL or firstName:LIKE:'B%')

There are some special cases:

  • If we want to use LIKE filter and query values that include a percent sign, it needs to be URL encoded (%25).
  • Dates and instants must be in ISO-8601 format, + sign must be URL encoded (%2B). Single quotes for value are required.
GET v1/customers?where=firstName:LIKE:'%somestring%25doe'
GET v1/customers?where=createdAt:GT:'2017-06-12T11:57:00%2B00:00'

Partial responses

We can select which fields we want returned in the resulting JSON with the fields parameter.

GET v1/customers?fields=firstName,lastName

Traversing OneToMany and ManyToOne relations

We can traverse entity attributes similar to JPQL style. Let's say each customer has many cars and we want to find owners of specific brand:

GET v1/customers?filter=cars.brand:EQ:bmw

It also works in reverse:

GET v1/cars?filter=customer.firstName:EQ:John

This would find all Cars that have an owner named John.

Combine pagination, sorting and filtering

Pagination, sorting and filtering of entities can be combined by separating them with &.

GET /v1/customers?offset=10&limit=5&order=id DESC&filter=age:GT:10 id:IN:[1,2,3] lastName:ISNOTNULL

Custom field mapping

Library supports custom property mappings set on entity properties in order to detach API schema from database model. This functionality turns out to be useful when persistence changes are needed and API needs to stay backwards compatible.

@RestMapping("experience")
private Integer years;

In this case both of the following queries will return same result:

GET /v1/customers?filter=years:EQ:5
GET /v1/customers?filter=experience:EQ:5

Library supports combination of properties with child properties using OneToOne mapping:

@RestMapping("emailAndCurrentPosition")
private String email;

@RestMapping(value = "emailAndCurrentPosition", toChildField = "currentPosition")
@OneToOne(mappedBy = "user")
private UserCareer career;

where result will return only email and current position within career relation.

Ignoring REST fields

By default, REST library returns field not found exception for non-existing entity fields. For some cases we may want to omit field check with annotation:

@Entity
@RestIgnore("userIgnoredField")
@Table(name = "users")
public class User {
   ...
}

Additional criteria query manipulation

Predicate constructed from query parameters can be further changed. For example:

List<Customer> allCustomers = JPAUtils.queryEntities(em, Customer.class,
(p, cb, r) -> cb.and(p, cb.equal(r.get("firstName"), "John")));

Where:

  • p is existing Predicate
  • cb is CriteriaBuilder and
  • r is Root

With this, programmer has the full power of Criteria API to further manipulate the query.

Building

Ensure you have JDK 8 (or newer), Maven 3.2.1 (or newer) and Git installed

    java -version
    mvn -version
    git --version

First clone the repository:

    git clone https://github.com/kumuluz/kumuluzee-rest.git
    cd kumuluzee-rest

To build run:

    mvn install

This will build all modules and run the testsuite.

Once completed you will find the build archives in the modules respected target folder and local .m2 repository.

Changelog

Recent changes can be viewed on Github on the Releases Page

Contribute

See the contributing docs

When submitting an issue, please follow the guidelines.

When submitting a bugfix, write a test that exposes the bug and fails before applying your fix. Submit the test alongside the fix.

When submitting a new feature, add tests that cover the feature.

License

MIT

kumuluzee-rest's People

Contributors

cen1 avatar dependabot[bot] avatar evader1337 avatar gpor0 avatar jamsek-m avatar jmezna avatar mbjuric avatar osbeorn avatar skrjanec avatar terli00 avatar tfaga avatar urbim avatar zvonegit avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

kumuluzee-rest's Issues

Documentation for using OR operation in search filter

Hi Team,

I have a use case where I need to make an OR query in my search filter. I noticed a PR (#11) has been merged for the same by @osbeorn

I essentially want to create a query that performs the following operation: inputValue = (field1.value OR field2.value)

It would be great if you could give an example for the filter for such a use case. Further, just raising this issue to highlight the need for documentation for some of the recent changes that have been merged!

RestMapping does not support dot names

RestMapping does not support renames containing dot.

Sample entity:
@RestMapping(value = "publish.version")
Integer publishVersion

Client request: ?filter=publish.version:EQ:1

Probable fix is to first process all RestMapping annotations, remove matched filters and then run the current logic on the remaining filters.

Not able to filter based on Date

Hi Team,

I am trying to use below filter:
GET v1/customers?where=createdAt:GT:'2017-06-12T11:57:00%2B00:00'

But i am getting error message as "A filter has too many argument or is malformed"

Please let me know what is the correct Format of Date ?

Unable to send "null" query parameter

Query parameters are optional by nature and it's currently not possible to omit them using this library. Would it be possible to skip query parameter inclusion if a null value is used?

Interface;

@GET
public Response findEntities(@QueryParam("search") String text, @QueryParam("optionalParam") UUID optionalParam);

Usage:

myApi.findEntities("ent%", null);

Result:
GET http://localhost:8080/entities?search=ent%

Not able to search String with single quote

I am trying to search a String which has single quote in it. It is not able to find it.
I tried with escape characters like \ and doubling '' it but no luck.

My string is - ably.qer-z'o pp

Queries which I have tried -

string:EQ:'ably.qer-z\'o pp'
string:EQ:'ably.qer-z''o pp'
string:EQ:'ably.qer-z^'o pp'

None of the above are working and returning empty result.

Can you confirm right escape character for such kind of queries ?

Unable to Search when using Special Characters in Encoded URL

Hi Team,

I referred to the documentation, and when using the below snippet to build the query:

QueryParameters query = qsd.builder().queryEncoded(uriInfo.getRequestUri().getRawQuery()).build();

I see that when I pass certain special characters (that are URL Encoded) such as parentheses or a comma as part of the filter string, they get excluded from the actual lower level query, and the final query is thus incorrect.

Could someone help me with how this can be corrected, or confirm if it is indeed a genuine bug? It seems like #28 is a similar issue for a different special character as well.

Thanks!
Mahir

Issue with filering fields of type Time and Timestamp

I have Entity class A which has this timestamp type field say "startTime". I want to filter records on the basis of "startTime".

I tried this way :
filter=startTime:GTE:ts'2021-12-10T10:00:00:000Z' - Here startTime corresponds to Timestamp field in MY SQL
filter=startTime:GTE:ts'10:00:00' - Here startTime corresponds to Time field in MY SQL
Although I am not getting any error as such but looks like this filter is getting ignored while fetching the data.

Kindly help me to understand how to pass the time and timestamp fields in the request query

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.