accessing properties loaded from backend java object from #value - spring

I am writing custom spring boot starter project where we have our own class VaultFactory which connects to the vault based on the consumer's properties defined in application.yml file.
In the custom starter project i can read all the secrets and corresponding values. Now, i want to expose all the these key/value pair as a property source so that consumers will directly access via #value
#Value{${some.vault.secret.name}}
private String value;
My starter project code
#Configuration
#EnableConfigurationProperties(VaultConfiguration.class)
#ConditionalOnClass(VaultFactory.class)
public class VaultEnableAutoConfiguration {
/**
* #param vaultConfiguration
* #return
* #throws VaultException
*/
#Bean
#ConditionalOnMissingBean
public Vault getVaultFactoryByProperties(VaultConfiguration vaultConfiguration) throws VaultException {
VaultFactory vaultFactory = VaultFactory.newInstance(vaultConfiguration.getVault().get("file.path"), vaultConfiguration.getVaultProperties());
return vaultFactory.getVault("vault-01");
}
}
#EnableConfigurationProperties(VaultConfiguration.class)
public class VaultPropertiesConfiguration {
#Autowired
Vault vault;
#Bean
public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer(VaultConfiguration vaultConfiguration) throws VaultException {
PropertySourcesPlaceholderConfigurer pspc = new PropertySourcesPlaceholderConfigurer();
ConfigurableEnvironment environment = new StandardEnvironment();
MutablePropertySources ps = environment.getPropertySources();
pspc.setIgnoreUnresolvablePlaceholders(Boolean.TRUE);
pspc.setIgnoreUnresolvablePlaceholders(true);
ps.addLast(new VaultPropertySource("vaultProperties", vault));
pspc.setPropertySources(ps);
return pspc;
}
}
public class VaultPropertySource extends PropertySource<Vault>{
Vault vault;
public VaultPropertySource(String name) {
super(name);
}
public VaultPropertySource(String name, Vault vault) {
super(name);
this.vault = vault;
}
#Override
public Object getProperty(String name) {
try {
return vault.getSecret(name).getValue();
} catch (VaultException e) {
e.printStackTrace();
}
return null;
}
}
#ConfigurationProperties("platform")
public class VaultConfiguration {
private final Map<String, String> vault = new HashMap<>();
public Map<String, String> getVault() {
return vault;
}
public Properties getVaultProperties() {
Properties p = new Properties();
vault.entrySet().stream().forEach( e -> p.put(e.getKey(), e.getValue()));
return p;
}
}
How can i do it ? Basically, to simply if i have a key/value map in my starter project, how i can make these avaliable to boot application via #value annotation ?
Facing Multiple issues :
VaultPropertiesConfiguration is always called first and the #EnableConfigurationProperties and #autowired is not working.
I harded coded to get workaround the above issue, but it is trying to load spring.messages.always-use-message-format property from propertysource which i dont have.

You cannot use #Value with .yml file same way as with .properties file.
Can you exmplain more the meaning of "i can read all the secrets and corresponding values". So how is it available in application?

Related

How to get Redis Hash Configuration such as Time To Live from application.properties at Spring Boot?

I use
#Value("${cache.host}")
private String redisHost;
#Value("${cache.port}")
private int redisPort;
I want to get timeToLive in #RedishHash from application properties. How can get this config?
#RedisHash(value = "UserModel", timeToLive = 5)
I give manually above however I want to give from application.properties
i'm not sure if you can do that from application.properties, but u can do it by configuring a RedisCacheManager bean with java based configuration like below :
#Bean
public RedisCacheManager RedisCacheManager(RedisConnectionFactory redisConnectionFactory) {
Map<String, RedisCacheConfiguration> cacheConfig = new HashMap<String, RedisCacheConfiguration>();
cacheConfig.put("UserModel", RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(5)));
RedisCacheManager rdisCacheManager = new RedisCacheManager(
RedisCacheWriter.lockingRedisCacheWriter(redisConnectionFactory),
RedisCacheConfiguration.defaultCacheConfig(), cacheConfig);
return rdisCacheManager;
}
PS : this method should be in a class with #Configuration annotation
You can create a #Component where you are going to take the values from properties
#Component
public class RedisHashCustom {
private static String redisHashValue;
public static String getRedisHashVaue() {
return redisHashValue;
}
#Value("${application.redis.redishash.value}")
public void setRedisHashValue(String newRedisHashValue) {
redisHashValue= newRedisHashValue;
}
}
Then you need to reference as
#RedisHash(value = "#{T(com.redis.model.RedisHashCustom).getRedisHashValue() }")

Hibernate Anntotaion #Converter need to be configure before Spring Anntotation #propertySource

