Spring Data Rest - Repository inheritance creates strange search endpoints
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
|
show 1 more comment
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
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
|
show 1 more comment
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
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
java spring-data-jpa spring-data-rest
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
|
show 1 more comment
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
|
show 1 more comment
1 Answer
1
active
oldest
votes
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
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
add a comment |
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
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
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
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
add a comment |
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
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
add a comment |
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
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
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
add a comment |
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
add a comment |
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.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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
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