-
Notifications
You must be signed in to change notification settings - Fork 562
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow customization of LinkCollector
#2042
Comments
Thanks for writing this up.
That's not quite correct. These two URI identify semantically different resources. The latter is the actually linked address, while the former is an association resource. I.e. it represents the assignment between the two aggregates and allows the modification of that assignment in idempotent fashion. It allows using Can you elaborate what you mean with "cache friendly"? The association resources' responses can be cached as well. If you'd like to avoid the additional request to actually access the representations of the related resources, they can be inlined in the person's representation by providing excerpt projections. Another aspects that's a little odd with the suggested approach is that you'd have to move the All that said, I'm a bit surprised that you allegedly got these customizations working by only exchanging the |
Thanks for your response. I should have specified better in my description that we are using custom caching in our application, not just using the browser's cache.
All we do is to extend LinkCollector and override getLinksFor() and getLinksForNested() and the latter is only needed to support a legacy system which we are slowly decommissioning. So, if you keep the default link collector implementation open for extension and you add your proposed interface and callback, that will work for us. |
What does the upgrade situation you're in look like? I guess you're on a SD REST version that still allows sub-classing of |
LinkCollector
We are on latest 3.5.2, but we monkey-patched DelegatingHandlerMapping to make it public so that subclassing does not fail at runtime. |
There's a build running containing the suggested changes. We have a release scheduled for tomorrow. Would be cool if you had a chance to check whether the snapshots allow you to plug what you need. |
I surely can, whould that be 3.6.0-SNAPSHOT? Is there a snapshot for spring-data-bom that would contain it? We get SPDR through spring-boot-dependencies and it would be easier to override that version for testing. |
Setting |
Moving to the new customization mechanism has been straightforward and it works as expected. |
Lovely, happy coding! 🙌 |
Here is a starting point for anyone interested in having the links array contain canonical URLs rather than "association resources". It works very well for me, but is likely probably a completely wrong way to do everything. fun Links.replace(link: Link): Links {
val linkMap = this.associateBy { it.rel }.toMutableMap()
linkMap[link.rel] = link
return Links.of(linkMap.values)
}
class AbsoluteLinkCollector(oldCollector: LinkCollector) : LinkCollector {
val logger = createLogger()
private val entities = oldCollector.javaClass.getDeclaredField("entities")
.apply { isAccessible = true }
.get(oldCollector) as PersistentEntities
private val associationLinks = oldCollector.javaClass.getDeclaredField("associationLinks")
.apply { isAccessible = true }
.get(oldCollector) as Associations
private val links = oldCollector.javaClass.getDeclaredField("links")
.apply { isAccessible = true }
.get(oldCollector) as SelfLinkProvider
override fun getLinksFor(obj: Any) = this.getLinksFor(obj, Links.NONE)
override fun getLinksFor(obj: Any, existing: Links): Links {
var newLinks = existing.replace(links.createSelfLinkFor(obj).withSelfRel())
val entity = entities.getRequiredPersistentEntity(obj::class.java)
entity.doWithAssociations { assoc ->
try {
if (!associationLinks.isLinkableAssociation(assoc)) {
return@doWithAssociations
}
val ownerMetadata = associationLinks.getMetadataFor(assoc.inverse.owner.type)
val propertyMapping = ownerMetadata.getMappingFor(assoc.inverse)
val rel = propertyMapping.rel
// if it is a collection, we can't get the value
if (assoc.inverse.isCollectionLike) {
return@doWithAssociations
}
val fieldValue = obj::class.java.getDeclaredField(assoc.inverse.name)
.apply { isAccessible = true }
.get(obj) ?: return@doWithAssociations
newLinks = newLinks.replace(links.createSelfLinkFor(fieldValue).withRel(rel))
} catch (e: Exception) {
// without this, we get a useless and confusing "Failed to write request"
// error response.
logger.error("Error getting links for $obj", e)
throw e
}
}
return newLinks
}
override fun getLinksForNested(obj: Any, existing: Links): Links {
return this.getLinksFor(obj, existing)
}
}
@Configuration
class RestConfiguration : RepositoryRestConfigurer {
override fun customizeLinkCollector(collector: LinkCollector): LinkCollector {
return AbsoluteLinkCollector(collector)
}
} |
Spring Data Rest creates links in a resource for all its associations which types have an exported REST repository, otherwise they are inlined as their configured JSON representation.
The created links are represented in terms of the containing resource though so the same referenced resource could have mutliple identifiers, which is not cache friendly.
For example given the following entities:
If both entities have an exported REST repository, a JSON representation would look something like:
If the related address has id=2, for example, then the same instance can be addressed as both:
We believe that a more cache friendly representation would be as:
Expanding on that, if the Person entity had an association to a collection of Addresses, we also believe that links to that collection could be more cache friendly if represented as:
We use that representation in our application and we were able to do that by overriding the LinkCollector bean.
Recently though, as reported in #1981, RepositoryRestMvcConfiguration can no longer be subclassed, so we would like to either restore the previous support, or add the ability to obtain the desired behaviour.
The text was updated successfully, but these errors were encountered: