简体   繁体   中英

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.

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)RestResource\u2019s `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 technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM