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

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() }")

Related

Spring #RefreshScope with #Configuration not refreshed dynamically

I am using spring boot version(2.2.5.RELEASE) and spring-cloud-dependencies(Hoxton.SR3).
I have a class as shown below:
#RefreshScope
#Configuration
public class JavaMailConfig {
#Value("${email.common.config.host:ERROR: Could not load email config host}")
private String host;
#Value("${email.common.config.port:ERROR: Could not load email config port}")
private String port;
#Value("${email.common.config.transport.protocol:ERROR: Could not load email config protocol}")
private String protocol;
#Value("${email.common.config.username:ERROR: Could not load email config username}")
private String mailUserName;
#Value("${email.common.config.password:ERROR: Could not load email config passsword}")
private String mailPassword;
#Value("${email.common.config.password:ERROR: Could not load email config smtpAuth}")
private String smtpAuth;
#Value("${email.common.config.password:ERROR: Could not load email config startTlsEnable}")
private String startTlsEnable;
#Value("${email.common.config.password:ERROR: Could not load email config sslTrust}")
private String sslTrust;
#Bean
public JavaMailSender getJavaMailSender() {
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
mailSender.setHost(host);
CommonUtility.setPort(mailSender, port);
mailSender.setUsername(mailUserName);
mailSender.setPassword(mailPassword);
Properties props = mailSender.getJavaMailProperties();
props.put("mail.transport.protocol", protocol);
props.put("mail.smtp.auth", smtpAuth);
props.put("mail.smtp.starttls.enable", startTlsEnable);
props.put("mail.smtp.ssl.trust", sslTrust);
return mailSender;
}
}
I am using spring cloud config to get my information from git. In the same project I have a class below:
#RestController
#RefreshScope
#RequestMapping("/email")
public class EmailController {
private static final Logger LOG = LoggerFactory.getLogger(EmailController.class);
#Autowired
SendMailService sendMailService;
#Value("${email.common.config.username:ERROR: Could not load email config username}")
private String mailUserName;
#PostMapping(value = "/sendMail")
//Note:Not to remove #RequestBody and #RequestBody as swagger UI will not interpret it correctly
public ResponseEntity<String> sendMail(#RequestBody EmailRequestDto emailRequestDto) {
if (checkAllEmailAddValid(emailRequestDto)) {
System.out.println("mailUserName from controller " + mailUserName);
System.out.println("profile " + profile);
sendMailService.sendEmail(emailRequestDto);
LOG.debug("Send mail completed successfully ");
return new ResponseEntity<>("Mail has been sent successfully", HttpStatus.OK);
} else {
LOG.error("Email addresse provided is invalid");
return new ResponseEntity<>("Email address provided is invalid", HttpStatus.BAD_REQUEST);
}
}
When I refresh the url with "actuator/refresh" the restcontroller get refreshed successfully but not the #Configuration class as stated earlier.
Update: The class below I am using the JavaMailSender:
#Component
#RefreshScope
public class SendMailServiceImpl implements SendMailService {
private static final Logger LOG = LoggerFactory.getLogger(SendMailServiceImpl.class);
#Autowired
private JavaMailSender javaMailSender;
/**
* {#inheritDoc}
*/
#Override
public void sendEmail(EmailRequestDto emailRequestDto) {
....
}
So is it possible to use refresh scope annotation with Configuration annotation?
Thanks in advance for any advice
Just in case if someone have this issue I manage to make it work but I don't know if it the correct way though.
I have added the ConfigurationProperties annotation with the prefix which contains the key of my properties file on top of my bean annotation:
#ConfigurationProperties(prefix = "email.common.config")
#Bean
public JavaMailSender getJavaMailSender() {
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
mailSender.setHost(host);
CommonUtility.setPort(mailSender, port);
mailSender.setUsername(mailUserName);
mailSender.setPassword(mailPassword);
Properties props = mailSender.getJavaMailProperties();
props.put("mail.transport.protocol", protocol);
props.put("mail.smtp.auth", smtpAuth);
props.put("mail.smtp.starttls.enable", startTlsEnable);
props.put("mail.smtp.ssl.trust", sslTrust);
return mailSender;
}

accessing properties loaded from backend java object from #value

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?

Spring, inject properties in a Bean instance based one of that Bean's field value, is it possible?

I have a Pojo I use to configure webservices' clients:
public class ServiceConfig {
private String url;
private String endpoint;
private String serviceName;
public ServiceConfig(String serviceName) {
super();
this.serviceName = serviceName;
}
}
Now, this is what my application.properties file looks like:
service1.url=http://localhost:8087/
service1.endpoint=SOME_ENDPOIT1
service2.url=http://localhost:8085/
service2.endpoint=SOME_ENDPOIT2
service3.url=http://localhost:8086/
service3.endpoint=SOME_ENDPOIT3
service4.url=http://localhost:8088/
service4.endpoint=SOME_ENDPOIT4
What I'm trying to achieve is for Spring to inject the correct properties when I instantiate ServiceConfig like this:
ServiceConfig sc = new ServiceConfig("service1");
Is it possible?
Are you using just spring or also spring-boot?
What about injecting org.springframework.core.env.Environment to your pojo and configuring it with it.
so something like this could work:
public class ServiceConfig {
private String url;
private String endpoint;
private String serviceName;
public ServiceConfig(String serviceName, Environment env) {
// TODO assert on serviceName not empty
this.serviceName = serviceName;
this.url = env.getProperty(serviceName.concat(".url");
this.endpoint = env.getProperty(serviceName.concat(".endpoint");
}
}
I guess there could be a simpler/more elegant solution, but I don't know your case.
spring-boot version
well with spring boot just define your pojo (field names must match property names)
public class ServiceConfig {
private String url;
private String endpoint;
// getters setters
}
and then in some configuration you can do this (note: value in ConfigurationProperties is prefix of your configuration in application.properties):
#Configuration
public class ServicesConfiguration {
#Bean
#ConfigurationProperties("service1")
ServiceConfig service1(){
return new ServiceConfig();
}
#Bean
#ConfigurationProperties("service2")
ServiceConfig service2(){
return new ServiceConfig();
}
}

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;
}
}

Is it possible to deactivate MongoHealthIndicator in the Springboot spring actuator health endpoint?

In the springboot project that I work on there is a transitive maven dependency on spring-data-mongodb. Therefore the MongoHealthIndicator seems to be activated automatically although the project does not actually use mongodb. Is it possible to deactivate specifically this HealthIndicator without deactivating the actuator health endpoint? A workaround that I found is excluding the dependency. But I was wondering if it is possible to do this specific deactivation of the MongoHealthIndicator.
From:
http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
# HEALTH INDICATORS (previously health.*)
...
management.health.mongo.enabled=true
...
You should be able to set that to false to disable the health indicator. From org.springframework.boot.actuate.autoconfigure.HealthIndicatorAutoConfiguration.java
#Configuration
#ConditionalOnBean(MongoTemplate.class)
#ConditionalOnProperty(prefix = "management.health.mongo", name = "enabled", matchIfMissing = true)
public static class MongoHealthIndicatorConfiguration {
Try this in your application.properties
management.health.mongo.enabled=false
application.properties
management.health.mongo.enabled=false
endpoints.mongo.enabled=true
MongoDBHealthCheckEndPoint.java
#ConfigurationProperties(prefix = "endpoints.mongo", ignoreUnknownFields = true)
#Component
public class MongoDBHealthCheckEndPoint extends AbstractEndpoint<Map<String, String>>
{
#Inject
MongoTemplate mongoTemplate;
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private static final Map<String, String> UP = new HashMap<String, String>() {{
put("mongo.status", "UP");
}};
private static final Map<String, String> DOWN = new HashMap<String, String>() {{
put("mongo.status", "DOWN");
}};
public MongoDBHealthCheckEndPoint() {
super("mongo", false);
}
public MongoDBHealthCheckEndPoint(Map<String, ? extends Object> mongo) {
super("mongo", false);
}
public Map<String, String> invoke() {
try {
return (new MongoHealthIndicator(mongoTemplate).health().getStatus().equals(Status.UP)) ? UP : DOWN;
} catch (Exception e) {
log.error("mongo database is down", e);
return DOWN;
}
}
}

Resources