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.
Related
I have an application where I want to use different DataSources. All the requests coming from the front-end will user the primary DataSource (this works so far), but I also have to perform operations every certain amount of minutes on another database with different schemas.
By looking in here, I found this approach:
Application.yml
datasource:
primary:
url: jdbc:mysql://SERVER_IP:3306/DATABASE?useSSL=false&useUnicode=true&useLegacyDatetimeCode=false&serverTimezone=UTC
username: MyUser
password: MyPassword
driver-class-name: com.mysql.cj.jdbc.Driver
secondary:
url: jdbc:mysql://localhost:3306/
urlEnd: ?useSSL=false&useUnicode=true&useLegacyDatetimeCode=false&serverTimezone=UTC
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
Here i separate "url" and "urlEnd" because in the middle I will paste the name of the schema to use in each case as shown later.
ContextHolder
public abstract class ContextHolder {
private static final Logger logger = LoggerFactory.getLogger(ContextHolder.class);
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
public static void setClient(String context) {
contextHolder.set(context);
}
public static String getClient() {
return contextHolder.get();
}
}
CustomRoutingDataSource
#Component
public class CustomRoutingDataSource extends AbstractRoutingDataSource {
private org.slf4j.Logger logger = LoggerFactory.getLogger(CustomRoutingDataSource.class);
#Autowired
DataSourceMap dataSources;
#Autowired
private Environment env;
public void setCurrentLookupKey() {
determineCurrentLookupKey();
}
#Override
protected Object determineCurrentLookupKey() {
String key = ContextHolder.getClient();
if(key == null || key.equals("primary")) {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName(env.getProperty("spring.datasource.primary.driver-class-name"));
ds.setPassword(env.getProperty("spring.datasource.primary.password"));
ds.setUsername(env.getProperty("spring.datasource.primary.username"));
ds.setUrl(env.getProperty("spring.datasource.primary.url"));
dataSources.addDataSource("primary", ds);
setDataSources(dataSources);
afterPropertiesSet();
return "primary";
}
else {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName(env.getProperty("spring.datasource.secondary.driver-class-name"));
ds.setPassword(env.getProperty("spring.datasource.secondary.password"));
ds.setUsername(env.getProperty("spring.datasource.secondary.username"));
ds.setUrl(env.getProperty("spring.datasource.secondary.url") + key + env.getProperty("spring.datasource.secondary.urlEnd"));
dataSources.addDataSource(key, ds);
setDataSources(dataSources);
afterPropertiesSet();
}
return key;
}
#Autowired
public void setDataSources(DataSourceMap dataSources) {
setTargetDataSources(dataSources.getDataSourceMap());
}
}
DatabaseSwitchInterceptor (Not used so far AFAIK)
#Component
public class DatabaseSwitchInterceptor implements HandlerInterceptor {
#Autowired
private CustomRoutingDataSource customRoutingDataSource;
private static final Logger logger = LoggerFactory
.getLogger(DatabaseSwitchInterceptor.class);
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
String hostname = request.getServerName();
ContextHolder.setClient(hostname);
return true;
}
}
DataSourceMap
#Component
public class DataSourceMap {
private static final Logger logger = LoggerFactory
.getLogger(DataSourceMap.class);
private Map<Object, Object> dataSourceMap = new ConcurrentHashMap<>();
public void addDataSource(String session, DataSource dataSource) {
this.dataSourceMap.put(session, dataSource);
}
public Map<Object, Object> getDataSourceMap() {
return dataSourceMap;
}
}
And last but not least, the controller where I am doing my test
#RestController
#RequestMapping("/open/company")
public class CompanyOpenController extends GenericCoreController<Company, Integer> {
#Autowired
private CompanyService companyService;
#Autowired
private CompltpvRepository compltpvRepository;
#Autowired
private CustomRoutingDataSource customRoutingDataSource;
#GetMapping("/prueba/{companyId}")
public List<CompltpvDTO> getAll(#PathVariable Integer companyId) throws ServiceException{
List<CompltpvDTO> response = new ArrayList<>();
ContextHolder.setClient(companyService.getById(companyId).getSchema());
for(Compltpv e : compltpvRepository.findAll()) {
response.add(new CompltpvDTO(e));
}
return response;
}
}
What I want all this to do is that, when I call "/open/company/test/3" it searches (in the main database) for the company with ID = 3. Then it retrieves its "schema" attribute value (let's say its "12345678" and then switches to the secondary datasource with the following url:
url = env.getProperty("spring.datasource.secondary.url") + key + env.getProperty("spring.datasource.secondary.urlEnd")
which is something like:
jdbc:mysql://localhost:3306/1245678?useSSL=false&useUnicode=true&useLegacyDatetimeCode=false&serverTimezone=UTC
When I try this and look into the DataSource pool, both exist with keys "primary" and "12345678", but it's always using the "primary" one.
How can I tell Spring to use the DataSource I need it to use?
EDIT: Found the solution
I finally got a deeper understaing of what was happening and also found the problem.
In case someone is interested on this approach, the problem I was having was this line in my application.yml:
spring:
jpa:
open-in-view: true
which does the following:
Default: true
Register OpenEntityManagerInViewInterceptor. Binds a JPA EntityManager to the thread for the entire processing of the request.
And that was the reason that, despite creating the datasource for every company (tenant), it wasn't using it. So if you are reading this and are in my situation, find that line and set it to false. If you don't find that property, notice that by default it'll be set to true.
I am using a #Configuration to configure the creation of a RestTemplate bean, that needs some information such as API-key and host etc.
The thing is, I need to be able to make a number of beans, matching a number of API-keys, fetched from a database.
My code right now, looks like this:
#Configuration
public class DandomainProperties {
private String apiKey;
private String host;
private String ordersPath;
private String orderPath;
private String manufacturerPath;
private DanDomainRestTemplate danDomainRestTemplate;
#Bean
DanDomainRestTemplate danDomainRestTemplate(RestTemplateBuilder restTemplateBuilder) {
return restTemplateBuilder
.basicAuthentication("", this.apiKey)
.build(DanDomainRestTemplate.class);
}
So basically, I need to change the configuration and then create a matching RestTemplate bean, any number of times.
As far I as know from your question, you want to create a restTemplate for each individual situation, maybe you could replace an #Configuration bean provider with a Service that provides different kinds of apis?
For example:
#Service
public class DandomainApiProvider {
#Autowired
private ApiRepository apiRepository;
#Autowired
private DanDomainRestTemplate danDomainRestTemplate;
DanDomainRestTemplate restTemplateOf(String queryId) {
// Fetch apikeys from DB by repository
String apiKey = apiRepository.queryApiKey(queryId);
return restTemplateBuilder
.basicAuthentication("", apiKey)
.build(DanDomainRestTemplate.class);
}
With DanDomainRestTemplate your own implementation, and ApiRepository some definition like:
public interfadce ApiRepository {
// Query apiKey by ID
String queryApiKey(String queryId);
}
I am trying to run Quartz Scheduler using SpringBoot. Using Quartz Jdbc Data Store. Due to security reasons , we wish to pick the Db credentials from the properties file. From what I understand from here(Using env variable in Spring Boot's application.properties) and springBoot docs, SpringBoot automatically replaces environment variables in application.properties but I am not seeing this . Here is my system environment file which i am sourcing before running the application
export DB_HOST=localhost
export DB_PORT=11111
export DB_USER=root
export DB_PASSWORD=root
export QUARTZ_DB_NAME=quartz
And here is my application.properties
org.quartz.dataSource.quartzDataSource.URL =jdbc:mysql://${DB_HOST}:${DB_PORT}/${QUARTZ_DB_NAME}
org.quartz.dataSource.quartzDataSource.user = ${DB_USER}
org.quartz.dataSource.quartzDataSource.password = ${DB_PASSWORD}
And my configuration class
#Configuration
public class ConfigureQuartz {
#Autowired
private ApplicationContext applicationContext;
#Bean
public SchedulerFactoryBean schedulerFactoryBean() throws IOException
{
final SchedulerFactoryBean quartzScheduler = new SchedulerFactoryBean();
quartzScheduler.setSchedulerName("mdsScheduler");
quartzScheduler.setQuartzProperties(quartzProperties());
final AutoWiringSpringBeanJobFactory jobFactory = new AutoWiringSpringBeanJobFactory();
jobFactory.setApplicationContext(applicationContext);
quartzScheduler.setJobFactory(jobFactory);
return quartzScheduler;
}
#Bean
public Properties quartzProperties() throws IOException {
final PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
propertiesFactoryBean.setLocation(new ClassPathResource("/application.properties"));
propertiesFactoryBean.afterPropertiesSet();
return propertiesFactoryBean.getObject();
}
But when i run my spring application using java -jar <>.java , I dont see the values substitued .
I can workaround by reading the values using System.getEnv() but would be great if the values can be substitued . Not sure why its not working :(
Spring-boot allows us several methods to provide externalized configurations , you can try using application.yml or yaml files instead of the property file and provide different property files setup according to different environments.We can separate out the properties for each environment into separate yml files under separate spring profiles.Then during deployment you can use :
java -jar -Drun.profiles=SpringProfileName
to specify which spring profile to use.Note that the yml files should be name like
application-{environmentName}.yml
for them to be automatically taken up by springboot.
Reference : https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html#boot-features-external-config-profile-specific-properties
To read from the application.yml or property file :
The easiest way to read a value from the property file or yml is to use the spring #value annotation.Spring automatically loads all values from the yml to the spring environment , so we can directly use those values from the environment like :
#Component
public class MyBean {
#Value("${name}")
private String name;
// ...
}
Or another method that spring provides to read strongly typed beans is as follows:
YML
acme:
remote-address: 192.168.1.1
security:
username: admin
roles:
- USER
- ADMIN
Corresponding POJO to read the yml :
#ConfigurationProperties("acme")
public class AcmeProperties {
private boolean enabled;
private InetAddress remoteAddress;
private final Security security = new Security();
public boolean isEnabled() { ... }
public void setEnabled(boolean enabled) { ... }
public InetAddress getRemoteAddress() { ... }
public void setRemoteAddress(InetAddress remoteAddress) { ... }
public Security getSecurity() { ... }
public static class Security {
private String username;
private String password;
private List<String> roles = new ArrayList<>(Collections.singleton("USER"));
public String getUsername() { ... }
public void setUsername(String username) { ... }
public String getPassword() { ... }
public void setPassword(String password) { ... }
public List<String> getRoles() { ... }
public void setRoles(List<String> roles) { ... }
}
}
The above method works well with yml files.
Reference:
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html
To read from the environment :
If you are running the application on linux set the env as below :
export DB_HOST=localhost
export DB_PORT=11111
export DB_USER=root
export DB_PASSWORD=root
export QUARTZ_DB_NAME=quartz
if you are on windows set it like :
SET DB_HOST=localhost
SET DB_PORT=11111
SET DB_USER=root
SET DB_PASSWORD=root
SET QUARTZ_DB_NAME=quartz
In your application.properties if you keep the keys as below , it should automatically resolve the value from the environment :
spring.datasource.url = ${DB_HOST}/"nameofDB"
spring.datasource.username = ${DB_USER}
spring.datasource.password = ${DB_PASSWORD}
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 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! =)