#Configuration #ImportResource with FileSystemResource reading properties from Web-Inf - spring

I am trying to read a properties file from WEB-INF using Java-based Configuration of my Spring MVC app. I can make this work when I put the properties directory in the src directory and use class: (ClassPathResource).
I want to use #ImportResource with file: (FileSystemResource) that will read from properties/ or resources/properties/ or /properties/ or /resources/properties/ when this is located in WebContent or WEB-INF
When I use file: I get a FileNotFoundException.
I have tried moving the properties directory around and used #ImportResource with "file:/properties/properties-config.xml", "file:/WebContent/properties/properties-config.xml", and /WEB-INF/properties/properties-config.xml".
I mapped in my app-servlet.xml and tried "file:/resources/properties/properties-config.xml"
This should be straightforward and not uncommon. But I can't find an example of getting properties files this way.
#ImportResource("file:/properties/properties-config.xml")
#Configuration
public class AppConfig {
protected final Log logger = LogFactory.getLog(getClass());
private #Value("#{v1Properties['v1.dataUrl']}") String dataUrl;
private #Value("#{v1Properties['v1.metaUrl']}") String metaUrl;
private #Value("#{v1Properties['v1.user']}") String v1User;
private #Value("#{v1Properties['v1.password']}") String v1Password;
#Bean
V1Config v1Config() {
V1Config v1Config = new V1Config();
v1Config.setDataUrl(dataUrl);
v1Config.setMetaUrl(metaUrl);
v1Config.setUserId(v1User);
v1Config.setPassword(v1Password);
return v1Config;
}
#Bean
ViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/jsp/");
resolver.setSuffix(".jsp");
return resolver;
}
}

Try this out,
#ImportResource("classpath:properties-config.xml")

Related

Spring Boot: Multiple WAR deployment in same tomcat different properties

