Configuring an enum in Spring using application.properties - java

I have the following enum:
public enum MyEnum {
NAME("Name", "Good", 100),
FAME("Fame", "Bad", 200);
private String lowerCase;
private String atitude;
private long someNumber;
MyEnum(String lowerCase, String atitude, long someNumber) {
this.lowerCase = lowerCase;
this.atitude = atitude;
this.someNumber = someNumber;
}
}
I want to setup the someNumber variable different for both instances of the enum using application.properties file.
Is this possible and if not, should i split it into two classes using an abstract class/interface for the abstraction?

You can't/shouldn't change values of a enum in Java. Try using a class instead:
public class MyCustomProperty {
// can't change this in application.properties
private final String lowerCase;
// can change this in application.properties
private String atitude;
private long someNumber;
public MyCustomProperty (String lowerCase, String atitude, long someNumber) {
this.lowerCase = lowerCase;
this.atitude = atitude;
this.someNumber = someNumber;
}
// getter and Setters
}
Than create a custom ConfigurationProperties:
#ConfigurationProperties(prefix="my.config")
public class MyConfigConfigurationProperties {
MyCustomProperty name = new MyCustomProperty("name", "good", 100);
MyCustomProperty fame = new MyCustomProperty("fame", "good", 100);
// getter and Setters
// You can also embed the class MyCustomProperty here as a static class.
// For details/example look at the linked SpringBoot Documentation
}
Now you can change the values of my.config.name.someNumber and my.config.fame.someNumber in the application.properties file. If you want to disallow the change of lowercase/atitude make them final.
Before you can use it you have to annotate a #Configuration class with #EnableConfigurationProperties(MyConfigConfigurationProperties.class). Also add the org.springframework.boot:spring-boot-configuration-processor as an optional dependency for a better IDE Support.
If you want to access the values:
#Autowired
MyConfigConfigurationProperties config;
...
config.getName().getSumeNumber();

Well what you can do is the following:
Create a new class: MyEnumProperties
#ConfigurationProperties(prefix = "enumProperties")
#Getter
public class MyEnumProperties {
private Map<String, Long> enumMapping;
}
Enable ConfigurationProperties to your SpringBootApplication/ any Spring Config via
#EnableConfigurationProperties(value = MyEnumProperties.class)
Now add your numbers in application.properties file like this:
enumProperties.enumMapping.NAME=123
enumProperties.enumMapping.FAME=456
In your application code autowire your properties like this:
#Autowired
private MyEnumProperties properties;
Now here is one way to fetch the ids:
properties.getEnumMapping().get(MyEnum.NAME.name()); //should return 123
You can fetch this way for each Enum value the values defined in your application.properties

Related

How to override constructor values

