Search code examples
spring-data-rest

Spring Data Rest ResourceProcessor not applied on Projections


I am using a ResourceProcessor to add additional links to my resource object when listed in a collection or fetched individually. However, when I apply a projection (or an excerpt project) to my repository, the ResourceProcessor does not get run and thus my links for that resource do not get created. Is there a means to allow my custom resource links to be added to a resource regardless of how the resource content is projected?


Solution

  • I think this issue is describing your case: https://jira.spring.io/browse/DATAREST-713

    Currently, spring-data-rest does not offer functionality to solve your problem.

    We are using a little workaround that still needs a separate ResourceProcessor for each projection but we do not need to duplicate the link logic:

    We have a base class that is able to get the underlying Entity for a Projection and invokes the Entity's ResourceProcessor and applies the links to the Projection. Entity is a common interface for all our JPA entities - but I think you could also use org.springframework.data.domain.Persistable or org.springframework.hateoas.Identifiable.

    /**
     * Projections need their own resource processors in spring-data-rest.
     * To avoid code duplication the ProjectionResourceProcessor delegates the link creation to
     * the resource processor of the underlying entity.
     * @param <E> entity type the projection is associated with
     * @param <T> the resource type that this ResourceProcessor is for
     */
    public class ProjectionResourceProcessor<E extends Entity, T> implements ResourceProcessor<Resource<T>> {
    
        private final ResourceProcessor<Resource<E>> entityResourceProcessor;
    
        public ProjectionResourceProcessor(ResourceProcessor<Resource<E>> entityResourceProcessor) {
            this.entityResourceProcessor = entityResourceProcessor;
    
        }
    
        @SuppressWarnings("unchecked")
        @Override
        public Resource<T> process(Resource<T> resource) {
            if (resource.getContent() instanceof TargetAware) {
                TargetAware targetAware = (TargetAware) resource.getContent();
                if (targetAware != null
                        && targetAware.getTarget() != null
                        && targetAware.getTarget() instanceof Entity) {
                    E target = (E) targetAware.getTarget();
                    resource.add(entityResourceProcessor.process(new Resource<>(target)).getLinks());
                }
            }
            return resource;
        }
    
    }   
    

    An implementation of such a resource processor would look like this:

    @Component
    public class MyProjectionResourceProcessor extends ProjectionResourceProcessor<MyEntity, MyProjection> {
    
        @Autowired
        public MyProjectionResourceProcessor(EntityResourceProcessor resourceProcessor) {
            super(resourceProcessor);
        }
    }
    

    The implementation itself just passes the ResourceProcessor that can handle the entity class and passes it to our ProjectionResourceProcessor. It does not contain any link creation logic.