I am using #converter (Hibernate )to convert pojo in encrypted format which is from hibernate but key are placed in property file which would not be resolve by #propertySource (Spring annotation)
is there any way to manage bean creation seq in above case.
Please find the below code snippet for Converter, I had created another bean from encryption/decryption, but you can create config bean for properties and read properties from there.
#Component
#Converter
#Configurable
public class HashMapConverter implements AttributeConverter<Map<String, Object>, String> {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
/*
* Define your application properties bean here and read the properties from
* there
*/
private static ConfigEncryptionKeyConverter configEncryptionKeyConverter;
#Autowired
public void initEncryptionKeyConverter(ConfigEncryptionKeyConverter configEncryptionKeyConverter) {
// Set your beans here.
HashMapConverter.configEncryptionKeyConverter = configEncryptionKeyConverter;
}
#Override
public String convertToDatabaseColumn(Map<String, Object> attribute) {
try {
return configEncryptionKeyConverter.convertToDatabaseColumn(OBJECT_MAPPER.writeValueAsString(attribute));
} catch (final JsonProcessingException e) {
throw new ApplicationErrorException(e.getLocalizedMessage());
}
}
#SuppressWarnings("unchecked")
#Override
public Map<String, Object> convertToEntityAttribute(String dbData) {
Map<String, Object> attribute = null;
if (dbData != null) {
try {
attribute = OBJECT_MAPPER.readValue(configEncryptionKeyConverter.convertToEntityAttribute(dbData),
Map.class);
} catch (final IOException e) {
throw new ApplicationErrorException(e.getLocalizedMessage());
}
}
return attribute;
}
}
Hope this will help.

How to take a map and use it as values in spring-boot

I'm working on a spring-boot application and trying to use properties defined in a map as values that can be injected into various services. The code I am working with defines an object PropertyLoader which is able to return a map based on an environment. It looks something like this:
public interface PropertyLoader {
public Map<String, String> load(String env);
}
How can I make the entries in the map returned by this method available in #Value injections in spring components.
You can define custom application listener that will override default properties.
Old answer - below is a better way to achieve it
public class PropertyInjector implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
final ConfigurableEnvironment environment = event.getEnvironment();
final Properties props = new Properties();
//inject properties here
props.put("key", "value");
final PropertiesPropertySource propertySource = new PropertiesPropertySource("propertySource", props);
environment.getPropertySources().addFirst(propertySource);
}
}
UPDATE - I have found better way that easily works with #Value annotation
#Configuration
public class SomeConfig {
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
final PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
final Properties props = new Properties();
props.put("some_dynamic_key", "some_dynamic_value");
final PropertiesPropertySource propertySource = new PropertiesPropertySource("propertySource", props);
propertySourcesPlaceholderConfigurer.setProperties(props);
return propertySourcesPlaceholderConfigurer;
}
#Value("${some_dynamic_key}")
String property;
}
In such way value some_dynamic_key is available along all the application

spring-boot unit testing get application properties

I'm new to spring-boot, currently trying to develop a kafka producer
I want to test method that use value define in properties file. but it show value is null how solve this.I have added my property files to separate resource file in test folder also
this is my folder structure
#SpringBootTest
public class KafkaProducerImplTest {
#BeforeEach
void setUp() {
}
#Test
void check() {
KafkaProducerImpl kpi = new KafkaProducerImpl();
kpi.check();
}
}
#Service
public class KafkaProducerImpl implements KafkaProducerInterface
{
#Value("${kafka.brokers.local}")
private String kafkaBrokers;
#Value("${schema-registry}")
private String schemaRegistry;
private Properties config()
{
Properties props = new Properties();
props.setProperty("bootstrap.servers",kafkaBrokers);
props.setProperty("acks", "1");
props.setProperty("reties", "10");
props.setProperty("key.serializer", StringSerializer.class.getName());
props.setProperty("value.serializer",Serializer.class.getName());
props.setProperty("schema.registry.url",schemaRegistry);
return props;
}
public <K,T>KafkaProducer<K,T> getProducer()
{
return new KafkaProducer<>(config());
}
public <T>ProducerRecord createRecord(String Topic,T msg)
{
return new ProducerRecord<>(
Topic,msg
);
}
public void sendMessage(KafkaProducer producer,ProducerRecord record)
{
producer.send(record, (recordMetadata, e) -> {
if (e == null){
System.out.println("success");
}
});
producer.flush();
}
public void closeProducer(KafkaProducer producer){
producer.close();
}
public void check(){
System.out.println(schemaRegistry);
}
}
finally i find way, thanks everyone helping me.
#RunWith(SpringRunner.class)
#SpringBootTest
public class KafkaProducerImplTest {
#Autowired
private KafkaProducerInterface kpi;
#Test
public void check() {
kpi.check();
}
}
Annotate you test class with #RunWith(SpringRunner.class) which will load application context for you and instantiate the spring beans. To add spring boot support add #SpringBootTest(Which you already have).
You'll have to remove this line
"KafkaProducerImpl kpi = new KafkaProducerImpl();"
and autowire using interface reference instead. Something like this:
#Autowired
KafkaProducerInterface kpi;
I'm assuming you have the properties used here(ex. "kafka.brokers.local") defined in your test properties file.

