Spring Data Rest - Repository inheritance creates strange search endpoints












1















Based on a different thread here at stackoverflow, I am trying to implement a soft deletion behavior with Spring Data Rest. Basically, many of the JPA queries need to be overwritten using the @Query annotation. It all works well when I use @Query and all the @PreAuthorize, @PostFilter, etc annotation on my actual repository, but I wanted to generalize the soft deletion in my own repository type from which I wanted to derive those repositories that get exported via Spring Data Rest.



Here is what I did:
1) BaseEntity so that the @Query annotations in SoftDeleteRepository know how to identify a entity type
2) SoftDeletable to have a contract of how the soft deletion flag is available
3) The SoftDeletionRepository that puts all the @Query annotations to the methods
4) The TrainingRequestRepository extends SoftDeletionRepository, adds security annotations and is then exported by Spring Data Rest.



public interface BaseEntity {
public Long getId();
public void setId(Long id);
}

public interface SoftDeletable {
public Boolean getDeleted();
public void setDeleted(Boolean deleted);
}

@RepositoryRestResource
public interface SoftDeleteRepository<T extends BaseEntity & SoftDeletable, I extends Serializable> extends CrudRepository<T, I> {

@Query("update #{#entityName} e set e.deleted = true where e.id = ?#{#request.id}")
@Modifying
@Override
public void delete(@Param("request") T entity);

@Transactional
@Query("update #{#entityName} e set e.deleted = true where e.id = ?1")
@Modifying
@Override
public void deleteById(I id);

@Query("update #{#entityName} e set e.deleted = true")
@Transactional
@Modifying
@Override
public void deleteAll();

@Query("select e from #{#entityName} e where e.deleted = false")
@Override
public Iterable<T> findAll();

@Transactional(readOnly = true)
@Query("select e from #{#entityName} e where e.id in ?1 and e.deleted = false")
@Override
public Iterable<T> findAllById(Iterable<I> requests);

@Transactional(readOnly = true)
@Query("select e from #{#entityName} e where e.id = ?1 and e.deleted = false")
@Override
public Optional<T> findById(@Param("id") I id);

@Transactional(readOnly = true)
@Query("select e from #{#entityName} e where e.deleted = true")
public Iterable<T> findDeleted();

@Override
@Transactional(readOnly = true)
@Query("select count(e) from #{#entityName} e where e.deleted = false")
public long count();

}

@RepositoryRestResource
public interface TrainingRequestRepository extends SoftDeleteRepository<TrainingRequest, Long> {

@PreAuthorize("hasAuthority('ADMIN') or principal.company.id == #request.owner.id")
@Override
public void delete(@Param("request") TrainingRequest request);

@PreAuthorize("hasAuthority('ADMIN') or requests.?[owner.id != principal.company.id].empty")
@Override
public void deleteAll(Iterable<? extends TrainingRequest> entities);

@PreAuthorize("hasAuthority('ADMIN') or @companyService.isOwnerOfRequest(id, principal)")
@Override
public void deleteById(Long id);

@PreAuthorize("hasAuthority('ADMIN')")
@Override
public void deleteAll();

@PreAuthorize("isFullyAuthenticated()")
@PostFilter("hasAuthority('ADMIN') or hasAuthority('TRAINER') or filterObject.owner.id == principal.company.id")
@Override
public Iterable<TrainingRequest> findAll();

@PreAuthorize("isFullyAuthenticated()")
@PostFilter("hasAuthority('ADMIN') or hasAuthority('TRAINER') or !filterObject.owner.?[id == #root.principal.company.id].empty")
@Override
public Iterable<TrainingRequest> findAllById(Iterable<Long> requests);

@PreAuthorize("isFullyAuthenticated()")
@PostAuthorize("hasAuthority('ADMIN') or hasAuthority('TRAINER') or @ownershipValidator.isOwnerOf(principal.company, returnObject.orElse(null))")
@Override
public Optional<TrainingRequest> findById(@Param("id") Long id);

@PreAuthorize("isFullyAuthenticated()")
@PostFilter("hasAuthority('ADMIN') or hasAuthority('TRAINER') or filterObject.owner.id == principal.company.id")
@Query("select e from #{#entityName} e where e.deleted = true")
public Iterable<TrainingRequest> findDeleted();

@PreAuthorize("hasAuthority('ADMIN') or (requests.?[id != null].empty or requests.?[owner.id != principal.owner.id].empty)")
@Override
public <S extends TrainingRequest> Iterable<S> saveAll(Iterable<S> requests);

@PreAuthorize("hasAuthority('ADMIN') or (hasAuthority('CUSTOMER') and (#request.id == null or #request.owner.id == principal.owner.id))")
@Override
public <S extends TrainingRequest> S save(@Param("request") S request);

}


It all works nice and well! I can delete instances using HTTP DELETE and I can verify that only the "deleted" flag is changed in the database. Even the security annotations are honored so that we can conclude that the annotations in both repos (parent and child) become effective.



BUT: When I hit the /search endpoint of the repository, I can see endpoints for all methods mentioned in the repos. I looks like all methods from TrainingRequestRepository are listed as search endpoints:



