How to deserialize json object referring related data using hal links with Jackson and GraalVm? - spring-boot

I'm currently struggling to implement a restfull api using
-> Spring Boot 3.0.0, using spring data rest and spring hateoas
-> Spring native support
Basically, I would like to patch an entity, submitting a new value for a field (pointing to an existing entity in Database).
The reference is handled with an hal link. below is the Json object I "PATCH" to my EntityRepository controller:
{
"shouldBeChecked": true,
"name": "Amical6",
"parent": null,
"authority": "https://myurl/api/authorities/e52cdfb6-6f3c-4552-8ea4-e1357b5d052c"
}
Everything is fine when I run the app from my IDE (without GraalVM Native compilation). But when I go live on my test environment (copiled with GraalVM), I get the following errors :
in the web navigator (running the client app):
{"cause":{"cause":null,"message":"Cannot construct instance of `org.[xxx].Authority` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('https://myurl/api/authorities/e52cdfb6-6f3c-4552-8ea4-e1357b5d052c')\n at [Source: UNKNOWN; byte offset: #UNKNOWN] (through reference chain: org.[xxx].MyObject[\"authority\"])"},"message":"Could not read payload"}
And, in my application logs :
Failed to evaluate Jackson deserialization for type [[simple type, class org.springframework.data.rest.webmvc.PersistentEntityResource]]: java.lang.NullPointerException
I'm pretty sure I've got to add some Native Hints related to Jackson (or maybe Spring hateoas ?). Could someone help me with this?
Regards,
EDIT 2022.11.30
Please find below a small reproducer project:
https://github.com/mathieupedreropro/spring-hal-jackson-graalvm
TLDR
I tried to use Spring Hateoas link handling to patch data to my Rest Webservice in a Native compiled Spring boot 3.0.0 application.
It fails at runtime, when the same application works like a charm using a traditional JDK

Related

Classcastexception for CloudEventMessageConverter to AbstractMessageConverter

