Open spring-projects-issues opened 8 years ago
Oliver Drotbohm commented
I am not sure how this is supposed to work. If you use a projection, there simply is not Resource<MyEntity>
but a Resource<MyExcerptProjection>
, so we simply can't just invoke the other processor
Mathias D commented
So you are saying this is works as designed? So what would you recommend to access the id of the entity to generate links?
Currently my projections contains the id annotated with @JsonIgnore
to be able to access it in the ResourceProcessor
.
@Projection(name = "myExcerptProjection", types = MyEntity.class)
interface MyEntityExcerptProjection {
//this is needed in ResourceProcessor to build the link
@JsonIgnore Long getId();
//... other getters
}
I do not know how the implementation looks like but the projections could keep a reference to the entity and the resource assembler could use this information to bring in the ResourceProcessor of the corresponding Entity?!
Also I think you could use the projection annotation to know which entity the projection belongs to...
Oliver Drotbohm commented
Sort of, yes. In case you're using a projection there simply will never be a Resource<MyEntity>
but a Resource<MyEntityProjection>
. That makes pretty obvious that a ResourceProcessor<MyEntity>
can't be invoked as this would cause ClassCastExceptions
as soon as you access getContent()
.
Couldn't your entity and your projection implement / extend a common interface that has the methods you need?
Mathias D commented
Thanks for the quick feedback - good to know that we did not do anything wrong. We thought about the common interface - maybe we go this way. Also the ResourceProcessor is not too complex so we might just live with the code duplication
Mathias D commented
See previous comments /discussion - do you want to close the issue?
Mathias D commented
I just had a look at the code regarding the issue I described. I saw that the ProxyProjectionFactory is also assigning the TargetAware interface to the proxy that is created. So I can cast the projection instance to TargetAware and access the underlying entity.
I think the ResourceProcessors are applied in org.springframework.data.rest.webmvc.ResourceProcessorHandlerMethodReturnValueHandler#invokeProcessorsFor
So we could add the links generated from the entity resource processor to the projection resources like this:
private Object invokeProcessorsFor(Object value, TypeInformation<?> targetType) {
Object currentValue = value;
boolean handleTargetAwareResourceProcessor = (currentValue instanceof Resource)
&& (((Resource<?>) currentValue).getContent() instanceof TargetAware);
Object targetValue = null;
if (handleTargetAwareResourceProcessor) {
TargetAware targetAwareValue = (TargetAware) ((Resource<?>) currentValue).getContent();
targetValue = new Resource(targetAwareValue.getTarget());
}
// Process actual value
for (ProcessorWrapper wrapper : this.processors) {
if (wrapper.supports(targetType, currentValue)) {
currentValue = wrapper.invokeProcessor(currentValue);
}
if (handleTargetAwareResourceProcessor && wrapper.supports(targetType, targetValue)) {
targetValue = wrapper.invokeProcessor(targetValue);
((Resource<?>) currentValue).add(((Resource<?>) targetValue).getLinks());
}
}
return currentValue;
}
What do you think? Is it worth a pull request? I would like to implement it if you think it has chances to be accepted.
Oliver Drotbohm commented
I am afraid it's not. While this hack certainly makes it work somehow I see a couple of downsides:
ResourceProcessorHandlerMethodReturnValueHandler
now knows about TargetAware
and thus projections implicitly. I don't think that this is knowledge it should have. Especially in the light of us trying to move it to Spring HATEOAS to make it available without Spring Data REST.ResourceSupport
instead of Resource
? The invocation would all of a sudden work differently depending on which base type is usedResourceProcessor<Resource<EntityType>>
would now start to see an inconsistent set of Resource
instances. The ones prepared with links in the controller and the synthetic one you create here without any links at all. Of course the links could be copied over but we again run into the issue of what if a dedicated subtype of Resource
is used.Again, I am actually not arguing the implementation here but the additional conceptual complexity and inconsistencies introduced are not worth it IMO. I prefer a clean consistent way of working with some minor overhead left to some users over a incredibly sophisticated approach that exposes some inconsistencies in some cases which just puzzle most users
Mathias D commented
Thanks for the feedback - I think I was not seeing the whole picture. It was worth a try and interesting to see how it works internally. From my point of view you can close the issue then
Petar Tahchiev commented
Guys, any update here? We have a lot of different projections and this hack means we must have 10-12 different ResourceProcessor
-s with the same duplicated code inside. It would be really nice if there was a way to specify that this ResourceProcessor
should be invoked for every entity or projection, something like this:
public class MyEntityResourceProcessor implements ResourceProcessor<Projection<Resource<MyEntity>>> {
Oliver Drotbohm commented
If it's literally the same code, isn't introducing a common interface and referring to that a reasonable solution?
Petar Tahchiev commented
Hi Olver, let me see if I follow this through. So let's say we have:
projection A -> renders JSON A
projection B -> renders JSON B
......
projection Z -> renders JSON Z
I assume what you are saying is to introduce a new interface MySuperProjection
and then have:
projectionA extends MySuperProjection -> renders JSON A
projectionB extends MySuperProjection -> renders JSON B
.....
projectionZ extends MySuperProjection -> renders JSON Z
And then define two resource processors: one for the entity class, and another one for the MySuperProjection
.
My conclusion is:
ResourceProcessor
and in case of 5 new common annotations, 10 new ResourceProcessors
needs to be created, which will eventually lead to crappy code .ResourceProcessor
for every projection and it gets messy.Again it would be really nice if we could define in the syntax of the class that this ResourceProcessor
is to be invoked for the resource itself, as well as for all the projections:
public class MyEntityResourceProcessor implements ResourceProcessor<Projection<Resource<MyEntity>>> {
This way one could inspect the Projection
object and get the name of the projection and construct their own logic inside:
if (projectionName == null) {
// invoked on resource directly (no projection)
}
if (projectionName.equals("search")) {
//do something
} else if (projectionName.equals("other")) {
// do something else...
}
Oliver Drotbohm commented
I still have a hard time Imagine this to work as you expect us to call a method Resource<MyEntity> process(Resource<MyEntity> resource)
but all we have at this point is a Resource<ProjectionA>
, right? How's that supposed to work from an invocation point of view?
The following works for me:
@Component
public class ProjectionProcessor implements RepresentationModelProcessor<EntityModel<TargetAware>> {
private final RepresentationModelProcessorInvoker processorInvoker;
public ProjectionProcessor(@Lazy RepresentationModelProcessorInvoker processorInvoker) {
this.processorInvoker = processorInvoker;
}
@Override
public EntityModel<TargetAware> process(EntityModel<TargetAware> entityModel) {
TargetAware content = entityModel.getContent();
if (content != null) {
entityModel.add(processorInvoker.invokeProcessorsFor(EntityModel.of(content.getTarget())).getLinks());
}
return entityModel;
}
}
Mathias D opened DATAREST-713 and commented
I am using spring-data-rest 2.4.1 to expose a entity as a rest resource.
I also implemented a
ResourceProcessor
to add a custom link to the resourceThis works fine for the single item resource. But I also have setup a
ExcerptProjection
that reduces the attributes shown in the collection resource:When the projection is used my
MyEntityResourceProcessor
is not invoked and the custom link is missing.I can bring in the link by implementing a
ResourceProcessor
for the projection like so:But I would like to avoid this because:
By default I would expect spring data rest to use the ResourceProcessor of the Entity the Projection belongs to
Affects: 2.4.1 (Gosling SR1)
Reference URL: http://stackoverflow.com/questions/33501648/excerpt-projection-and-custom-links-from-resourceprocessor
1 votes, 4 watchers