Search code examples
inheritancespring-bootspring-data-restspring-hateoasrel

How to configure a custom RelProvider for Spring HATEOAS when using Spring Boot?


I'm using the party model:

@Entity
@Inheritance(strategy=...)
@JsonTypeInfo(use= JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@DiscriminatorColumn(name = "type")
public abstract class Party {

  @Column(updatable = false, insertable = false)
  private String type;

  ...
}

@Entity
public class Individual extends Party {
  ...
}

@Entity class Organization extends Party {
  ...
}

Spring Data REST responds like this:

{
  "_embedded": {
    "organizations": [
      {
        "type":"Organization",
        "name": "Foo Enterprises",
        "_links": {
          "self": {
            "href": "http://localhost/organization/2"
          },
          "organization": {
            "href": "http://localhost/organization/2"
          }
        }
      }
    ],
    "individuals": [
      {
        "type":"Individual",
        "name": "Neil M",
        "_links": {
          "self": {
            "href": "http://localhost/individual/1"
          },
          "individual": {
            "href": "http://localhost/individual/1"
          }
        }
      }
    ]
  }
}

But I need it to respond like this:

{
  "_embedded": {
    "parties": [
      {
        "type": "Organization",
        "name": "Foo Enterprises",
        "_links": {
          "self": {
            "href": "http://localhost/party/2"
          },
          "organization": {
            "href": "http://localhost/party/2"
          }
        }
      },
      {
        "type": "Individual",
        "name": "Neil M",
        "_links": {
          "self": {
            "href": "http://localhost/party/1"
          },
          "individual": {
            "href": "http://localhost/party/1"
          }
        }
      }
    ]
  }
}

To do so, I understand I need to provide a custom RelProvider:

@Order(Ordered.HIGHEST_PRECEDENCE)
@Component
public class MyRelProvider implements RelProvider {

    public MyRelProvider() {}

    @Override
    public String getItemResourceRelFor(Class<?> aClass) {
        return "party";

    }

    @Override
    public String getCollectionResourceRelFor(Class<?> aClass) {
        return "parties";
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return aClass.isAssignableFrom(Party.class);
    }
}

I tried configuring it in Application.java:

@SpringBootApplication
public class Application {

    public static void main(String[] args) throws Exception {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    RelProvider myRelProvider() {
        return new MyRelProvider();
    }
}

This doesn't work though. It seems to not get registered, or get registered correctly. See http://andreitsibets.blogspot.ca/2014/04/hal-configuration-with-spring-hateoas.html

How can I fix this?


Solution

  • Have you annotated your controller classes with @ExposesResourceFor

    From the spring hateos docs

    1. SPIs

    3.1. RelProvider API

    When building links you usually need to determine the relation type to be used > for the link. In most cases the relation type is directly associated with a (domain) type. We encapsulate the detailed algorithm to lookup the relation types behind a RelProvider API that allows to determine the relation types for single and collection resources. Here’s the algorithm the relation type is looked up:

    1. If the type is annotated with @Relation we use the values configured in the annotation.

    2. if not, we default to the uncapitalized simple class name plus an appended List for the collection rel.

    3. in case the EVO inflector JAR is in the classpath, we rather use the plural of the single resource rel provided by the pluralizing algorithm.

    4. @Controller classes annotated with @ExposesResourceFor (see EntityLinks for details) will transparently lookup the relation types for the type configured in the annotation, so that you can use relProvider.getSingleResourceRelFor(MyController.class) and get the relation type of the domain type exposed.

    A RelProvider is exposed as Spring bean when using @EnableHypermediaSupport automatically. You can plug in custom providers by simply implementing the interface and exposing them as Spring bean in turn.