users@jersey.java.net

[Jersey] Re: Implementing a Query by Example

From: Antonio Goncalves <antonio.mailing_at_gmail.com>
Date: Tue, 22 Oct 2013 17:38:39 +0200

JAXB usually does the job and everything is automatic, but @QueryParam
needs Strings (that's why one technic is to have a constructor with a
single String).

The technic you propose (POST, get the search URI and then GET) could work,
but I think doing a single GET is appropriate : instead of passing several
Strings @QueryParam, I just want to pass a single String (in an XML format,
but that should be transparent).

As for the QBE, I use the Query API with if statements, something like :


    private Predicate[] getSearchPredicates(Root<Book> root, Book example) {

        CriteriaBuilder builder = em.getCriteriaBuilder();
        List<Predicate> predicatesList = new ArrayList<>();

        String isbn = example.getIsbn();
        if (isbn != null && !"".equals(isbn)) {

predicatesList.add(builder.like(builder.lower(root.<String>get("isbn")),
'%' + isbn.toLowerCase() + '%'));
        }
        String title = example.getTitle();
        if (title != null && !"".equals(title)) {

predicatesList.add(builder.like(builder.lower(root.<String>get("title")),
'%' + title.toLowerCase() + '%'));
        }
        String description = example.getDescription();
        if (description != null && !"".equals(description)) {

predicatesList.add(builder.like(builder.lower(root.<String>get("description")),
'%' + description.toLowerCase() + '%'));
        }
        String publisher = example.getPublisher();
        if (publisher != null && !"".equals(publisher)) {

predicatesList.add(builder.like(builder.lower(root.<String>get("publisher")),
'%' + publisher.toLowerCase() + '%'));
        }
        Integer nbOfPages = example.getNbOfPages();
        if (nbOfPages != null && nbOfPages != 0) {
            predicatesList.add(builder.equal(root.get("nbOfPages"),
nbOfPages));
        }

        return predicatesList.toArray(new Predicate[predicatesList.size()]);
    }



2013/10/22 Noah White <emailnbw_at_gmail.com>