curl -s -XGET -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" http://localhost:2222/trainingRequests/search
{
"_links" : {
"findById" : {
"href" : "http://localhost:2222/trainingRequests/search/findById{?id}",
"templated" : true
},
"deleteById" : {
"href" : "http://localhost:2222/trainingRequests/search/deleteById{?id}",
"templated" : true
},
"count" : {
"href" : "http://localhost:2222/trainingRequests/search/count"
},
"delete" : {
"href" : "http://localhost:2222/trainingRequests/search/delete{?request}",
"templated" : true
},
"findAllById" : {
"href" : "http://localhost:2222/trainingRequests/search/findAllById{?requests}",
"templated" : true
},
"findAll" : {
"href" : "http://localhost:2222/trainingRequests/search/findAll"
},
"deleteAll" : {
"href" : "http://localhost:2222/trainingRequests/search/deleteAll"
},
"findOwn" : {
"href" : "http://localhost:2222/trainingRequests/search/findOwn"
},
"findByOwner" : {
"href" : "http://localhost:2222/trainingRequests/search/findByOwner{?owner}",
"templated" : true
},
"findForeign" : {
"href" : "http://localhost:2222/trainingRequests/search/findForeign"
},
"findByTraining" : {
"href" : "http://localhost:2222/trainingRequests/search/findByTraining{?training}",
"templated" : true
},
"findDeleted" : {
"href" : "http://localhost:2222/trainingRequests/search/findDeleted"
},
"self" : {
"href" : "http://localhost:2222/trainingRequests/search"
}
}
}


If anyone could point me in the direction, that would be great!



EDIT: The question is: Why am I seeing methods like findAll, delete, deleteAll, etc in the /trainingRequests/search endpoint while only findDeleted, findByTraining, findForeign, findByOwner, findOwn should be in the list. Without the SoftDeletionRepository as a parent to TrainingRequestRepository, those are not in the list as it should be.










share|improve this question

























  • What, exactly, is your question because I don't see one in what you have posted?

    – Alan Hay
    Nov 16 '18 at 12:06











  • I edited above to hopefully clearify

    – user3235738
    Nov 16 '18 at 12:16











  • Is it not exactly clear to me why only a subset of the exposed endpoints would be expected to appear in the list.

    – Alan Hay
    Nov 16 '18 at 15:10











  • Ok, so here's my take on SDR as I understood it so far: SDR takes a JPA Repository and builds REST endpoints to it. There is a mapping that links certain HTTP requests to JPA repo methods. Example: A GET /foo/5 goes to findById(Long), etc In addition, there's a /search endpoint for each repo (/foo/search) but it's empty as long as I don't extend the repo with custom query methods (docs.spring.io/spring-data/jpa/docs/current/reference/html/…). In the example above, we see methods that a not custom query methods in the /search. Does it make sense now?

    – user3235738
    Nov 16 '18 at 15:31













  • The documentation notes that "All query method resources are exposed under the search resource". If you want to hide, or customize the links you can use @RepositoryRestResource annotation on the methods.

    – Alan Hay
    Nov 16 '18 at 15:48


















1















Based on a different thread here at stackoverflow, I am trying to implement a soft deletion behavior with Spring Data Rest. Basically, many of the JPA queries need to be overwritten using the @Query annotation. It all works well when I use @Query and all the @PreAuthorize, @PostFilter, etc annotation on my actual repository, but I wanted to generalize the soft deletion in my own repository type from which I wanted to derive those repositories that get exported via Spring Data Rest.



Here is what I did:
1) BaseEntity so that the @Query annotations in SoftDeleteRepository know how to identify a entity type
2) SoftDeletable to have a contract of how the soft deletion flag is available
3) The SoftDeletionRepository that puts all the @Query annotations to the methods
4) The TrainingRequestRepository extends SoftDeletionRepository, adds security annotations and is then exported by Spring Data Rest.



public interface BaseEntity {
public Long getId();
public void setId(Long id);
}

public interface SoftDeletable {
public Boolean getDeleted();
public void setDeleted(Boolean deleted);
}

@RepositoryRestResource
public interface SoftDeleteRepository<T extends BaseEntity & SoftDeletable, I extends Serializable> extends CrudRepository<T, I> {

@Query("update #{#entityName} e set e.deleted = true where e.id = ?#{#request.id}")
@Modifying
@Override
public void delete(@Param("request") T entity);

@Transactional
@Query("update #{#entityName} e set e.deleted = true where e.id = ?1")
@Modifying
@Override
public void deleteById(I id);

@Query("update #{#entityName} e set e.deleted = true")
@Transactional
@Modifying
@Override
public void deleteAll();

@Query("select e from #{#entityName} e where e.deleted = false")
@Override
public Iterable<T> findAll();

@Transactional(readOnly = true)
@Query("select e from #{#entityName} e where e.id in ?1 and e.deleted = false")
@Override
public Iterable<T> findAllById(Iterable<I> requests);

@Transactional(readOnly = true)
@Query("select e from #{#entityName} e where e.id = ?1 and e.deleted = false")
@Override
public Optional<T> findById(@Param("id") I id);

@Transactional(readOnly = true)
@Query("select e from #{#entityName} e where e.deleted = true")
public Iterable<T> findDeleted();

@Override
@Transactional(readOnly = true)
@Query("select count(e) from #{#entityName} e where e.deleted = false")
public long count();

}

