MapStruct : Conditional mapping of object property - spring

I am using mapstruct in springboot application for mapping StudentBean(source) to StudentDTO(target).
I want to map properties on condition basis, conditions are as below :
Condition 1: If target(StudentDTO) property has value then don't map from source(StudentBean) property.
Condition 2: If source(StudentBean)property has value then only map into target(StudentDTO)else target value should not be updated.
Source class as below :
StudentBean{
String id;
int marks;
}
Target class as below :
StudentDTO{
String id;
int marks;
}
Mapper class as below :
#Mapper
Interface studentMapper{
#mapping(target="id", source="id")// apply condition 1
#mapping(target="marks", source="marks")// apply condition 2
StudentDTO toDTO(StudentBean);
}
How to achieve this with mapstruct?

From the conditions you described, I assume you want to map a source StudentBean to an existing StudentDTO target, otherwise, your first condition will not make sense. You can read more about mapping to existing bean from Mapstruct documentation here.
Your condition 2 can be easily achieved by setting nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE on the #Mapping annotation. This setting will let Mapstruct ignore the mapping if the source's propeties are null.
Your condition 1 is a little bit tricky, but it can be achieved with the newest feature Conditional Mapping in 1.5.0.Beta1 release. Please note this feature is only available in beta release at this moment.
Your mapper would be set up and written like below:
pom.xml
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.5.0.Beta1</version>
</dependency>
mapper
#Mapper
Interface studentMapper{
#Mapping(conditionExpression = "java(studentDTO.getId() == null)", target="id", source="id", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
#Mapping(target="marks", source="marks", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
StudentDTO toExistingStudentDTO(StudentBean studentBean, #MappingTarget StudentDTO studentDTO);
}
conditionExpression property on the first #Mapping is available in the beta release. The NullValuePropertyMappingStrategy.IGNORE setting is also set on this mapping because otherwise Mapstruct will set id to null by default if the condition in conditionExpression is not met.

Related

#ConstructorBinding data class Properties with numbers as names

i'm adding property validation to an existing big project. It has hundrets of webservices and there are some that have simple numbers as names.
Now im trying to write a data class using #Validated, #ConstructorBinding and #ConfigurationProperties.
So imagine a property
dummy.941=http:...
The name of the variable would need to be 941 now, as far as i can tell, but kotlin/java dont allow variable names starting with numbers.
#Validated
#ConstructorBinding
#ConfigurationProperties(value = "dummy", ignoreUnknownFields = false)
data class DummyProperties(
val abc: Abc = Abc(), ....
val 941: Ws941: Ws941()
)
Is there any workaround, some annotation, that says which property is meant? It is not possible to change the name of the property, since the same property database is in use different working systems and people told me thats off the table.
Thanks for any help!
EDIT:
I found a way, spring offers a #Name annotation (org.springframework.boot.context.properties.bind)
#Valid
#Name(value = "703")
val s703: S703 = S703(),
Works like a charm:)
Some time ago, I had a similar issue. You can solve it, at least for Java, by using a custom setter. I have no idea about Kotlin, but I assume it works in the same way for Spring Kotlin.
#ConfigurationProperties(value = "dummy", ignoreUnknownFields = false)
public class DummyProperties {
private Ws941 _941;
public void set941(Ws941 _941) {
this._941 = _941;
}
public Ws941 get941() {
return this._941;
}
}
Spring can map using the setter, so the variable can have a different name.

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.)

How do you make Spring fail fast when using placeholders with #ConfiguraitonProperties and an environment variable is not set?

