Spring Boot - Custom De/Serializer - spring-boot

Want: Create SerDes for Cassandra's LocalDate into String.
Setup: Have a private LocalDateSerDes class with a Serializer and Deserializer internal class that both extend JsonDe/Serializer
Tried the following:
Adding to DTO fields with #JsonSerialize and #JsonDeserialize (Works, but not wanted)
Used #JsonComponent set up via Baeldung's example (does not work)
Created a Module bean on my WebMvcConfigurationSupport configuration (does not work)
Implemented WebMvcConfigurationSupport.extendMessageConverters to add the Module to the instance of MappingJackson2HttpMessageConverter (does not work)
Iterated through the list of converters, removed the instance, and replaced with the default converter builder code but with the module (does not work)
Any idea where I went wrong?

Related

Spring #AliasFor not working for #Profile annotation

Environment: Kotlin 1.5.30, SpringBoot 2.5.4(Spring 5.3.9)
Background & Issue
I'm trying to create a composed annotation to simplify similar template annotation codes. The annotation class is like this:
#Profile("default") //NOTE 1: use a placeholder, see the investigations below
annotation class ProfileAware(
#get: AliasFor(annotation = Profile::class, attribute = "value")
val profiles: Array<String>,
//properties delegated for other annotations
)
expected usage:
#Component
#ProfileAware(profiles = ["dev"])
class TheBean {
init {
LoggerFactory.getLogger("TheBean").info("TheBean: I'm registered")
}
}
in application.yaml:
spring:
profiles:
active: dev
But after the application starts, TheBean is not registered as expected.
Investigation & Try
First I've search in github spring repository, and found this: Regression: custom composed #Profile annotation without runtime retention no longer supported with component scanning. So I tried to add #Retention(AnnotationRetention.RUNTIME) on #ProfileAware, but no effect.
Tried to remove ("default") value from meta annotation (and, whether add the default value to profiles attribute or not), but got java.lang.IllegalArgumentException: Must specify at least one profile.
Tried to remove #Profile("default") from meta annotation, but got AnnotationConfigurationException: #AliasFor declaration on attribute 'profile' in annotation ... which is not meta-present.
(Important) Try to use #Profile("dev") directly on bean instead of ProfileAware , works as expected.
(Important) Try to change the value on meta annotaion as "dev", it works, but obviously it is hardcoded and not match my need.
Is there something I did wrong? Or is it possible to create composed annotation with dynamic #Profile value?
Thanks for your reading and help.
#Profile is looked up by org.springframework.context.annotation.ProfileCondition whose matches(...) method uses org.springframework.core.type.AnnotatedTypeMetadata.getAllAnnotationAttributes(String) to look up the #Profile annotations, and the Javadoc for that method states the following (bold emphasis added by me).
Retrieve all attributes of all annotations of the given type, if any (i.e. if defined on the underlying element, as direct annotation or meta-annotation). Note that this variant does not take attribute overrides into account.
Thus, you currently cannot use #Profile with #AliasFor for annotation attribute overrides.

How does spring boot #Value("${somevalue}") annotation work?

I have some #Value annotation in spring-boot project. To Simplify, I have few classes: a restcontroller, service (annotated with #Service) and a pojo.
In each of these classes, I declared a variable as below:
#Value("${somevalue}")
private String somevalueVariable
In the controller class, the value is getting populated as defined in the application.properties. So no problem here.
In the service class, the value is showing up as null. This is my issue, how should i fix it to get the value from the application.properties
In the pojo, the value is showing up as null, I am thinking this is expected behaviour as spring does not manage this class.
Try this:
import org.springframework.beans.factory.annotation.Value;
#Value("#{${somevalue}}")
private String somevalueVariable
ideally service class should have #Service anotation over it, either you missed that or this class is not scanned by spring context, so please add ComponentScan anotation for service class package over main class to scan classes uner this package -
#SpringBootApplication
#ComponentScan({"com.in28minutes.springboot"})
public class Application
It uses Spring Expression Language (SpEL):
https://docs.spring.io/spring-integration/docs/5.3.0.RELEASE/reference/html/spel.html
Also there is 2 #Value : org.springframework.beans.factory.annotation.Value and lombok.Value;
Make sure you are using the right one.
To get value from property try this:
#Value("${systemValue}")
private String systemValue;
For more information I find this useful:
https://www.baeldung.com/spring-value-annotation