So I have to deploy the same springboot app as multiple apps in the same tomcat server.
eg /app1 /app2 /app3.
They share most of the same configuration except for datasource configuration.
I've been searching for a way to externalise the datasource configuration based on the servlet-context or something like that.
Using springs externalised configuration, I am able to get it to load the same external data source file for all apps, but they need to be different. eg.
#PropertySource(value = "file:${HOME}/datasource-override.properties", ignoreResourceNotFound=false)
Using the embedded tomcat mode, ie via say .\gradlew bootRun I think I can achieve it.
I just need to use the following as the application.properties for that profile sets the server.context-path property. (as this is a single app) eg.
#PropertySource(value = "file:${HOME}/${server.context-path}/datasource-override.properties", ignoreResourceNotFound=false),
Searching around, I thought it might be something like (and combinations of) the following, but it didn't work. eg
#PropertySource(value = "file:${HOME}/${server.servlet.context-path}/datasource-override.properties", ignoreResourceNotFound=false)
All examples I've found so far deal with either the embedded tomcat, or a single externalised property file for a single app.
Ideally I would like it to find the file in either it's own directory
file:${HOME}/${server.servlet.context-path}/datasource.properties
So for the three apps it would be something like the following, where it detects from it's deployed context, what the location for it's property file is. eg:
file:${HOME}/app1/datasource.properties
file:${HOME}/app2/datasource.properties
file:${HOME}/app3/datasource.properties
Obviously if the app was deployed as /funky_chicken then it would have a matching funky_chicken/datasource.properties
Any thoughts ? I know I am probably close, and I've tried dumping all the environmental properties. (you are probably are going to tell me to get it from JNDI as it's the only one I haven't dumped looking for the context)
Now I know ${HOME} is not the best place to store config items, it really is just to make it easier to describe the issue.
Update:
Thanks to the suggestions to use profiles, is there a way to have three active profiles in the one tomcat server, /app1, /app2 and /app3 from the same WAR file?
Why you want to deploy in tomcat? Springboot app can work lonely. Hope below steps helpful to you.
add application.yml(application.properties is ok too) in /resources. In this file, you configure common setting here.
Then you add files named from application-app1.yml to application-app3.yml in /resources too. In these files, you configure different db setting.
launch your app: for example, I suppose app1 using port 10000, app2 using port 10001...
after maven well,
app1: java -jar target/[***SNAPSHOT].jar --server.port=10000 --spring.profiles.active=app1
app2: java -jar target/[***SNAPSHOT].jar --server.port=10001 --spring.profiles.active=app2
app3: java -jar target/[***SNAPSHOT].jar --server.port=10002 --spring.profiles.active=app3
You can solve problem with spring profiles and there is no need to use #PropertySource
for application 1 just activate profiles: spring.profiles.active=app1 - this assume that in classpath you have application-app1.properties file. Same for app2, app3..appN. And file application.properies will contains common properties for all of services
I had a similar requirement. My approach is to pass the config directory to tomcat as environment variable, lets assume you pass
spring.config.location=/home/user/config
Then under /home/user/config folder you should have files matching the contextPath for each application instance. In your case you should have
app1.properties
app2.properties
app3.properties
If you don't want to duplicate common parameters, you can put all common properties in a separate file and use "spring.config.import" to import it from each application specific configuration file. Note that importing another file is supported since Spring Boot 2.4 See section "Importing Additional Configuration"
For Spring Boot application to load the properties file according to the context path, you should override "createRootApplicationContext" to get the context path, and override "configure" to set it as the properties file name as below.
#SpringBootApplication
public class TestApp extends SpringBootServletInitializer {
private static Class<TestApp> applicationClass = TestApp.class;
private String contextPath;
public static void main(String[] args) {
try {
SpringApplication.run(applicationClass, args);
}
catch (Exception e) {
// Handle error
}
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(applicationClass).properties("spring.config.name:" + contextPath);
}
#Override
protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
contextPath = servletContext.getContextPath();
return super.createRootApplicationContext(servletContext);
}
}
You can try the RoutingDataSource approach, This lets you switch the datasource at runtime/realtime.
To implement this you have to pass some datasource identifier initially (You can set it in your auth token for rest based requests or in a session)
eg -
localhost:80/yourcontext/{app1}
localhost:80/yourcontext/{app2}
localhost:80/yourcontext/app3
Here app1, app2, app3 will be your datasource identifiers
App controller
#Controller
public class AppController extends SuperController{
#GetMapping("/{client}")
public String login(ModelMap map, #PathVariable("client") String client, HttpServletRequest request){
//Logic to set the path variable
return "loginPage";
}
}
Routing datasource Config
#Configuration
#EnableTransactionManagement
#ComponentScan({ "com.components_package" })
#PropertySource(value = { "classpath:database.properties" })
public class RoutingDataSource extends AbstractRoutingDataSource {
#Override
protected Object determineCurrentLookupKey() {
String tenant = DbContextHolder.getDbType();
LoginDetails LoginDetails = currentUser();
if(LoginDetails != null){
tenant = LoginDetails.getTenantId();
}
logger.debug("tenant >>>> "+tenant);
return tenant;
}
private LoginDetails currentUser() {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication instanceof UserAuthentication) {
return ((UserAuthentication) authentication).getDetails();
}
//If not authenticated return anonymous user
return null;
}
}
Hibernate config
#PropertySource(value = { "classpath:database.properties" })
public class HibernateConfiguration {
#Autowired
private Environment environment;
#Bean
#Primary
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dynamicDataSource());
em.setPackagesToScan("com.entities_package");
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
em.setJpaProperties(hibernateProperties());
return em;
}
/** RoutingDataSource datasource initialized enabling the application
* to switch multiple databases at runtime
* #return
*/
private RoutingDataSource dynamicDataSource(){
RoutingDataSource routingDataSource = new RoutingDataSource();
/*Set default datasource*/
routingDataSource.setDefaultTargetDataSource(defaultDataSource());
/*Set all datasource map*/
routingDataSource.setTargetDataSources(fetchDatasourceMap());
routingDataSource.afterPropertiesSet();
return routingDataSource;
}
/** This is a default datasource
* #return
*/
private DataSource defaultDataSource() {
final JndiDataSourceLookup dsLookup = new JndiDataSourceLookup();
DataSource dataSource = dsLookup.getDataSource("jdbc/defaultDs");
return dataSource;
}
/** This method sets all predefined client specific datasources in a map
* #return Map<Object, Object>
*/
private Map<Object, Object> fetchDatasourceMap(){
Map<Object, Object> dataSourcesMap = new HashMap<>();
//database.clients=app1,app2,app3
String client = environment.getRequiredProperty("database.clients");
String[] allClients = client.split(",");
if(allClients != null && allClients.length > 0){
for (Integer i = 0; i < allClients.length; i++) {
String clientKey = allClients[i].trim();
dataSourcesMap.put(clientKey, dataSource(clientKey));
}
}
return dataSourcesMap;
}
private DataSource dataSource(String clientKey) {
final JndiDataSourceLookup dsLookup = new JndiDataSourceLookup();
String lookupKey = "jdbc/"+clientKey;
DataSource dataSource = dsLookup.getDataSource(lookupKey);
return dataSource;
}
private Properties hibernateProperties() {
Properties properties = new Properties();
properties.put("hibernate.dialect", environment.getRequiredProperty("hibernate.dialect"));
properties.put("hibernate.show_sql", environment.getRequiredProperty("hibernate.show_sql"));
// properties.put("hibernate.format_sql",
// environment.getRequiredProperty("hibernate.format_sql"));
return properties;
}
#Bean
JpaTransactionManager transactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
return transactionManager;
}
}
You have to define your datasource properties in context.xml for JNDI lookup.
Hope this helps