@RepositoryRestResource
public interface TrainingRequestRepository extends SoftDeleteRepository<TrainingRequest, Long> {

@PreAuthorize("hasAuthority('ADMIN') or principal.company.id == #request.owner.id")
@Override
public void delete(@Param("request") TrainingRequest request);

@PreAuthorize("hasAuthority('ADMIN') or requests.?[owner.id != principal.company.id].empty")
@Override
public void deleteAll(Iterable<? extends TrainingRequest> entities);

@PreAuthorize("hasAuthority('ADMIN') or @companyService.isOwnerOfRequest(id, principal)")
@Override
public void deleteById(Long id);

@PreAuthorize("hasAuthority('ADMIN')")
@Override
public void deleteAll();

@PreAuthorize("isFullyAuthenticated()")
@PostFilter("hasAuthority('ADMIN') or hasAuthority('TRAINER') or filterObject.owner.id == principal.company.id")
@Override
public Iterable<TrainingRequest> findAll();

@PreAuthorize("isFullyAuthenticated()")
@PostFilter("hasAuthority('ADMIN') or hasAuthority('TRAINER') or !filterObject.owner.?[id == #root.principal.company.id].empty")
@Override
public Iterable<TrainingRequest> findAllById(Iterable<Long> requests);

@PreAuthorize("isFullyAuthenticated()")
@PostAuthorize("hasAuthority('ADMIN') or hasAuthority('TRAINER') or @ownershipValidator.isOwnerOf(principal.company, returnObject.orElse(null))")
@Override
public Optional<TrainingRequest> findById(@Param("id") Long id);

@PreAuthorize("isFullyAuthenticated()")
@PostFilter("hasAuthority('ADMIN') or hasAuthority('TRAINER') or filterObject.owner.id == principal.company.id")
@Query("select e from #{#entityName} e where e.deleted = true")
public Iterable<TrainingRequest> findDeleted();

@PreAuthorize("hasAuthority('ADMIN') or (requests.?[id != null].empty or requests.?[owner.id != principal.owner.id].empty)")
@Override
public <S extends TrainingRequest> Iterable<S> saveAll(Iterable<S> requests);

@PreAuthorize("hasAuthority('ADMIN') or (hasAuthority('CUSTOMER') and (#request.id == null or #request.owner.id == principal.owner.id))")
@Override
public <S extends TrainingRequest> S save(@Param("request") S request);

}


It all works nice and well! I can delete instances using HTTP DELETE and I can verify that only the "deleted" flag is changed in the database. Even the security annotations are honored so that we can conclude that the annotations in both repos (parent and child) become effective.



BUT: When I hit the /search endpoint of the repository, I can see endpoints for all methods mentioned in the repos. I looks like all methods from TrainingRequestRepository are listed as search endpoints:



curl -s -XGET -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" http://localhost:2222/trainingRequests/search
{
"_links" : {
"findById" : {
"href" : "http://localhost:2222/trainingRequests/search/findById{?id}",
"templated" : true
},
"deleteById" : {
"href" : "http://localhost:2222/trainingRequests/search/deleteById{?id}",
"templated" : true
},
"count" : {
"href" : "http://localhost:2222/trainingRequests/search/count"
},
"delete" : {
"href" : "http://localhost:2222/trainingRequests/search/delete{?request}",
"templated" : true
},
"findAllById" : {
"href" : "http://localhost:2222/trainingRequests/search/findAllById{?requests}",
"templated" : true
},
"findAll" : {
"href" : "http://localhost:2222/trainingRequests/search/findAll"
},
"deleteAll" : {
"href" : "http://localhost:2222/trainingRequests/search/deleteAll"
},
"findOwn" : {
"href" : "http://localhost:2222/trainingRequests/search/findOwn"
},
"findByOwner" : {
"href" : "http://localhost:2222/trainingRequests/search/findByOwner{?owner}",
"templated" : true
},
"findForeign" : {
"href" : "http://localhost:2222/trainingRequests/search/findForeign"
},
"findByTraining" : {
"href" : "http://localhost:2222/trainingRequests/search/findByTraining{?training}",
"templated" : true
},
"findDeleted" : {
"href" : "http://localhost:2222/trainingRequests/search/findDeleted"
},
"self" : {
"href" : "http://localhost:2222/trainingRequests/search"
}
}
}


If anyone could point me in the direction, that would be great!



EDIT: The question is: Why am I seeing methods like findAll, delete, deleteAll, etc in the /trainingRequests/search endpoint while only findDeleted, findByTraining, findForeign, findByOwner, findOwn should be in the list. Without the SoftDeletionRepository as a parent to TrainingRequestRepository, those are not in the list as it should be.










share|improve this question

























  • What, exactly, is your question because I don't see one in what you have posted?

    – Alan Hay
    Nov 16 '18 at 12:06











  • I edited above to hopefully clearify

    – user3235738
    Nov 16 '18 at 12:16











  • Is it not exactly clear to me why only a subset of the exposed endpoints would be expected to appear in the list.

    – Alan Hay
    Nov 16 '18 at 15:10











  • Ok, so here's my take on SDR as I understood it so far: SDR takes a JPA Repository and builds REST endpoints to it. There is a mapping that links certain HTTP requests to JPA repo methods. Example: A GET /foo/5 goes to findById(Long), etc In addition, there's a /search endpoint for each repo (/foo/search) but it's empty as long as I don't extend the repo with custom query methods (docs.spring.io/spring-data/jpa/docs/current/reference/html/…). In the example above, we see methods that a not custom query methods in the /search. Does it make sense now?

    – user3235738
    Nov 16 '18 at 15:31













  • The documentation notes that "All query method resources are exposed under the search resource". If you want to hide, or customize the links you can use @RepositoryRestResource annotation on the methods.

    – Alan Hay
    Nov 16 '18 at 15:48
