> Hi Antonio,
>
> Great question.
>
> I'm a little surprised that Book is not being automatically unmarshalled
> for you. I wonder if its due to it being a @QueryParam or the lack of
> @Consumes on the method.
>
> I've had success automatically unmarshalling from JSON (I don't think I've
> tried XML) using JAXB (legacy app or I would have opted for Jackson
> w/POJOs), JAXBContextResolver and Natural Notation when dealing with an
> entity body, for example w/a @POST. It's looked something like this:
>
> @POST
> @Path("/foos")
> @Consumes(MediaType.APPLICATION_JSON)
> @Produces(MediaType.APPLICATION_JSON)
> public Response createFoo(Foo myFoo) {
> …
> }
>
> In that case when a JSON representation of Foo (a JAXB entity bean just
> like your Book example) is in the POSTs entity body it will be
> automatically unmarshalled into myFoo - w/missing properties set to
> null/default values. I would think since it's using JAXB as described above
> if your entity is in the JAXB context it would automatically unmarshal it
> for you. Perhaps it has to due w/the fact its a @QueryParam.
>
> One option which adds an extra step but gives you automatic unmarshalling
> for free would be to use a @POST with the QBE entity as the entity body.
> This has an advantage over using a query param in that you don't have to
> deal with the length restriction imposed on the URL if you are dealing with
> a lot of XML grammar etc. On the server you would create a search query
> entity using the request entity body and the Response would include a link<https://jersey.java.net/documentation/latest/user-guide.html#d0e7669> to
> run the query and return the results when the client performs a GET on it.
>
> Or you could be less 'RESTy' and skip the search query entity
> creation/link response and instead run the search and return the results in
> the POST response.
>
> BTW - I haven't used QBE, how does JPA/Eclipselink handle QBE when some of
> the properties are NULL? Does it leave them out of the query or AND them in
> with propName=NULL? I'm curious because if you want to perform a projection
> style QBE you'd only be specifying certain properties and JAXB will
> initialize the missing ones the appropriate defaults which may be NULL.
>
> FWIW I've implemented projection style queries w/Jersey but I used bean
> validation to intercept and parse the query param containing property names
> (as simple name=val text), then used reflection against the resource entity
> to validate the parsed param name and values and finally in the controller
> I used the Criteria API to dynamically construct the projection query. This
> works pretty well but can get more complex if the query is trying to
> include relations. This was all before JPA-RS which I haven't played with
> yet. I'm curious to know if/how it supports queries of this type.
>
> -Noah
>
> On Oct 22, 2013, at 9:28 AM, Antonio Goncalves <antonio.mailing_at_gmail.com>
> wrote:
>
> Hi all,
>
> I'm using a very manual approach to solve this problem and I would like to
> share it with you in case you have a better idea. Basically my resource
> doesn't do much, it just uses a @QueryParam to get the XML and JPA to do a
> QBE :
>
> @GET
> @Path("*/query*")
> @Produces("application/xml")
> public Response findByQBE(*_at_QueryParam("example")* *Book* example) {
>
> CriteriaBuilder builder = em.getCriteriaBuilder();
> CriteriaQuery<Book> criteria = builder.createQuery(Book.class);
> // ...
> // query by example
> return Response.ok(books).build();
> }
>
>
> The manual work that I do is in the Book entity. Basically I give it a
> constructor with a String (needed by JAX-RS) to be able to unmarshall the
> XML into the bean itself, and I create a toXML() method that marshalls the
> bean into XML :
>
> @Entity
> *_at_XmlRootElement*
> public class Book implements Serializable {
>
> @Id @GeneratedValue
> private String isbn;
> private String title;
> private String description;
> // ... other attributes
>
> public Book() {
> }
>
> public *Book(String xml)* throws JAXBException {
> // Uses JAXB to unmarshall the XML string into the bean itself
> JAXBContext ctx = JAXBContext.newInstance(Book.class);
> Unmarshaller m = ctx.createUnmarshaller();
> Book book = (Book) m.*unmarshal*(new StringReader(xml));
> this.isbn = book.getIsbn();
> this.title = book.getTitle();
> this.description = book.getDescription();
> this.nbOfPages = book.getNbOfPages();
> this.publisher = book.getPublisher();
> }
>
> public String *toXML()* throws JAXBException {
> // Uses JAXB to marshall the bean into an XML string
> StringWriter writer = new StringWriter();
> JAXBContext ctx = JAXBContext.newInstance(Book.class);
> Marshaller m = ctx.createMarshaller();
> m.*marshal*(this, writer);
> return writer.toString();
> }
>
> // ... getters & setters
> }
>
> Then, to invoke my resource with the Client API, I just call the toXML()
> method as follow :
>
> Book example = new Book();
> example.setTitle("Java");
> Response response = client.target(URL + "/books").path("/query")
> .queryParam("example", *example.toXML()*)
> .request(MediaType.APPLICATION_XML)
> .get();
>
> This way I invoke the resource with a request that looks like what I want
> :
>
> /rest/books/*query/example=<book><title>Java</title></book>*
>
>
> So it does work except there is many manual code that, in my mind, could
> be automatised. Am I wrong ? Do you have a better idea ? In this case I
> suppose a provider would make sense but I already use JAXB and XML so it
> feels weird to do the job a second time.
>
> Thanks for you thoughts
>
> Antonio
>
>
> 2013/10/22 Antonio Goncalves <antonio.mailing_at_gmail.com>
>
>> Hi all,
>>
>> I need to implement a query by example service and be able to consume it
>> with the new client API.
>>
>> The idea is that I have a Book entity with a JAXB annotation :
>>
>> @Entity
>> @XmlRootElement
>> public class Book implements Serializable {
>>
>> @Id @GeneratedValue
>> private Long id = null;
>>
>> private String isbn;
>> private String title;
>> private String description;
>> }
>>
>>
>> I now need a service that would be able to receive an 'example' of the
>> Book in XML format. So if the service receives :
>>
>> <book><isbn>1234</isbn></book>
>>
>> It will return all the books with an ISBN like '1234. If the service
>> receives :
>>
>> <book><isbn>1234</isbn><title>Java</title><description>Best
>> book</description></book>
>>
>> Il will return all the books with an ISBN like '1234' AND a title like
>> 'Java' AND a description like 'Best Book'. As you can see, the query string
>> depends on which attributes of the entity are set. So I was thinking of
>> having a URI that would look like :
>>
>> /rest/books/*query*=<book><isbn>1234</isbn></book>
>> /rest/books/*query*=<book><isbn>1234</isbn><title>Java</title><description>Best
>> book</description></book>
>>
>> So I assume that the resource would then look like :
>>
>> @GET
>> public Response findByQBE(@QueryParam("*query*") Book example) {
>> ...
>> }
>>
>> And the client API
>>
>> Book *example* = new Book();
>> example.setTitle("Java");
>> client.target(URL + "/books").path("/qbe").queryParam("*query*", *example
>> *).request(MediaType.APPLICATION_XML).get();
>>
>> But that doesn't work (I get a 404).
>>
>> I feel I don't need to write a provider to marshall the POJO into an XML
>> String. So I think I'm doing something wrong here.
>>
>> Any idea
>>
>> Thanks
>>
>> --
>> Antonio Goncalves
>> Software architect and Java Champion
>>
>> Web site <http://www.antoniogoncalves.org/> | Twitter<http://twitter.com/agoncal>
>> | LinkedIn <http://www.linkedin.com/in/agoncal> | Paris JUG<http://www.parisjug.org/>
>> | Devoxx France <http://www.devoxx.fr/>
>>
>
>
>
> --
> Antonio Goncalves
> Software architect and Java Champion
>
> Web site <http://www.antoniogoncalves.org/> | Twitter<http://twitter.com/agoncal>
> | LinkedIn <http://www.linkedin.com/in/agoncal> | Paris JUG<http://www.parisjug.org/>
> | Devoxx France <http://www.devoxx.fr/>
>
>
>


-- 
Antonio Goncalves
Software architect and Java Champion
Web site <http://www.antoniogoncalves.org/> |
Twitter<http://twitter.com/agoncal>
 | LinkedIn <http://www.linkedin.com/in/agoncal> | Paris
JUG<http://www.parisjug.org/>
 | Devoxx France <http://www.devoxx.fr/>