How to make externalized properties file available to spring boot integration tests? - spring

I am developing an application using Spring Boot. I have an externalized properties file on file system. Its location is stored in an environment variable as below--
export props=file://Path-to-file-on-filesystem/file.properties
Properties from this file are loaded on classpath and are made available to application like below--
List<String> argList = new ArrayList<>();
String properties = System.getenv().get("props");
try (InputStream is = BinaryFileReaderImpl.getInstance().getResourceAsStream(properties)) {
if (is != null) {
Properties props = new Properties();
props.load(is);
for(String prop : props.stringPropertyNames()) {
argList.add("--" + prop + "=" + props.getProperty(prop));
}
}
} catch(Exception e) {
//exception handling
}
This argList is passed to SpringBootApplication when it starts like below--
SpringApplication.run(MainApplication.class, argList);
I can access all the properties using ${prop.name}
However, I do not have access to these properties when I run JUnit Integration Tests. All my DB properties are in this externalized properties file. I do not want to keep this file anywhere in the application eg. src/main/resources
Is there any way I can load these properties in spring's test context?

I could finally read the externalized Properties file using #PropertySource on linux machine. Could not get it working on Windows instance though.
Changes done are as below-
export props=/path-to-file
Note that file:// and actual file name has been removed from environment variable.
#Configuration
#PropertySource({"file:${props}/file-test.properties", "classpath:some_other_file.properties"})
public class TestConfiguration {
}
Keep this configuration class in individual projects' src/test/java folder. Thank you Joe Chiavaroli for your comment above.

Related

Unit test case for loading properties file

I am trying to write unit test case for the following piece of code.
public static String maskUri(String uri) {
...
ClassPathResource resource = new ClassPathResource("application.properties");
Properties prop = new Properties();
try
{
prop.load(resource.getInputStream());
}
catch (IOException e)
{
//Log the exception
}
...
}
Actual code works fine. The problem is it looks for application.properties while executing the junit test case and fails as it couldn't find the file.
Can someone please suggest pointers on how to proceed with this?
Is it possible to add another properties file and use it for test cases? As the file name is directly given in the source code, it always looks for application.properties only.
Is there a way to dynamically change the property file like to use application.properties for core functionality and application-test.properties for Junit?

Using a custom classloader with Spring's ComponentScan and PropertyPlaceholderConfigurer

I want to create multiple application contexts in my Tomcat application.
Some of these application contexts have the same package and class names, but they all refer to different jars.
For example:
application0 use service.jar, model.jar
application1 use service-a.jar, model-a.jar
application2 use service-b.jar, model-b.jar
application0 context is OK because is in orign project.
I reference some web page to custom application1, I use my custom classloader to start applicationContext.
File file0 = new File("D://git/project1/service-a.jar");
File file1 = new File("D://git/project1/modele-a.jar");
// convert the file to URL format
URL url0 = file0.toURI().toURL();
URL url1 = file1.toURI().toURL();
List<URL> urls = new LinkedList<>();
List<File> libs = listFilesForFolder(new File("D://protal//apache-tomcat-8.0.39//lib"));
for(File lib : libs) {
urls.add(lib.toURI().toURL());
}
urls.add(url1);
urls.add(url0);
final URLClassLoader customClassLoader = new URLClassLoader(urls.toArray(new URL[urls.size()]));
ClassPathXmlApplicationContext context1 = new ClassPathXmlApplicationContext("applicationContext.xml") {
protected void initBeanDefinitionReader(XmlBeanDefinitionReader reader)
{
super.initBeanDefinitionReader(reader);
reader.setValidationMode(XmlBeanDefinitionReader.VALIDATION_NONE);
reader.setBeanClassLoader(customClassLoader);
}
};
allApplicationContexts.add(context1);
The Spring contexts start OK, but they fail to create the component-scan bean, and PropertyPlaceholderConfigurer isn't working. Everything else seems correct.
I sure my config is correct because it works without the custom classloader. Libs contains all spring lib.
Is it possible to get this working with multiple Spring contexts?

Custom property loader with Spring Cloud Config

