Search code examples
javaspringspring-bootspring-hateoas

Migrate Spring hateos ResourceAssembler to RepresentationModelAssembler


According to this post ResourceAssembler is changed to RepresentationModelAssembler

I have this code which is using Spring HATEOAS 1.0:

import org.springframework.hateoas.ResourceAssembler;

public class BaseAssembler<T extends BaseTransaction, D extends BaseResource>
        implements ResourceAssembler<T, D> {
        ...
}

After migration to implementation 'org.springframework.boot:spring-boot-starter-hateoas:2.6.4' I changed it to:

public class BaseAssembler<T extends BaseTransaction, D extends BaseResource>
        implements RepresentationModelAssembler<T, D> {
        .........
}

But I get error:

Type parameter 'D' is not within its bound; should extend 'org.springframework.hateoas.RepresentationModel<?>'

Do you know how I can fix this issue?


Solution

  • The compiler is reporting that the type parameter D is not within its bound in your definition:

    public class BaseAssembler<T extends BaseTransaction, D extends BaseResource>
            implements RepresentationModelAssembler<T, D> {
            .........
    }
    

    In other words, it means that you cannot use D extends BaseResource to implement RepresentationModelAssembler<T, D> (note the type parameter D here) because that type should extend 'org.springframework.hateoas.RepresentationModel<?>'.

    RepresentationModelAssembler gives you the ability to convert between domain types, your entities, to RepresentationModels, a based class conceived to enrich your DTOs to collect links.

    It is defined as follows:

    public interface RepresentationModelAssembler<T, D extends RepresentationModel<?>>
    

    Note again the definition of the type parameter D.

    In your code you need to use something like:

    public class BaseAssembler<T extends BaseTransaction, D extends RepresentationModel<?>>
            implements RepresentationModelAssembler<T, D> {
            .........
    }
    

    Please, consider read for instance some this or this other article, they provide a great variety of examples and uses cases about showcasing how you can implement the desired behavior.

    For example, given the following entity, extracted from one of the cited articles:

    @Entity
    public class Director {
       @Id
       @GeneratedValue
       @Getter
       private Long id;
       @Getter
       private String firstname;
       @Getter
       private String lastname;
       @Getter
       private int year;
       @OneToMany(mappedBy = "director")
       private Set<Movie> movies;
    }
    

    And the following DTO:

    @Builder
    @Getter
    @EqualsAndHashCode(callSuper = false)
    @Relation(itemRelation = "director", collectionRelation = "directors")
    public class DirectorRepresentation extends RepresentationModel<DirectorRepresentation> {
       private final String id;
       private final String firstname;
       private final String lastname;
       private final int year;
    }
    

    Your RepresentationModelAssembler will look like:

    @Component
    public class DirectorRepresentationAssembler implements RepresentationModelAssembler<Director, DirectorRepresentation> {
        @Override
        public DirectorRepresentation toModel(Director entity) {
            DirectorRepresentation directorRepresentation = DirectorRepresentation.builder()
                    .id(entity.getId())
                    .firstname(entity.getFirstname())
                    .lastname(entity.getLastname())
                    .year(entity.getYear())
                    .build();
     
            directorRepresentation.add(linkTo(methodOn(DirectorController.class).getDirectorById(directorRepresentation.getId())).withSelfRel());
            directorRepresentation.add(linkTo(methodOn(DirectorController.class).getDirectorMovies(directorRepresentation.getId())).withRel("directorMovies"));
     
            return directorRepresentation;
        }
     
        @Override
        public CollectionModel<DirectorRepresentation> toCollectionModel(Iterable<? extends Director> entities) {
            CollectionModel<DirectorRepresentation> directorRepresentations = RepresentationModelAssembler.super.toCollectionModel(entities);
     
            directorRepresentations.add(linkTo(methodOn(DirectorController.class).getAllDirectors()).withSelfRel());
     
            return directorRepresentations;
        }
    }
    

    In terms of your interfaces and object model:

    @Entity
    public class Director extends BaseTransaction{
       @Id
       @GeneratedValue
       @Getter
       private Long id;
       @Getter
       private String firstname;
       @Getter
       private String lastname;
       @Getter
       private int year;
       @OneToMany(mappedBy = "director")
       private Set<Movie> movies;
    }
    
    public class DirectorRepresentationAssembler
      extends BaseAssembler<Director, DirectorRepresentation>
      implements RepresentationModelAssembler<Director, DirectorRepresentation> {
      //... the above code
    }
    

    DirectorRepresentation is the same as presented above.

    The Spring HATEOAS reference guide itself provides some guidance as well about the changes performed in Spring HATEOAS 1.0 and about how to migrate from the previous version. It even includes a script that may be of help.

    In any case, as indicated above, in your use case you only need to modify the BaseAssembler interface to be defined in terms of the type D extends RepresentationModel<?>; then try relating in some way BaseResource to RepresentationModel or get rid of BaseResources and use RepresentationModels instead.

    For example, you couild try defining BaseResource as follows:

    public class BaseResource extends RepresentationModel<BaseResource>{
      // your implementation
    }
    

    Then, the bound will be right:

    public class BaseAssembler<T extends BaseTransaction, D extends BaseResource>
        implements RepresentationModelAssembler<T, D> {
      // your implementation
    }
    

    With these changes, DirectorRepresentation will extend BaseResource:

    public class DirectorRepresentation extends BaseResource {
    
    }
    

    And you can extend BaseAssembler like this:

    public class DirectorRepresentationAssembler
        extends BaseAssembler<Director, DirectorRepresentation>
        implements RepresentationModelAssembler<Director, DirectorRepresentation> {
      // your implementation
    }
    

    In my opinion, the code you published in your repository is mostly fine. I think the only problem is in this line of code, as I mentioned before, I think you need to provide the type parameter when defining your BaseResource class. For instance:

    package com.hateos.test.entity.web.rest.resource;
    
    import com.fasterxml.jackson.annotation.JsonProperty;
    import io.swagger.annotations.ApiModelProperty;
    import org.joda.time.DateTime;
    import org.springframework.hateoas.RepresentationModel;
    
    import java.util.UUID;
    
    public class BaseResource extends RepresentationModel<BaseResource> {
    
      @JsonProperty
      @ApiModelProperty(position = 1, required = true)
      public UUID id;
    
      @JsonProperty
      public DateTime creationTime;
    
      @JsonProperty
      public DateTime lastUpdatedTime;
    
    }
    

    Please, note the inclusion of the code fragment RepresentationModel<BaseResource> after the extends keyword.

    I am not sure if it will work but at least with this change every compiles fine and it seems to work properly.