1












1








1








Based on a different thread here at stackoverflow, I am trying to implement a soft deletion behavior with Spring Data Rest. Basically, many of the JPA queries need to be overwritten using the @Query annotation. It all works well when I use @Query and all the @PreAuthorize, @PostFilter, etc annotation on my actual repository, but I wanted to generalize the soft deletion in my own repository type from which I wanted to derive those repositories that get exported via Spring Data Rest.



Here is what I did:
1) BaseEntity so that the @Query annotations in SoftDeleteRepository know how to identify a entity type
2) SoftDeletable to have a contract of how the soft deletion flag is available
3) The SoftDeletionRepository that puts all the @Query annotations to the methods
4) The TrainingRequestRepository extends SoftDeletionRepository, adds security annotations and is then exported by Spring Data Rest.



public interface BaseEntity {
public Long getId();
public void setId(Long id);
}

public interface SoftDeletable {
public Boolean getDeleted();
public void setDeleted(Boolean deleted);
}

@RepositoryRestResource
public interface SoftDeleteRepository<T extends BaseEntity & SoftDeletable, I extends Serializable> extends CrudRepository<T, I> {

@Query("update #{#entityName} e set e.deleted = true where e.id = ?#{#request.id}")
@Modifying
@Override
public void delete(@Param("request") T entity);

@Transactional
@Query("update #{#entityName} e set e.deleted = true where e.id = ?1")
@Modifying
@Override
public void deleteById(I id);

@Query("update #{#entityName} e set e.deleted = true")
@Transactional
@Modifying
@Override
public void deleteAll();

@Query("select e from #{#entityName} e where e.deleted = false")
@Override
public Iterable<T> findAll();

@Transactional(readOnly = true)
@Query("select e from #{#entityName} e where e.id in ?1 and e.deleted = false")
@Override
public Iterable<T> findAllById(Iterable<I> requests);

@Transactional(readOnly = true)
@Query("select e from #{#entityName} e where e.id = ?1 and e.deleted = false")
@Override
public Optional<T> findById(@Param("id") I id);

@Transactional(readOnly = true)
@Query("select e from #{#entityName} e where e.deleted = true")
public Iterable<T> findDeleted();

@Override
@Transactional(readOnly = true)
@Query("select count(e) from #{#entityName} e where e.deleted = false")
public long count();

}

@RepositoryRestResource
public interface TrainingRequestRepository extends SoftDeleteRepository<TrainingRequest, Long> {

@PreAuthorize("hasAuthority('ADMIN') or principal.company.id == #request.owner.id")
@Override
public void delete(@Param("request") TrainingRequest request);

@PreAuthorize("hasAuthority('ADMIN') or requests.?[owner.id != principal.company.id].empty")
@Override
public void deleteAll(Iterable<? extends TrainingRequest> entities);

@PreAuthorize("hasAuthority('ADMIN') or @companyService.isOwnerOfRequest(id, principal)")
@Override
public void deleteById(Long id);

@PreAuthorize("hasAuthority('ADMIN')")
@Override
public void deleteAll();

@PreAuthorize("isFullyAuthenticated()")
@PostFilter("hasAuthority('ADMIN') or hasAuthority('TRAINER') or filterObject.owner.id == principal.company.id")
@Override
public Iterable<TrainingRequest> findAll();

@PreAuthorize("isFullyAuthenticated()")
@PostFilter("hasAuthority('ADMIN') or hasAuthority('TRAINER') or !filterObject.owner.?[id == #root.principal.company.id].empty")
@Override
public Iterable<TrainingRequest> findAllById(Iterable<Long> requests);

@PreAuthorize("isFullyAuthenticated()")
@PostAuthorize("hasAuthority('ADMIN') or hasAuthority('TRAINER') or @ownershipValidator.isOwnerOf(principal.company, returnObject.orElse(null))")
@Override
public Optional<TrainingRequest> findById(@Param("id") Long id);

@PreAuthorize("isFullyAuthenticated()")
@PostFilter("hasAuthority('ADMIN') or hasAuthority('TRAINER') or filterObject.owner.id == principal.company.id")
@Query("select e from #{#entityName} e where e.deleted = true")
public Iterable<TrainingRequest> findDeleted();

@PreAuthorize("hasAuthority('ADMIN') or (requests.?[id != null].empty or requests.?[owner.id != principal.owner.id].empty)")
@Override
public <S extends TrainingRequest> Iterable<S> saveAll(Iterable<S> requests);

@PreAuthorize("hasAuthority('ADMIN') or (hasAuthority('CUSTOMER') and (#request.id == null or #request.owner.id == principal.owner.id))")
@Override
public <S extends TrainingRequest> S save(@Param("request") S request);

}