I'm using Spring Cloud Config in my spring-boot application and I need to write some custom code to handle properties to be read from my corporate password vault when property is flagged as such. I know spring cloud supports Hashicorp Vault, but that's not the one in case.
I don't want to hard-code specific properties to be retrieved from a different source, for example, I would have a properties file for application app1 with profile dev with values:
spring.datasource.url=jdbc:mysql://localhost/test
spring.datasource.username=dbuser
spring.datasource.password=dbpass
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
but for some other profiles such as prod, I would have:
spring.datasource.url=jdbc:mysql://localhost/test
spring.datasource.username=prod-user
spring.datasource.password=[[vault]]
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
So I need the custom property vault to intercept the property loaded whenever it finds a returned value equals to [[vault]] (or some other type of flag), and query from the corporate vault instead. In this case, my custom property loader would find the value of property spring.datasource.password from the corporate password vault. All other properties would still be returned as-is from values loaded by standard spring cloud config client.
I would like to do that using annotated code only, no XML configuration.
You can implement your own PropertySourceLocator and add entry to
spring.factories in directory META-INF.
#spring.factories
org.springframework.cloud.bootstrap.BootstrapConfiguration=/
foo.bar.MyPropertySourceLocator
Then you can you can refer to keys in your corporate password vault like a normal properties in spring.
spring.datasource.url=jdbc:mysql://localhost/test
spring.datasource.username=prod-user
spring.datasource.password=${lodaded.password.from.corporate.vault}
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
Implementation by HasiCorp: VaultPropertySourceLocatorSupport
While trying to solve the identical problem, I believe that I have come to work-around that may be acceptable.
Here is my solution below.
public class JBossVaultEnvironmentPostProcessor implements EnvironmentPostProcessor {
#Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
MutablePropertySources propertySources = environment.getPropertySources();
Map<String, String> sensitiveProperties = propertySources.stream()
.filter(propertySource -> propertySource instanceof EnumerablePropertySource)
.map(propertySource -> (EnumerablePropertySource<?>) propertySource)
.map(propertySource -> {
Map<String, String> vaultProperties = new HashMap<>();
String[] propertyNames = propertySource.getPropertyNames();
for (String propertyName : propertyNames) {
String propertyValue = propertySource.getProperty(propertyName).toString();
if (propertyValue.startsWith("VAULT::")) {
vaultProperties.put(propertyName, propertyValue);
}
}
return vaultProperties;
})
.reduce(new HashMap<>(), (m1, m2) -> {
m1.putAll(m2);
return m1;
});
Map<String, Object> vaultProperties = new HashMap<>();
sensitiveProperties.keySet().stream()
.forEach(key -> {
vaultProperties.put(key, VaultReader.readAttributeValue(sensitiveProperties.get(key)));
});
propertySources.addFirst(new MapPropertySource("vaultProperties", vaultProperties));
}

Specifying relative path in application.properties in Spring

Is there a way we can lookup file resources using relative path in application.properties file in Spring boot application as specified below
spring.datasource.url=jdbc:hsqldb:file:${project.basedir}/db/init
I'm using spring boot to build a upload sample, and meet the same problem, I only want to get the project root path. (e.g. /sring-boot-upload)
I find out that below code works:
upload.dir.location=${user.dir}\\uploadFolder
#membersound answer is just breaking up the hardcoded path in 2 parts, not dynamically resolving the property. I can tell you how to achieve what you're looking for, but you need to understand is that there is NO project.basedir when you're running the application as a jar or war. Outside the local workspace, the source code structure doesn't exist.
If you still want to do this for testing, that's feasible and what you need is to manipulate the PropertySources. Your simplest option is as follows:
Define an ApplicationContextInitializer, and set the property there. Something like the following:
public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(ConfigurableApplicationContext appCtx) {
try {
// should be /<path-to-projectBasedir>/build/classes/main/
File pwd = new File(getClass().getResource("/").toURI());
String projectDir = pwd.getParentFile().getParentFile().getParent();
String conf = new File(projectDir, "db/init").getAbsolutePath();
Map<String, Object> props = new HashMap<>();
props.put("spring.datasource.url", conf);
MapPropertySource mapPropertySource = new MapPropertySource("db-props", props);
appCtx.getEnvironment().getPropertySources().addFirst(mapPropertySource);
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}}
Looks like you're using Boot, so you can just declare context.initializer.classes=com.example.MyApplicationContextInitializer in your application.properties and Boot will run this class at startup.
Words of caution again:
This will not work outside the local workspace as it depends on the source code structure.
I've assumed a Gradle project structure here /build/classes/main. If necessary, adjust according to your build tool.
If MyApplicationContextInitializer is in the src/test/java, pwd will be <projectBasedir>/build/classes/test/, not <projectBasedir>/build/classes/main/.
your.basedir=${project.basedir}/db/init
spring.datasource.url=jdbc:hsqldb:file:${your.basedir}
#Value("${your.basedir}")
private String file;
new ClassPathResource(file).getURI().toString()

Log4j2 in a spring web app with servlet 3.0

I need to change the default location of log4j2 configuration file. I followed the documentation here
https://logging.apache.org/log4j/2.x/manual/webapp.html
But the only file log4j2 can see is log4j2.xml in the classpath. otherwise I get "no log4j2 configuration file found"
I tried:
-1 setting context parameters
-2 setting system property Log4jContextSelector to "org.apache.logging.log4j.core.selector.JndiContextSelector". and using the JNDI selector
as described here
https://logging.apache.org/log4j/2.x/manual/webapp.html#ContextParams
-3 lookups: web, env, sys, ctx and bundle. the first 4 failed only bundle worked but you can only lookup inside the classpath.
-4 set isLog4jAutoInitializationDisabled to true, and I am not sure how to configure the filter in this case. If I include them in the web.xml the app will not deploy.
jar in the project
./WEB-INF/lib/log4j-jcl-2.4.1.jar
./WEB-INF/lib/log4j-core-2.4.1.jar
./WEB-INF/lib/log4j-slf4j-impl-2.4.1.jar
./WEB-INF/lib/log4j-api-2.4.1.jar
In my situation with .propeties file I use code shown below
#Plugin(name = "LogsConfigurationFactory", category = ConfigurationFactory.CATEGORY)
public class CustomLogsConfigurationFactory extends PropertiesConfigurationFactory {
#Override
public Configuration getConfiguration(String name, URI configLocation) {
File propFile = new File("/path_to/log4j2.properties");
return super.getConfiguration(name, propFile.toURI());
}
#Override
protected String[] getSupportedTypes() {
return new String[] {".properties", "*"};
}
}
I think you can change CustomLogsConfigurationFactory on XmlConfigurationFactory, and change return typse in getSupportedTypes method. I hope this will help you.

Resources