0

For a new project i'm building a rest api that references resources from a second service. For the sake of client convenience i want to add this association to be serialized as an _embedded entry.

Is this possible at all? i thought about building a fake CrudRepository (facade for a feign client) and manually change all urls for that fake resource with resource processors. would that work?

Laures
  • 5,389
  • 11
  • 50
  • 76

2 Answers2

1

a little deep dive into the functionality of spring-data-rest:

Data-Rest wraps all Entities into PersistentEntityResource Objects that extend the Resource<T> interface that spring HATEOAS provides. This particular implementation has a list of embedded objects that will be serialized as the _embedded field.

So in theory the solution to my problem should be as simple as implementing a ResourceProcessor<Resource<MyType>> and add my reference object to the embeds.

In practice this aproach has some ugly but solvable issues:

PersistentEntityResource is not generic, so while you can build a ResourceProcessor for it, that processor will by default catch everything. I am not sure what happens when you start using Projections. So that is not a solution.

PersistentEntityResource implements Resource<Object> and as a result can not be cast to Resource<MyType> and vice versa. If you want to to access the embedded field all casts have to be done with PersistentEntityResource.class.cast() and Resource.class.cast().

Overall my solution is simple, effective and not very pretty. I hope Spring-Hateoas gets full fledged HAL support in the future.

Here my ResourceProcessor as a sample:

@Bean
public ResourceProcessor<Resource<MyType>> typeProcessorToAddReference() {
    // DO NOT REPLACE WITH LAMBDA!!!
    return new ResourceProcessor<>() {
        @Override
        public Resource<MyType> process(Resource<MyType> resource) {

            try {
                // XXX all resources here are PersistentEntityResource instances, but they can't be cast normaly
                PersistentEntityResource halResource = PersistentEntityResource.class.cast(resource);

                List<EmbeddedWrapper> embedded = Lists.newArrayList(halResource.getEmbeddeds());
                ReferenceObject reference = spineClient.findReferenceById(resource.getContent().getReferenceId());
                embedded.add(embeddedWrappers.wrap(reference, "reference-relation"));

                // XXX all resources here are PersistentEntityResource instances, but they can't be cast normaly
                resource = Resource.class.cast(PersistentEntityResource.build(halResource.getContent(), halResource.getPersistentEntity())
                    .withEmbedded(embedded).withLinks(halResource.getLinks()).build());

            } catch (Exception e) {
                log.error("Something went wrong", e);
                // swallow
            }
            return resource;
        }
    };
}
Laures
  • 5,389
  • 11
  • 50
  • 76
0

If you would like to work in type safe manner and with links only (addition references to custom controller methods), you can find inspiration in this sample code:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.hateoas.EntityModel;
import org.springframework.hateoas.server.RepresentationModelProcessor;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;

@Configuration
public class MyTypeLinkConfiguration {
    public static class MyType {}

    @Bean
    public RepresentationModelProcessor<EntityModel<MyType>> MyTypeProcessorAddLifecycleLinks(MyTypeLifecycleStates myTypeLifecycleStates) {
        // WARNING, no lambda can be passed here, because type is crucial for applying this bean processor.
        return new RepresentationModelProcessor<EntityModel<MyType>>() {
            @Override
            public EntityModel<MyType> process(EntityModel<MyType> resource) {
                // add custom export link for single MyType
                myTypeLifecycleStates
                        .listReachableStates(resource.getContent().getState())
                        .forEach(reachableState -> {
                            try {
                                // for each possible next state, generate its relation which will get us to given state
                                switch (reachableState) {
                                    case DRAFT:
                                        resource.add(linkTo(methodOn(MyTypeLifecycleController.class).requestRework(resource.getContent().getId(), null)).withRel("requestRework"));
                                        break;
                                    case IN_REVIEW:
                                        resource.add(linkTo(methodOn(MyTypeLifecycleController.class).requestReview(resource.getContent().getId(), null)).withRel("requestReview"));
                                        break;
                                    default:
                                        throw new RuntimeException("Link for target state " + reachableState + " is not implemented!");
                                }
                            } catch (Exception ex) {
                                // swallowed
                                log.error("error while adding lifecycle link for target state " + reachableState + "! ex=" + ex.getMessage(), ex);
                            }
                        });
                return resource;
            }
        };
    }

}

Note, that myTypeLifecycleStates is autowired "service"/"business logic" bean.

Lubo
  • 1,621
  • 14
  • 33