Как правильно настроить и обработать ответ REST в формате HAL с помощью Spring HATEOAS и Feign?
Я пытаюсь обработать такой ответ с сервера:
{
"items" : [ {
"StandardReceiptId" : 300000005930306,
"ReceiptNumber" : "00000000289",
… a lot of attributes here
"@context" : {
"key" : "300000005930306",
"headers" : {
"ETag" : "ACED0005…
},
"links" : [ {
"rel" : "self",
…
} ]
}
} ],
"count" : 1,
"hasMore" : true,
"limit" : 1,
"offset" : 0,
"links" : [ {
"rel" : "self",
"href" : "https://server.oraclecloud.com:444/fscmRestApi/resources/11.13.18.05/standardReceipts",
"name" : "standardReceipts",
"kind" : "collection"
} ]
}
Когда я пытался десериализовать это ответ с таким типом возврата CollectionModel<EntityModel<StandardReceipts>> all(
для симуляции, я получил такую ошибку:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.util.ArrayList<org.springframework.hateoas.EntityModel<ru.otus.spring.ocae.model.StandardReceipt>>` out of START_OBJECT token
Когда я попробовал EntityModel<Iterable<StandardReceipt>> all(
, то получил следующее:
java.lang.ClassCastException: class java.util.LinkedHashMap cannot be cast to class java.lang.Iterable (java.util.LinkedHashMap and java.lang.Iterable are in module java.base of loader 'bootstrap')
Я пробовал разные объявления для возвращаемого типа, но мне удалось получить мои предметы, только объявить его как EntityModel<Map<String,StandardReceipt>>
Я не думаю, что это правильный путь.
Код:
@Configuration
public class FeignConfig {
@Bean
public Decoder feignDecoder() {
ObjectMapper mapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.registerModule(new Jackson2HalModule());
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setSupportedMediaTypes(MediaType.parseMediaTypes("application/hal+json"));
converter.setObjectMapper(mapper);
ObjectFactory<HttpMessageConverters> objectFactory = () -> new HttpMessageConverters(converter);
return new ResponseEntityDecoder(new SpringDecoder(objectFactory));
}
@FeignClient(value = "standardReceipts", url ="https://server.oraclecloud.com")
public interface StandardReceiptClient {
@GetMapping("/fscmRestApi/resources/11.13.18.05/standardReceipts")
EntityModel<Map<String,StandardReceipt>> all(
@RequestHeader("Authorization") String authHeader,
@RequestHeader(value = "REST-Framework-Version", defaultValue = "7") String restFrameworkVersion,
@RequestParam(required = false, defaultValue = "0", value = "offset") int offset,
@RequestParam(required = true, defaultValue = "1", value = "limit") int limit
);
}
@RestController
public class StandardReceiptsController {
StandardReceiptClient standardReceiptClient;
private String token;
public StandardReceiptsController(
@Autowired StandardReceiptClient standardReceiptClient,
@Value("${oracle.jwt.token}") String token
) {
this.token = token;
this.standardReceiptClient = standardReceiptClient;
}
@GetMapping(value = "/standardreceipts")
public @ResponseBody List<StandardReceipt> all (
@RequestParam(required = false, defaultValue = "0", value = "offset") int offset,
@RequestParam(required = true, defaultValue = "1", value = "limit") int limit
) {
EntityModel<Map<String,StandardReceipt>> coll =
standardReceiptClient.all("Bearer " + token,"7", offset, limit);
Object list = coll.getContent().get("items");
return (ArrayList)list;
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class StandardReceipt {
long StandardReceiptId; //Standard Receipt ID
String ReceiptNumber; //Receipt Number
… a lot of attributes
@EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL)
@EnableFeignClients
@SpringBootApplication
public class OracleCloudAppExtensionApplication {
public static void main(String[] args) {
SpringApplication.run(OracleCloudAppExtensionApplication.class, args);
}
}
Пом. xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath/>
</parent>
<groupId>ru.otus.spring</groupId>
<artifactId>oracle-cloud-app-extension</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>oracle-cloud-app-extension</name>
<description>Oracle Cloud Application extension (OCAE)</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.batch</groupId>
<artifactId>spring-batch-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.hateoas</groupId>
<artifactId>spring-hateoas</artifactId>
<version>1.0.5.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>