Missing Converter when using Spring LdapTemplate with Grails Validateable annotation

I'm using the Spring LDAP (docs) library in a Grails application. I have a class annotated with the #Entry annotation, so it is mapped to an LDAP server. This all works quite beautifully.
However, when I add the Grails #Validateable annotation (to enable validating the LDAP class similarly to Grails domain classes) and attempt to retrieve data from LDAP (i.e. a findAll operation on the LdapUserRepo, or similar), I get the following exception:
Message: Missing converter from class java.lang.String to interface org.springframework.validation.Errors, this is needed for field errors on Entry class com.ldap.portal.LdapUser
Basically, it seems like the AST transformation performed by the #Validateable annotation is producing extra fields (namely the errors field) on the LdapUser object. It appears that Spring LDAP, in processing the #Entry logic, assumes a default mapping for the fields property (probably interpreting it as a string field on the LDAP object). When it gets nothing from the LDAP server, it attempts to set the field of type ValidationErrors to a value of type String -- an empty string.
I did some looking in github and found this code that seems relevant and may support my theory.
My question is: is this behavior expected for annotations, and how can one prevent fields added by one annotation from being inappropriately processed by another annotation?
At present the best workaround I've come up with for my specific issue is to add an errors field to my LdapUser object and mark it as transient (so that LDAP ignores it):
#Transient
ValidationErrors errors

#ConditionalOnProperty conditionally works

I have code like the following:
#Scheduled(cron = "${cron.foo.bar}")
#ConditionalOnProperty(name="cron.foo.bar.enabled", relaxedNames = false)
public void parseFooBar() {
... blah blah blah ...
}
In my properties file, I have:
cron.foo.bar=1 * * * * ?
cron.foo.bar.enabled=false
This does not work, and parseFooBar gets executed every minute on the 1st second.
However, if I add the field:
#Value("${cron.foo.bar.enabled}")
private String enabledProp;
so that I can do a log and see what it is, parseFooBar does NOT get executed. Removing the injected String once again sees parseFooBar execute. What am I doing wrong?
Edit: This is using Spring 4.1.5, Spring Boot 1.2.1, and JDK 8
Edit 2: moving the annotation to the type also works. (without having to force the #Value). But the annotation is both a Method and a Type annotation? It gives me a little more flexibility to do it on the method...
A condition in Spring Framework is used to control whether or not a component is registered in the application context. From the javadoc of #Conditional:
The #Conditional annotation may be used in any of the following ways:
as a type-level annotation on any class directly or indirectly annotated with #Component, including #Configuration classes
as a meta-annotation, for the purpose of composing custom stereotype annotations
as a method-level annotation on any #Bean method
When the condition is declared on parseFooBar it has no effect as it's not a #Bean method. It works as you expect when you declare it on the type as it then makes the component conditional such that it's not registered in the application context when the property doesn't match.

Jackson deserializing custom classes in an OSGi environment

I have some trouble using Jackson 2.1 in an OSGi environment, when deserializing a class that way:
ObjectMapper mapper = new ObjectMapper();
User user = mapper.readValue(new File("user.json"), User.class);
class User {
public Class clazz = org.example.MyClass.class;
}
Because Jackson is in a different bundle as my custom classes I want to deserialize, I often get a java.lang.ClassNotFoundException - usually on MyClass1 or MyClass2.
I traced it back to the class com.fasterxml.jackson.databind.util.ClassUtil which uses Class.forName(..) for retrieving a class for deserializing. Because of the different class-loaders on OSGI it only sees the classes of the JRE and of Jackson but not my custom classes.
Is there a simple way to make Jackson find all the required custom classes (I have dozens of them), e.g by adding a class-loader?
As the client of Jackson you have visibility of the classes that you want to deserialize into. The trick is to pass these classes into Jackson, rather than force Jackson to use dynamic reflection to find the classes.
The Jackson documentation indicates that the method ObjectMapper.readValue can take a Class object as its parameter. If you use this method then Jackson should not need to call Class.forName(). The docs give the following example:
ObjectMapper mapper = new ObjectMapper();
User user = mapper.readValue(new File("user.json"), User.class);
Here, User is the domain class which is visible to your client but not to Jackson. This invocation should work fine in OSGi... if it does not then I would suggest Jackson may have a bug.

Resources