Spring boot server.port not picked up in unit test

Spring Boot :: v2.0.0.BUILD-SNAPSHOT
spring-core :: v5.0.1.RELEASE
I have an application.properties file in my src/main/resources folder that is part of my classpath.
In that file is server.port
In my Configuration class I have :
#Value("${server.port}")
private String localServerPort;
and a bean that uses it:
#Bean
public InetSocketAddress tcpSocketAddress() {
return new InetSocketAddress(Integer.parseInt(this.localServerPort));
}
The server.port is not retrieved from the application.properties file when the tcpSocketAddress bean is called, it is -1 at that point.
I have tried a number of different annotations and methods short of reading the file in manually which I think defeats the point.
Any suggestions?
Thanks
full configuration:
#Configuration
#PropertySource("application.properties")
public class ChronosConfiguration {
final Logger log = Loggers.getLogger(ChronosConfiguration.class);
#Value("${server.port}")
private String localServerPort;
#Bean
public CountDownLatch latch() {
return new CountDownLatch(10);
}
#Bean
public InetSocketAddress tcpSocketAddress() {
return new InetSocketAddress(Integer.parseInt(this.localServerPort));
}
}

Different yml files for different Beans in Spring

In my spring boot application, I have the main application.yml file. I have a lot of properties, and therefore I would like to have another yml files, which contains the specified properties, grouped by their logic or something.
How can I configure a Bean, to load and work all the properties from one new yml file, and another Bean from another new yml? What is the best practice for it?
I found examples using YamlPropertiesFactoryBean, and this bean can read several resources (yml files), but in an another Bean, when I autowire this YamlPropertiesFactoryBean, I cannot get that specific yml, because the getObject() of this YamlPropertiesFactoryBean will have all the yml resources I added to it.
Finally I have it! This is how it works:
I have a properties configuration class, which loads the yml files:
#Configuration
public class PropertiesConfig {
public static final String PERSONS_FILE_NAME = "persons.yml";
public static final String FOODS_FILE_NAME = "foods.yml";
#Bean
public PropertySourcesPlaceholderConfigurer properties() {
final PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
final YamlPropertiesFactoryBean personsYaml = personsPropertiesFromYamlFile();
final YamlPropertiesFactoryBean foodsYaml = foodsPropertiesFromYamlFile();
propertySourcesPlaceholderConfigurer.setPropertiesArray(personsYaml.getObject(), foodsYaml.getObject());
return propertySourcesPlaceholderConfigurer;
}
#Bean
#Qualifier(PersonsManager.QUALIFIER_NAME)
public YamlPropertiesFactoryBean personsPropertiesFromYamlFile() {
final YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
yaml.setResources(new ClassPathResource(PERSONS_FILE_NAME));
return yaml;
}
#Bean
#Qualifier(FoodsManager.QUALIFIER_NAME)
public YamlPropertiesFactoryBean foodsPropertiesFromYamlFile() {
final YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
yaml.setResources(new ClassPathResource(FOODS_FILE_NAME));
return yaml;
}
}
And finally, I have two beans (managers), which hold only the corresponding yml properties:
#Component
public class PersonsManager extends YmlPropertiesManager {
public static final String QUALIFIER_NAME = "personsYaml";
#Autowired
public PersonsManager(#Qualifier(QUALIFIER_NAME) YamlPropertiesFactoryBean yamlObject) {
super(yamlObject);
}
...
}
and:
#Component
public class FoodsManager extends YmlPropertiesManager {
public static final String QUALIFIER_NAME = "personsYaml";
#Autowired
public FoodsManager(#Qualifier(QUALIFIER_NAME) YamlPropertiesFactoryBean yamlObject) {
super(yamlObject);
}
...
}
So the main thing here is the #Qualifier annotation.
Beans shouldn't be aware of yaml files. The yaml files are just sources that use used to build up the Spring Environment instance.
If you want specific properties for specific beans, the best way is to prefix those properties in application.yaml, and then use the #ConfigurationProperties with an argument of the prefix you want to use, to bind those properties to the bean in question.
See here:
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html