When using placeholders to externalise configuration in an application.yaml file, and an associated properties class, how do you make sure Spring fails during startup when it can't resolve a placeholder, instead of just using the placeholder itself as the verbatim value?
For example, given this application.yaml file:
example.key: ${MY_ENV_VAR}
and this properties POJO:
#ConfigurationProperties(prefix="example")
public class AcmeProperties {
public String key;
// Getters, setters, constructors omitted...
}
if MY_ENV_VAR is not set on the system, how do you make Spring throw an exception at startup, instead of setting key to literally ${MY_ENV_VAR}?
Note, Spring doesn't return an empty String, which we could force by defining a default with ${MY_ENV_VAR:defaultValue}, or null (SpEL is not evaluated in #ConfigurationProperties, so this can not be defined as the default, and the behaviour of System.getenv("MY_ENV_VAR") returning null in the case of an undefined environment variable isn't mirrored), it literally just uses the placeholder as the value. We'd rather Spring stopped launching the app altogether instead, for all properties in AcmeProperties. Just using #Validated with Hibernate Validator on the classpath doesn't do it, neither does #ConfigurationProperties(prefix="example", ignoreInvalidFields=false) (which is the default anyway).
A manual check for the value containing ${...} could of course be added to all String properties in the POJO, but this is more error-prone, and would also not work if the actual environment variable was set to a string containing that character sequence. Is there a way to check if the placeholder can be resolved, instead of if the value after resolution is complete?
You can have a custom validator which could look like:
object UnresolvedPropertiesValidator : Validator {
override fun supports(clazz: Class<*>): Boolean {
return clazz == String::class.java
}
override fun validate(target: Any, errors: Errors) {
val stringVal = target as String
if (stringVal.startsWith(SystemPropertyUtils.PLACEHOLDER_PREFIX) && stringVal.endsWith(SystemPropertyUtils.PLACEHOLDER_SUFFIX)) {
errors.reject(
"prop.validation",
"Could not resolve placeholder $target"
)
}
}
}
And then create a bean with the name configurationPropertiesValidator:
class UnresolvedPropertiesValidatorAutoConfiguration {
#Bean
fun configurationPropertiesValidator(): UnresolvedPropertiesValidator {
return UnresolvedPropertiesValidator
}
}
As you can see from these https://github.com/spring-projects/spring-boot/blob/24a52aa66ddb92cd14acb2b41d9f55b957a44829/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/EnableConfigurationProperties.java#L47
https://github.com/spring-projects/spring-boot/blob/9630f853be3183f4872428e2e65b6ef4be7a9b7a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinder.java#L105,
the validator with the name above will be applied after resolving the prop, and it simply ensures the final value doesn't start with ${ and end with }.
This works for us. But hopefully, the issue will be resolved soon, and we will remove this workaround.

What is purpose of #ConditionalOnProperty annotation?

I just modified spring boot configuration, and encountered
#ConditionalOnProperty(prefix = "spring.social.", value = "auto-connection-views")
from org.springframework.boot.autoconfigure.social.TwitterAutoConfiguration
#Bean(name = { "connect/twitterConnect", "connect/twitterConnected" })
#ConditionalOnProperty(prefix = "spring.social.", value = "auto-connection-views")
public View twitterConnectView() {
return new GenericConnectionStatusView("twitter", "Twitter");
}
I don't understand purpose of this annotation. I guess this might be enable to use bean only if property value exist(e.g. "spring.social", "auto-connection-views").
The annotation is used to conditionally create a Spring bean depending on the configuration of a property. In the usage you've shown in the question the bean will only be created if the spring.social.auto-connection-views property exists and it has a value other than false. This means that, for this View bean to be created, you need to set the spring.social.auto-connection-views property and it has to have a value other than false.
You can find numerous other uses of this annotation throughout the Spring Boot code base. Another example is:
#ConditionalOnProperty(prefix = "spring.rabbitmq", name = "dynamic", matchIfMissing = true)
public AmqpAdmin amqpAdmin(CachingConnectionFactory connectionFactory) {
return new RabbitAdmin(connectionFactory);
}
Note the use of matchIfMissing. In this case the AmqpAdmin bean will be created if the spring.rabbitmq.dynamic property exists and has a value other than false or the property doesn't exist at all. This makes the creation of the bean opt-out rather than the example in the question which is opt-in.
In case you are using this property on TYPE-level, i.e. on one of your #Configuration classes... Keep in mind that in such case the annotation is evaluated/checked against the default properties file, i.e. application.properties
#ConditionalOnProperty on TYPE level w/ #Configuration
Rather, it is the opposite. A precondition for implementing the method, if the property is set in the environment (development, approval, production) and is true value with the method can be executed.
If the property is not set in the environment annotation not prevented the execution of the method.

#Value annotation in child context

I think there are some problems when using #Value annotation and default values. I am using the following annotation on a variable. I expect to see the variable propAVar set with the property value of propA however, its always set to the default value -10
#Component
public class SomeClass {
#Value("${propA:-10}")
public String propAVar;
}
I am using PropertyPlaceholderConfigurer in the parent and child context. The child context has the context:component-scan element which scans the SomeClass. On digging into the Spring code i found the following method where the issue might lie.
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
//...
public String resolveEmbeddedValue(String value) {
String result = value;
for (StringValueResolver resolver : this.embeddedValueResolvers) {
if (result == null) {
return null;
}
result = resolver.resolveStringValue(result);
}
return result;
}
//...
}
Looks like resolveEmbeddedValue will go through all embeddedValueResolvers and ascertain value of that property. However, if we define a default it will look for that property in the first embeddedValueResolvers and return the default value if its not found there. Shouldn't it go through all resolvers and then return the default value? Would like to mention that this works fine without the default value.
Would be great if someone could help explain the expected behavior of #Value with default value in case of parent-child context's.
[cross-posted on spring forum]
I believe you are seeing this bug. There are some workarounds mentioned by other users in there. Also, I would suggest voting for fixing it, in case your situation is the same as the one described there.

Resources