It all works nice and well! I can delete instances using HTTP DELETE and I can verify that only the "deleted" flag is changed in the database. Even the security annotations are honored so that we can conclude that the annotations in both repos (parent and child) become effective.



BUT: When I hit the /search endpoint of the repository, I can see endpoints for all methods mentioned in the repos. I looks like all methods from TrainingRequestRepository are listed as search endpoints:



curl -s -XGET -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" http://localhost:2222/trainingRequests/search
{
"_links" : {
"findById" : {
"href" : "http://localhost:2222/trainingRequests/search/findById{?id}",
"templated" : true
},
"deleteById" : {
"href" : "http://localhost:2222/trainingRequests/search/deleteById{?id}",
"templated" : true
},
"count" : {
"href" : "http://localhost:2222/trainingRequests/search/count"
},
"delete" : {
"href" : "http://localhost:2222/trainingRequests/search/delete{?request}",
"templated" : true
},
"findAllById" : {
"href" : "http://localhost:2222/trainingRequests/search/findAllById{?requests}",
"templated" : true
},
"findAll" : {
"href" : "http://localhost:2222/trainingRequests/search/findAll"
},
"deleteAll" : {
"href" : "http://localhost:2222/trainingRequests/search/deleteAll"
},
"findOwn" : {
"href" : "http://localhost:2222/trainingRequests/search/findOwn"
},
"findByOwner" : {
"href" : "http://localhost:2222/trainingRequests/search/findByOwner{?owner}",
"templated" : true
},
"findForeign" : {
"href" : "http://localhost:2222/trainingRequests/search/findForeign"
},
"findByTraining" : {
"href" : "http://localhost:2222/trainingRequests/search/findByTraining{?training}",
"templated" : true
},
"findDeleted" : {
"href" : "http://localhost:2222/trainingRequests/search/findDeleted"
},
"self" : {
"href" : "http://localhost:2222/trainingRequests/search"
}
}
}


If anyone could point me in the direction, that would be great!



EDIT: The question is: Why am I seeing methods like findAll, delete, deleteAll, etc in the /trainingRequests/search endpoint while only findDeleted, findByTraining, findForeign, findByOwner, findOwn should be in the list. Without the SoftDeletionRepository as a parent to TrainingRequestRepository, those are not in the list as it should be.










share|improve this question
















Based on a different thread here at stackoverflow, I am trying to implement a soft deletion behavior with Spring Data Rest. Basically, many of the JPA queries need to be overwritten using the @Query annotation. It all works well when I use @Query and all the @PreAuthorize, @PostFilter, etc annotation on my actual repository, but I wanted to generalize the soft deletion in my own repository type from which I wanted to derive those repositories that get exported via Spring Data Rest.



Here is what I did:
1) BaseEntity so that the @Query annotations in SoftDeleteRepository know how to identify a entity type
2) SoftDeletable to have a contract of how the soft deletion flag is available
3) The SoftDeletionRepository that puts all the @Query annotations to the methods
4) The TrainingRequestRepository extends SoftDeletionRepository, adds security annotations and is then exported by Spring Data Rest.



public interface BaseEntity {
public Long getId();
public void setId(Long id);
}

public interface SoftDeletable {
public Boolean getDeleted();
public void setDeleted(Boolean deleted);
}

@RepositoryRestResource
public interface SoftDeleteRepository<T extends BaseEntity & SoftDeletable, I extends Serializable> extends CrudRepository<T, I> {

@Query("update #{#entityName} e set e.deleted = true where e.id = ?#{#request.id}")
@Modifying
@Override
public void delete(@Param("request") T entity);

@Transactional
@Query("update #{#entityName} e set e.deleted = true where e.id = ?1")
@Modifying
@Override
public void deleteById(I id);

@Query("update #{#entityName} e set e.deleted = true")
@Transactional
@Modifying
@Override
public void deleteAll();

@Query("select e from #{#entityName} e where e.deleted = false")
@Override
public Iterable<T> findAll();

@Transactional(readOnly = true)
@Query("select e from #{#entityName} e where e.id in ?1 and e.deleted = false")
@Override
public Iterable<T> findAllById(Iterable<I> requests);

@Transactional(readOnly = true)
@Query("select e from #{#entityName} e where e.id = ?1 and e.deleted = false")
@Override
public Optional<T> findById(@Param("id") I id);

@Transactional(readOnly = true)
@Query("select e from #{#entityName} e where e.deleted = true")
public Iterable<T> findDeleted();

@Override
@Transactional(readOnly = true)
@Query("select count(e) from #{#entityName} e where e.deleted = false")
public long count();

}