I have a configuration class with this constructor:
public CxfConfigurerImpl(#Value("${cxfclient.timeout.connection}") long connectionTimeout,
#Value("${cxfclient.timeout.connection-request}") long connectionRequestTimeout,
#Value("${cxfclient.timeout.receive}") long receiveTimeout) {
this.receiveTimeout = receiveTimeout;
this.connectionTimeout = connectionTimeout;
this.connectionRequestTimeout = connectionRequestTimeout;
}
But I have different values to set according with my endpoints requests.
Ex:
#Value("${cxfclient.timeout-three.connection}")
private long connectionTimeout3;
#Value("${cxfclient.timeout-three.connection-request}")
private long connectionRequestTimeout3;
#Value("${cxfclient.timeout-three.receive}")
private long receiveTimeout3;
#Autowired
private CxfConfigurerImpl cxfConfigurer3Seg = new CxfConfigurerImpl(connectionTimeout3, connectionRequestTimeout3, receiveTimeout3);
My environment variables are:
CXFCLIENT_TIMEOUT_CONNECTION=1000
CXFCLIENT_TIMEOUT_CONNECTIONREQUEST=1000
CXFCLIENT_TIMEOUT_RECEIVE=1000
CXFCLIENT_TIMEOUTTHREE_CONNECTION=3000
CXFCLIENT_TIMEOUTTHREE_CONNECTIONREQUEST=3000
CXFCLIENT_TIMEOUTTHREE_RECEIVE=3000
The problem is that for the object "cxfConfigurer3Seg" I'm getting the default constructor values (1000). Is there any way to override the values?
OBS: I can't change the "CxfConfigurerImpl" constructor implementation.
Please notes that
#Value : by definition is an Annotation used at the field or method/constructor parameter levelthat indicates a default value expression for the annotated element.
And Note that actual processing of the #Value annotation is performedby a BeanPostProcessor
So values are setted after bean built, otherwise you will already find the value 1000 in your variables
So you should change your implementation here :
You should init configuration in the application level and use #Qualifier for two different bean instead the #Value constructor injection:
#Component
#Getter
#Setter
#ToString
public class B {
private long receiveTimeout;
private long connectionTimeout;
private long connectionRequestTimeout;
public B () {
}
public B ( long connectionTimeout,
long connectionRequestTimeout,
long receiveTimeout) {
this.receiveTimeout = receiveTimeout;
this.connectionTimeout = connectionTimeout;
this.connectionRequestTimeout =
connectionRequestTimeout;
}
}
In the spring boot application startup class : build two beans with two configurations : (b1, b2)
#Value("${cxfclient.timeout-three.connection}")
private long connectionTimeout3;
#Value("${cxfclient.timeout-three.connection-request}")
private long connectionRequestTimeout3;
#Value("${cxfclient.timeout-three.receive}")
private long receiveTimeout3;
#Value("${cxfclient.timeout.connection}")
private long receiveTimeout;
#Value("${cxfclient.timeout.connection-request}")
private long connectionTimeout;
#Value("${cxfclient.timeout.receive}")
private long connectionRequestTimeout;
#Bean ("b1")
public B createAsB1() {
return new B (connectionTimeout3, connectionRequestTimeout3, receiveTimeout3);
}
#Bean ("b2")
public B createAsB2() {
return new B(connectionTimeout, connectionRequestTimeout, receiveTimeout);
}
use them by :
#Component
#Getter
#Setter
#ToString
public class UseB {
#Autowired
#Qualifier ("b1")
private B b1;
#Autowired
#Qualifier ("b2")
private B b2;
}
String d = ab.getB1().toString();
System.out.println(d);
d = ab.getB2().toString();
System.out.println(d);
result of run :
B(receiveTimeout=3000, connectionTimeout=3000,
connectionRequestTimeout=3000)
B(receiveTimeout=1000, connectionTimeout=1000,
connectionRequestTimeout=1000)
There are two options -
1- if you want both the beans available in your application context then you can create two separate beans with two different values.
2- If you want to have both values in your application but only one active bean at a time then you can use #ConditionalOnBean, attaching [here][1] a document for your help. Below is sample rough conditionalonbean code snippet and can enable only one of bean from your configurations file.
#ConditionalOnBean(
"prefix" = "..",
"havingValue" = "true",
"name" = "abc")
public A methodA() {}
#ConditionalOnBean(
"prefix" = "..",
"havingValue" = "false",
"name" = "abc")
public A methodB() {}

Handling multiple same properties with #ConfigurationProperties

