#ConstructorBinding data class Properties with numbers as names - spring

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.

Related

Kotlin #ConstructorBinding #ConfigurationProperties class for multiple instances

I came across a problematic to which I can't find any nice solution. Some context: we work with several micro-services, most of which use rest clients. We found out that a lot of them will use similar configurations for similar issues (i.e. resiliency). Naturally, we want to extract common, heavily duplicated, non business code into a library. But here is the thing: How can I extract a #ConstructorBinding #ConfigurationProperties data class in a library (especially if there could be several instances of these classes in the code base that uses the library)?
Here is some example code:
#ConstructorBinding
#ConfigurationProperties(prefix = "rest.client")
data class MyDuplicatedRestClientProperties(
val host: String,
val someOtherField: Int,
val someFieldWithDefaultValue: String = "default value"
)
I would like to import this in a project to configure 2 different REST clients. I tried:
Creating an abstract class my ClientProperties would extend. Sadly, I need to expose all the fields of the parent class which doesn't really help with duplication:
abstract class MyAbstractClient(
val host: String,
val someOtherField: Int,
val someFieldWithDefaultValue: String = "default value"
)
#ConstructorBinding
#ConfigurationProperties(prefix = "rest.client")
class MyImplematationClient(
val host: String,
val someOtherField: Int,
val someFieldWithDefaultValue: String = "default value"
): MyAbstractClient(
host,
someOtherField,
someFieldWithDefaultValue
)
Instantiating the properties as a #Bean method with the #ConfigurationProperties but this doesn't work well either as it forces me to put fields with #Value in the #Configuration class:
#Configuration
class MyConfigurationClass {
#Value("${my.client.host}")
lateinit var host: String
#Value("${my.client.someOtherField}")
lateinit var someOtherField: Int
#Value("${my.client.someFieldWithDefaultValue:default value}")
lateinit var someFieldWithDefaultValue: String
#Bean
#ConfigurationProperties
fun myClient() = MyDuplicatedRestClientProperties(
host,
someOtherField,
someFieldWithDefaultValue
)
}
From my experience, you're on a wrong road. Why?
Duplication in microservices is allowed. Code is not too large, it's decoupled and can be easily changed.
From Distributed Systems theory, sharing classes between multiple components it's a bad thing. Why? Because doing this will couple the components via those classes.
A better approach will be to encapsulate all the integration into a specific library such as a REST client. For example, accessing Service A can be done via a service-a-client.jar which will contain the configuration and the integration that is necessary in order to call the Service A and will expose one or multiple interfaces that can be used as Spring Beans.
Putting the configuration into a library gives you no advantage, configurations are not business related, they are somehow synthetic objects and have no value in the architecture.

Is it possible to only load specific Annotations based on a profile?

Is it possible to only load specific Annotations only during tests or only during a run in Spring Boot?
I am facing a situation where there are Annotations affecting the tests, yet work well in the live run, so wanted to know whether it was possible to exclude them only during tests, but include them when running, similar to how one can include specific beans based on a Spring profile
Apologies if this has been asked before, I have tried searching to no avail
You could use the #ConditionalOnProperty annotation which creates a bean depending on which property (in the application.properties -> app.val = false) is set. For example for a service:
#Service
#ConditionalOnProperty(name = "app.val", havingValue = "false")
public class TestService {
...
}
Also you could use the #Profile annotation and annotate them to the methods which have for example a test profile (defined in the application.properties as well -> spring.profiles = test).
#Profile({"test"})
public String getValue() {
return "test value";
}
#Profile({"production"})
public String getValue() {
return "production value";
}

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.

spring boot #Cachable returning all fields of superclasses filled with null values

we are facing a strange problem and I dont quit understand whats going on and hope someone else already had the same issue and has a clue what is going on.
We wrote a simple REST service making use of #Cachable:
#GetMapping(value = "/get/{" + PARAM_TENANT + "}/{" + PARAM_UID + "}")
#Cacheable(value = GET_ORDERS_BY_UID )
public GetOrdersResponseDto getOrdersByUid(#PathVariable final String tenant, #PathVariable final String uid) {
....
return new GetOrdersResponseDto(createCacheKey(), orderResponseDtos);
}
GetOrdersResponseDto consists of several fields. Some contain instances of custom classes, some lists of them and other simple primitive values.
When the GetOrdersResponseDto response is served from the cache all fields of objects that are stored inside a list AND are located in the objects superclass are filled with null values.
We are using hazelcast as the cache implementation. And our cache config is very basic:
#Component
public class HazelcastConfig extends Config {
#Autowired
public HazelcastConfig(final ConfigClient configClient) {
super();
final GroupConfig groupConfig = getGroupConfig();
final String name = configClient
.getConfigPropertyValueOrThrow("public", "com.orderservice.hazelcast.group.name");
groupConfig.setName("foogroup");
final String password = configClient
.getConfigPropertyValueOrThrow("public", "com.orderservice.hazelcast.group.password");
groupConfig.setPassword(password);
The response class looks as follows:
public class GetOrdersResponseDto implements Serializable {
private String cacheSerial;
private List<OrderResponseDto> orderResponseDtos;
}
And the problems occur only for fields of OrderResponseDto that are part of the super class of OrderResponseDto.
I hope someone can give us an hint what's the cause for this strange behaviour.
Edit: I found out, that the problem only occurs for objects that are stored inside lists...
This is Java behaviour. See https://docs.oracle.com/javase/8/docs/api/java/io/Serializable.html
If your object is serializable and extends an object that is not serializable, then instead of the NotSerializeException which would be useful, the fields of the parent object are only initialized which is why you have them as nulls.
You can prove this in a unit test.
Here's one to reuse - https://github.com/hazelcast/hazelcast-code-samples/blob/master/serialization/hazelcast-airlines/the-code/src/test/java/com/hazelcast/samples/serialization/hazelcast/airlines/V1FlightTest.java

Inject properties name into class anotation

Is it possible to inject property name into the procedureName?
im using spring boot.
Try to use the next the next construction:
procedureName = "${procedure}" but it doesnt work
Also to write the special PropertySourcesPlaceholderConfigurer i think it not a good idea .
#NamedStoredProcedureQueries({
#NamedStoredProcedureQuery(name = "test",
procedureName = "${procedure}",
parameters = {
})
})
public class R
try to get property from properties-test.yml
Spring properties used to inject values in bean properties like below,
public class ClassWithInjectedProperty {
#Value("${props.foo}")
private String foo;
}
you case is not valid for value injection.

Resources