@RepositoryRestResource
public interface TrainingRequestRepository extends SoftDeleteRepository<TrainingRequest, Long> {

@PreAuthorize("hasAuthority('ADMIN') or principal.company.id == #request.owner.id")
@Override
public void delete(@Param("request") TrainingRequest request);

@PreAuthorize("hasAuthority('ADMIN') or requests.?[owner.id != principal.company.id].empty")
@Override
public void deleteAll(Iterable<? extends TrainingRequest> entities);

@PreAuthorize("hasAuthority('ADMIN') or @companyService.isOwnerOfRequest(id, principal)")
@Override
public void deleteById(Long id);

@PreAuthorize("hasAuthority('ADMIN')")
@Override
public void deleteAll();

@PreAuthorize("isFullyAuthenticated()")
@PostFilter("hasAuthority('ADMIN') or hasAuthority('TRAINER') or filterObject.owner.id == principal.company.id")
@Override
public Iterable<TrainingRequest> findAll();

@PreAuthorize("isFullyAuthenticated()")
@PostFilter("hasAuthority('ADMIN') or hasAuthority('TRAINER') or !filterObject.owner.?[id == #root.principal.company.id].empty")
@Override
public Iterable<TrainingRequest> findAllById(Iterable<Long> requests);

@PreAuthorize("isFullyAuthenticated()")
@PostAuthorize("hasAuthority('ADMIN') or hasAuthority('TRAINER') or @ownershipValidator.isOwnerOf(principal.company, returnObject.orElse(null))")
@Override
public Optional<TrainingRequest> findById(@Param("id") Long id);

@PreAuthorize("isFullyAuthenticated()")
@PostFilter("hasAuthority('ADMIN') or hasAuthority('TRAINER') or filterObject.owner.id == principal.company.id")
@Query("select e from #{#entityName} e where e.deleted = true")
public Iterable<TrainingRequest> findDeleted();

@PreAuthorize("hasAuthority('ADMIN') or (requests.?[id != null].empty or requests.?[owner.id != principal.owner.id].empty)")
@Override
public <S extends TrainingRequest> Iterable<S> saveAll(Iterable<S> requests);

@PreAuthorize("hasAuthority('ADMIN') or (hasAuthority('CUSTOMER') and (#request.id == null or #request.owner.id == principal.owner.id))")
@Override
public <S extends TrainingRequest> S save(@Param("request") S request);

}


It all works nice and well! I can delete instances using HTTP DELETE and I can verify that only the "deleted" flag is changed in the database. Even the security annotations are honored so that we can conclude that the annotations in both repos (parent and child) become effective.



BUT: When I hit the /search endpoint of the repository, I can see endpoints for all methods mentioned in the repos. I looks like all methods from TrainingRequestRepository are listed as search endpoints:



curl -s -XGET -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" http://localhost:2222/trainingRequests/search
{
"_links" : {
"findById" : {
"href" : "http://localhost:2222/trainingRequests/search/findById{?id}",
"templated" : true
},
"deleteById" : {
"href" : "http://localhost:2222/trainingRequests/search/deleteById{?id}",
"templated" : true
},
"count" : {
"href" : "http://localhost:2222/trainingRequests/search/count"
},
"delete" : {
"href" : "http://localhost:2222/trainingRequests/search/delete{?request}",
"templated" : true
},
"findAllById" : {
"href" : "http://localhost:2222/trainingRequests/search/findAllById{?requests}",
"templated" : true
},
"findAll" : {
"href" : "http://localhost:2222/trainingRequests/search/findAll"
},
"deleteAll" : {
"href" : "http://localhost:2222/trainingRequests/search/deleteAll"
},
"findOwn" : {
"href" : "http://localhost:2222/trainingRequests/search/findOwn"
},
"findByOwner" : {
"href" : "http://localhost:2222/trainingRequests/search/findByOwner{?owner}",
"templated" : true
},
"findForeign" : {
"href" : "http://localhost:2222/trainingRequests/search/findForeign"
},
"findByTraining" : {
"href" : "http://localhost:2222/trainingRequests/search/findByTraining{?training}",
"templated" : true
},
"findDeleted" : {
"href" : "http://localhost:2222/trainingRequests/search/findDeleted"
},
"self" : {
"href" : "http://localhost:2222/trainingRequests/search"
}
}
}


If anyone could point me in the direction, that would be great!



EDIT: The question is: Why am I seeing methods like findAll, delete, deleteAll, etc in the /trainingRequests/search endpoint while only findDeleted, findByTraining, findForeign, findByOwner, findOwn should be in the list. Without the SoftDeletionRepository as a parent to TrainingRequestRepository, those are not in the list as it should be.







java spring-data-jpa spring-data-rest






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Nov 16 '18 at 12:16







user3235738

















asked Nov 16 '18 at 10:25









user3235738user3235738

4215




4215













  • What, exactly, is your question because I don't see one in what you have posted?

    – Alan Hay
    Nov 16 '18 at 12:06











  • I edited above to hopefully clearify

    – user3235738
    Nov 16 '18 at 12:16











  • Is it not exactly clear to me why only a subset of the exposed endpoints would be expected to appear in the list.

    – Alan Hay
    Nov 16 '18 at 15:10











  • Ok, so here's my take on SDR as I understood it so far: SDR takes a JPA Repository and builds REST endpoints to it. There is a mapping that links certain HTTP requests to JPA repo methods. Example: A GET /foo/5 goes to findById(Long), etc In addition, there's a /search endpoint for each repo (/foo/search) but it's empty as long as I don't extend the repo with custom query methods (docs.spring.io/spring-data/jpa/docs/current/reference/html/…). In the example above, we see methods that a not custom query methods in the /search. Does it make sense now?

    – user3235738
    Nov 16 '18 at 15:31













  • The documentation notes that "All query method resources are exposed under the search resource". If you want to hide, or customize the links you can use @RepositoryRestResource annotation on the methods.

    – Alan Hay
    Nov 16 '18 at 15:48





















  • What, exactly, is your question because I don't see one in what you have posted?

    – Alan Hay
    Nov 16 '18 at 12:06











  • I edited above to hopefully clearify

    – user3235738
    Nov 16 '18 at 12:16











  • Is it not exactly clear to me why only a subset of the exposed endpoints would be expected to appear in the list.

    – Alan Hay
    Nov 16 '18 at 15:10











  • Ok, so here's my take on SDR as I understood it so far: SDR takes a JPA Repository and builds REST endpoints to it. There is a mapping that links certain HTTP requests to JPA repo methods. Example: A GET /foo/5 goes to findById(Long), etc In addition, there's a /search endpoint for each repo (/foo/search) but it's empty as long as I don't extend the repo with custom query methods (docs.spring.io/spring-data/jpa/docs/current/reference/html/…). In the example above, we see methods that a not custom query methods in the /search. Does it make sense now?

    – user3235738
    Nov 16 '18 at 15:31













  • The documentation notes that "All query method resources are exposed under the search resource". If you want to hide, or customize the links you can use @RepositoryRestResource annotation on the methods.

    – Alan Hay
    Nov 16 '18 at 15:48



