I have Properties class with a few different beans. Values from application.yaml:
#Configuration
#Getter
#Setter
public class RabbitProperties {
private String requestExchangeName;
private String requestQueueName;
private String responseExchangeName;
private String deadLetterExchangeName;
#Bean
#ConfigurationProperties("rabbit-service.common-orders")
public RabbitProperties commonOrdersRabbitProperties() {
return new RabbitProperties();
}
#Bean
#ConfigurationProperties("rabbit-service.metrics")
public RabbitProperties metricsRabbitProperties() {
return new RabbitProperties();
}
...//more beans
}
I'm using this Configuration in another config class:
#Configuration
#RequiredArgsConstructor
public class RabbitServiceConfig {
private final RabbitProperties commonOrdersRabbitProperties;
private final RabbitProperties metricsRabbitProperties;
...//about 15 similar fields
#Bean("metricsRabbitService")
public RabbitService getMetricsRabbitService(AmqpAdmin amqpAdmin, Client rabbitClient) {
return new RabbitService(
metricsRabbitProperties.getRequestExchangeName(),
metricsRabbitProperties.getRequestQueueName(),
metricsRabbitProperties.getResponseExchangeName(),
metricsRabbitProperties.getDeadLetterExchangeName(),
rabbitClient,
amqpAdmin
);
}
#Bean("commonOrdersRabbitService")
public RabbitService getCommonOrdersRabbitService(AmqpAdmin amqpAdmin, Client rabbitClient) {
return new RabbitService(
commonOrdersRabbitProperties.getRequestExchangeName(),
commonOrdersRabbitProperties.getRequestQueueName(),
commonOrdersRabbitProperties.getResponseExchangeName(),
commonOrdersRabbitProperties.getDeadLetterExchangeName(),
rabbitClient,
amqpAdmin
);
}
...//etc
I'm adding new RabbitProperties field almost every week, so now it already has about 15-20 kinda same fields. How can I get rid of these fields and put them to Map for example? Where should I put values for this Map and initialize it? What is the proper way to use ConfigurationProperties here?
If I've understood the question properly, you could define a private final Map<String, RabbitProperties> rabbitPropertiesMap; in the RabbitServiceConfig class instead of all the fields. All the RabbitProperties will be bounded in the map by injection, with key equals to bean name.
Another different approach would be to update the implementation of RabbitProperties with something like
#ConfigurationProperties("rabbit-service")
#Value
#ConstructorBinding
public class RabbitServiceProperties {
Map<String, RabbitProperties> rabbitPropertiesMap;
#Value
static class RabbitProperties {
String requestExchangeName;
String requestQueueName;
String responseExchangeName;
String deadLetterExchangeName;
}
}
this way everything under the root rabbit-service in the application.yml with the structure described will be discovered and bound.

How to declare a variable or Object of any class type in Java

I am quite new to Java and I am trying to deserialize the JSON using Jackson and I facing some minor issue with regards to declaring the Object/Variable type. I will provide all the codes then explain the issue for easy understanding.
I have an enum that will have the required type values:
public enum IdentifierTypeValues {
Type1,
Type2,
Type3,
//Constructor and Getter of enum values
}
Then for each of these type, I have different classes which will have different input and do a completely different type of process:
public class GenerateType1 {
private String name;
private String age;
//Getter and Setter
//Some required process based on these values
}
public class GenerateType2 {
private String address;
private String city;
private String country;
//Getter and Setter
//Some required process based on these values
}
public class GenerateType3 {
private String firstName;
private String lastName;
private String fullName;
//Getter and Setter
//Some required process based on these values
}
Now I have a wrapper class for these type of classes which will take the type based on enum and typeInfo values. I want the typeInfo values to be any of the class based type something like this:
public class TypeSyntax {
private IdentifierTypeValues indeitiferType;
private GenerateType1 / GenerateType2 / GenerateType3 identifierTypeValues;
//Here the identifierTypeValues can have the values for anytype
//How to declare a variable of any of these class type?
}
This is the class that will be used by my JSON for deserializing. I know I can add a wrapper class of those 3 types and provide that wrapper class as a type class for this. Something like this:
public class WrapperClass{
private GenerateType1 type1;
private GenerateType2 type2;
private GenerateType3 type3;
}
public class TypeSyntax{
private IdentifierTypeValues indeitiferType;
private WrapperClass identifierTypeValues;
//But using this approach will change my JSON structure which I do not want to do.
}
My JSON structure is something like this and I would like to keep it in the same way.
{
"indeitiferType":"Type1",
"identifierTypeValues":{
"name":"Batman",
"age":"2008"
}
}
Is there a way I can declare the variable of multiple type class? or any better approach to handle this by keeping the json format same? I tried searching but I am unable to search what exactly so any help would be really appriciated.
Because the type identifier exists on a different level than the other properties a wrapper class TypeSyntax needed. There are several open feature requests to add wrapping functionality to Jackson e.g. https://github.com/FasterXML/jackson-databind/issues/512
Fortunately polymorphism is supported in Jackson with #JsonTypeInfo and #JsonSubTypes annotations.
Wrapper class should look like:
public class TypeSyntax {
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
property = "identifierType")
private GenerateTypeBase identifierTypeValues;
// getters and setters (omitted for brevity)
}
GenerateTypeBase is the common parent class
#JsonSubTypes({
#JsonSubTypes.Type(value = GenerateType1.class, name = "Type1"),
#JsonSubTypes.Type(value = GenerateType2.class, name = "Type2"),
})
public abstract class GenerateTypeBase {
private String name;
private String age;
// getters and setters (omitted for brevity)
}
In this different children classes will instantiated based on the identifierType property.
The children must extend this base class:
public class GenerateType2 extends GenerateTypeBase {
// additional properties
}
In a short test it will be:
#Test
void wrapperTest() throws IOException {
ObjectMapper mapper = new ObjectMapper();
GenerateType2 a = new GenerateType2();
a.setName("Foo");
a.setAge("13");
TypeSyntax w = new TypeSyntax();
w.setIdentifierTypeValues(a);
String json = mapper.writeValueAsString(w);
System.out.println(json);
}
and the output:
{
"identifierTypeValues":
{
"name":"Foo",
"age":"13"
},
"identifierType":"Type2"
}
Deserialization
#Test
void wrapperTest() throws IOException {
ObjectMapper mapper = new ObjectMapper();
String input = "{\"identifierTypeValues\": \"name\":\"Foo\",\"age\":\"13\"},\"identifierType\":\"Type2\"}";
TypeSyntax w = mapper.readValue(new StringReader(input), TypeSyntax.class);
assertAll(
() -> assertEquals(GenerateType2.class, o.getIdentifierTypeValues().getClass()),
() -> assertEquals("13", o.getIdentifierTypeValues().getAge())
);
}
If you want more flexibility you can write custom (de)serializer and / or custom resolver. Using custom TypeIdResolver that will possible to convert identifiers to types programmatically instead of using "key-value pairs" in #JsonSubTypes