Spring boot: InternalResourceViewResolver not working

I spent several hours trying to use InternalResourceViewResolver in order to append prefix and suffix to html views.
My views located under static/pages/ and by Spring docs, folder static is considered to be one of defaults for static content. So, I could access profile page by pages/profile.html. But what I really want to have is profile instead of pages/profile.html.
I've tried several answers, but that does not work, like:
#Bean
public ViewResolver getViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("pages/");
resolver.setSuffix(".html");
return resolver;
}
and adding
#Override
public void configureDefaultServletHandling(
DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
Still does not work properly. By adding any suffixes or prefixes I could not found page on any path. I am starting to get 404 on pages/profile.html, but it also does not appear on other urls.
Just need add your own custom configuration like this
#Configuration
public class WebMvcConfig {
#Bean
public InternalResourceViewResolver defaultViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/jsp");
resolver.setSuffix(".jsp");
return resolver;
}
}
Then you can inspect all your beans via "http://localhost:8080/beans"
And you can verfity is it using the custom configured bean:
{
"bean": "defaultViewResolver",
"scope": "singleton",
"type": "org.springframework.web.servlet.view.InternalResourceViewResolver",
"resource": "class path resource [io/cloudhuang/web/WebMvcConfig.class]",
"dependencies": [ ]
}
But the eaiest way should be config it in the application.properties
spring.mvc.view.prefix=
spring.mvc.view.suffix=
For application.yaml
spring:
mvc:
view:
prefix: templates/
suffix: .jsp
Using Spring Boot you actually don't need to declare your own InternalResourceViewResolver. Boot declares it for you, and you can just add a couple of properties to your application.properties file. E.g. in your case these would be:
spring.mvc.view.prefix=/jsp
spring.mvc.view.suffix=.jsp

#Value not getting injected in other packages

I have a property source defined in Spring where I am trying to load a properties file present in the classpath
#Configuration
#PropertySource(name = "props", value = "classpath:prod.properties")
public class PropertyPlaceholderConfigurerConfig {
#Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigurer() {
PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer();
Resource[] resources = new ClassPathResource[]
{ new ClassPathResource("prod.properties") };
ppc.setLocations(resources);
ppc.setIgnoreUnresolvablePlaceholders(true);
return ppc;
}
#Value("${DATABASE_NAME}") private String DATABASE_NAME;
#Bean
public String test() {
System.out.println(DATABASE_NAME);
}
}
My prod.properties file has 1 entry
DATABASE_NAME=proddb_123
Now in the test() bean prints as proddb_123
Two problems I am facing is
I have another class in a different package where I am trying to
inject it
#Value("${DATABASE_NAME}") private String DATABASE_NAME;
But the value of this is always "${DATABASE_NAME}"
I have different gradle projects like webapplication-gradle is one
where the #PropertySouce configuration is defined and another
project like storage-gradle. Can I inject the DATABASE_NAME in any
class present in storage-gradle project.
In the other classes where you would like to inject the #Value(${DATABASE_NAME}") you have to also yse the #PropertySource annotation. Another option is to remove the #PropertySource annotations and in the applicaiton-context.xml defined a bean of class org.springframework.beans.factory.condig.PropertyPlaceholderConfigurer and for the property locations list add a value for the classpath:prod.properties

Resources