Exposing link on collection entity using spring ha

2019-07-20 14:01发布

问题:

I have pretty the same question as it has been asked here (Exposing link on collection entity in spring data REST). But nothing from that topic helps me to add custom link to collection call.

@Component
public class EventListResourceProcessor implements ResourceProcessor<Resources<Event>> {

    @Autowired
    private RepositoryEntityLinks entityLinks;

    @Override
    public Resources<Event> process(Resources<Event> events) {
        events.add(entityLinks.linkToCollectionResource(Event.class).withRel("events"));
        return events;
    }
}

process method is never called in this case.

I need to call http://localhost:8080/event and get the following JSON with my_custom_link under _links section:

{
  "_embedded": {
    "event": [
      {
        "id": "1",
        "name": "Updated event"
      }]
  },
  "_links": {
    "self": {
      "href": "http://localhost:8080/event"
    },
    "profile": {
      "href": "http://localhost:8080/profile/event"
    },
    "my_custom_link": {
      "href": "http://localhost:8080/custom/"
    }
  },
  "page": {
    "size": 20,
    "totalElements": 4,
    "totalPages": 1,
    "number": 0
  }
}

}

Could you please advise me?

Thanks in advance!

回答1:

I was in a similar situation to your question: I had read through the question/answers you linked and found none of them could solve the problem. Here was the eventual answer to my problem:

@Component
public class MyResourceProcessor implements ResourceProcessor<Resource<MyInterface>> {

    @Autowired
    private EntityLinks entityLinks;

    @Override
    public Resource<MyInterface> process(Resource<MyInterface> event) {
        event.add(entityLinks.linkForSingleResource(MyClass.class, event.getContent().getId()).slash("custom").withRel("custom"));
        return event;
    }
}

This process method was called for each Resource in the relevant collection of Resources being returned. The MyInterface interface and MyClass class should be replaced with whatever you end up needing, but it was required to be written that way to get this to work. Here are the steps I used to get the process method called correctly and to determine the MyInterface type.

  1. I created a process method that simply took ResourceSupport as the parameter. I created a breakpoint in the code and inspected what underlying class was extending ResourceSupport. In my case it was PersistentEntityResource. This explained why using Resource<MyClass> or Resources<MyClass> were never causing the process method to be called: PersistentEntityResource extends Resource<Object>.

  2. I updated the process method take PersistentEntityResource as the parameter. This caused the process method to be called for more than my intended changes. I once again used the breakpoint to inspect the PersistentEntityResource object with the intent of discovering what class could be found in the Resource<Object> that it extended. I discovered it was a Proxy class, and could not be cast to MyClass as I had desired.

  3. I followed the answer found here to find out more information about the Proxy class: https://stackoverflow.com/a/3344386/1417690. While debugging, I discovered the list of interfaces that helped define this class. One of them was of type MyProjectionInterface. I now knew that the reason I couldn't use Resource<Portal> was because it was actually a Resource<MyProjectionInterface>.

  4. I had three different Projections that I needed to handle. Instead of creating three separate ResourcePorcoessors I created the MyInterface and had all three of my projection interfaces extend it. The MyInterface only contained a Long getId() method which all of the projections already supported anyway.

  5. I updated my code to use Resource<MyInterface> and added the linkForSingleResource using MyClass (that all of the projections relate to) and the getId() method I had defined in MyInterface. This successfully added the desired link to each of the resources being returned.

Hopefully these steps help others discover how to determine what type to use as the parameter for the process method.



回答2:

Because I faced the same issue and I hoped an answer here. The solution is to declare a bean ResourceProcessor implementing the right generic type which extends ResourceSupport.

In our case for a paginable resource, the right ResourceSupport is PagedResources<Resource<MyInterface>> as the following sample :

@Bean
public ResourceProcessor<PagedResources<Resource<Supplier>>> pageableSupplierProcessor() {
    return new ResourceProcessor<PagedResources<Resource<Supplier>>>() {
        @Override
        public PagedResources<Resource<Supplier>> process(PagedResources<Resource<Supplier>> resources) {
            Method registerMethod;
            try {
                registerMethod = SupplierRegistrationController.class.getMethod("register",
                        RegistrationRequest.class);
                Link registerLink = linkTo(registerMethod, new RegistrationRequest()).withRel("register");
                resources.add(registerLink);
                return resources;
            } catch (NoSuchMethodException | SecurityException e) {
                throw new IllegalStateException(e);
            }
        }
    };
}