I am using spring data rest with Mongo to expose a class with multiple subtypes. When I do this, HATEOAS is compartmentalizing the results based on the actual instantiated type, rather than the common base type. This results in the links being incorrect, and renders paging useless since it is a mixed type list.
I have tried explicitly adding the @Relation tag to all of the classes involved, and it seems to have no affect at all. I get the same results with or without it.
I am using spring boot dependencies 2.1.8.RELEASE with spring-boot-starter-data-rest and spring-cloud-dependencies Greenwich.SR1
The base class:
@Relation(collectionRelation = "notifications", value="notifications")
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "notificationType")
@JsonSubTypes({
@JsonSubTypes.Type(value = ModelNotification.class, name = Notification.MODEL_NOTIFICATION),
@JsonSubTypes.Type(value = BasicNotification.class, name = Notification.BASIC_NOTIFICATION)
})
public class Notification extends UUIDEntity implements Serializable {
private static final long serialVersionUID = 8199210081144334378L;
public static final String MODEL_NOTIFICATION = "MODEL_NOTIFICATION";
public static final String BASIC_NOTIFICATION = "BASIC_NOTIFICATION";
public enum Severity {
TRACE,
DEBUG,
INFO,
WARN,
ERROR,
FATAL
}
public Notification() {
this.notificationType = BASIC_NOTIFICATION;
}
@JsonProperty("notificationType")
private String notificationType;
@JsonProperty("createdDate")
@CreatedDate
private Instant createdDate;
@JsonProperty("lastModifiedDate")
@LastModifiedDate
private Instant lastModifiedDate;
@JsonProperty("createdBy")
@CreatedBy
private String createdBy;
@JsonProperty("lastModifiedBy")
@LastModifiedBy
private String lastModifiedBy;
@JsonProperty("severity")
private Severity severity;
@JsonProperty("message")
private String message;
The no additional member version:
@Relation(collectionRelation = "notifications", value="notifications")
public class BasicNotification extends Notification implements Serializable {
private static final long serialVersionUID = 8063077545983014320L;
}
And the extension version:
@Relation(collectionRelation = "notifications", value="notifications")
public class ModelNotification extends Notification implements Serializable {
private static final long serialVersionUID = 3700576594274374440L;
@JsonProperty("storedModel")
private StoredModel storedModel;
public ModelNotification() {
super();
this.setNotificationType(Notification.MODEL_NOTIFICATION);
}
I would expect, given adding the @Relation tag, that all of the results would appear under notifications, which is the correct url for the spring data rest endpoint. Note that all of the endpoints are working correctly, but just the HATEOAS stuff is incorrect, and the bundling creates problems. When accessing at: /api/notifications
I get back:
{
"_embedded" : {
"modelNotifications" : [ {
"notificationType" : "MODEL_NOTIFICATION",
"createdDate" : "2019-10-02T15:53:42.127Z",
"lastModifiedDate" : "2019-10-02T15:53:42.127Z",
...
[SNIP FOR BREVITY]
...
} ]
},
"_links" : {
"self" : {
"href" : "http://fastscore:8088/api/modelNotification/ef81c342-29d3-48fb-bab3-d416e80bc5f6"
},
"modelNotification" : {
"href" : "http://fastscore:8088/api/modelNotification/ef81c342-29d3-48fb-bab3-d416e80bc5f6"
}
}
} ],
"notifications" : [ {
"notificationType" : "BASIC_NOTIFICATION",
"createdDate" : "2019-10-02T15:52:10.261Z",
"lastModifiedDate" : "2019-10-02T15:52:10.261Z",
"createdBy" : "anonymousUser",
"lastModifiedBy" : "anonymousUser",
"severity" : "INFO",
"message" : "Interval Process Completed Successfully",
"_links" : {
"self" : {
"href" : "http://fastscore:8088/api/notifications/93fa5d6b-1457-4fa6-976c-cfdddc422976"
},
"notification" : {
"href" : "http://fastscore:8088/api/notifications/93fa5d6b-1457-4fa6-976c-cfdddc422976"
}
}
...
[SNIP]
...
},
},
"_links" : {
"self" : {
"href" : "http://fastscore:8088/api/notifications{?page,size,sort}",
"templated" : true
},
"profile" : {
"href" : "http://fastscore:8088/api/profile/notifications"
},
"search" : {
"href" : "http://fastscore:8088/api/notifications/search"
}
},
"page" : {
"size" : 20,
"totalElements" : 4,
"totalPages" : 1,
"number" : 0
}
}
This obviously seems incorrect, as modelNotifications is not a spring data rest endpoint. Also, this would make paging useless, as if I have a ton of notifications, the paging works only for those, and I receive modelNotifications every time even if I have only one... So on page two, I would receive the second page of notifications, but still have the modelNotifications on the second page even if I had only a single entry.
This kind of renders the HATEOAS support unusable with spring data rest.
Well after playing around with this all day, the @Relation tag seems to not work in this situation at all. So I decided to find a work around. What I did was implement a RelProvider to properly check for inheritance, and return the correct collection name:
@Order(Ordered.HIGHEST_PRECEDENCE)
@Component
public class NotificationRelProvider implements RelProvider {
@Override
public String getItemResourceRelFor(Class<?> aClass) {
return "notification";
}
@Override
public String getCollectionResourceRelFor(Class<?> aClass) {
return "notifications";
}
@Override
public boolean supports(Class<?> aClass) {
return Notification.class.isAssignableFrom(aClass);
}
}
I then tagged the spring data rest repository, as described in the docs with:
@ExposesResourceFor(Notification.class)
@RepositoryRestResource()
public interface NotificationRepository extends MongoRepository<Notification, UUID> {
List<Notification> findAllBySeverityOrderByCreatedDateDesc(@Param("severity") Notification.Severity severity);
}
After doing this, I now get the correct and expected behavior of:
"_embedded" : {
"notifications" : [ {
"notificationType" : "BASIC_NOTIFICATION",
"createdDate" : "2019-10-02T23:06:16.802Z",
"lastModifiedDate" : "2019-10-02T23:06:16.802Z",
"createdBy" : "anonymousUser",
"lastModifiedBy" : "anonymousUser",
"severity" : "INFO",
"message" : "Interval Process Completed Successfully",
"_links" : {
"self" : {
"href" : "http://fastscore:8088/api/notifications/eb214276-2880-43e0-8c7f-1519b1e7e343"
},
"notification" : {
"href" : "http://fastscore:8088/api/notifications/eb214276-2880-43e0-8c7f-1519b1e7e343"
}
}
}, {
"notificationType" : "MODEL_NOTIFICATION",
"createdDate" : "2019-10-02T23:07:32.649Z",
"lastModifiedDate" : "2019-10-02T23:07:32.649Z",
"createdBy" : "anonymousUser",
"lastModifiedBy" : "anonymousUser",
....
So this resolves the original error, but is kind of a manual process rather than just using the annotations as described in the document. Unfortunately it means I have to create one of these RelProvider classes for each base class I have in repositories, but at least it works and is usable for paging.