I am using the dropwizard framework which uses Yaml for configuration.I wanted to specify class names in the config file.Is there a way in which Yaml will parse it to Class objects or do I have to do it using Strings ?
class Config {
.........
List<Class<?> resourceClasses;
}
YAML file
#resource classes
resourceClasses :
- com.abc.StoresResource
- com.abc.MerchantResource
Are you just looking for a convenient way to register your resources or do you really want to configure the resources in the YAML configuration? If it's the former this is what I'd do:
for (ClassInfo classInfo: ClassPath.from(getClass().getClassLoader()).getTopLevelClasses("com.abc")) {
environment.jersey().register(i.getInstance(classInfo.load()));
}
If it's the latter you'd have to parse those strings yourself. I don't think that the YAML configuration will do that for you. Would something like this work:
class Config {
List<Class<?>> resourceClasses = new ArrayList<Class<?>>();
#JsonProperty("resourceClasses")
public void setResourceClasses(Collection<String> classNames) throws ClassNotFoundException {
for (String className : classNames) {
resourceClasses.add(Class.forName(className));
}
}
}
Related
In a multi-tenant Spring Boot application, I'm trying to load configuration objects. Ideally, I'd like to load certain properties file into a configuration object programmatically. I'm looking for a simple way to load the configuration by passing a properties file and the final class to map it to. The following is just an example of what I'm trying to achieve.
Directory structure of the configurations:
config/
- common.properties
all_tenants_config/
- foo_tenant/
- database.properties
- api.properties
- bar_tenant/
- database.properties
- api.properties
Configuration POJOs:
class DatabaseProperties {
#Value("${database.url}")
private String url;
}
class APIProperties {
#Value("${api.endPoint}")
private String endPoint;
}
Configuration Provider:
#Singleton
class ConfigurationProvider {
private Map<String, DatabaseProperties> DB_PROPERTIES = new HashMap<>();
private Map<String, APIProperties> API_PROPERTIES = new HashMap<>();
public ConfigurationProvider(#Value(${"tenantsConfigPath"}) String tenantsConfigPath) {
for (File tenant : Path.of(tenantsConfigPath).toFile().listFiles()) {
String tenantName = tenant.getName();
for (File configFile : tenant.listFiles()) {
String configName = configFile.getName();
if ("database.properties".equals(configName)) {
// This is what I'm looking for. An easy way to load the configuration by passing a properties file and the final class to map it to.
DB_PROPERTIES.put(tenant, SPRING_CONFIG_LOADER.load(configFile, DatabaseProperties.class));
} else if ("api.properties".equals(configName)) {
API_PROPERTIES.put(tenant, SPRING_CONFIG_LOADER.load(configFile, API.class));
}
}
}
}
public currentTenantDBProperties() {
return DB_PROPERTIES.get(CURRENT_TENANT_ID);
}
public currentTenantAPIProperties() {
return API_PROPERTIES.get(CURRENT_TENANT_ID);
}
}
In short, is there a way in Spring that allows to map a properties file to an object without using the default Spring's configuration annotations.
Well, in this case you do not need any Spring's feature.
Spring is a bean container, but in this place you just new an object by yourself and put it on your map cache.
Step 1: decode property file to Java Properties Class Object
Step 2: turn your properties object to your target object, just use some utils like objectmapper
FileReader reader = new FileReader("db.properties"); // replace file name with your variable
Properties p = new Properties();
p.load(reader);
ObjectMapper mapper = new ObjectMapper();
DatabaseProperties databaseProperties = mapper.convertValue(p,
DatabaseProperties.class);
I know how I can access the application.properties values in #Service classes in Java Spring boot like below
#Service
public class AmazonClient {
#Value("${cloud.aws.endpointUrl}")
private String endpointUrl;
}
But I am looking for an option to access this value directly in any class (a class without #Service annotation)
e.g.
public class AppUtils {
#Value("${cloud.aws.endpointUrl}")
private String endpointUrl;
}
But this returns null. Any help would be appreciated.
I have already read here but didn't help.
There's no "magic" way to inject values from a property file into a class that isn't a bean. You can define a static java.util.Properties field in the class, load values from the file manually when the class is loading and then work with this field:
public final class AppUtils {
private static final Properties properties;
static {
properties = new Properties();
try {
ClassLoader classLoader = AppUtils.class.getClassLoader();
InputStream applicationPropertiesStream = classLoader.getResourceAsStream("application.properties");
applicationProperties.load(applicationPropertiesStream);
} catch (Exception e) {
// process the exception
}
}
}
You can easily achievw this by annotating ur app utils class with #component annotation . spring will take care of loading properties.
But if you don't want to do that approach , then look at the link below .
https://www.baeldung.com/inject-properties-value-non-spring-class
I have a springboot commandline app where one of the production commandline args is the absolute base path. For this example we will call it
"/var/batch/"
I'm setting the basepath in my production.yml file like so with a default value.
company:
basePath: ${basePath:/var/default/}
I then have an ApplicationConfig.java file that uses that base path to create a bunch of file paths like so.
#ConfigurationProperties(prefix = "company")
public class ApplicationConfig {
private String basePath;
public String getPrimaryCarePath() {
return basePath + "ADAP-2-PCProv.dat";
}
public String getPrimaryCareDetailPath() {
return basePath + "ADAP-2-" + getBatchNo() + ".det";
}
... additional files.
}
Lastly the file paths get passed into my css parser like so.
public List<T> readCsv() throws IOException {
try (BufferedReader bufferedReader = Files.newBufferedReader(Paths.get(filePath))) {
return new CsvToBeanBuilder(bufferedReader)
.withFieldAsNull(CSVReaderNullFieldIndicator.EMPTY_SEPARATORS)
.withType(subClass)
.withSeparator('\t')
.withIgnoreLeadingWhiteSpace(true)
.build().parse();
}
}
Now everything works fine in production, but we face some issues when attempting to run mutation test. It appears as if the csv parser is looking for an absolute path rather than a relative path. We have the following path in our application-test.yml file.
company:
basePath: src/test/resources/
All our test files are stored in the test resources package, so my question is how can we use a relative path to the test resources populating the ApplicationConfig.java file while still being able to use an absolute path for production? I was thinking I could override the basepath with the test setup using ClassPathResource, but was wondering if there was a better approach.
You need 2 types of configurations : one for resources and one for absolute path.
I would suggest to add a new property app.file.path.type with values resources and absolute. You can define a new interface named FileProvider.
public interface FilePathProvider(){
Path getFilePath();
}
You can define 2 different beans with #ConditionalOnProperty and set the file path strategy:
#Configuration
public class ApplicationConfig{
#Bean
#ConditionalOnProperty(
name = "app.file.path.type",
havingValue = "absolute")
public FilePathProvider absoluteFilePathProvider(ApplicationConfig applicationConfig){
return () -> Paths.get(applicationConfig.getBasePath());
}
#ConditionalOnProperty(
name = "app.file.path.type",
havingValue = "resources")
#Bean
public FilePathProvider resourceFilePathProvider(ApplicationConfig applicationConfig){
return () -> Paths.get(this.getClass().getClassLoader().getResource(applicationConfig.getBasePath()).getPath());
}
}
In development and test mode, you will have app.file.path.type=resources and in production you will have app.file.path.type=absolute.
The advantage of this approach is that you can set the property to absolute in development as well.
My application expects to find a configuration file called MyPojo.json, loaded into MyPojo class by MyService class:
#Data // (Lombok's) getters and setters
public class MyPojo {
int foo = 42;
int bar = 1337;
}
It's not a problem if it doesn't exist: in that case, the application will create it with default values.
The path where to read/write MyPojo.json is stored in /src/main/resources/settings.properties:
the.path=cfg/MyPojo.json
which is passed to MyService through Spring's #PropertySource as follows:
#Configuration
#PropertySource("classpath:settings.properties")
public class MyService {
#Inject
Environment settings; // "src/main/resources/settings.properties"
#Bean
public MyPojo load() throws Exception {
MyPojo pojo = null;
// "cfg/MyPojo.json"
Path path = Paths.get(settings.getProperty("the.path"));
if (Files.exists(confFile)){
pojo = new ObjectMapper().readValue(path.toFile(), MyPojo.class);
} else { // JSON file is missing, I create it.
pojo = new MyPojo();
Files.createDirectory(path.getParent()); // create "cfg/"
new ObjectMapper().writeValue(path.toFile(), pojo); // create "cfg/MyPojo.json"
}
return pojo;
}
}
Since MyPojo's path is relative, when I run this from a Unit Test
#Test
public void testCanRunMockProcesses() {
try (AnnotationConfigApplicationContext ctx =
new AnnotationConfigApplicationContext(MyService.class)){
MyPojo pojo = ctx.getBean(MyPojo.class);
String foo = pojo.getFoo();
...
// do assertion
}
}
the cfg/MyPojo.json is created under the root of my project, which is definitely not what I want.
I would like MyPojo.json to be created under my target folder, eg. /build in Gradle projects, or /target in Maven projects.
To do that, I've created a secondary settings.properties under src/test/resources, containing
the.path=build/cfg/MyPojo.json
and tried to feed it to MyService in several ways, without success.
Even if called by the test case, MyService is always reading src/main/resources/settings.properties instead of src/test/resources/settings.properties.
With two log4j2.xml resources instead (src/main/resources/log4j2.xml and src/test/resources/log4j2-test.xml), it worked :/
Can I do the same with a property file injected by Spring with #PropertySource ?
You can use #TestPropertySource annotation for this.
Example:
For single property:
#TestPropertySource(properties = "property.name=value")
For property file
#TestPropertySource(
locations = "classpath:yourproperty.properties")
So, you provide path for MyPojo.json like
#TestPropertySource(properties = "path=build/cfg/MyPojo.json")
I am loading my properties file as following:
#Configuration
#PropertySource("classpath:app.properties")
class MyApp {
#Bean
public PropertySourcesPlaceholderConfigurer PropertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
In the properties file, I have several database-related properties:
database.dataSource.url=jdbc:postgresql://localhost:${db-port:5432}/mydb
database.dataSource.x=...
database.dataSource.y=...
database.dataSource.z=...
Note:
${db-port} should be replaced by either the value of property/environment variable db-port or 5432. In my case, I am defining the environment variable db-port when spawning the Tomcat container.
All database-related properties are grouped under database. root. This is intentional, see below.
I want to avoid that I have to enumerate/hardcode all possible database-related properties in my code. Luckily, the database layer in use (Hikari) has the nice feature that I can pass all properties via a java.util.Properties. So, I want retrieve all defined properties under database.* and simply forward it to Hikari.
For this, I wrote the following utility:
#Component
public class PropertyFetcher
{
#Autowired
private ConfigurableEnvironment environment;
public Properties get(final String key) {
final Properties p = new Properties();
for (final PropertySource<?> s : environment.getPropertySources()) {
if (s instanceof EnumerablePropertySource) {
for (final String k : ((EnumerablePropertySource) s).getPropertyNames()) {
if (k.startsWith(key) && k.length() > key.length()) {
p.put(k.substring(key.length()), s.getProperty(k));
}
}
}
}
return p;
}
}
Now, when calling get("database."), I have all database-related properties as defined in the properties file. Great! But, the value for property dataSource.url is now
jdbc:postgresql://localhost:${db-port:5432}/mydb
instead of
jdbc:postgresql://localhost:9876/mydb
So, for some reason, the ${db-port:5432} is not resolved (yet?) when going via this route (ConfigurableEnvironment).
How can this be fixed? Or is there a better way to get all properties under a certain root without having to enumerate/hardcode them into the code?
Please note that in the default scenario, the ${db-port:5432} in property database.dataSource.url=jdbc:postgresql://localhost:${db-port:5432}/mydb is correctly resolved. I tested this by defining the following member and logging it:
#Value("${database.dataSource.url}")
final String url; // holds jdbc:postgresql://localhost:9876/mydb
You should read the property values from real environment only. Then only you will get actual or effective value of a property.
This will require a little change in your code.
change this line:
p.put(k.substring(key.length()), s.getProperty(k));
to this:
p.put(k.substring(key.length()), environment.getProperty(k));