8000 Add generics support on RestTemplate methods by DayS · Pull Request #330 · androidannotations/androidannotations · GitHub 8000
[go: up one dir, main page]

Skip to content
This repository was archived by the owner on Feb 26, 2023. It is now read-only.

Conversation

@DayS
8000 Copy link
Contributor
@DayS DayS commented Oct 2, 2012

We still need to import Jackson, but this commit will allow developer to define rest method returning generics like List, Map<String, String>, and so on.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You actually need to catch Throwable here, due to a codemodel bug. Basically, at some point com.sun.codemodel.JCodeModel.ref(String) may be called by codeModel.parseType(fullyQualifiedClassName);. This methods tries to load the class, and loading the class may actually throw some Exception and / or Error.

@pyricau
Copy link
Contributor
pyricau commented Oct 4, 2012

Another question, quite tricky :) . I see that the test are passing, which is good. But are you sure the generated code is actually working ? I don't think so.

For instance :

    @Get("/events/{year}/{location}")
    List<Event> getEventsGenericsList(String location, int year) throws RestClientException;

Generates the following code :

    @Override
    @SuppressWarnings("unchecked")
    public List<com.googlecode.androidannotations.test15.rest.Event> getEventsGenericsList(java.lang.String location, int year) {
        HashMap<java.lang.String, Object> urlVariables = new HashMap<java.lang.String, Object>();
        urlVariables.put("year", year);
        urlVariables.put("location", location);
        HttpHeaders httpHeaders = new HttpHeaders();
        HttpEntity<Object> requestEntity = new HttpEntity<Object>(httpHeaders);
        return restTemplate.exchange(rootUrl.concat("/events/{year}/{location}"), HttpMethod.GET, requestEntity, List.class, urlVariables).getBody();
    }

Appart from the fully qualified name used instead of imports (see my previous comments), there is a part that is really problematic here : restTemplate.exchange() takes a List.class. Therefore, the only information that the parser has is that it should create a List of something. It doesn't know what this something is supposed to be, and this will basically not work :) .

The only solution for that is to create a List subclass that is parameterized, because the type erasure doesn't apply for parameterized class. Maybe something like new ArrayList<Event>() {}.getClass() would work, but this doesn't look good. Gson, Guice and jackson all have their own way to support describing a generic class at runtime, using Type of TypeReference class, but I don't think Spring RestTemplate support these.

@DayS
Copy link
Contributor Author
DayS commented Oct 4, 2012

Well... That's, indeed, a tricky question. I tested my code with simple generics use like List.
But if we want a List, this doesn't work. RestTemplate will return a List...

A solution could be returning a String in exchange() method and then parse it with Jackson, for example.
So, the generated code will looks like this :

public List<MyObject> test() throws RestClientException {
    HttpHeaders httpHeaders = new HttpHeaders();
    HttpEntity<Object> requestEntity = new HttpEntity<Object>(httpHeaders);
    String body = restTemplate.exchange(rootUrl.concat("/test.php"), HttpMethod.GET, requestEntity, String.class).getBody();

    try {
        return objectMapper.readValue(body, new TypeReference<List<MyObject>>() {
        });
    } catch (IOException e) {
        throw new RestClientException("Can't parse result into List<MyObject : " + e.getMessage());
    }
}

@pyricau
Copy link
Contributor
pyricau commented Oct 9, 2012

I haven't completely checked these two commits yet, just did a style comment :) .

Regarding your suggestion, it seems like a good idea at first, but then I realized that :

  • The point of RestTemplate is to hide the serial / deserial mechanism and select the best fit based on the mime types
  • We have no idea whether the users wants to parse Json (vs XML), and even in that case, we don't know for sure that he's using Jackson.

@DayS
Copy link
Contributor Author
DayS commented Nov 9, 2012

So... After some tests, using an inner static class which inherit of the generic class works. So, the generated code will be :

public List<MyObject> test2B() {
    HttpHeaders httpHeaders = new HttpHeaders();
    HttpEntity<Object> requestEntity = new HttpEntity<Object>(httpHeaders);
    return restTemplate.exchange(rootUrl.concat("/test.php"), HttpMethod.GET, requestEntity, ListMyObject.class).getBody();
}

public static class ListMyObject extends ArrayList<MyObject> {}

So we let the serial / deserial mechanism to RestTemple.

This solution raise two more questions :

  • Where should we generate theses specific classes ?
  • In this example I want a List<MyObject> and we can't extend of List (obviously :)). So, which implementation should we use for basic interfaces like List, Map, and so on ?

@DayS
Copy link
Contributor Author
DayS commented Nov 9, 2012

I was thinking on a dedicated class (RestGenericClasses for example) holding theses specific classes (here ListMyObject).
And Jackson already make good choices for implementation. We could just make the same, isn't it ?

@pyricau
Copy link
Contributor
pyricau commented Nov 10, 2012

I thought method local classes would help, but of course they won't because these are inner classes holding references to the outer class so they do not have an empty constructor.

You cannot have a dedicated class for holding these classes, because of incremental compilation steps (at least not until we start maintaining a list of the compiled classes).

However, one thing we know is that all annotations of a single class will be handled in one step, because the whole class is compiled. So using static inner classes should work, maybe with something as stupid as a counter...

@DayS
Copy link
Contributor Author
DayS commented Nov 12, 2012

Ok. For now, I just generate theses classes on the same package as the @rest annotated class. Seems to work pretty fine :)

@DayS
Copy link
Contributor Author
DayS commented Nov 14, 2012

So... I just finish to refactoring code and adding support of generics for rest methods :)
I also add some integration tests for theses methods to ensure the mapping is working.

I still generated specific classes into the same package as the @rest annotated class.

@pyricau
Copy link
Contribut 8000 or
pyricau commented Nov 17, 2012

"This pull request cannot be automatically merged."

:) Please merge develop into this branch :)

@DayS
Copy link
Contributor Author
DayS commented Nov 25, 2012

Fuuuu. There still have a bug with wilcards and typevars... I'm working on it

@DayS
Copy link
Contributor Author
DayS commented Dec 4, 2012

I think that's ok now. Someone to make a review ?

@pyricau
Copy link
Contributor
pyricau commented Dec 6, 2012

Now we're talking :) . I'll have a look.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you extract to a local variable the substring statement at least ?

Thx ;)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dead code here. This method is never called, can you delete it ?

@mathieuboniface mathieuboniface merged commit fb6432c into androidannotations:develop Feb 4, 2013
@mathieuboniface
Copy link
Contributor

Good work :)

@pyricau
Copy link
Contributor
pyricau commented Feb 27, 2013

This pull request should have a milestone set. I'll set it to 3.0, correct me if I'm wrong.

@DayS DayS deleted the 220_rest_template_generics branch December 24, 2013 11:25
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

0