Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring HATEOAS recursive representation model processors?

I have a question concerning the representation model processors of Spring HATEOAS. We are experimenting to process models before serializing them to the client. Our use case is to enrich the imageUrl field of UserModel objects at runtime, as we have to build the URL based on values from a config bean (AWS S3 bucket URL differs for DEV / PROD setup).

@Data
public class UserModel {
    // ...
    private String imageUrl;
}

Therefore, we create a UserProcessor to implement this:

public class UserProcessor implements RepresentationModelProcessor<EntityModel<UserModel>> {

    private final ConfigAccessor configAccessor;

    public UserProcessor(ConfigAccessor configAccessor) {
        this.configAccessor = configAccessor;
    }

    @Override
    public EntityModel<UserModel> process(EntityModel<UserModel> model) {
        if (model.getContent() != null)
            // do the enrichment and set "imageUrl" field
        }
        return model;
    }
}

This works perfectly if we have a controller method like this:

@ResponseBody
@GetMapping("/me")
public EntityModel<UserModel> getCurrentUser(@AuthenticationPrincipal Principal principal) {
    UserModel user = ... // get user model
    return EntityModel.of(user);
}

However, we are struggling now with the enrichment whenever a UserModel is referenced in another model class, e.g., the BookModel:

@Data
public class BookModel {
    private String isbn;
    // ...
    private EntityModel<UserModel> user;  // or "private UserModel user;"
}

A controller method returning type EntityModel<BookModel> only applies the processor for its type, but not for types that are referenced. It seems the processors are not applied recursively.

Is this intentional or are we doing something wrong?

Thanks for any input and help, Michael

like image 536
Michael Wurster Avatar asked Jul 06 '20 10:07

Michael Wurster


People also ask

What is the default response format for spring HATEOAS representationmodel?

By default, Spring hateoas generated responses are in application/hal+json format. It is the default mediatype even if we pass application/json as well. In HAL, the _links entry is a JSON object. The property names are link relations and each value is single or multiple links. 2. Spring HATEOAS RepresentationModel Example

What is spring HATEOAS?

Spring HATEOAS. Spring HATEOAS provides some APIs to ease creating REST representations that follow the HATEOAS principle when working with Spring and especially Spring MVC. The core problem it tries to address is link creation and representation assembly.

Can spring HATEOAS decoupling the client and server without breaking clients?

In this article, we're going to build an example using Spring HATEOAS with the goal of decoupling the client and server, and theoretically allowing the API to change its URI scheme without breaking clients. 3. Preparation First, let's add the Spring HATEOAS dependency:

How does methodon work in Spring Boot?

The methodOn () obtains the method mapping by making dummy invocation of the target method on the proxy controller and sets the customerId as the path variable of the URI. 7. Spring HATEOAS in Action Let's put the self-link and method link creation all together in a getAllCustomers () method:


1 Answers

I encountered the same issue and I resolved it by manually assembling resources, in your case that would be implementing RepresentationModelAssembler of the BookModel and then manually invoking the processor on the userModel object that is inside the book.

Make the outer resource a representation model

First consider the BookModel to extend RepresentationModel so that you can manually add links and assemble inner resources (which you would like for the EntityModel<UserModel> object)

@Data
public class BookModel extends RepresentationModel<BookModel> {...}

Write a model assembler

Now write the assembler that takes your book entity and transforms it into a representation model or a collection of these models. You will implement here what EntityModel.of(...) does for you automagically.

@Component
public class BookModelAssembler implements RepresentationModelAssembler<Book, BookModel> {
    
    @Autowired
    private UserProcessor userProcessor;

    @Override
    public BookModel toModel(Book entity) {
        var bookModel = new BookModel(entity) // map fields from entity to model
        // Transform the user entity to an entity model of user
        var user = entity.getUser();       
        EntityModel<UserModel> userModel = EntityModel.of(user);
        userModel = userProcessor.process(userModel);

        bookModel.setUserModel(userModel);

        return bookModel;
    }
}

I might be going out on a limb but I suppose the reason for this is that the processors get invoked when an MVC endpoint returns a type that has a registered processor, which in the case of embedded types is not invoked. My reasoning is based on the docs for RepresentationModelProcessor, which states that processor processes representation models returned from Spring MVC controllers.

like image 109
milorad Avatar answered Nov 26 '22 10:11

milorad