What, exactly, is your question because I don't see one in what you have posted?

– Alan Hay
Nov 16 '18 at 12:06





What, exactly, is your question because I don't see one in what you have posted?

– Alan Hay
Nov 16 '18 at 12:06













I edited above to hopefully clearify

– user3235738
Nov 16 '18 at 12:16





I edited above to hopefully clearify

– user3235738
Nov 16 '18 at 12:16













Is it not exactly clear to me why only a subset of the exposed endpoints would be expected to appear in the list.

– Alan Hay
Nov 16 '18 at 15:10





Is it not exactly clear to me why only a subset of the exposed endpoints would be expected to appear in the list.

– Alan Hay
Nov 16 '18 at 15:10













Ok, so here's my take on SDR as I understood it so far: SDR takes a JPA Repository and builds REST endpoints to it. There is a mapping that links certain HTTP requests to JPA repo methods. Example: A GET /foo/5 goes to findById(Long), etc In addition, there's a /search endpoint for each repo (/foo/search) but it's empty as long as I don't extend the repo with custom query methods (docs.spring.io/spring-data/jpa/docs/current/reference/html/…). In the example above, we see methods that a not custom query methods in the /search. Does it make sense now?

– user3235738
Nov 16 '18 at 15:31







Ok, so here's my take on SDR as I understood it so far: SDR takes a JPA Repository and builds REST endpoints to it. There is a mapping that links certain HTTP requests to JPA repo methods. Example: A GET /foo/5 goes to findById(Long), etc In addition, there's a /search endpoint for each repo (/foo/search) but it's empty as long as I don't extend the repo with custom query methods (docs.spring.io/spring-data/jpa/docs/current/reference/html/…). In the example above, we see methods that a not custom query methods in the /search. Does it make sense now?

– user3235738
Nov 16 '18 at 15:31















The documentation notes that "All query method resources are exposed under the search resource". If you want to hide, or customize the links you can use @RepositoryRestResource annotation on the methods.

– Alan Hay
Nov 16 '18 at 15:48







The documentation notes that "All query method resources are exposed under the search resource". If you want to hide, or customize the links you can use @RepositoryRestResource annotation on the methods.

– Alan Hay
Nov 16 '18 at 15:48














1 Answer
1






active

oldest

votes


















0














The problem is that SpringDataRest automatically generates CRUD endpoints for each model, and exposes them following the HATEOS paradigm.



If you don't need this feature, just remove the SpringDataRest dependency.
[EDIT] I just re-read the question title. @RepositoryRestResource is what introduces the automatically generated endpoints, not the inheritance.[/EDIT]



If you need this features, you should configure what to expose. There is the official documentation here, and the following example taken from here.



# Exposes all public repository interfaces but considers @(Repository)RestResourceu2019s `exported flag.
spring.data.rest.detection-strategy=default

# Exposes all repositories independently of type visibility and annotations.
spring.data.rest.detection-strategy=all

# Only repositories annotated with @(Repository)RestResource are exposed, unless their exported flag is set to false.
spring.data.rest.detection-strategy=annotated

# Only public repositories annotated are exposed.
spring.data.rest.detection-strategy=visibility