How to bind a Map object containing Pojos to a yaml file?

In a spring-boot/ spring-cloud application, I would like to bind a Map object to my application.yml but I've got a "Elements ... where lef unbound error".
In my class called Ebox, I would like to bind a map called infosTenants, indentified by a string and containing values of type InfosTenant.
Below my application.yml (without the getters / setters of each classes or subclasses)
#ConfigurationProperties(prefix = "application", ignoreUnknownFields = false)
public class ApplicationProperties {
private Ebox ebox = new Ebox();
public ApplicationProperties() {
}
// getters/setters ...
public static class Ebox {
private String authUrl;
private Map<String, InfosTenant> infosTenants = new HashMap<>();
public Ebox() {
}
public class InfosTenant{
private String clientId="";
private String clientSecret="";
public InfosTenant() {
}
// getters/setters ...
}
}
}
In my application.yml, I defined one tenant in my tenants map, indentified by the key tenant1.
application:
ebox:
auth-url: https://oauth-server/api/oauth/token
infos-tenants:
tenant1:
client-id: myclient
client-secret: secret
But all values under infos-tenants were left unbound.
Does somebody have an idea ?
Thanks
I found my error, inner classes should be static, I forgot the static before class InfosTenant.
public static class InfosTenant{
private String clientId="";
private String clientSecret="";
public InfosTenant() {
}
// getters/setters ...
}

Spring-boot: set default value to configurable properties

I have a properties class below in my spring-boot project.
#Component
#ConfigurationProperties(prefix = "myprefix")
public class MyProperties {
private String property1;
private String property2;
// getter/setter
}
Now, I want to set default value to some other property in my application.properties file for property1. Similar to what below example does using #Value
#Value("${myprefix.property1:${somepropety}}")
private String property1;
I know we can assign static value just like in example below where "default value" is assigned as default value for property,
#Component
#ConfigurationProperties(prefix = "myprefix")
public class MyProperties {
private String property1 = "default value"; // if it's static value
private String property2;
// getter/setter
}
How to do this using #ConfigurationProperties class (rather typesafe configuration properties) in spring boot where my default value is another property ?
Check if property1 was set using a #PostContruct in your MyProperties class. If it wasn't you can assign it to another property.
#PostConstruct
public void init() {
if(property1==null) {
property1 = //whatever you want
}
}
In spring-boot 1.5.10 (and possibly earlier) setting a default value works as-per your suggested way. Example:
#Component
#ConfigurationProperties(prefix = "myprefix")
public class MyProperties {
#Value("${spring.application.name}")
protected String appName;
}
The #Value default is only used if not overridden in your own property file.

Categories

Resources