when using spring cloud stream latest version with cloud events spring sdk
Facing classcastexception in below scenario.
Message has contentType = application/*+avro.
SmartcompositeMessageConverter contains converter list like below -
CloudEventMessageConverter
AvroSchemaRegisteryClientMessageConverter
and some ootb converters.
While converting to message from producer..
SmartcompositeMessageConverter has this line of code for wildcard contentType
((AbstractMessageConverter) converter).getSupportedMimeTypes()
But CloudEventMessageConverter is not an instance of AbstractMessageConverter, hence it throws an exception.
Please guide what should be overridden in this case.
If I create a new CloudEventMessageConverter which extends AbstractMessageConverter, I cann't add headers as only method allowed to override is convertFromInternal which returns just the payload.

Spring Boot 3 native image with Jackson

I'm trying to setup a new application with latest SpringBoot 3 and everything works fine until I try to create and run my application with Native compilation. Just for your reference here is error that I receive from running unit tests:
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `*****`: cannot deserialize from Object value (no delegate- or property-based Creator): this appears to be a native image, in which case you may need to configure reflection for the class that is to be deserialized
at [Source: (String)"[{"T":"success","msg":"authenticated"}]"; line: 1, column: 3] (through reference chain: java.util.ArrayList[0])
com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1909)
com.fasterxml.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:408)
com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1349)
com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1417)
com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:352)
I'm trying to convert JSON string to Object. It works fine when I run it as regular JAR , but it fails with I compile with Navite (GraalVM).
in spring native, Jackson doesn't know how to serialize/deserialize objects without prior knowledge about them, since it must know all the types in compile time.
to run your app in a native mode, you will need to register hints with knowledge about needed proxies, reflected methods, additional resource files/paths, or objects to serialize/deserialize.
to do so, implement the RuntimeHintsRegistrar interface. you can see an example in the docs

Get Jackson ObjectMapper in Quarkus

I am writing a custom OpenApiConfigurator that adds some examples to my api dynamically.
When I add the examples using the value field of io.smallrye.openapi.api.models.examples.ExampleImpl, which is an object, the example is null in swagger-ui. It only works when I added the actual json.
To add the actual json I have to generate it from my response dto using Jackson. But how can I access the quarkus object mapper, for which I have some customisations using ObjectMapperCustomizer, if in the OpenApiConfigurator CDI is not available?
It's actually possible to access the CDI container statically with Arc.container().instance(ObjectMapper::class.java).get()
That solved it for me.

Couchbase modify meta data experation using Spring

When using Spring Couchbase connector I can easily get version for optimistic locking by having this in my class:
public class MyClass {
#Version
private String version;
.... rest of class omitted ....
}
I'm now trying to find a similar way to get and be able to modify the meta data for expiration. I'm unable to find how to do this.
Can someone please give an example? Thanks!
With spring data couchbase library (until the latest Version 3.0.8.RELEASE), document expiry can be defined by using #Document(expiry = 10) or #Document(expiryExpression = "${valid.document.expiry}") on the class. There is also an optional boolean attribute touchOnRead which needs to be added with #Document, which would reset the expiry timer whenever the document is directly read. Please note that currently the expiry of an existing document cannot be read/modified directly with this library. One way would be to access the below APIs exposed by Couchbase's own java SDK (com.couchbase.client.java)
getAndTouch - allows you to retrieve a document while modifying its expiration time
touch - allows you to modify a document’s expiration time without otherwise accessing the document
You can find the method signatures of the above two here : http://docs.couchbase.com/sdk-api/couchbase-java-client-2.2.4/com/couchbase/client/java/Bucket.html
The above two APIs can be accessed via the spring data couchbase library as follows
couchbaseTemplate.getCouchbaseBucket().touch(...)
couchbaseTemplate.getCouchbaseBucket().getAndTouch(...)
The getCouchbaseBucket() method of the spring library returns a reference to com.couchbase.client.java.Bucket using which the touch and getAndTouch methods can be used.

How to use Spring Hatoas ControllerLinkBuilder for a Thymeleaf templated scheduled email

I'm using Spring Hateoas in a Boot app to avoid manual creation of links in the view. It works great in Thymeleaf views, it works when a controller calls a service to send an email that is also rendered by Thymeleaf.
The code to create link is pretty standard
this.readLink = linkTo(methodOn(PostController.class)
.readPost(eventId, postId))
.withRel("ReadPost");
But for a #Scheduled service generated email, it fails like this
015-08-23 22:28:40.886 ERROR 1180 --- [pool-2-thread-1] o.s.s.s.TaskUtils$LoggingErrorHandler : Unexpected error occurred in scheduled task.
java.lang.IllegalStateException: Could not find current request via RequestContextHolder. Is this being called from a Spring MVC handler?
at org.springframework.util.Assert.state(Assert.java:392) ~[spring-core-4.2.0.RELEASE.jar:4.2.0.RELEASE]
at org.springframework.hateoas.mvc.ControllerLinkBuilder.getCurrentRequest(ControllerLinkBuilder.java:242) ~[spring-hateoas-0.18.0.RELEASE.jar:na]
at org.springframework.hateoas.mvc.ControllerLinkBuilder.getBuilder(ControllerLinkBuilder.java:189) ~[spring-hateoas-0.18.0.RELEASE.jar:na]
at org.springframework.hateoas.mvc.ControllerLinkBuilderFactory.linkTo(ControllerLinkBuilderFactory.java:121) ~[spring-hateoas-0.18.0.RELEASE.jar:na]
Is there anything I can do to get around the lack of an HttpServletRequest due to the code running as a #Scheduled job?
ControllerLinkBuilder currently can only be used from within a request as only that allows it to create a fully qualified link using server and port information from it.
Within an #Scheduled-invoked method that information is not available. If you provide more information on what you're actually creating in that very method, I can suggest workarounds.

Resources