users@jersey.java.net

[Jersey] Re: Implementing a Query by Example

From: Antonio Goncalves <antonio.mailing_at_gmail.com>
Date: Sun, 27 Oct 2013 19:25:30 +0100

Hi all,

I'm still struggling with this issue. It looks like my ParamConverter is
not registered (it's not being invoked). So here is where I am. The REST
service has a findByQBE that takes a Book class as a query parameter
(Bookis annotated with an
@XmlRootElement)

@Transactional
@Path("*/books*")
public class BookEndpoint {

    @GET
    @Path("*/query*")
    @Consumes(MediaType.APPLICATION_XML)
    @Produces(MediaType.APPLICATION_XML)
    public Response findByQBE(@QueryParam("*example*") *Book* example) {
        // ...
        return Response.ok(books).build();
    }
}

The BookParamConverterProvider is in charge of converting a Book into XML
and vice versa :

*_at_Provider*
public class BookParamConverterProvider implements ParamConverterProvider {

    @Override
    public <T> ParamConverter<T> getConverter(Class<T> rawType, Type
genericType, Annotation[] annotations) {
        if (rawType != Book.class) {
            return null;
        }

        return (ParamConverter<T>) new ParamConverter<Book>() {

            @Override
            public Book *fromString*(String value) throws
IllegalArgumentException {
                try {
                    *JAXBContext* ctx = JAXBContext.newInstance(Book.class);
                    Unmarshaller m = ctx.createUnmarshaller();
                    Book book = (Book) m.*unmarshal*(new
StringReader(value));
                    return book;
                } catch (JAXBException e) {
                    return null;
                }
            }

            @Override
            public String *toString*(Book bean) throws
IllegalArgumentException {
                try {
                    StringWriter writer = new StringWriter();
                    *JAXBContext* ctx = JAXBContext.newInstance(Book.class);
                    Marshaller m = ctx.createMarshaller();
                    m.*marshal*(this, writer);
                    return writer.toString();
                } catch (JAXBException e) {
                    return null;
                }
            }

        };
    }
}


I can make this invocation work with cURL as follow :

curl -X GET -H "Accept: application/xml"
http://localhost:8080/sampleJaxRsQBEProvider/rest/books/*query*
curl -X GET -H "Accept: application/xml"
http://localhost:8080/sampleJaxRsQBEProvider/rest/books/*
query?example='<book><title>Beginning</title></book>'*
curl -X GET -H "Accept: application/xml"
http://localhost:8080/sampleJaxRsQBEProvider/rest/books/*
query?example='<book><title>Java</title><description>Enterprise</description><isbn>143024</isbn></book>'
*


With the Client API I can make it work if I explicitly pass the XML as a
String into the example query parameter :

response = client.target(URL + "/books").path("/query")
          .queryParam("*example*", "*<book><title>Java</title></book>*")
          .request(MediaType.APPLICATION_XML)
          .get();


But what I really want to do, is pass the Book object :

Book *example* = new Book();
example.setTitle("Java");

// 404
Response response = client.target(URL + "/books").path("/query")
       .queryParam("*example*", *example*)
       .request(MediaType.APPLICATION_XML)
       .get();

// 404
response = client.target(URL + "/books").path("/query")
       .queryParam("example", example)
       *.register(BookParamConverterProvider.class)*
       .request(MediaType.APPLICATION_XML)
       .get();

// 406
response = client.target(URL + "/books").path("/query")
       .queryParam("*example*", *example*)
       .request(MediaType.*APPLICATION_FORM_URLENCODED_TYPE*)
       .get();

Even when I register the BookParamConverter with the Client API, I cannot
invoke my service. I can access the service with cURL, it's just a matter
of having the POJO --> XML transformation work on the client side.

Any idea ?

Thanks



2013/10/22 Antonio Goncalves <antonio.mailing_at_gmail.com>

> Ivar, I've created a provider that uses JAXB to marshall/unmarshall my
> book :
>
> *_at_Provider*
> public class BookParamConverterProvider implements ParamConverterProvider {
>
> @Override
> public <T> ParamConverter<T> getConverter(Class<T> type, Type
> genericType, Annotation[] annotations) {
> if (type.equals(Book.class)) {
> return (ParamConverter<T>) new BookParamConverter();
> } else {
> return null;
> }
>
> }
>
> private static class BookParamConverter implements
> ParamConverter<Book> {
> @Override
> public Book *fromString*(String value) {
> try {
> JAXBContext ctx = JAXBContext.newInstance(Book.class);
> Unmarshaller m = ctx.createUnmarshaller();
> Book book = (Book) m.unmarshal(new StringReader(value));
> return book;
> } catch (JAXBException e) {
> return null;
> }
> }
>
> @Override
> public String *toString*(Book value) {
> try {
> StringWriter writer = new StringWriter();
> JAXBContext ctx = JAXBContext.newInstance(Book.class);
> Marshaller m = ctx.createMarshaller();
> m.marshal(this, writer);
> return writer.toString();
> } catch (JAXBException e) {
> return null;
> }
> }
> }
> }
>
> But when I invoke the resource with no toXML() method (got rid of it into
> my bean), just the bean itself (example) :
>
> Book example = new Book();
> example.setTitle("Java");
> Response response = client.target(URL + "/books").path("/query")
> .queryParam("example", *example*)
> .request(MediaType.APPLICATION_XML)
> .get();
>
> I get a 404. I registered the provider (as follow) but still get a 404.
>
> Any idea ? What am I doing wrong ?
> Thanks
>
>
>
>
> Response response = client.target(URL + "/books").path("/query")
> .queryParam("example", example)
> *.register(BookParamConverterProvider.class)*
> .request(MediaType.APPLICATION_XML)
> .get();
>
> And the application config class :
>
> @ApplicationPath("/rest")
> public class ApplicationConfig extends Application {
>
> private final Set<Class<?>> classes;
>
> public ApplicationConfig() {
> HashSet<Class<?>> c = new HashSet<>();
> c.add(BookEndpoint.class);
> *c.add(BookParamConverterProvider.class);*
> classes = Collections.unmodifiableSet(c);
> }
>
> public Set<Class<?>> getClasses() { return classes;
> }
> }
>
>
> 2013/10/22 Ivar <ivarconr_at_gmail.com>
>
>> Hi,
>>
>> What you could do is have a a registered implementation of
>> javax.ws.rs.ext.ParamConverterProvider JAX-RS extension SPI that returns a
>> javax.ws.rs.ext.ParamConverter instance capable of a "from string"
>> conversion for the Book type.
>>
>> Ivar Østhus
>>
>>
>>
>> Med vennlig hilsen,
>>
>> Ivar Conradi Østhus
>> 920 43 382
>> ivarconr_at_gmail.com
>>
>>
>> On 22 October 2013 15:28, 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/>
>



-- 
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/>