Spring JUnit ApplicationContextInitializer parameters - java

We have implemented an extended .properties format. Such a properties file can contain an optional include property. The value of this properties is the classpaths of other properties files to load recursively.
I could configure the Spring environment for my application but I have a problem to engage this mechanism with SpringJUnit4ClassRunner.
I thought I could use the initializer property of the ContextConfiguration annotation but it looks it can only be instantiated with a no-arg constructor.
I need to give it the root file of my properties files hierarchy. It could eventually be another annotation on my test class, but again, how can I access it ?
The only think I have found so far is to set this file as a system property in my test class static initializer. ugly?:
#ActiveProfiles("qacs.controller.channels=mock")
#ContextConfiguration(initializer=ContainerTestContextInitializer.class)
public class QacsControllerTest
{
static
{
System.setProperty(ContainerTestContextInitializer.SYSTEM_PROPERTY, "classpath:com/xxx/qacs/QacsControllerTest.properties");
}
#Test
void test() {}
}
}
public class ContainerTestContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext>
{
public static final String SYSTEM_PROPERTY = "icomp.test.properties";
#Override
public void initialize(ConfigurableApplicationContext pApplicationContext)
{
String path = System.getProperty(SYSTEM_PROPERTY);
if (path == null)
{
throw new IllegalStateException("Missing system property " + SYSTEM_PROPERTY);
}
final DefaultPropertiesLoader loader;
loader = new DefaultPropertiesLoader(System.getProperties());
try
{
loader.load(path);
}
catch (IOException e)
{
throw new IllegalStateException(e.getMessage(), e);
}
MutablePropertySources sources = pApplicationContext.getEnvironment().getPropertySources();
MapPropertySource mps = new MapPropertySource(Launcher.ICOMP_PROPERTY_SOURCE, (Map) loader.getProperties());
sources.addFirst(mps);
}
}

Related

How to post process Spring boot properties?

I have defined some external properties in my main class as follows:
#PropertySources({
#PropertySource(value = "classpath:application.properties", ignoreResourceNotFound = true),
#PropertySource(value = "file:/opt/app/conf/database.properties", ignoreResourceNotFound = true),
#PropertySource(value = "file:/opt/app/conf/overrides.properties", ignoreResourceNotFound = true)
})
For some of the properties I would like to do some post processing, for example encrypting or enrichment before they are actually used by any beans. One such property is spring.datasource.password
I have done the following:
Write an ApplicationContextInitializer<ConfigurableApplicationContext> and tried to process those properties in the initialize() method
public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
String value = environment.getProperty("spring.datasource.password");
System.out.println("The value in initializer is " + value);
System.out.println("The database url in initializer is " + environment.getProperty("spring.datasource.url"));
System.out.println("The database username in initializer is " + environment.getProperty("spring.datasource.username"));
// .. other code
}
}
and included the above class in the META-INF/spring.factories as follows:
org.springframework.context.ApplicationContextInitializer=com.myapp.MyApplicationContextInitializer
I am seeing null values in all of the above properties that are printed though both database.properties and overrides.properties are present. They are the very first statements to be printed (even before the banner)
Another approach I tried is org.springframework.boot.env.EnvironmentPostProcessor and adding in META-INF/spring.factories as
org.springframework.boot.env.EnvironmentPostProcessor=com.myapp.PropertiesProcessor
But still, I get the same null value.
Interestingly, when I pass in the
-Dspring.config.location="file:/opt/app/conf/database.properties, file:/opt/app/conf/overrides.properties"
system property before executing the war, it works i.e. the values are printed.
But I do not want to manually pass in the system property at runtime. Is there a way to do it?
What is wrong with my code or approach. Are there any other ways of doing the post-processing before actually creating the beans?
I solved similar problem by adding property source with highest precedence.
Add META-INF/spring.factories with content:
org.springframework.boot.env.EnvironmentPostProcessor=
com.example.SettingsPropertiesAddingPostProcessor
SettingsPropertiesAddingPostProcessor implementation:
public class SettingsPropertiesAddingPostProcessor implements EnvironmentPostProcessor {
private static final String SETTINGS_CONFIG_PATH = "/tmp/settings.properties";
#Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
File settingsFile = new File(SETTINGS_CONFIG_PATH);
if (!settingsFile.exists()) {
log.debug("Config file not found, skipping adding custom property source");
return;
}
log.debug("Config file found, adding custom property source");
Properties props = loadProperties(settingsFile);
MutablePropertySources propertySources = environment.getPropertySources();
propertySources.addFirst(new PropertiesPropertySource("settings-source", props));
}
private Properties loadProperties(File f) {
FileSystemResource resource = new FileSystemResource(f);
try {
return PropertiesLoaderUtils.loadProperties(resource);
} catch (IOException ex) {
throw new IllegalStateException("Failed to load local settings from " + f.getAbsolutePath(), ex);
}
}
}
That should be all.
Following the advice of #M. Deinum, regarding using "spring.config.additional-location", I have made a workaround as follows:
#SpringBootApplication
#EnableSwagger2
public class MyApp extends SpringBootServletInitializer {
public static void main(String[] args) {
System.setProperty("spring.config.additional-location", "file:/opt/app/conf/database.properties, file:/opt/app/conf/overrides.properties");
SpringApplication springApplication = new SpringApplication(MyApp.class);
springApplication.run(args);
}
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
System.out.println("Setting properties onStartup");
System.setProperty("spring.config.additional-location", "file:/opt/app/conf/database.properties, file:/opt/app/conf/overrides.properties");
super.onStartup(servletContext);
}
}
I have called the System.setProperty() in the onStartup() method of the SpringBootServletInitializer by overriding it as above and then invoked the super class' onStartup()
The latter part i.e. setting system property in onStartup method helps when the application is deployed in a web container like Tomcat.
Note: We can also append the properties to spring.config.additional-location instead of set so that other additional locations can also be added during runtime.

