JsonTypeInfo не работает с Hateoas EntityModel - PullRequest
1 голос
/ 03 августа 2020

Я использую 2 класса с наследованием:

@Entity
@Data
@Inheritance(strategy = InheritanceType.JOINED)
@JsonIdentityInfo(generator = JSOGGenerator.class,
        property  = "material_id",
        scope = Long.class)
@EqualsAndHashCode(exclude = {"materialCommentaries"})
@ToString(exclude = {"materialCommentaries"})
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
public abstract class Material {
    @Id
    @Setter(AccessLevel.NONE)
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;
    
    @ManyToOne(optional = false)
    private Lesson lesson;

    @NotNull
    private String title;

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "material")
    private Set<MaterialCommentary> materialCommentaries;
}

@EqualsAndHashCode(callSuper = true)
@Entity
@Data
@ToString(callSuper = true)
public class VideoMaterial extends Material {
    private String summary;

    @JsonIgnore
    @ContentId
    private String contentId;

    @JsonIgnore
    @ContentLength
    private long contentLength;

    @JsonIgnore
    @MimeType
    private String mimeType;
}

У меня все нормально JSON с полем @class, когда я даю только VideoMaterial в контроллере:

@PostMapping("video_materials")
public VideoMaterial save(@RequestAttribute("material") VideoMaterial videoMaterial,
                          @RequestParam("content") MultipartFile multipartFile,
                          @AuthenticationPrincipal User creator) throws IOException {
    validateSavingRequest(videoMaterial, creator);
    return videoMaterialService.save(videoMaterial, multipartFile);
}

И JSON в ответ:

{
    "@class": "ru.sstu.online_studying.entities.material.hierarchy.VideoMaterial",
    "material_id": "1",
    "id": 11,
    "lesson": {/*A lot of fields here*/},
    "title": "Title",
    "materialCommentaries": null,
    "summary": "Summary"
}

Но когда я пытаюсь вернуть EntityModel, я получаю JSOn без поля @class и получаю исключение:

com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Missing type id when trying to resolve subtype of [simple type, class ru.sstu.online_studying.entities.material.hierarchy.VideoMaterial]: missing type id property '@class' (for POJO property 'content')
 at [Source: (String)"{"material_id":"1","id":11,"lesson":{"type":"Lesson","lesson_id":"2","id":10,"title":"TestLesson","description":"Test description","section":{"section_id":"3","id":9,"name":"Test","description":"Test descr","startDate":null,"closeDate":null,"course":{"course_id":"4","id":8,"title":"TestCourse","description":null,"maxNumStudents":null,"requirements":null,"image":null,"teachers":[{"user_id":"5","id":2,"username":"7LAWWtL","password":"$2a$10$o8FNl6krC/gEmXxNfI3ByOlmU7cmJoF9h4jWPztOIZB5opwhd59DK","f"[truncated 1554 chars]; line: 1, column: 2054]

    at com.fasterxml.jackson.databind.exc.InvalidTypeIdException.from(InvalidTypeIdException.java:43)
    at com.fasterxml.jackson.databind.DeserializationContext.missingTypeIdException(DeserializationContext.java:1771)
    at com.fasterxml.jackson.databind.DeserializationContext.handleMissingTypeId(DeserializationContext.java:1300)
    at com.fasterxml.jackson.databind.jsontype.impl.TypeDeserializerBase._handleMissingTypeId(TypeDeserializerBase.java:299)
    at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer._deserializeTypedUsingDefaultImpl(AsPropertyTypeDeserializer.java:164)
    at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromObject(AsPropertyTypeDeserializer.java:105)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeWithType(BeanDeserializerBase.java:1196)
    at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:527)
    at com.fasterxml.jackson.databind.deser.impl.ObjectIdReferenceProperty.deserializeSetAndReturn(ObjectIdReferenceProperty.java:94)
    at com.fasterxml.jackson.databind.deser.impl.ObjectIdReferenceProperty.deserializeAndSet(ObjectIdReferenceProperty.java:87)
    at com.fasterxml.jackson.databind.deser.impl.UnwrappedPropertyHandler.processUnwrapped(UnwrappedPropertyHandler.java:62)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeWithUnwrapped(BeanDeserializer.java:673)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:321)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:159)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4218)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3214)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3197)
    at ru.sstu.online_studying.integration.controller.material.VideoMaterialControllerTest.save(VideoMaterialControllerTest.java:65)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:567)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:74)
    at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:84)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)

С этим JSON:

{
    "material_id": "1",
    "id": 11,
    "lesson": {/*a lot of fields here*/},
    "title": "Title",
    "materialCommentaries": null,
    "summary": "Summary",
    "_links": {
        "self": {
            "href": "http://localhost/api/video_materials/11"
        },
        "delete": {
            "href": "http://localhost/api/video_materials/11"
        },
        "updateContent": {
            "href": "http://localhost/api/video_materials/11/content"
        },
        "updateSummary": {
            "href": "http://localhost/api/video_materials/11/summary"
        }
    }
}

Я думаю, странно, что JsonIdentityInfo 'material_id' все еще находится в этом JSON, но @class исчез. Мой метод контроллера, который возвращает EntityModel:

@PostMapping("video_materials")
public EntityModel<VideoMaterial> save(@RequestAttribute("material") VideoMaterial videoMaterial,
                                       @RequestParam("content") MultipartFile multipartFile,
                                       @AuthenticationPrincipal User creator) throws IOException {
    validateSavingRequest(videoMaterial, creator);
    return videoAssembler.toModel(videoMaterialService.save(videoMaterial, multipartFile));
}

Мой метод PresentationModelAssembler:

public EntityModel<VideoMaterial> toModel(VideoMaterial entity) {
    return new EntityModel<>(entity,
            linkTo(methodOn(controllerClass).getVideoContent(entity.getId(), null)).withSelfRel(),
            linkTo(methodOn(controllerClass).deleteVideoMaterial(entity.getId(), null)).withRel("delete"),
            linkTo(methodOn(controllerClass).updateVideoContent(entity.getId(), null, null)).withRel("updateContent"),
            linkTo(methodOn(controllerClass).updateSummary(entity.getId(), null, null)).withRel("updateSummary")

    );
}

Мой макет Mvc запрос:

mockMvc.perform(
                multipart("/api/video_materials/")
                        .file(mockMultipartFile)
                        .requestAttr("material", videoMaterial)
                        .with(user(sender))
        )
.andExpect(status().isOk())
.andExpect(content().contentType(MediaTypes.HAL_JSON)
.andReturn()
.getResponse()
.getContentAsString()
...