Search code examples
spring-data-restspring-hateoas

How to extend existing links to resources with custom Spring Data Rest Controller


I'm using Spring boot 1.5.10 with spring-boot-starter-data-rest.

I created domain entities:

@Entity
public class User {

    @Column
    private long id;

    @Column
    private String firstName;

    @Column
    private String secondName;

    @Column
    private int age;

    @Fetch(FetchMode.SELECT)
    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    private Address address;

    ...

}
@Entity
public class Address {

    @Column
    private long id;

    @Column
    private String city;

    @Column
    private String street;

    @Column
    private String build;

    ...

}

And I've created repositories for this domain entities:

interface UserRepository extends JpaRepository<User, String> {

    User getUserByFirstNameAndSecondName(String firstName, String secondName);

    @RestResource(exported = false)
    User getUserByAddress(Address address);


}
interface AddressRepository extends JpaRepository<Address, String> {

    Address getAddressByStreet(String street);

}

After that I give next HATEOAS root endpoints:

{
  "_links" : {
    "users" : {
      "href" : "http://localhost:8080/api/v1/users{?page,size,sort}",
      "templated" : true
    },
    "address" : {
      "href" : "http://localhost:8080/api/v1/address{?page,size,sort}",
      "templated" : true
    }
}

And if I navigate to http://localhost:8080/api/v1/users I'll get something like that:

{
  "_links" : {
    "getUserByFirstNameAndSecondName" : {
      "href" : "http://localhost:8080/api/v1/users/search/getUserByFirstNameAndSecondName{?firstName, secondName ,projection}",
      "templated" : true
    },
    "self" : {
      "href" : "http://localhost:8081/api/v1/users/search"
    }
  }
}

But I want to add a new endpoint to "http://localhost:8080/api/v1/users/search" use a custom controller, with @RepositoryRestController, for example:

@RepositoryRestController
public class UserRestController {

    private UserRepository userRepository;

    @Autowired
    public UserRestController(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @RequestMapping("/users/search/getUserByStreetAddress")
    public User getUserByStreetAddress(String street) {

        Address address = new Address();
        address.setStreet(street);

        return userRepository.getUserByAddress(address);
    }

}

But my controller doesn't add anything to existing endpoints. I've read How to add links to root resource in Spring Data REST? and this decision works fine for root endpoints but I want add getUserByStreetAddress endpoint to users/search and eventually get the following:

{
  "_links" : {
    "getUserByFirstNameAndSecondName" : {
      "href" : "http://localhost:8080/api/v1/users/search/getUserByFirstNameAndSecondName{?firstName, secondName ,projection}",
      "templated" : true
    },

    //Method from my custom controller added to http://localhost:8080/api/v1/users/search" endpoint
    "getUserByStreetAddress" : {
      "href" : "http://localhost:8080/api/v1/users/search/getUserByStreetAddress{?street ,projection}",
      "templated" : true
    },
    "self" : {
      "href" : "http://localhost:8080/api/v1/users/search"
    }
  }
}

Is it possible to do this?


Solution

  • You need to implement a ResourceProcessor<RepositorySearchesResource> and add the link manually.

    // other imports here
    import static org.springframework.hateoas.TemplateVariable.VariableType.REQUEST_PARAM;
    
    
    @Component
    public class RepositorySearchesResourceProcessor implements ResourceProcessor<RepositorySearchesResource> {
    
        @Autowired
        private RepositoryRestConfiguration restConfiguration;
    
        @Override
        public RepositorySearchesResource process(RepositorySearchesResource resource) {
            // early exit if we're not dealing with a User resource
            if (!User.class.equals(resource.getDomainType())) {
                return resource;
            }
    
            // add a custom link to /users/search
            String search = resource.getId().getHref();
    
            List<TemplateVariable> list = new ArrayList<>;
            list.add(new TemplateVariable("street", REQUEST_PARAM);
            boolean addProjection = restConfiguration.getProjectionConfiguration().hasProjectionFor(Parameter.class);
            if (addProjection) {
                // should be "projection" unless you configured it differently
                list.add(restConfiguration.getProjectionConfiguration().getParameterName());
            }
    
            TemplateVariables tvs = new TemplateVariables(list);
    
            Link link = new Link(new UriTemplate(search + "/getUserByStreetAddress", tvs"), "getUserByStreetAddress");
            resource.add(link);
    
            return resource;
    }
    

    I put in the RepositoryRestConfiguration and UriTemplate, so as to give a hint about adding other rest parameters (pagination, sorting...) later if necessary.