I have add some config inside my application.yml file and I want to read it from my Java code.
The added node inside the YAML file looks like this:
myConfig:
projectOne:
mantisID: 501
user: username
password: passwd
projectTwo:
mantisID: 502
user: username
password: passwd
What I want is to get a List of Project objects where
Project.mantisID = 501,
Project.user = "username",
Project.password = "passwd",
etc...
I know spring can read this file with some #Value annotation but how can I use this in order to get what I need?
You can use #ConfigurationProperties annotation to map your configuration to a Bean, then you'll be able to inject your Bean anywhere and fetch those properties.
To do so, first create a class which represents the data structure in your configuration. Then annotate it with #ConfigurationProperties and #Configuration annotations.
#Configuration
#ConfigurationProperties
public class MyConfig {
private final Map<String, Project> myConfig = new HashMap<>();
public Map<String, Project> getMyConfig() {
return myConfig;
}
public static class Project {
private String mantisID;
private String password;
private String user;
// Getters and setters...
}
}
Note that getters and setters are required in the Project class. Also keep in mind that naming of getters and setters is important here.
After you have setup this class, you can inject it anywhere in your project and access its properties.
#Service
public class SomeService {
private final Map<String, MyConfig.Project> projects;
#Autowired
public SomeService(MyConfig config) {
this.projects = config.getMyConfig();
projects.get("projectOne").getMantisID();
projects.get("projectTwo").getPassword();
}
}
You can read more about this here.
Just to finish, I answered myself to my second question.
This is what my service looks like now :
#Service
public class MantisProjectService {
private final Map<String, MantisProjectConfiguration.Project> projects;
private List<MantisProjectConfiguration.Project> mantisProjects = new ArrayList<>();
#Autowired
public MantisProjectService(MantisProjectConfiguration mantisProjectConfiguration)
{
this.projects = mantisProjectConfiguration.getMantisProjectConfiguration();
for (Map.Entry<String, MantisProjectConfiguration.Project> project : projects.entrySet())
{
MantisProjectConfiguration.Project mantisProject = project.getValue();
mantisProject.setName(project.getKey());
mantisProjects.add(mantisProject);
}
}
public List<MantisProjectConfiguration.Project> getMantisProjects()
{
return mantisProjects;
}
}
It returns a List of all the projects. And it is awesome! =)
Related
I know how I can access the application.properties values in #Service classes in Java Spring boot like below
#Service
public class AmazonClient {
#Value("${cloud.aws.endpointUrl}")
private String endpointUrl;
}
But I am looking for an option to access this value directly in any class (a class without #Service annotation)
e.g.
public class AppUtils {
#Value("${cloud.aws.endpointUrl}")
private String endpointUrl;
}
But this returns null. Any help would be appreciated.
I have already read here but didn't help.
There's no "magic" way to inject values from a property file into a class that isn't a bean. You can define a static java.util.Properties field in the class, load values from the file manually when the class is loading and then work with this field:
public final class AppUtils {
private static final Properties properties;
static {
properties = new Properties();
try {
ClassLoader classLoader = AppUtils.class.getClassLoader();
InputStream applicationPropertiesStream = classLoader.getResourceAsStream("application.properties");
applicationProperties.load(applicationPropertiesStream);
} catch (Exception e) {
// process the exception
}
}
}
You can easily achievw this by annotating ur app utils class with #component annotation . spring will take care of loading properties.
But if you don't want to do that approach , then look at the link below .
https://www.baeldung.com/inject-properties-value-non-spring-class
I am using AWS ECS to host my application and using DynamoDB for all database operations. So I'll have same database with different table names for different environments. Such as "dev_users" (for Dev env), "test_users" (for Test env), etc.. (This is how our company uses same Dynamo account for different environments)
So I would like to change the "tableName" of the model class using the environment variable passed through "AWS ECS task definition" environment parameters.
For Example.
My Model Class is:
#DynamoDBTable(tableName = "dev_users")
public class User {
Now I need to replace the "dev" with "test" when I deploy my container in test environment. I know I can use
#Value("${DOCKER_ENV:dev}")
to access environment variables. But I'm not sure how to use variables outside the class. Is there any way that I can use the docker env variable to select my table prefix?
My Intent is to use like this:
I know this not possible like this. But is there any other way or work around for this?
Edit 1:
I am working on the Rahul's answer and facing some issues. Before writing the issues, I'll explain the process I followed.
Process:
I have created the beans in my config class (com.myapp.users.config).
As I don't have repositories, I have given my Model class package name as "basePackage" path. (Please check the image)
For 1) I have replaced the "table name over-rider bean injection" to avoid the error.
For 2) I printed the name that is passing on to this method. But it is Null. So checking all the possible ways to pass the value here.
Check the image for error:
I haven't changed anything in my user model class as beans will replace the name of the DynamoDBTable when the beans got executed. But the table name over riding is happening. Data is pulling from the table name given at the Model Class level only.
What I am missing here?
The table names can be altered via an altered DynamoDBMapperConfig bean.
For your case where you have to Prefix each table with a literal, you can add the bean as such. Here the prefix can be the environment name in your case.
#Bean
public TableNameOverride tableNameOverrider() {
String prefix = ... // Use #Value to inject values via Spring or use any logic to define the table prefix
return TableNameOverride.withTableNamePrefix(prefix);
}
For more details check out the complete details here:
https://github.com/derjust/spring-data-dynamodb/wiki/Alter-table-name-during-runtime
I am able to achieve table names prefixed with active profile name.
First added TableNameResolver class as below,
#Component
public class TableNameResolver extends DynamoDBMapperConfig.DefaultTableNameResolver {
private String envProfile;
public TableNameResolver() {}
public TableNameResolver(String envProfile) {
this.envProfile=envProfile;
}
#Override
public String getTableName(Class<?> clazz, DynamoDBMapperConfig config) {
String stageName = envProfile.concat("_");
String rawTableName = super.getTableName(clazz, config);
return stageName.concat(rawTableName);
}
}
Then i setup DynamoDBMapper bean as below,
#Bean
#Primary
public DynamoDBMapper dynamoDBMapper(AmazonDynamoDB amazonDynamoDB) {
DynamoDBMapper mapper = new DynamoDBMapper(amazonDynamoDB,new DynamoDBMapperConfig.Builder().withTableNameResolver(new TableNameResolver(envProfile)).build());
return mapper;
}
Added variable envProfile which is an active profile property value accessed from application.properties file.
#Value("${spring.profiles.active}")
private String envProfile;
We have the same issue with regards to the need to change table names during runtime. We are using Spring-data-dynamodb 5.0.2 and the following configuration seems to provide the solutions that we need.
First I annotated my bean accessor
#EnableDynamoDBRepositories(dynamoDBMapperConfigRef = "getDynamoDBMapperConfig", basePackages = "my.company.base.package")
I also setup an environment variable called ENV_PREFIX which is Spring wired via SpEL.
#Value("#{systemProperties['ENV_PREFIX']}")
private String envPrefix;
Then I setup a TableNameOverride bean:
#Bean
public DynamoDBMapperConfig.TableNameOverride getTableNameOverride() {
return DynamoDBMapperConfig.TableNameOverride.withTableNamePrefix(envPrefix);
}
Finally, I setup the DynamoDBMapperConfig bean using TableNameOverride injection. In 5.0.2, we had to setup a standard DynamoDBTypeConverterFactory in the DynamoDBMapperConfig builder to avoid NPE.:
#Bean
public DynamoDBMapperConfig getDynamoDBMapperConfig(DynamoDBMapperConfig.TableNameOverride tableNameOverride) {
DynamoDBMapperConfig.Builder builder = new DynamoDBMapperConfig.Builder();
builder.setTableNameOverride(tableNameOverride);
builder.setTypeConverterFactory(DynamoDBTypeConverterFactory.standard());
return builder.build();
}
In hind sight, I could have setup a DynamoDBTypeConverterFactory bean that returns a standard DynamoDBTypeConverterFactory and inject that into the getDynamoDBMapperConfig() method using the DynamoDBMapperConfig builder. But this will also do the job.
I up voted the other answer but here is an idea:
Create a base class with all your user details:
#MappedSuperclass
public abstract class AbstractUser {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String firstName;
private String lastName;
Create 2 implentations with different table names and spirng profiles:
#Profile(value= {"dev","default"})
#Entity(name = "dev_user")
public class DevUser extends AbstractUser {
}
#Profile(value= {"prod"})
#Entity(name = "prod_user")
public class ProdUser extends AbstractUser {
}
Create a single JPA respository that uses the mapped super classs
public interface UserRepository extends CrudRepository<AbstractUser, Long> {
}
Then switch the implentation with the spring profile
#RunWith(SpringJUnit4ClassRunner.class)
#DataJpaTest
#Transactional
public class UserRepositoryTest {
#Autowired
protected DataSource dataSource;
#BeforeClass
public static void setUp() {
System.setProperty("spring.profiles.active", "prod");
}
#Test
public void test1() throws Exception {
DatabaseMetaData metaData = dataSource.getConnection().getMetaData();
ResultSet tables = metaData.getTables(null, null, "PROD_USER", new String[] { "TABLE" });
tables.next();
assertEquals("PROD_USER", tables.getString("TABLE_NAME"));
}
}
I have a little SpringBoot Application, which can execute different functions via OpenLdap.
getUser
createUser
deleteUser
etc.
That works fine. Now i want to create an application.Yml, where i can manage different environments with different credentials. I read some tutorials, but i still have some understanding problems. Actually my code looks like that:
UserController:
...
protected static String serverURL = "xxxxx:90xx";
protected static String LdapBindDn = "cn=admin, xxxxx";
protected static String LdapPassword = "xxxx";
...
#RequestMapping(value = "/{userid:.+}",method = RequestMethod.GET,consumes="application/json",produces = "application/json")
public UserData getUser(#PathVariable String userid) {
DirContext context = connectToLdap();
//some operations...
context.close();
return user;
}
... // same for the other functions
My plan is now, that i want to specify the credentials in an extra application.yml instead of at the beginning of the UserController (see above).
Then i have created an application.yml in the src/main/resources:
# Actual environment
spring:
profiles.actives: development
---
# Dev Profile
spring:
profiles: dev
datasource:
serverUrl: ldaps://xxxxxx:90xx
AdminName: xxxx
AdminPassword: xxxxxx
BaseDN: xxxxx
---
# Production Profile
spring:
profiles: prod
datasource:
serverUrl: ldaps://xxxx2:90xx
AdminName: xxxxx2
AdminPassword: xxxxx2
BaseDN: xxxxxx
Now i need to call this configuration. I have read in one tutorial (http://therealdanvega.com/blog/2017/06/26/spring-boot-configuration-using-yaml) that i have to create an extra class "ApplicationProperties" for the properties of the .yml file.
#Component
#ConfigurationProperties("datasource")
public class ApplicationProperties {
private String serverURL;
private String adminName;
private String adminPassword;
private String baseDN;
// Getter-/Setter-Methods
}
Now i need to define my variables from the beginning with the values from the .yml, right? I went back to my UserController and tried something like that:
private String serverURL;
private String adminName;
private String adminPassword;
private String baseDN;
#Autowired
ApplicationProperties appProp;
#RequestMapping(value = "/{userid:.+}",method = RequestMethod.GET,consumes="application/json",produces = "application/json")
public UserData getUser(#PathVariable String userid) {
DirContext context = connectToLdap();
//some operations...
context.close();
return user;
}
... // same for the other functions
private DirContext connectToLdap(){
System.out.prinln(appProp.getServerURL());
System.out.prinln(appProp.getAdminName());
System.out.prinln(appProp.getAdminPassword());
.... // Code for the Ldap connection
}
But the variable "appProp" is still empty. I know, that here is somewhere a big understanding problem. I don't know how to call these properties from the .yml file.
Thanks for every help in advance!
You can get properties from your .yml file by creating a config class:
application.yml
datasource:
serverUrl: ldaps://xxxxxx:90xx
adminName: xxxx
adminPassword: xxxxxx
baseDN: xxxxx
ApplicationConfig.java class :
#Configuration
#EnableConfigurationProperties
#ConfigurationProperties(prefix = "datasource")
public class ApplicationConfig {
private String serverUrl;
private String adminName;
private String adminPassword;
private String baseDN;
//getters setters
}
Then you can call your ApplicationConfig class
#Autowired
public ApplicationConfig app;
public UserData getUser(#PathVariable String userid) {
DirContext context = connectToLdap();
//some operations...
appProp.getServerUrl();
appProp.getAdminName();
context.close();
return user;
}
And I recommend you to create profile based properties as spring boot picks them automatically, like application-{profile}.{properties|yml}
You can create application-production.yml file and set your profile by adding #Profile("production") annotation in your class.
I want to load a yaml file & store it in Config.java.
Here is my yaml file: (Its much bigger. I am giving a simplified version)
---
application:
admin:
jobInterValTime: 1440
customer: lc
system:
mongo:
host: localhost
port: 27017
dbName: LC_Test
collections:
groupsCollection: groups
membershipCollection: memberships
personsCollection: persons
Here is Config.java:
public class Config {
private Application application;
private System system;
//Getter setter
}
Application.java
public class Application {
private Admin admin;
//Getter Setter
}
Admin.java
public class Admin {
private String jobInterValTime;
private String customer;
//Getter Setter
}
System.java
public class System {
private Mongo mongo;
//Getter Setter
}
Mongo.java
public class Mongo {
private String host;
private String port;
private String dbName;
private Map<String, String> collections;
//Getter Setter
}
But the application & system object inside Config.java is coming null.No exception is happening. Can anybody help?
Here is what I have written.
Config config = null;
ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
try{
config = objectMapper.readValue(new File("src/test/java/resources/test1.yaml"), Config.class);
//System.out.println(application.getAdmin().getCustomer());
// System.out.println(unidataConfig.getApplication().getAdmin().getCustomer());
} catch(Exception e) {
e.printStackTrace();
}
I don't know the root cause here, code looks good. But one thing you could try is to read contents as Map or JsonNode first, and see how structure looks like. There may well be a mismatch.
I solved the problem. The mistake was very stupid. In one setter method I have written like this: var1 = var1 instead of this.var1 = var1.
I'm building a web service that needs to switch between two sets of properties depending on the request URL coming in. I'm not sure which is the best method of handling this.
I've got a Spring Boot app that has an yaml properties file. Inside the properties file the structure looks something like this;
optionA:
foo:
urls:
- a
- b
bar:
something: hello
optionB:
foo:
urls:
- x
- y
bar:
something: bye
Both optionA and optionB have pretty much all the same properties, just different values.
So a request comes in, I check the request and decide if I need optionA or optionB.
I've been trying to get #ConfigurationProperties to handle this but the properties are initialised on startup so it can't be dynamic. Another possibility is that I have two Configuration classes, one for each option but then my code gets full of checks to switch between the two classes and the classes are pretty much identical, not really nice either.
Any best practices or recommendations on how to best manage this would be appreciated, cheers!
If you have not too many options I would go this way: (Just made example with smaller config)
options.yml:
optionA:
name: optionA
optionB:
name: optionB
I created a Option class for extension:
public class Option {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
And two Option classes where the #ConfigurationProperties are getting set: (For now these classes are empty but you have the opportunity to be more specific on each different option)
#Component
#ConfigurationProperties(prefix ="optionA", locations = "classpath:options.yml")
public class OptionA extends Option{
}
#Component
#ConfigurationProperties(prefix ="optionB", locations = "classpath:options.yml")
public class OptionB extends Option{
}
For the decision of the different options I created an interface:
public interface OptionService {
Option findOption(boolean businessLogic);
}
And in the implementation I inject both options and the implementation of the business logic: (in an easy way)
#Service
public class OptionServiceImpl implements OptionService {
private OptionA optionA;
private OptionB optionB;
#Override
public Option findOption(boolean businessLogic) {
if(businessLogic){
return getOptionA();
} else {
return getOptionB();
}
}
public OptionA getOptionA() {
return optionA;
}
#Autowired
public void setOptionA(OptionA optionA) {
this.optionA = optionA;
}
public OptionB getOptionB() {
return optionB;
}
#Autowired
public void setOptionB(OptionB optionB) {
this.optionB = optionB;
}
}
And at the end your controller just call the OptionServiceImpl class and deceide which option should be used:
#Controller
public class YourController {
private OptionService optionServiceImpl;
#RequestMapping("/")
public String getIndex(){
Option option = getOptionServiceImpl().findOption(true);
System.out.println(option.getName());
option = getOptionServiceImpl().findOption(false);
System.out.println(option.getName());
return "Hello World";
}
public OptionService getOptionServiceImpl() {
return optionServiceImpl;
}
#Autowired
public void setOptionServiceImpl(OptionService optionServiceImpl) {
this.optionServiceImpl = optionServiceImpl;
}
}
Output of System.out.println:
optionA
optionB
So your business logic to decide which option should be used is not an if - else construct. You are able to create the rules for the decission in the interface and its implementation. I think you are able to create more rules for more controllers.
Change your yml to:
options:
- name: optionA
foo:
urls:
- a
- b
bar:
something: hello
- name: optionB
foo:
urls:
- x
- y
bar:
something: bye
Add Config class:
#Data
#ConfigurationProperties
#Configuration
public class MyConfig {
private List<Option> options;
}
Use it:
#Component
public class UseConfig {
#Autowired
public UseConfig(final MyConfig config) {
System.out.println(config.getOptions());
}
}
Result:
[Option(name=optionA, foo=Foo(urls=[a, b]), bar=Bar(something=hello)), Option(name=optionB, foo=Foo(urls=[x, y]), bar=Bar(something=bye))]
You can define key value pairs in application.properties.
where key is web service name and value is option(list of properties)
Make use of #ConfigurationProperties
#ConfigurationProperties
class Configuration {
Map<String,Option> options;
// getters and setters
}
#Component
class ChooseServiceBasedConfiguration {
#Autowired
Configuration configuration;
public void serviceMethod(String key ){
//get appropriate properties of the web service
configuration.getOptions().get(key);
}
}
based on web service get the values required using the key .