Spring. How to inject a dependence according on application arguments?

I am new in Spring framework. I develop a standalone console application. App will get several files of different format ( CSV, JSP, XML) as arguments. I want inject a certain implementation of parser according to file format.
my service and parsers
These is my service:
#Service
public class ParsingService {
private final Parser parser;
#Autowired
public ParsingService(Parser parser) {
this.parser = parser;
}
public List<Order> parse(String filePath) {
try {
return parser.parse(filePath);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
My main class:
public class Main {
public static void main(String[] args) throws IOException {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConf.class);
for (String arg : args) {
ParsingService service = context.getBean(ParsingService.class);
List<Order> listOfParsedObjects = service.parse(arg);
listOfParsedObjects.forEach(System.out::println);
}
}
}
I will pass to command line several file paths and i need Spring to inject necessary implementation depending on file format.
Assuming that Parser is your own interface you can add a method telling the format it's able to parse:
public interface Parser {
List<Order> parse(String filePath);
String getFormat();
}
Then override it in all the implementations:
#Component
public class CsvParser implements Parser {
public static final String FORMAT = "csv";
public String getFormat(){
return FORMAT;
}
// ...
}
Configure your parser beans either by annotating the classes with #Bean/#Component or by creating instances in your config class. (If you're using SpringBoot I would suggest using #ConditionalOn... annotations in order to avoid creation of unnecessary beans)
Now you can inject all of your Parser instances into ParserService.
#Service
public class ParsingService {
private final Map<String, Parser> parsers;
#Autowired
public ParsingService(List<Parser> allParsers) {
this.parsers = allParsers
.stream()
.collect(Collectors.toMap(Parser::getFormat, p -> p));
}
public List<Order> parse(String filePath) {
try {
String format = getFormat(filePath);
Parser parser = parsers.get(format);
if(parser == null) {
// Replace this exception by a more appropriate one
throw new RuntimeException("No parsers found for format : " + format);
} else {
return parser.parse(filePath);
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private String getFormat(String filePath){
int i = filePath.lastIndexOf('.');
if (i > 0) {
return filePath.substring(i+1).toLowerCase();
} else {
// Replace this exception by a more appropriate one
throw new RuntimeException("Cannot determine the file format!");
}
}
}
This way neither your ParserService nor Main classes will depend from your custom Parser implementations. Once you need a new parser you can simply define a new class implementing the interface. No more changes needed.
UPDATE
Adding Main and AppConfig classes
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConf.class);
ParsingService service = context.getBean(ParsingService.class);
for (String arg : args) {
List<Order> listOfParsedObjects = service.parse(arg);
listOfParsedObjects.forEach(System.out::println);
}
}
}
#Configuration
#ComponentScan(basePackages = "your.root.package")
public class AppConf {
// Do something here
}
For parallel processing try replacing your for-loop in Main with the following code:
Arrays.stream(args)
.parallel()
.map(service::parse)
.flatMap(List::stream)
.forEach(System.out::println);
Or you can use an ExecutorService:
int poolSize = 3;
ExecutorService executorService = new ThreadPoolExecutor(poolSize, poolSize, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
for (String arg : args) {
executorService.submit(() -> {
service.parse(arg).forEach(System.out::println);
});
}
My recommendation would be to consider using Spring Boot and the #ConditionalOnProperty annotation. In the code example below, there will only ever be a bean called csvParserImpl if the property of my.parser has the value of csv. By changing the property value from csv to json, jsonParserImpl will be created instead of csvParserImpl. If my.parser is not defined or set to a value which doesn't include neither csv nor json, then there will be no instance of Parser.
#Configuration
public class MyAutoconfiguration {
#Bean
#ConditionalOnProperty(name="my.parser", havingValue="csv")
CsvParserImpl csvParserImpl() {
return new CsvParserImpl();
}
#Bean
#ConditionalOnProperty(name="my.parser", havingValue="json")
JsonParserImpl jsonParserImpl() {
return new JsonParserImpl();
}
}
When I'm referring to "property", that has a specific meaning within spring boot. Externalized Configuration in spring boot can pull in property values from a multiple of sources, including environment variables, system variables, and command line variables.
You may want to inject a collection of Parsers
#Autowired
private List<Parser> parsers;
And then choose the correct parser from that list.
Also, that is possible to do through a Map
Spring Annotations - Injecting Map of Objects
You can define the method in the parser interface, that returns a collection of extensions, like this
public interface Parser {
List<String> getExtensions();
}
Then you can utilize Java 8 streams for looking for correct parser:
parsers.stream().filter(p->p.getExtensions().contains(extension)).findFirst();
This will return the optional which may contain the needed parser
When you add a parser, what you need is to add a parser and define the extensions. No need to change the code in main

Google Guice Properties Management

I would like to create a proper properties management strategy in a java webapp that relays on google guice as a DI framework.
I would like to have a mechanism answering the following 3 requirements:
I would like to be able to inject properties using guice (#Named)
I would like to be able to access properties in a static way
The mechanism should support prioritization of properties, meaning that a property can be wrapped in the deployed war with a certain value but it can also be redundant in the target system level or local file system (of the target machine I deploy on), in such a case the value in the war will be overridden by the value that exists in the target machine.
I believe this is a standard requirement. Now, using guice standard binder I can easily get the first requirement but not the other two. To get the other two I created my own class that does the following:
Wraps and exposes the binding methods of guice (those that binds properties) For example:
public static void bindString(AnnotatedBindingBuilder<String> binder, String property, String defaultValue) {
binder.annotatedWith(Names.named(property)).toInstance(getProperty(property, defaultValue));
}
Where the getProperty method knows how to handle my properties (get the value from the war or system level) and exposes the properties statically as well.
So basically as long as I'm using this utility that I created for properties bindings I'm good, it covers all my requirements but once I use the standard guice bindings I'm losing the second and third requirement.
Is there a way to override guice bindings and get all those 3 requirements?
Once I had the same challange in a spring based app and was pretty easy. I implemented ApplicationContextInitializer with the following method:
#Override
public void initialize(ConfigurableWebApplicationContext ctx) {
PropertySource<Map<String, Object>> localProps = null;
try {
localProps = new ResourcePropertySource(new ClassPathResource(LOCAL_PROPERTIES_FILE_NAME));
} catch (IOException e) {
LOG.fatal("Could not load local properties from classpath " + LOCAL_PROPERTIES_FILE_NAME);
return;
}
LOG.info("Loaded configuration from classpath local file " + LOCAL_PROPERTIES_FILE_NAME);
ctx.getEnvironment().getPropertySources().addFirst(localProps);
}
so this gave me a way to add local properties with highest priority to my Environment. In case of overlap with war properties the local ones had higher priority. In addition I exposed my Environment statically so I has static access to my properties (for services that are not managed by the container, legacy mostly).
How can I achieve this with guice?
Unfortunately, I don't think that you are going to find anything that gives you a truly clean and satisfying implementation. Especially, I don't think that you will find anything that gives you exactly what you want without implementing at least portions of it yourself.
If I had those needs, I would make sure that my injector is created in a central InjectorFactory. If you require a large number of parameters from outside to create your injector, I would simply create it once at the very beginning of my application and then cache the injector into a static final field. This would make it available to a static method. I would bind my "fall-back" property loading to an explicit provider. That way, instead of using the standard Names.bindProperties(...) method, I would bind it directly to a Provider. This provider then implements the logic that is necessary to perform the fallback or to merge multiple property files. Having the injector cached to a static field means that I can call a static method to access properties from a global-context outside of my injected classes.
Using your own provider seems initially unpleasant, but can provide some additional benefits. For starters, you can implement your fallback strategy exactly how you want. Additionally, you can add additional behaviors such as auto-reloading your property files, etc (not shown in my code sample).
public class InjectorFactory {
private static Injector injector = null;
public static synchronized Injector getOrCreateInjector() {
if(injector == null) {
injector = Guice.createInjector(new AbstractModule() {
#Override
protected void configure() {
Properties properties1 = createProperties("file1.properties");
Properties properties2 = createProperties("file2.properties");
Set<Object> propertyNames = new HashSet<Object>();
propertyNames.addAll(properties1.keySet());
propertyNames.addAll(properties2.keySet());
for (Object object : propertyNames) {
String propertyName = (String) object;
bind(String.class).annotatedWith(Names.named(propertyName)).toProvider(new StringProvider(properties1, properties2, propertyName));
}
}
private Properties createProperties(String propertyFileName) {
try {
InputStream stream = InjectorFactory.class.getResourceAsStream(propertyFileName);
try {
Properties properties = new Properties();
properties.load(stream);
return properties;
} finally {
stream.close();
}
} catch (IOException exception) {
throw new RuntimeException("Could not load properties file");
}
}
});
}
return injector;
}
public static String getProperty(String propertyName) {
return getOrCreateInjector().getInstance(Key.get(String.class, Names.named(propertyName)));
}
}
Given the above code and file1.properties:
property1=Property1Value
property2=Property2Value
And file.properties:
property2=IncorrectProperty2Value
property3=Property3Value
with the provider
public class StringProvider implements Provider<String> {
private Properties properties1;
private Properties properties2;
private String propertyName;
public StringProvider(Properties properties1, Properties properties2,
String propertyName) {
this.properties1 = properties1;
this.properties2 = properties2;
this.propertyName = propertyName;
}
public String get() {
if(properties1.containsKey(propertyName)) {
return properties1.getProperty(propertyName);
}
return properties2.getProperty(propertyName);
}
}
The following usage:
public class InjectorFactoryTest {
public static void main(String ... parameters) {
System.out.println(InjectorFactory.getProperty("property1"));
System.out.println(InjectorFactory.getProperty("property2"));
System.out.println(InjectorFactory.getProperty("property3"));
}
}
Outputs:
Property1Value
Property2Value
Property3Value

Servlet init and Class

I actually have a programm with a servlet :
#WebServlet("/Controler")
public class Controler extends HttpServlet {
}
I need to use a property file : file.properties in my program. To load it, I have a class :
public class PropLoader {
private final static String m_propertyFileName = "file.properties";
public static String getProperty(String a_key){
String l_value = "";
Properties l_properties = new Properties();
FileInputStream l_input;
try {
l_input = new FileInputStream(m_propertyFileName); // File not found exception
l_properties.load(l_input);
l_value = l_properties.getProperty(a_key);
l_input.close();
} catch (Exception e) {
e.printStackTrace();
}
return l_value;
}
}
My property file is in the WebContent folder, and I can access it with :
String path = getServletContext().getRealPath("/file.properties");
But I can't call theses methods in another class than the servlet...
How can I access to my property file in the PropLoader class ?
If you want to read the file from within the webapp structure, then you should use ServletContext.getResourceAsStream(). And of course, since you load it from the webapp, you need a reference to the object representing the webapp: ServletContext. You can get such a reference by overriding init() in your servlet, calling getServletConfig().getServletContext(), and pass the servlet context to the method loading the file:
#WebServlet("/Controler")
public class Controler extends HttpServlet {
private Properties properties;
#Override
public void init() {
properties = PropLoader.load(getServletConfig().getServletContext());
}
}
public class PropLoader {
private final static String FILE_PATH = "/file.properties";
public static Properties load(ServletContext context) {
Properties properties = new Properties();
properties.load(context.getResourceAsStream(FILE_PATH));
return properties;
}
}
Note that some exceptions must be handled.
Another solution would be to put the file under WEB-INF/classes in the deployed webapp, and use the ClassLoader to load the file: getClass().getResourceAsStream("/file.properties"). This way, you don't need a reference to ServletContext.
I would recommend to use the getResourceAsStream method (example below). It would need that the properties file be at the WAR classpath.
InputStream in = YourServlet.class.getClassLoader().getResourceAsStream(path_and_name);
Regards
Luan

How to use config properties file throughout the classes?

I need my Java app to read the config properties from a file and use them throughout the classes. I'm thinking of a separate class, that would return a map of property_key:property_value for each of the properties in the file. Then I would read the values from this map in other classes.
Maybe there are other, more commonly used options?
My properties file is simple and has about 15 entries.
Just use java.util.Properties to load it. It implements Map already.
You can load and get hold of the properties statically. Here's an example assuming that you've a config.properties file in the com.example package:
public final class Config {
private static final Properties properties = new Properties();
static {
try {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
properties.load(loader.getResourceAsStream("com/example/config.properties"));
} catch (IOException e) {
throw new ExceptionInInitializerError(e);
}
}
public static String getSetting(String key) {
return properties.getProperty(key);
}
// ...
}
Which can be used as
String foo = Config.getSetting("foo");
// ...
You could if necessary abstract this implementation away by an interface and get the instance by an abstract factory.

Categories

Resources