Defining a resource assembler for a REST Spring HATEOAS controller
Asked Answered
D

1

10

I'm trying to add HATEOAS links to a JSON resource served by a Spring REST controller.

I see I should use a resource assembler as described at https://github.com/spring-projects/spring-hateoas

The example displays a Person class and a PersonResource class.

I understand the PersonResource class is defined as:

public class PersonResource extends ResourceSupport {
}

What is then the Person class ? Is it a data domain class ?

In my case, I have defined an Admin class that is a REST domain class, and I specified it as having resource support:

public class Admin extends ResourceSupport {

    private String firstname;
    private String lastname;
    private String email;
    private String login;
    private String password;
    private String passwordSalt;

    public Admin() {
    }

    public String getFirstname() {
        return this.firstname;
    }

    public void setFirstname(String firstname) {
        this.firstname = firstname;
    }

    public String getLastname() {
        return this.lastname;
    }

    public void setLastname(String lastname) {
        this.lastname = lastname;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getLogin() {
        return this.login;
    }

    public void setLogin(String login) {
        this.login = login;
    }

    public String getPassword() {
        return this.password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getPasswordSalt() {
        return passwordSalt;
    }

    public void setPasswordSalt(String passwordSalt) {
        this.passwordSalt = passwordSalt;
    }

    public EventAdmin toEventAdmin() {
        EventAdmin eventAdmin = new EventAdmin();

        BeanUtils.copyProperties(this, eventAdmin);

        return eventAdmin;
    }

    public static Admin fromEventAdmin(EventAdmin eventAdmin) {
        Admin admin = new Admin();

        BeanUtils.copyProperties(eventAdmin, admin);

        return admin;
    }

}

My REST controller sees only this Admin class as it is a REST domain class. It does not know, and should not know, of anything data domain class.

So I wonder how to use the resource assembler support here.

I don't understand why I should have an additional data domain Admin class here.

kind Regards,

Following Mike's answer here is how my controller now looks like:

@RequestMapping(method = RequestMethod.POST, produces = "application/json; charset=utf-8")
@ResponseBody
public ResponseEntity<Admin> add(@RequestBody Admin admin, UriComponentsBuilder builder) {
    AdminCreatedEvent adminCreatedEvent = adminService.add(new CreateAdminEvent(admin.toEventAdmin()));
    HttpHeaders responseHeaders = new HttpHeaders();
    responseHeaders.add("Content-Type", "application/json; charset=utf-8");
    responseHeaders.setLocation(builder.path("/admin/{id}").buildAndExpand(adminCreatedEvent.getAdminId()).toUri());
    Admin createdAdmin = adminResourceAssembler.toResource(adminCreatedEvent.getEventAdmin());
    ResponseEntity<Admin> responseEntity = new ResponseEntity<Admin>(createdAdmin, responseHeaders, HttpStatus.CREATED);
    return responseEntity;
}

Before, instead of using the resource assembler I was doing a:

Admin createdAdmin = Admin.fromEventAdmin(adminCreatedEvent.getEventAdmin());
createdAdmin.add(linkTo(methodOn(AdminController.class).add(createdAdmin, builder)).withSelfRel());

But it was not giving me the resource id in the url.

Domitiladomonic answered 7/11, 2013 at 8:40 Comment(0)
K
9

Your ResourceAssembler implementation needs to know about both the data domain class and the REST domain class, because its job is to convert the former to the latter.

If you want to keep knowledge of your data classes out of your controller, you could make a resource conversion service which would retrieve the data from the repo and use a ResourceAssembler to turn it into resources that the controller can know about.

@Component
public class AdminResourceAssembler extends ResourceAssemblerSupport<Admin, AdminResource> {
    public AdminResourceAssembler() {
        super(AdminController.class, AdminResource.class);
    }

    public AdminResource toResource(Admin admin) {
        AdminResource adminResource = createResourceWithId(admin.getId(), admin); // adds a "self" link
        // TODO: copy properties from admin to adminResource
        return adminResource;
    }
}

@Service
public class AdminResourceService {
    @Inject private AdminRepository adminRepository;
    @Inject private AdminResourceAssembler adminResourceAssembler;

    @Transactional
    public AdminResource findOne(Long adminId) {
        Admin admin = adminRepository.findOne(adminId);
        AdminResource adminResource = adminResourceAssembler.toResource(admin);
        return adminResource;
    }
}

@Controller
@RequestMapping("/admins")
public class AdminController {
    @Inject private AdminResourceService adminResourceService;

    @RequestMapping(value="/{adminId}", method=RequestMethod.GET)
    public HttpEntity<AdminResource> findOne(@PathVariable("adminId") Long adminId) {
        AdminResource adminResource = adminResourceService.findOne(adminId);
        return new ReponseEntity<>(adminResource, HttpStatus.OK);
    }
}
Kimi answered 18/11, 2013 at 19:28 Comment(4)
The fact is that I'm also already trying to decouple my data domain from the REST domain with the use of events acting as communication ports with the core business application. I wonder if that is not overkill. Anyway, it worked fine with the following in my controller: Admin createdAdmin = adminResourceAssembler.toResource(adminCreatedEvent.getEventAdmin());Domitiladomonic
I now wonder how to have the links of a sub resource. For now, inside my main resource, the admin resource does not have its links, since the toResource method of the assembler only provided the links for the child main resource.Domitiladomonic
Spring Data REST does that, and PersistentEntityResourceAssembler may be a good place to start figuring out how that part works.Kimi
The service class should be unaware of xxxResource, because a resource is a representational aspect belonging to the controller layer only.Fenland

© 2022 - 2024 — McMap. All rights reserved.