'Is there a way to incorporate other classes into a RepresentationModelAssembler in Spring HATEOAS?

I'm trying to server a Course object with all its objects into HAL + JSON content.

Each course has an additional object called a RoleManager, and each RoleManager is effectively a map between a Role object and a String (the name of the Role). The RoleManager also has a list of Roles based on their sorted priority.

So here's what my Course object looks like: (I'm using lombok)

@Data
@Builder
@Document
@RequiredArgsConstructor
@AllArgsConstructor(onConstructor_ = {@PersistenceConstructor})
public class Course {

    @Id
    private EuclidID id;
    private String courseName;
    private String term;
    private String courseCode;
    @DocumentReference
    private User creator;
    @Singular
    @DocumentReference
    @ToString.Exclude
    @EqualsAndHashCode.Exclude
    private Set<User> users;
    private EuclidID image;
    private RoleManager roleManager;

}

Role data:

@Data
@RequiredArgsConstructor
@AllArgsConstructor(onConstructor_ = {@PersistenceConstructor})
public class Role {
    private String name;
    private boolean isVisible;
    private String color;
    @DocumentReference
    private Set<User> users;
}

The CourseController serves all the Courses, a specific Course, all Roles in a Course, and just 1 Role.

@RestController
@RequiredArgsConstructor
public class CourseController {

    private final CourseRepository repository;
    private final CourseModelAssembler courseModelAssembler;
    private final RoleAssembler roleAssembler;

    @GetMapping(value = "/internal/course/{id}")
    public EntityModel<Course> singleCourse(@PathVariable String id) {
        val euclidID = new EuclidID(id);
        val course = repository.findById(euclidID).orElseThrow(() -> new NotFoundException(Course.class, "Cannot find course with id : " + id));
        return courseModelAssembler.toModel(course);
    }

    @GetMapping(value = "/internal/courses")
    public CollectionModel<EntityModel<Course>> allCourses() {
        val models = repository.findAll().stream().map(courseModelAssembler::toModel).collect(Collectors.toSet());
        return CollectionModel.of(models, linkTo(methodOn(CourseController.class).allCourses()).withSelfRel());
    }

    @GetMapping(value = "/internal/course/{id}/roles")
    public CollectionModel<EntityModel<Role>> allRoles(@PathVariable String id) {
        val euclidID = new EuclidID(id);
        val course = repository.findById(euclidID).orElseThrow(() -> new NotFoundException(Course.class, id));
        return roleAssembler.toCollectionModel(course);
    }

    @GetMapping(value = "/internal/course/{id}/role")
    public EntityModel<Role> singleRole(@RequestParam(name = "role") String string, @PathVariable String id) {
        val euclidID = new EuclidID(id);
        val course = repository.findById(euclidID).orElseThrow(() -> new NotFoundException(Course.class, id));
        val role = course.getRoleManager().getRoles().get(string);
        if (role == null) throw new NotFoundException(Role.class, string);
        return roleAssembler.toRoleModel(course, role);
    }

}

Currently, the CourseModelAssembler component is just a basic RepresentationModelAssembler that converts a Course into an EntityModel with a single link. I'm more interested in how I'm supposed to deal with Roles.

In my implementation of Roles, no data regarding the Course is stored, meaning that given a Role, I can't tell what Course it's part of.

I tried using a RepresentationModelAssembler to convert a Role into an EntityModel and likewise a RoleManager into a CollectionModel, but since RepresentationModelAssembler#toModel doesn't accept any other args, and the route to access a Role object requires the Course id (check the controller above), I can't really use it. Therefore, I made a simple class that accomplishes what I want:

@Component
@RequiredArgsConstructor
public class RoleAssembler {
    // Because this requires the course data, we can't implement the RepresentingEntityAssembler
    public CollectionModel<EntityModel<Role>> toCollectionModel(Course course) {
        return CollectionModel.of(course.getRoleManager().getRoles().values()
                .stream().map(value -> toRoleModel(course, value)).collect(Collectors.toSet()));
    }
    public EntityModel<Role> toRoleModel(Course course, Role entity) {
        return EntityModel.of(
                entity,
                linkTo(methodOn(CourseController.class).singleRole(entity.getName(), course.getId().toString())).withSelfRel()
        );
    }

}

This works fine for the time being, but this solution feels half baked, and what I really want is whether there is an actual solution built into Spring for this kind of stuff.

That being said, I'm relatively new to Spring, so your ideas are most definitely better than mine.

Thanks :)



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source