Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Feign and HAL/resources

I've got a server that exposes resources through spring-data-rest and this uses, as far as I understand HAL or HATEOAS. But when I try to use it in combination with Feign, I can't seem to be able to register a Jackson2HalModule that gets picked up.

Is there something I have to do to connect the Feign "client" to the new converter? Does it use another ObjectMapper than the one I got here?

Code:

@Inject
public void configureObjectMapper(ObjectMapper mapper, RestTemplate template) {
    mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    mapper.registerModule(new Jackson2HalModule());

    MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
    converter.setSupportedMediaTypes(MediaType.parseMediaTypes("application/hal+json"));
    converter.setObjectMapper(mapper);

    template.getMessageConverters().add(converter);
}

Response from server:

{
  "_links" : {
    "self" : {
      "href" : "http://localhost:13372/user{?page,size,sort}",
      "templated" : true
    },
    "search" : {
      "href" : "http://localhost:13372/user/search"
    }
  },
  "_embedded" : {
    "user" : [ {
      "id" : "5567613e5da543dba4201950",
      "version" : 0,
      "created" : "2015-05-28T18:41:02.685Z",
      "createdBy" : "system test",
      "edited" : "2015-05-28T18:41:02.713Z",
      "editedBy" : "system test",
      "username" : "devuser",
      "email" : "[email protected]",
      "roles" : [ "USER" ],
      "_links" : {
        "self" : {
          "href" : "http://localhost:13372/user/5567613e5da543dba4201950"
        }
      }
    } ]
  },
  "page" : {
    "size" : 20,
    "totalElements" : 1,
    "totalPages" : 1,
    "number" : 0
  }
}

Exception:

Caused by: com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.util.ArrayList out of START_OBJECT token
 at [Source: java.io.PushbackInputStream@7b6c6e70; line: 1, column: 1]
    at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:148)
    at com.fasterxml.jackson.databind.DeserializationContext.mappingException(DeserializationContext.java:762)
    at com.fasterxml.jackson.databind.DeserializationContext.mappingException(DeserializationContext.java:758)
    at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.handleNonArray(CollectionDeserializer.java:275)
    at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:216)
    at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:206)
    at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:25)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3066)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2221)
    at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:205)
like image 439
LG87 Avatar asked May 28 '15 19:05

LG87


3 Answers

I found the problem. The Exception occured due to the fact that the response from the REST API was a single response. So it failed to see it as a List of entities.

When I added (building on the code above):

mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);

It works

Edit: On a side note, I had implemented the FeignClient like this:

@Service
@FeignClient(UsersConstants.USER_SERVICE_NAME)
public interface UsersServices {

    @RequestMapping(method = RequestMethod.GET, value = "/user")
    List<User> getUsers();
}

But how it should be, since it's a pageable resource:

@Service
@FeignClient(UsersConstants.USER_SERVICE_NAME)
public interface UsersServices {

    @RequestMapping(method = RequestMethod.GET, value = "/user")
    List<PagedResources<User>> getUsers();
}

The PagedResource is found within HATEOAS dependency:

<dependency>
    <groupId>org.springframework.hateoas</groupId>
    <artifactId>spring-hateoas</artifactId>
</dependency>

It also has a lot of other classes that can help out, such as Resource, Resources and so forth.

like image 138
LG87 Avatar answered Oct 24 '22 22:10

LG87


This worked for me:

@EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL)
@SpringBootApplication
@EnableFeignClients
public class Application {
....
}

Note the @EnableHypermediaSupport

@FeignClient(url = "http://localhost:8080")
public interface PersonClient {
    @RequestMapping(method = RequestMethod.GET, value = "/persons")
    Resources<Person> getPersons();

    @RequestMapping(method = RequestMethod.GET, value = "/persons/{id}")
    Resource<Person> getPerson(@PathVariable("id") long id);
}

I have created a fully working example here: https://github.com/jtdev/spring-feign-data-rest-example

Note that simply switchen the maven pom from spring-boot to spring-cloud (without changing code), may easily result in json errors.

like image 39
Devabc Avatar answered Oct 25 '22 00:10

Devabc


You should define a feignDecoder bean in your application. If you have spring-hateoas in your environment then try something like this:

@Bean
public Decoder feignDecoder() {
    ObjectMapper mapper = new ObjectMapper()
            .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
            .registerModule(new Jackson2HalModule());

    return new ResponseEntityDecoder(new JacksonDecoder(mapper));
}

Then you can consume your HAL as PagedResource.

like image 23
flecno Avatar answered Oct 24 '22 23:10

flecno