Spring boot common application properties

Spring boot application properties needs to follow convention from https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html when we use any DB like cassandra/mongo. In case if we want to declare our own properties for DB setup instead of spring-boot convention, what are all the steps we need to do for setting up DB?
You can do this: Spring boot - custom variables in Application.properties
or you can just create your own property in your application.properties file like:
my.property.someDb.hostname=http://wherever.comand then reference to it in your code like:
#Value("${my.property.someDb.hostname}")
private String someDbHostname;
Update 1:
If you want to create the MongoDb with your own properties you have to define the right Java Beans in an #Configuration file. For MongoDB it could look like the following:
#Configuration
public class MyMongoConfig extends AbstractMongoConfiguration{
#Value("${my.property.someDb.hostname}")
private String someDbHostname;
#Value("${my.property.someDb.myOwnPortDefinition}")
private int myOwnPortDefinition;
#Value("${my.property.someDb.myDatabasename}")
private String myDatabasename;
#Override
protected String getDatabaseName() {
return myDatabasename;
}
#Override
#Bean
public Mongo mongo() throws Exception{
return new MongoClient(someDbHostname, myOwnPortDefinition );
}
#Bean
public MongoTemplate mongoTemplate() throws Exception{
return new MongoTemplate(mongo(), getDatabaseName());
}
}
These are the essential steps you need in order to get a data source like Jdbc, mongodb set up in Spring Boot
Need a #Configuration class that has transaction management enabled
on it
Read the environment properties for the datasource i.e. dataSource
url, username, password etc.
Create beans for datasource, session factory, transaction manager
etc.
Once all of the above setup, use this #Configuration in your
consumer to initialize the spring application context
Here are some snippets of wiring mongodb datasource in spring boot
DataSourceConfiguration.java
#Configuration
#EnableTransactionManagement
#ComponentScan(basePackages = {"com.example.xyz"})
public class DatabaseEntityConfiguration {
public static final String DATABASE_ENTITY_DATA_SOURCE = "databaseDataSource";
public static final String DATABASE_HIBERNATE_PROPERTIES = "databaseHibernateProperties";
public static final String DATABASE_ENTITY_SESSION_FACTORY = "databaseSessionFactory";
public static final String DATABASE_ENTITY_TRANSACTION_MANAGER = "databaseTransactionManager";
public static final String DATABASE_ENTITY_DB_CONFIG_DAO = "dmdatabaseDbConfigDao";
public static final String DATABASE_ENTITY_DB_CONFIG_SERVICE = "dmdatabaseDbConfigService";
private static final String ENTITY_PACKAGE = "com.example.xyz.database.entity";
#Autowired
private org.springframework.core.env.Environment environment;
#Bean(name = DATABASE_ENTITY_DATA_SOURCE)
public DataSource databaseEntitydataSource() throws PropertyVetoException {
// mongodb properties
String driverClass = environment.getProperty("databaseEntity.mongodb.driverClassName");
String mongodbUrl = environment.getProperty("databaseEntity.mongodb.dmdatabaseDataSource.url");
String user = environment.getProperty("databaseEntity.mongodb.dmdatabaseDataSource.username");
String password = environment.getProperty("databaseEntity.mongodb.dmdatabaseDataSource.password");
Preconditions.checkArgument(StringUtils.isNotBlank(driverClass), "The property mongodb driverClass must not be null or blank");
Preconditions.checkArgument(StringUtils.isNotBlank(mongodbUrl), "The property mongodb mongodbUrl must not be null or blank");
Preconditions.checkArgument(StringUtils.isNotBlank(user), "The property mongodb user must not be null or blank");
Preconditions.checkArgument(StringUtils.isNotBlank(password), "The property mongodb password must not be null or blank");
dataSource.setDriverClass(driverClass);
dataSource.setmongodbUrl(mongodbUrl);
dataSource.setUser(user);
dataSource.setPassword(password);
return dataSource;
}
#Bean(name = DATABASE_ENTITY_SESSION_FACTORY)
public AnnotationSessionFactoryBean databaseEntitySessionFactory() throws PropertyVetoException {
AnnotationSessionFactoryBean annotationSessionFactoryBean = new AnnotationSessionFactoryBean();
annotationSessionFactoryBean.setDataSource(databaseEntitydataSource());
annotationSessionFactoryBean.setPackagesToScan(ENTITY_PACKAGE);
annotationSessionFactoryBean.setAnnotatedClasses(DBConfig.class);
annotationSessionFactoryBean.setHibernateProperties(databaseEntityHibernateProperties());
return annotationSessionFactoryBean;
}
#Bean(name = DATABASE_ENTITY_TRANSACTION_MANAGER)
public HibernateTransactionManager databaseEntityTransactionManager() throws PropertyVetoException {
HibernateTransactionManager transactionManager = new HibernateTransactionManager();
transactionManager.setSessionFactory(databaseEntitySessionFactory().getObject());
return transactionManager;
}
}

Resources