I have made a small rest api with Spring-boot and i am reading certain things from a config file. Since i cant hardcode the path to the config file, since it changes place in production, i decided to get it from runtime arguments. I use the path when instantiating my ConfigService.Now the problem is that all my tests are failing because it needs to instantiate the ConfigService, but it hasnt reached the main (where it gets the path) when running the tests. My main looks like this:
@SpringBootApplication
public class SecurityService {
private static final Logger LOG = LogManager.getLogger(SecurityService.class);
private static String[] savedArgs;
public static String[] getArgs(){
return savedArgs;
}
public static void main(String[] args) throws IOException {
savedArgs = args;
final String configPath = savedArgs[0];
// final String configPath = "src/config.xml";
ConfigService configService = new ConfigService(configPath);
if (configService.getConfigurations().getEnableHttps()) {
LOG.info("Using HTTPS on port {}", configService.getConfigurations().getPort());
configService.setSSL();
}
SpringApplication.run(SecurityService.class, args);
}
}
I load the config before starting the Spring application because i need to set SSL settings etc, before the server starts. Now when it runs the SpringApplication it instantiates all the classes, including ConfigService again. The ConfigService looks like this:
@Configuration
@Service
public class ConfigService {
private static final Logger LOG = LogManager.getLogger(ConfigService.class);
private static String[] args = SecurityService.getArgs();
private static final String CONFIG_PATH = args[0];
private Configurations configurations;
public ConfigService() {
this(CONFIG_PATH);
}
public ConfigService(String configPath) {
configurations = setConfig(configPath);
}
// Reads config and assigns values to an object, configurations
private Configurations setConfig(String configPath) {
Configurations configurations = new Configurations();
try {
ApplicationContext appContext =
new ClassPathXmlApplicationContext("applicationContext.xml");
XMLConverter converter = (XMLConverter) appContext.getBean("XMLConverter");
configurations = (Configurations) converter.convertFromXMLToObject(configPath);
} catch (IOException e) {
e.printStackTrace();
}
LOG.info("Loaded settings from config.xml");
return configurations;
}
// Checks if EnableHttps is true in config.xml and then sets profile to secure and sets SSL settings
public void setSSL() {
System.setProperty("spring.profiles.active", "Secure");
System.setProperty("server.ssl.key-password", configurations.getKeyPass());
System.setProperty("server.ssl.key-store", configurations.getKeyStorePath());
}
// Spring profiles
// If EnableHttps is false it uses the Default profile, also sets the port before starting tomcat
@Component
@Profile({"Default"})
public class CustomContainer implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
@Override
public void customize(ConfigurableServletWebServerFactory container) {
container.setPort(configurations.getPort());
}
}
// If EnableHttps is True it will use the "Secure" profile, also sets the port before starting tomcat
@Component
@Profile({"Secure"})
public class SecureCustomContainer implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
@Override
public void customize(ConfigurableServletWebServerFactory container) {
container.setPort(configurations.getPort());
}
}
public Configurations getConfigurations() {
return configurations;
}
}
When trying to run as JAR file it spits a bunch of nullpointerexceptions because args[0] is null because it hasnt actually gotten the arguments yet.
Can i somehow work around this? Like giving it the path src/config.xml first, and then overwriting it to the runtime args path later, when it actually starts?
One of my test classes look like this:
@RunWith(SpringRunner.class)
@SpringBootTest
public class SecurityServiceTests {
@Test
public void contextLoads() {
}
@Test
public void testGetPort() {
String configPath = "src/config.xml";
ConfigService configService = new ConfigService(configPath);
int actualPort = configService.getConfigurations().getPort();
int expectedPort = 8443;
assertEquals(expectedPort, actualPort);
}
@Test
public void testGetTTL(){
String configPath = "src/config.xml";
ConfigService configService = new ConfigService(configPath);
int actualTTL = configService.getConfigurations().getTTL();
int expectedTTL = 15000;
assertEquals(expectedTTL, actualTTL);
}
@Test
public void testSSL(){
String configPath = "src/config.xml";
ConfigService configService = new ConfigService(configPath);
String expectedKeyPass = "changeit";
String expectedKeyStore = "classpath:ssl-server.jks";
configService.setSSL();
assertEquals(expectedKeyPass,System.getProperty("server.ssl.key-password"));
assertEquals(expectedKeyStore,System.getProperty("server.ssl.key-store"));
}
}
Configurations class:
// Model class that we map config.xml to
@Component
public class Configurations {
private int port;
private boolean enableHttps;
private String keyStorePath;
private String keyPass;
private int TokenTtlMillis;
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public boolean getEnableHttps() {
return enableHttps;
}
public void setEnableHttps(boolean enableHttps) {
this.enableHttps = enableHttps;
}
public String getKeyStorePath() {
return keyStorePath;
}
public void setKeyStorePath(String keyStorePath) {
this.keyStorePath = keyStorePath;
}
public String getKeyPass() {
return keyPass;
}
public void setKeyPass(String keyPass) {
this.keyPass = keyPass;
}
public int getTTL() {
return TokenTtlMillis;
}
public void setTTL(int TTL) {
this.TokenTtlMillis = TTL;
}
}
And my config.xml that is mapped to the configurations class:
<?xml version="1.0" encoding="UTF-8"?>
<Configurations>
<Port>8443</Port>
<EnableHttps>true</EnableHttps>
<KeyStorePath>classpath:ssl-server.jks</KeyStorePath>
<KeyPass>changeit</KeyPass>
<TokenTtlMillis>15000</TokenTtlMillis>
</Configurations>