Asked  7 Months ago    Answers:  5   Viewed   187 times

Given the following working repository in our application:

public interface PersonRepository extends PagingAndSortingRepository<Person, Integer> {

}

The Repository is exposed via spring-data-rest with URI "/api/persons" and works as expected.

We now want to override the post-method of the repository in a method of a RestController:

@RestController
@RequestMapping("/persons")
public class PersonController {

@RequestMapping(value = "/**", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> savePerson(@RequestBody Person person) {
      //do something fancy
      return "it works";
}

If we post data to "/api/persons" the method of the PersonController is called but none of the methods of the PersonRepository (e.g. GET) can be accessed via rest. We constantly get an 405 error and the following exception:

org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'GET' not supported

After some playing around we found out that everything works as expected (methods of repository and controller can be called) if we change the value-property of the @RequestMapping annotation from

value="/**"

to

value="/save"

After reading this question and the linked documenation it should also work if the value-property is "/**"

 Answers

2

Finally, after upgrading to new versions of spring/spring-data/spring-data-rest everything works as expected.

Tuesday, November 9, 2021
2
new TypeReference<Page<StoryResponse>>() {}

The problem with this statement is that Jackson cannot instantiate an abstract type. You should give Jackson the information on how to instantiate Page with a concrete type. But its concrete type, PageImpl, has no default constructor or any @JsonCreators for that matter, so you can not use the following code either:

new TypeReference<PageImpl<StoryResponse>>() {}

Since you can't add the required information to the Page class, It's better to create a custom implementation for Page interface which has a default no-arg constructor, as in this answer. Then use that custom implementation in type reference, like following:

new TypeReference<CustomPageImpl<StoryResponse>>() {}

Here are the custom implementation, copied from linked question:

public class CustomPageImpl<T> extends PageImpl<T> {
    private static final long serialVersionUID = 1L;
    private int number;
    private int size;
    private int totalPages;
    private int numberOfElements;
    private long totalElements;
    private boolean previousPage;
    private boolean firstPage;
    private boolean nextPage;
    private boolean lastPage;
    private List<T> content;
    private Sort sort;

    public CustomPageImpl() {
        super(new ArrayList<>());
    }

    @Override
    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }

    @Override
    public int getSize() {
        return size;
    }

    public void setSize(int size) {
        this.size = size;
    }

    @Override
    public int getTotalPages() {
        return totalPages;
    }

    public void setTotalPages(int totalPages) {
        this.totalPages = totalPages;
    }

    @Override
    public int getNumberOfElements() {
        return numberOfElements;
    }

    public void setNumberOfElements(int numberOfElements) {
        this.numberOfElements = numberOfElements;
    }

    @Override
    public long getTotalElements() {
        return totalElements;
    }

    public void setTotalElements(long totalElements) {
        this.totalElements = totalElements;
    }

    public boolean isPreviousPage() {
        return previousPage;
    }

    public void setPreviousPage(boolean previousPage) {
        this.previousPage = previousPage;
    }

    public boolean isFirstPage() {
        return firstPage;
    }

    public void setFirstPage(boolean firstPage) {
        this.firstPage = firstPage;
    }

    public boolean isNextPage() {
        return nextPage;
    }

    public void setNextPage(boolean nextPage) {
        this.nextPage = nextPage;
    }

    public boolean isLastPage() {
        return lastPage;
    }

    public void setLastPage(boolean lastPage) {
        this.lastPage = lastPage;
    }

    @Override
    public List<T> getContent() {
        return content;
    }

    public void setContent(List<T> content) {
        this.content = content;
    }

    @Override
    public Sort getSort() {
        return sort;
    }

    public void setSort(Sort sort) {
        this.sort = sort;
    }

    public Page<T> pageImpl() {
        return new PageImpl<>(getContent(), new PageRequest(getNumber(),
                getSize(), getSort()), getTotalElements());
    }
}
Tuesday, June 29, 2021
 
tpow
 
2

Make sure your proxy is adding X-Forwarded-Host: proxy.com header to the request that is passed to backend.com. Then Spring Hateoas will automatically generate link hrefs with proxy.com.

X-Forwarded-Host can contain port.

Also see other X-Forwarded-* headers, which are supported too.

Thursday, July 1, 2021
 
3

The reason for that is pretty simple: the relation names for associated entities are derived from the property names of the containing class. So both PersonDetails and PersonChildren want to create an outbound link to a Person named person. If we rendered that, it would look something like this

{ _links : { 
   person : { href : … }, <- the one from PersonDetails
   person : { href : … }  <- the one from PersonChildren
}

This is of course invalid. Also, lining up the two links in an array would not allow you to distinguish between the two links anymore (which one is coming from PersonDetails and which one is coming from PersonChildren.

So there are a few options here:

  1. Manually name the relations of those types. You can annotate the Person properties with @RestResource and configure the rel attribute of the annotation to something different than person.
  2. You want any of the two not exported at all. The very same annotation can be used to prevent the link from being rendered at all. Simply set the exported flag in @RestResource to false and the link will not be rendered. This might be useful if the pointer e.g. from PersonDetails is just relevant within the code, but actually not in a JSON representation.
Friday, August 13, 2021
 
Puneet
 
4

Is there any way to prevent Spring Data REST from creating a /search URLs for overridden repository methods?

I found following trick to solve this issue:

@Override
default Page<Employee> findAll(Pageable pageable) {
    return findBy(pageable);
}

@RestResource(exported = false)
Page<Employee> findBy(Pageable pageable);

More other this trick allows you to set default sort order for 'get all records' request:

@Override
default Page<Employee> findAll(Pageable p) {
    if (p.getSort() == null) {      
        // The default sort order
        return findBy(new PageRequest(p.getPageNumber(), p.getPageSize(), Sort.Direction.DESC, "myField"));
    }
    return findBy(pageable);
}

Enjoy! ))


@RestResource(exported=false) just for overridden method will not help 'cause this blocks GET 'all records' request (

Monday, October 25, 2021
 
Angolao
 
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :  
Share