Using a Spring Data Rest @Projection as a representation for a resource in a custom controller
Asked Answered
O

3

6

Is there any way to use a @Projection interface as the default representation for a resource in SDR? Either through the SDR repositories or through a custom controller?

It used to be possible in a custom controller to do this by injecting a ProjectionFactory and using the createProjection method, but this has been broken by a recent Spring Data Rest update.

I would like to enforce a particular view on an entity, and the SDR projections seem like an ideal method for doing this, especially in the context of a HAL API, as opposed to writing hard DTO classes for a custom controller and mapping between them etc. Excerpt projections are not what I am after, as these only apply when looking at an associated resource.

Obala answered 22/10, 2015 at 18:48 Comment(0)
O
19

To answer my own question, there's now a couple of easyish ways to do this.

You can make SDR repository finders return projections by default:

public interface PersonRepository extends PagingAndSortingRepository<Person,Long> {

    Set<PersonProjection> findByLastName(String lastName);

}

You can also selectively override responses that SDR would have handled for you by default by creating a custom Spring MVC controller with the @BasePathAwareController. You will need to inject the ProjectionFactory and possibly the PagedResourcesAssembler if you're planning on providing a paged response.

@BasePathAwareController
public class CustomPersonController {

@Autowired
private ProjectionFactory factory;

@Autowired
private PersonRepository personRepository;

@Autowired
private PagedResourcesAssembler<PersonProjection> assembler;

@RequestMapping(value="/persons", method = RequestMethod.GET, produces = "application/hal+json")
public ResponseEntity<?> getPeople(Pageable pageable) {
    Page<Person> people = personRepository.findAll(pageable);
    Page<PersonProjection> projected = people.map(l -> factory.createProjection(PersonProjection.class, l));
    PagedResources<Resource<PersonProjection>> resources = assembler.toResource(projected);
    return ResponseEntity.ok(resources);
}
Obala answered 8/7, 2016 at 11:43 Comment(1)
In addittion to @adam respone, it is needed to add a @Bean in some @Configuration file e.g. @Configuration class SomeConfig { @Bean public SpelAwareProxyProjectionFactory projectionFactory() { return new SpelAwareProxyProjectionFactory(); } } sourceGimcrackery
M
3

I would like to suggest yet another solution.

I used custom controller as @adam suggested untill I had to PATCH a resource and get new representation back. There is no way to override save method of a repository to use a projection by default. Implementing custom controllers all the time brings some boilerplate into the project.

Since I already used ResourceProcessors I decided to apply default projection right inside those processors.

@Component
public class ProductResourceProcessor implements ResourceProcessor<Resource<Product>> {

    @Autowired
    private ProjectionFactory projectionFactory;

    @Override
    @SuppressWarnings("unchecked")
    public Resource<Product> process(Resource<Product> resource) {
        Product content = resource.getContent();
        ProductInline projection = projectionFactory.createProjection(ProductInline.class, content);
        Resource<ProductInline> result = new Resource<>(projection);
        //copying and adding links
        return (Resource) result;
    }
}

The processor repacks the content of processing resource. Thsi way the projection is applied no matter which handler returned the resource as a response (GET, POST, PATCH anything).

The drawback of this approach is it assumes that spring-data-rest doesn't require that the type of the content would be the same after processing. That may change anytime in the future releases.

Mastermind answered 19/4, 2017 at 10:48 Comment(0)
A
2

No it is not possible out of the box. Excerpt projections are always used if a resource is embedded. And on a single resource you can provide the desired projection as a query param.

What you can do is to use Jackson Mixins to change the json representation.

You can find some good examples here: https://github.com/olivergierke/spring-restbucks/blob/master/src/main/java/org/springsource/restbucks/JacksonCustomizations.java

Amnesty answered 22/10, 2015 at 21:4 Comment(3)
Thanks. Previously (pre-SDR 2.4.0), it was possible to just inject an instance of the SpelAwareProxyProjectionFactory into your controller class, and then in your custom controllers, call createProjection on the domain object you want projected, and then return the projection in the response, but this has since unfortunately been broken by one or more changes on the SDR side. the Jackson Mixins route is much more involved and clunkier.Obala
There was similar question before and Oliver Gierke directly anwered it with some nice argumentation why it is not a good idea to use projections for single resources - see #30220833Amnesty
Yeah, I saw that, though that was mostly related to excerpts and using the standard SDR exported repositories. Though, Oliver also outlined how to do exactly what I am after (and what I was doing) here: #29376590 and here: spring.io/blog/2015/03/26/what-s-new-in-spring-data-fowler but these code samples no longer work in the current version of SDR, throwing a Jackson exception.Obala

© 2022 - 2024 — McMap. All rights reserved.