share|improve this answer


























  • Maybe I am misunderstanding your point. The problem is not that Spring Data Rest exports the TrainingRequestRepository as a rest resource. The problem is that there are /search/* endpoints that should not be there.

    – user3235738
    Nov 16 '18 at 11:58














Your Answer






StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");

StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});

function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});


}
});














draft saved

draft discarded


















StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53335903%2fspring-data-rest-repository-inheritance-creates-strange-search-endpoints%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown

























1 Answer
1






active

oldest

votes








1 Answer
1






active

oldest

votes









active

oldest

votes






active

oldest

votes









0














The problem is that SpringDataRest automatically generates CRUD endpoints for each model, and exposes them following the HATEOS paradigm.



If you don't need this feature, just remove the SpringDataRest dependency.
[EDIT] I just re-read the question title. @RepositoryRestResource is what introduces the automatically generated endpoints, not the inheritance.[/EDIT]



If you need this features, you should configure what to expose. There is the official documentation here, and the following example taken from here.



# Exposes all public repository interfaces but considers @(Repository)RestResourceu2019s `exported flag.
spring.data.rest.detection-strategy=default

# Exposes all repositories independently of type visibility and annotations.
spring.data.rest.detection-strategy=all

# Only repositories annotated with @(Repository)RestResource are exposed, unless their exported flag is set to false.
spring.data.rest.detection-strategy=annotated

# Only public repositories annotated are exposed.
spring.data.rest.detection-strategy=visibility





share|improve this answer


























  • Maybe I am misunderstanding your point. The problem is not that Spring Data Rest exports the TrainingRequestRepository as a rest resource. The problem is that there are /search/* endpoints that should not be there.

    – user3235738
    Nov 16 '18 at 11:58


















0














The problem is that SpringDataRest automatically generates CRUD endpoints for each model, and exposes them following the HATEOS paradigm.



If you don't need this feature, just remove the SpringDataRest dependency.
[EDIT] I just re-read the question title. @RepositoryRestResource is what introduces the automatically generated endpoints, not the inheritance.[/EDIT]



If you need this features, you should configure what to expose. There is the official documentation here, and the following example taken from here.



# Exposes all public repository interfaces but considers @(Repository)RestResourceu2019s `exported flag.
spring.data.rest.detection-strategy=default

# Exposes all repositories independently of type visibility and annotations.
spring.data.rest.detection-strategy=all

# Only repositories annotated with @(Repository)RestResource are exposed, unless their exported flag is set to false.
spring.data.rest.detection-strategy=annotated

# Only public repositories annotated are exposed.
spring.data.rest.detection-strategy=visibility





share|improve this answer


























  • Maybe I am misunderstanding your point. The problem is not that Spring Data Rest exports the TrainingRequestRepository as a rest resource. The problem is that there are /search/* endpoints that should not be there.

    – user3235738
    Nov 16 '18 at 11:58
















0












0








0







The problem is that SpringDataRest automatically generates CRUD endpoints for each model, and exposes them following the HATEOS paradigm.



If you don't need this feature, just remove the SpringDataRest dependency.
[EDIT] I just re-read the question title. @RepositoryRestResource is what introduces the automatically generated endpoints, not the inheritance.[/EDIT]



If you need this features, you should configure what to expose. There is the official documentation here, and the following example taken from here.



# Exposes all public repository interfaces but considers @(Repository)RestResourceu2019s `exported flag.
spring.data.rest.detection-strategy=default

# Exposes all repositories independently of type visibility and annotations.
spring.data.rest.detection-strategy=all

# Only repositories annotated with @(Repository)RestResource are exposed, unless their exported flag is set to false.
spring.data.rest.detection-strategy=annotated

# Only public repositories annotated are exposed.
spring.data.rest.detection-strategy=visibility





share|improve this answer















The problem is that SpringDataRest automatically generates CRUD endpoints for each model, and exposes them following the HATEOS paradigm.



If you don't need this feature, just remove the SpringDataRest dependency.
[EDIT] I just re-read the question title. @RepositoryRestResource is what introduces the automatically generated endpoints, not the inheritance.[/EDIT]



If you need this features, you should configure what to expose. There is the official documentation here, and the following example taken from here.



# Exposes all public repository interfaces but considers @(Repository)RestResourceu2019s `exported flag.
spring.data.rest.detection-strategy=default

# Exposes all repositories independently of type visibility and annotations.
spring.data.rest.detection-strategy=all

# Only repositories annotated with @(Repository)RestResource are exposed, unless their exported flag is set to false.
spring.data.rest.detection-strategy=annotated

# Only public repositories annotated are exposed.
spring.data.rest.detection-strategy=visibility






share|improve this answer














share|improve this answer



share|improve this answer








edited Nov 16 '18 at 10:44

























answered Nov 16 '18 at 10:30









Tu.maTu.ma

818320




818320













  • Maybe I am misunderstanding your point. The problem is not that Spring Data Rest exports the TrainingRequestRepository as a rest resource. The problem is that there are /search/* endpoints that should not be there.

    – user3235738
    Nov 16 '18 at 11:58





















  • Maybe I am misunderstanding your point. The problem is not that Spring Data Rest exports the TrainingRequestRepository as a rest resource. The problem is that there are /search/* endpoints that should not be there.

    – user3235738
    Nov 16 '18 at 11:58



















Maybe I am misunderstanding your point. The problem is not that Spring Data Rest exports the TrainingRequestRepository as a rest resource. The problem is that there are /search/* endpoints that should not be there.

– user3235738
Nov 16 '18 at 11:58







Maybe I am misunderstanding your point. The problem is not that Spring Data Rest exports the TrainingRequestRepository as a rest resource. The problem is that there are /search/* endpoints that should not be there.

– user3235738
Nov 16 '18 at 11:58






















draft saved

draft discarded




















































Thanks for contributing an answer to Stack Overflow!


  • Please be sure to answer the question. Provide details and share your research!

But avoid



  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.


To learn more, see our tips on writing great answers.




draft saved


draft discarded














StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53335903%2fspring-data-rest-repository-inheritance-creates-strange-search-endpoints%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown





















































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown

































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown







Popular posts from this blog

Xamarin.iOS Cant Deploy on Iphone

Glorious Revolution

Dulmage-Mendelsohn matrix decomposition in Python