This may be a stupid questions, but is it possible to populate a list form an application.properties file in Spring Boot. Here is a simple example:
public class SomeClass {
#Value("${hermes.api.excluded.jwt}")
private List<String> excludePatterns = new ArrayList<>();
// getters/settings ....
}
application.properties
// Is something along these lines possible????
hermes.api.excluded.jwt[0]=/api/auth/
hermes.api.excluded.jwt[1]=/api/ss/
I know I could explode a comma separated string, but I was just curious if there is a native spring boot way to do this?
Turns out it does work. However, it seems you have to use configuration properties, since simple #Value("${prop}") seems to use a different path under the hood. (There are some hints to DataBinder in this secion. Not sure if related.)
application.properties
foo.bar[0]="a"
foo.bar[1]="b"
foo.bar[2]="c"
foo.bar[3]="d"
and in code
#Component
#ConfigurationProperties(prefix="foo")
public static class Config {
private final List<String> bar = new ArrayList<String>();
public List<String> getBar() {
return bar;
}
}
#Component
public static class Test1 {
#Autowired public Test1(Config config) {
System.out.println("######## #ConfigProps " + config.bar);
}
}
results in
######## #ConfigProps ["a", "b", "c", "d"]
While
#Component
public static class Test2 {
#Autowired public Test2(#Value("${foo.bar}") List<String> bar) {
System.out.println("######## #Value " + bar);
}
}
results in
java.lang.IllegalArgumentException: Could not resolve placeholder 'foo.bar' in string value "${foo.bar}"
at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(...
...
Related
I am testing my application but while running the test cases I am getting NUllPointer Exception as it's not able to map the value from YML file.
Can you please let me know how to achieve this ?
ControllerClass
class ControllerClass {
#Value("${app.items}")
String[] items; -- coming as null while running test cases
// remaing code
}
application-test.yml
app:
items: a, b, c, d
Test class
#SpringJUnitConfig
#SpringBootTest
#ActiveProfiles("test)
class TestControllerClass {
#InjectMock
ControllerClass controller;
#Mock
ServiceClass service;
#Test
//test case
}
Mockito doesn't know what to do - you can do it manually though:
#Before
public void setUp() {
String[] items = new String[2];
items[0] = "a";
items[1] = "b";
ReflectionTestUtils.setField(controller, "items",
items);
}
Naturally, we'll need a properties file to define the values we want to inject with the #Value annotation. And so, we'll first need to define a #PropertySource in our configuration class — with the properties file name.
#PropertySource("classpath:values.properties")
class ControllerClass {
#Value("${app.items}")
String[] items; -- coming as null while running test cases
// remaing code
}
if it is not working use . properties file like mentioned here.
For my Quarkus application I'm looking for a way to define a configuration map from within a custom ConfigProperties class. I tried the following:
import io.quarkus.arc.config.ConfigProperties;
import io.quarkus.runtime.annotations.ConfigItem;
#ConfigProperties(prefix = "my-properties")
public class MyPropertiesConfiguration {
#ConfigItem
public Map<String, FooConfiguration> foo;
// ...
}
import io.quarkus.runtime.annotations.ConfigGroup;
import io.quarkus.runtime.annotations.ConfigItem;
#ConfigGroup
public class FooConfiguration {
#ConfigItem
public String myProperty;
}
Given those two classes and the following application.properties file...
my-properties.foo.anystring.my-property=bar
on startup the application fails with error message:
javax.enterprise.inject.spi.DeploymentException: No config value of type [java.util.Map] exists for: my-properties.foo
As far as I understand https://quarkus.io/guides/writing-extensions#configuration-maps the sample should work. What am I doing wrong? Could it happen that this functionality is just limited to Quarkus extensions only?
As written in this Quarkus github issue, this is currently not supported.
My dirty workaround was to use the ConfigProvider directly. Use with care.
public static Map<String, String> getMapFromConfig(String prefix) {
final Config config = ConfigProvider.getConfig();
final Iterable<String> propertyNames = config.getPropertyNames();
return StreamSupport.stream(propertyNames.spliterator(), false)
.filter(name -> name.startsWith(prefix) && !name.equalsIgnoreCase(prefix))
.collect(
Collectors.toMap(
propertyName -> cleanupPropertyName(propertyName.substring(prefix.length() + 1)),
propertyName -> config.getOptionalValue(propertyName, String.class).orElse("")));
}
/** Remove start and end double quotes */
public static String cleanupPropertyName(String name) {
if (name.startsWith("\"") && name.endsWith("\"")) {
return name.substring(1, name.length() - 1);
}
return name;
}
My config looks like this:
property-templates:
"my.key": value 1
"my.second.key": value 2
Declare the configuration like this
import io.quarkus.arc.config.ConfigProperties;
#ConfigProperties(prefix = "myapp")
public class AppSpecificConfig {
public String property;
}
The application.properties file will contain
myapp.property=foo
And then you can #Inject an instance of this class anywhere within your application.
For more details, see https://quarkus.io/guides/config#using-configproperties
I would like to achieve autoconfiguration for a RestRepositoryResource (and some additional standard functionality) for an entity. I am trying to achieve it through an annotation on a #Configuration or #SpringBootApplication annotated class.
something like this:
#EnableRestRepo(single="foo", collection="foos",entity=Foo.class, id=String.class)
#SpringBootApplication
public class App{
public void main(String[] args){
SpringApplication.run(App.class,args);
}
}
#Entity
public class Foo{
String id;
String bar;
... getters & setters
}
this should then setup a (or functionality similar too, I'm fine with creating my own endpoints if need be) #RestRepositoryResource like this:
#RestRepositoryResource(itemResourceRel = "foo", collectionResourceRel = "foos")
public interface Repo extends CrudRepository<Foo,String> {
#RestResource(rel = "foo")
Foo findOneById(#Param("id") String id);
}
The goal here is to reduce some boiler plate on configuring some basic functionality. Obviously this example will be extended with some more autoconfiguration stuff, but that should work in a similar way.
The question is not so much about the RestRepositoryResource as it is about autoconfiguration with annotations which require arguments and generic type classes. I would not mind spending some time implementing this, however I have no idea where to start.
Is something like this even possible and if so, how?
Not sure if I understood you 100 %, but the example code here runs fine and creates beans runtime based on an annotation. Annotation also has som metadata.
The generic interface, will be proxied later:
public interface GenericRepository<T extends GenericType, Long> extends JpaRepository<GenericType, Long> {
}
Annotation to put on different entities:
#Target(ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#OverrideAutoConfiguration(enabled = false)
#ImportAutoConfiguration
#Import({RestResourceAutoConfiguration.class})
public #interface EnableRestRepo {
Class<?> entity();
String id();
}
A configuration class that can register beans in runtime:
#Configuration
#AutoConfigureAfter(value = WebMvcAutoConfiguration.class)
#ConditionalOnClass({CrudRepository.class})
public class RestResourceAutoConfiguration implements BeanDefinitionRegistryPostProcessor {
#Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
Reflections reflections = new Reflections("jav");
Set<Class<?>> annotated = reflections.getTypesAnnotatedWith(EnableRestRepo.class);
for (Class<?> c : annotated) {
EnableRestRepo declaredAnnotation = c.getDeclaredAnnotation(EnableRestRepo.class);
Class<?> entity = declaredAnnotation.entity();
String id = declaredAnnotation.id();
Supplier<GenericRepository> genericRepositorySupplier = () -> (GenericRepository) Proxy.newProxyInstance( // register a proxy of the generic type in spring context
c.getClassLoader(),
new Class[]{GenericRepository.class},
new MyInvocationHandler(entity));
beanDefinitionRegistry.registerBeanDefinition(id + "-" + UUID.randomUUID().toString(),
new RootBeanDefinition(GenericRepository.class, genericRepositorySupplier)
);
}
}
spring.factories under META-INF
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
jav.RestResourceAutoConfiguration
I'm using Spring Boot 1.4.3 #AutoConfiguration where I create beans automatically based on properties user specifies. User can specify an array of services, where name and version are required fields:
service[0].name=myServiceA
service[0].version=1.0
service[1].name=myServiceB
service[1].version=1.2
...
If the user forgets to specify a required field on even just one service, I want to back-off and not create any beans. Can I accomplish this with #ConditionalOnProperty? I want something like:
#Configuration
#ConditionalOnProperty({"service[i].name", "service[i].version"})
class AutoConfigureServices {
....
}
This is the custom Condition I created. It needs some polishing to be more generic (ie not hardcoding strings), but worked great for me.
To use, I annotated my Configuration class with #Conditional(RequiredRepeatablePropertiesCondition.class)
public class RequiredRepeatablePropertiesCondition extends SpringBootCondition {
private static final Logger LOGGER = LoggerFactory.getLogger(RequiredRepeatablePropertiesCondition.class.getName());
public static final String[] REQUIRED_KEYS = {
"my.services[i].version",
"my.services[i].name"
};
#Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
List<String> missingProperties = new ArrayList<>();
RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(context.getEnvironment());
Map<String, Object> services = resolver.getSubProperties("my.services");
if (services.size() == 0) {
missingProperties.addAll(Arrays.asList(REQUIRED_KEYS));
return getConditionOutcome(missingProperties);
}
//gather indexes to check: [0], [1], [3], etc
Pattern p = Pattern.compile("\\[(\\d+)\\]");
Set<String> uniqueIndexes = new HashSet<String>();
for (String key : services.keySet()) {
Matcher m = p.matcher(key);
if (m.find()) {
uniqueIndexes.add(m.group(1));
}
}
//loop each index and check required props
uniqueIndexes.forEach(index -> {
for (String genericKey : REQUIRED_KEYS) {
String multiServiceKey = genericKey.replace("[i]", "[" + index + "]");
if (!resolver.containsProperty(multiServiceKey)) {
missingProperties.add(multiServiceKey);
}
}
});
return getConditionOutcome(missingProperties);
}
private ConditionOutcome getConditionOutcome(List<String> missingProperties) {
if (missingProperties.isEmpty()) {
return ConditionOutcome.match(ConditionMessage.forCondition(RequiredRepeatablePropertiesCondition.class.getCanonicalName())
.found("property", "properties")
.items(Arrays.asList(REQUIRED_KEYS)));
}
return ConditionOutcome.noMatch(
ConditionMessage.forCondition(RequiredRepeatablePropertiesCondition.class.getCanonicalName())
.didNotFind("property", "properties")
.items(missingProperties)
);
}
}
Old question, but I hope my answer will help for Spring2.x:
Thanks to #Brian, I checked migration guide, where I was inspired by example code. This code works for me:
final List<String> services = Binder.get(context.getEnvironment()).bind("my.services", List.class).orElse(null);
I did try to get List of POJO (as AutoConfigureService) but my class differs from AutoConfigureServices. For that purpose, I used:
final Services services = Binder.get(context.getEnvironment()).bind("my.services", Services.class).orElse(null);
Well, keep playing :-D
Here's my take on this issue with the use of custom conditions in Spring autoconfiguration. Somewhat similar to what #Strumbels proposed but more reusable.
#Conditional annotations are executed very early in during the application startup. Properties sources are already loaded but ConfgurationProperties beans are not yet created. However we can work around that issue by binding properties to Java POJO ourselves.
First I introduce a functional interface which will enable us to define any custom logic checking if properties are in fact present or not. In your case this method will take care of checking if the property List is empty/null and if all items within are valid.
public interface OptionalProperties {
boolean isPresent();
}
Now let's create an annotation which will be metannotated with Spring #Conditional and allow us to define custom parameters. prefix represents the property namespace and targetClass represents the configuration properties model class to which properties should be mapped.
#Target({ElementType.TYPE, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Conditional(OnConfigurationPropertiesCondition.class)
public #interface ConditionalOnConfigurationProperties {
String prefix();
Class<? extends OptionalProperties> targetClass();
}
And now the main part. The custom condition implementation.
public class OnConfigurationPropertiesCondition extends SpringBootCondition {
#Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
MergedAnnotation<ConditionalOnConfigurationProperties> mergedAnnotation = metadata.getAnnotations().get(ConditionalOnConfigurationProperties.class);
String prefix = mergedAnnotation.getString("prefix");
Class<?> targetClass = mergedAnnotation.getClass("targetClass");
// type precondition
if (!OptionalProperties.class.isAssignableFrom(targetClass)) {
return ConditionOutcome.noMatch("Target type does not implement the OptionalProperties interface.");
}
// the crux of this solution, binding properties to Java POJO
Object bean = Binder.get(context.getEnvironment()).bind(prefix, targetClass).orElse(null);
// if properties are not present at all return no match
if (bean == null) {
return ConditionOutcome.noMatch("Binding properties to target type resulted in null value.");
}
OptionalProperties props = (OptionalProperties) bean;
// execute method from OptionalProperties interface
// to check if condition should be matched or not
// can include any custom logic using property values in a type safe manner
if (props.isPresent()) {
return ConditionOutcome.match();
} else {
return ConditionOutcome.noMatch("Properties are not present.");
}
}
}
Now you should create your own configuration properties class implementing OptionalProperties interface.
#ConfigurationProperties("your.property.prefix")
#ConstructorBinding
public class YourConfigurationProperties implements OptionalProperties {
// Service is your POJO representing the name and version subproperties
private final List<Service> services;
#Override
public boolean isPresent() {
return services != null && services.stream().all(Service::isValid);
}
}
And then in Spring #Configuration class.
#Configuration
#ConditionalOnConfigurationProperties(prefix = "", targetClass = YourConfigurationProperties.class)
class AutoConfigureServices {
....
}
There are two downsides to this solution:
Property prefix must be specified in two locations: on #ConfigurationProperties annotation and on #ConditionalOnConfigurationProperties annotation. This can partially be alleviated by defining a public static final String PREFIX = "namespace" in your configuration properties POJO.
Property binding process is executed separately for each use of our custom conditional annotation and then once again to create the configuration properties bean itself. It happens only during app startup so it shouldn't be an issue but it still is an inefficiency.
You can leverage the org.springframework.boot.autoconfigure.condition.OnPropertyListCondition class. For example, given you want to check for the service property having at least one value:
class MyListCondition extends OnPropertyListCondition {
MyListCondition() {
super("service", () -> ConditionMessage.forCondition("service"));
}
}
#Configuration
#Condition(MyListCondition.class)
class AutoConfigureServices {
}
See the org.springframework.boot.autoconfigure.webservices.OnWsdlLocationsCondition used on org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration#wsdlDefinitionBeanFactoryPostProcessor for an example within Spring itself.
Given the following simple spring boot example application:
#SpringBootApplication
public class Application {
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
}
and
#PropertySource(value="classpath:properties/tables/adres.properties")
#PropertySource(value="classpath:properties/tables/person.properties")
#Component
public class Sample {
#Value("${table.name}")
private String tableName;
#Value("${table.columns}")
private String[] columns;
#Value("${table.data.types}")
private String[] dataTypes;
#PostConstruct
public void init() {
// do something with values from properties...
for (String column : columns) {
System.out.println(column);
}
}
}
and the following example properties:
adres.properties:
table.name=adres
table.columns=personId,streetName,cityName
table.data.types=integer,string,string
person.properties:
table.name=person
table.columns=personId,firstName,lastName
table.data.types=integer,string,string
I want to
add all the properties files from a directory using one import instead of having to add each individual property as a #PropertySource
retreive the values for each individual property file and do something with them
I have tried the following:
1.
Using the wildcard * to get all properties from a directory ( http://docs.spring.io/spring/docs/current/spring-framework-reference/html/resources.html#resources-classpath-wildcards ) as follows:
#PropertySource(value="classpath*:properties/tables/*.properties")
throws java.io.FileNotFoundException: class path resource [properties/tables/*.properties] cannot be opened because it does not exist, so it looks like it is parsing the * as a literal value instead of a wildcard.
Adding a PropertySourcesPlaceholderConfigurer to the Application class as suggested in How to read multiple properties having the same keys in Spring?:
#Bean
public static PropertySourcesPlaceholderConfigurer properties() {
PropertySourcesPlaceholderConfigurer config = new PropertySourcesPlaceholderConfigurer();
config.setIgnoreUnresolvablePlaceholders(true);
return config;
}
Doesn't seem to work either because the java.io.FileNotFoundException gets thrown before the PropertySourcesPlaceholderConfigurer is loaded.
2.
Retreiving the values also prove difficult because each individual property uses the same keys. This is done for consistency and maintability of the property files. I have tried solving this by using some more placeholders:
table.name=person
{table.name}.columns=personId,firstName,lastName
{table.name}.data.types=integer,string,string
and in the Sample.class
#Value("${table.name}")
private String tableName;
#Value("${{table.name}.columns}")
private String[] columns;
#Value("${{table.name}.data.types}")
private String[] dataTypes;
The placeholders work, but I still have to manually add all the #PropertySource and can only get the #Value from the #PropertySource that was loaded last.
EDIT: the placeholders actually dont work. When using the the following syntax:
${table.name}.columns=personId,firstName,lastName and #Value("${${table.name}.columns}") the following exception occurs:
Could not resolve placeholder 'person.columns' in string value "${${table.name}.columns}"
Question
How do I solve my problem with regard to loading multiple property files and then retrieving the values from each individual property file in a java configuration style manner (but still using the same key names) ?
EDIT 2: Partial solution / Workaround
Managed to create a workaround solution with regard to the value clashing:
#PropertySource(value="classpath:properties/tables/tables.properties")
#PropertySource(value="classpath:properties/tables/person.properties")
#PropertySource(value="classpath:properties/tables/address.properties")
#Component
public class Sample {
private static final String COLUMNS = ".columns";
private static final String DATA_TYPES = ".data.types";
#Autowired
private Environment env;
#Value("${table.names}")
private String[] tableNames;
#PostConstruct
public void init() {
for (String tableName : tableNames) {
getTableValues(tableName);
}
}
private void getTableValues(String tableName) {
String col = env.getProperty(tableName + COLUMNS);
List<String> columns = Arrays.asList(col.split("\\s*,\\s*"));
for (String column : columns) {
System.out.println(String.format("The table %s contains the column %s", tableName, column));
}
String types = env.getProperty(tableName + DATA_TYPES);
List<String> dataTypes = Arrays.asList(types.split("\\s*,\\s*"));
// do more stuff
}
}