Lombok Builder with defaults in Spring Data - spring

I have the following (simplified) class:
#Entity
#Data
#Builder
#NoArgsConstructor // #Builder creates the all-args version but I need this one elsewhere.
#AllArgsConstructor(access = AccessLevel.PRIVATE) // Having #NoArgsConstructor disables the #AllArgsConstructor needed by #Builder unless I have this explicitly applied
public class MyClass {
// ...
#Builder.Default
private String foo = "Some Default Value"
// ...
}
This works fine in the general case, but a problem comes about when this Entity gets built by Spring (specifically Spring Data Repository in response to a REST request to create it). Spring sees the all-args constructor and uses it, setting a null value for foo (in this case it wasn't set in the POST data) and I end up with a null value for foo instead of the expected "Some Default Value". The #Builder.Default.
It does not work to simply create the setter and prevent nulls. I could write out the AllArgsConstructor explicitly, but that (somewhat) defeats the purpose of Lombok to some extent. Not a horrible solution, but not ideal.
Can anyone think of any workarounds for this?

I know you can specify default values with the non-lombok default values. I haven't tried it with this particular case, but its worth a shot.
#Value("#{prop['my.property'] ?: 12345}")
Maybe you should try using the RequiredArgs Constructor which only specifies a constructor that uses "one argument per final / non-null field" and that won't override the var.
https://projectlombok.org/features/constructor

I was able to work around this using a PrePersist annotation in my model class. With the PrePersist I can make sure that null values get their defaults set regardless of when they were set to null, or by whom. Not truly a fix for this, but something I should probably be doing anyway so it's an acceptable workaround.

Related

What 'final' keyword next to the field stands for?

In a legacy code, I'm working with, I found the following thing:
#Autowired
final lateinit var controller: CustomController
what does this final keyword mean here?
In a Kotlin documentation I found a short description about final keyword that is blocking overriding of the methods in open classes but no information about fields. Also - the class within which I found the line is not open
A final property or a method in Kotlin prevents overriding of the field / method. That being said, Kotlin by default considers a property or a method/function to be final unless specified by the keyword open. In your case, the final keyword is redundant.
Here's a small demo test case to illustrate the same.
open class Parent {
open val someValue = 0
final val otherValue = 13 // redundant modifier 'final' warning in Android Studio
}
class Child : Parent() {
override val someValue = 5
// override val otherValue = 19 // compile error
}
There is an interesting problem called Fragile Base Class in OOP and why some languages like Kotlin prefer final by default.
What you have there is a property, not a field.
It looks just like a field, as it would in Java; but in Kotlin, it actually defines a public getter method, a public setter method, and a private backing field*.
So the final modifier applies to the accessor methods, preventing those from being overridden in a subclass.  (As you say, the backing field itself can't be overridden anyway.)
As Siddharth says, final is the default in Kotlin, so you usually wouldn't need to specify it, though there are a few situations in which it would be needed — e.g. if it were already overriding something, or you were using the all-open or kotlin-spring compiler plug-ins.  (The use of #Autowired suggests that this is a Spring module, which probably explains why final is needed here.)  In any case, your IDE would probably indicate where it's not needed, e.g. by showing it greyed-out.
(* Only the getter is necessary; the setter isn't generated for a val, and the backing field isn't generated if you override the accessor(s) and they don't refer to it.)

Spring boot `#Value` is always zero, despite default

I am using
#Service
public class Foo
{
#Value ("${this.does.not.exist: 10}")
private static int bar;
}
Because the value does not exist in the configuration, I was expecting bar to have value 10 but it is 0.
I also tried #Value ("${this.does.not.exist: #{10}}") as per this answer, it's still zero.
Why didn't this work?
I omitted the static keyword in the OP (now edited) and that was the fault.
The following code works for me:
#Service
public class MyService {
#Value("${this.does.not.exist: 10}")
private int val;
With the last version of spring-boot (and it should with any other version basically).
So the issue must be elsewhere.
Please check the following:
Make sure that Foo is indeed managed by spring (it finds it in the component scanning process). Otherwise no-one will inject the value.
Make sure that you're not trying to read the value from constructor or something:
In spring it first creates the object (and at that moment the value of the field is 0 indeed) and only then triggers a bean post processors for injecting values, autowiring, etc.

Is it possible to pass a key as default value in #Value annotation of Spring

I have a situation where we are reading one property from properties file and now we have been asked to point to another endpoint and for some time we have to manage both these endpoints unless this new endpoint is tested and validated throughly.
I wanted to handle this situation by adding this newer property in properties file and in the actual class were we are reading this property with #Value Annotation the old one can be passed as default with its key as value something like
#Value("${backend.endpoint:${older.endpoint}}"). is it possible ?
Yes you can do it, I have tested it, my sample code
code:
#Value("#{ ${spring.myapp.usenewval} ? '${spring.myapp.newval}' : '${spring.myapp.oldval}}'}")
private String message;
Properties
spring:
myapp:
usenewval: false
newval: hello
oldval: world.....
You can always set spring.myapp.usenewval from outside like
java -jar -Dspring.myapp.usenewval=true myapp.jar
You can use it like this. (I've personally never done it, so forgive me if I'm wrong)
#Configuration
public class PropertyConfiguration {
#Value("{'${backend.endpoint:${older.endpoint:}}'}")
private String myValue;
}
This #Value annotation uses backend.endpoint, if it is provided and defaults to older.endpoint, if backend.endpoint is not provided.
If neither is provided, the property must be set null.
There are other ways to handle this as well. Probably, use #Value for both the property and handle in code.
Here is quick fix for you. Kindly refer it.
You can set default value to #Value annotation of spring as following.
#Controller
#RequestMapping(value = "/your path")
public class MyController {
#Value("${key:true}")
private boolean booleanWithDefaultValue;
}
Here, I take Boolean variable and set default value as "true".
Hope this solution works.

Jackson deserializer priority?

I have a Spring Boot app that is modeling ActityStreams objects and for the most part Jackson's Polymorphic Deserialization works well.
There are 'objects' in the JSON which are references (links) and not JSON objects with type information. For instance
"actor":"https://some.actors.href/ rather than
"actor":{
"type":"Actor",
"name":"SomeActor"
}
I've written custom deserializers and and placed them on the fields to deal with this
#JsonDeserialize (using = ActorOrLinkDeserializer.class)
private Actor actor;
However my ActorOrLinkDeserializer is instantiated but never called and Jackson complains with Missing type id when trying to resolve subtype of [simple type, class org.w3.activity.streams.Actor]: missing type id property 'type' (for POJO property 'actor') which is from the polymorphic deserializer.
It appears that the polymorphic deserialization code takes precedence over my local #JsonDeserialize annotation and I need a way to force my code to run first.
I've tried using my own ObjectMapper rather than Boot's and there's no difference.
I'd appreciate pointers and suggestions.
It turns-out there's a fairly simple solution to this problem using a DeserializationProblemHandler.
What I've implemented that works for all test cases so far is
1.
objectMapper.addHandler(new DeserProblemHandler());
or register with Spring Boot.
2.
public class DeserProblemHandler extends DeserializationProblemHandler {
public JavaType handleMissingTypeId(DeserializationContext ctxt, JavaType baseType, TypeIdResolver idResolver, String failureMsg) {
return TypeFactory.defaultInstance().constructType(baseType.getRawClass());
}
}
Add a constructor to each of the polymorphic classes that takes a string argument which is the href.

Spring-Wicket: optional but named bean still required

Consider following (wicket) code:
#SpringBean(required=false)
private StatusCheckService service;
In my use case there is no bean of type StatusCheckService in my context.
This is not a problem, it is marked as optional so the (wicket) page will initialize just fine.
However:
#SpringBean(name = "statusCheckService", required=false)
private StatusCheckService service;
This fails:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'statusCheckService' is defined
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:529)
at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1095)
at org.springframework.beans.factory.support.AbstractBeanFactory.isSingleton(AbstractBeanFactory.java:400)
at org.springframework.context.support.AbstractApplicationContext.isSingleton(AbstractApplicationContext.java:1113)
at org.apache.wicket.spring.SpringBeanLocator.isSingletonBean(SpringBeanLocator.java:100)
at org.apache.wicket.spring.injection.annot.AnnotProxyFieldValueFactory.getFieldValue(AnnotProxyFieldValueFactory.java:140)
I would expect it makes no difference. OK, there is no bean with such a name but I marked it as optional so why the exception? Is there any way around this?
By doing a usage search in eclipse, you can see that the only time the annotation is queried for its required value is on line 215 of AnnotProxyFieldValue:
if (names.isEmpty())
{
if (annot.required())
{
throw new IllegalStateException("bean of type [" + clazz.getName() + "] not found");
}
return null;
}
Here you can see that the 'required' field of the #SpringBean annotation is only used if the names list (set higher in the function) is empty...
This explains the behaviour you are experiencing, because when you don't specify a name, the names list is empty (because it could not find your class), and null is returned. However when you do specify a name it doesn't bother trying to see if the class exists, and so sends your supplied class-name for spring to look up which proceeds to complain when it cannot find the class.
This could be a bug depending on designers intentions, personally I think if you have specified that the field is not required then the function should return early... If you agree then maybe consider putting in a jira ticket with a quick start.
As for a solution, you haven't mentioned much about your use case, and why you need to set the name even though you do not require the bean to be injected. But assuming you are doing it to dynamically inject the bean based on certain criteria, you could do something like the following: (untested)
In your class where the SpringBean is injected, replace:
#SpringBean(name="statusCheckService", required=false)
private StatusCheckService service;
With:
MyCustomSpringBeanInjector injectedService;
And then MyCustomSpringBeanInjector class is something like:
import org.apache.wicket.injection.Injector;
import org.apache.wicket.spring.injection.annot.SpringBean;
public class MyCustomSpringBeanInjector
{
#SpringBean public StatusCheckService service;
public MyCustomSpringBeanInjector()
{
Injector.get().inject(this);
}
}
Then back in the class where the SpringBean was being injected, put something like:
if(statusCheckServiceRequired)
injectedService = new MyCustomSpringBeanInjector();
and replace "service." with "injectedService.service" where necessary.
Obviously this is a qwik-e fix off the top of my head, and I'm sure there's a better way of doing it, but you get the idea! :)
Best of luck!

Resources