I want to group configuration items in Quarkus and use them as a Map. Something along the lines of the next application.properties:
greeting.names = tom,jane
greeting.tom.message = hello
greeting.tom.name = tom
greeting.jane.message = hi
greeting.jane.name = jane
And have them in the application in a Map, like this:
#ConfigProperties
public class GreetingConfig {
private String name;
private String message;
// getters / setters
}
And use them like this:
private Map<String, GreetingConfig> config;
They are now name indexed, but a List would also be fine (and is what I actually need):
greeting.1.message = hello
greeting.1.name = tom
greeting.2.message = hi
greeting.2.name = jane
Any ideas on how to realize this? I have a programmatic solution, but would prefer a solution by annotation only
I've had a similar problem these days.
So I wrote a simple Quarkus extension which helped me with the configuration.
You can use the guides from the Quarkus site: Quarkus - Writing Your Own Extension and Quarkus - Building my first extension,
but basically these are the steps taken in order to create the configuration:
Have some maven multi module project (not the project where the configuration will be consumed).
Execute a similar command form the project directory:
mvn io.quarkus:quarkus-maven-plugin:1.4.2.Final:create-extension -N \
-Dquarkus.artifactIdBase=keycloak-extension \
-Dquarkus.artifactIdPrefix=otaibe-commons-quarkus- \
-Dquarkus.nameBase="Keycloak extension"
This will create 'sub multi module' project there with the following modules: runtume and deployment.
Go to the runtime module and add and annotate your config class. It should be something similar to this class
In runtime module create a producer class which should register the configuration class as CDI bean:
#ApplicationScoped
public class OtaibeKeycloakQuarkusProducer {
private volatile OtaibeKeycloakConfig otaibeKeycloakConfig;
void initialize(OtaibeKeycloakConfig config) {
this.otaibeKeycloakConfig = config;
}
#Singleton
#Produces
public OtaibeKeycloakConfig otaibeKeycloakConfig() {
return otaibeKeycloakConfig;
}
}
Create a Recorder class which should initialize the Producer:
#Recorder
public class OtaibeKeycloakRecorder {
public void initOtaQuarkusProducer(BeanContainer container, OtaibeKeycloakConfig configuration) {
OtaibeKeycloakQuarkusProducer producer = container.instance(OtaibeKeycloakQuarkusProducer.class);
producer.initialize(configuration);
}
}
In deployment module you have a Processor class. Go there and register your Producer to be injectable as CDI bean and your Recorder to initialize it. Add the similar code:
#BuildStep
AdditionalBeanBuildItem beans() {
return AdditionalBeanBuildItem.builder().addBeanClasses(OtaibeKeycloakQuarkusProducer.class).build();
}
#BuildStep
#Record(ExecutionTime.RUNTIME_INIT)
void otaibeKeycloackConfigBuildItem(BeanContainerBuildItem beanContainer,
OtaibeKeycloakConfig otaibeKeycloakConfig,
OtaibeKeycloakRecorder recorder) {
recorder.initOtaQuarkusProducer(beanContainer.getValue(), otaibeKeycloakConfig);
}
You can find my implementation here.
Now, go to your initial project where the configuration will be consumed and add the runtime module as a dependency.
In order to ensure that the configuration is added properly execute the following maven command:
mvn quarkus:generate-config
Now, you can check the file src/main/resources/application.properties.example and verify whether your properties are added there. The property group should start with quarkus. plus the name of your #ConfigRoot annotation. In my case for example it will start with quarkus.otaibe.keycloak
That's it!
Now in Quarkus 2.9, you do it with #ConfigMapping (#ConfigProperties is deprecated).
#StaticInitSafe
#ConfigMapping
public interface GreetingConfigMapping {
Map<Integer, GreetingDetail> greeting();
}
interface GreetingDetail {
String name();
String message();
}
Try it in a #QuarkusTest; #Inject it.
Related
I am updating a web app from Spring Boot 2.2.4 to 2.5.6 and have used Open Rewrite to handle the tedium. The run configuration in IntelliJ has spring-boot:run -Dspring.profiles.active=local -f pom.xml in the command line (this has been working with 2.2.4). After adding a try/catch to main, to actually learn why I kept getting exit code 1 with no additional info, I found java.lang.IllegalArgumentException: Could not resolve placeholder 'spring.profiles.active' in value "classpath:application-${spring.profiles.active}.properties" to be the root issue.
Apparently passing local on the command line for spring.profiles.active is now ignored… So, I added it to application.yml which gives me a message about how it is invalid to use spring.profiles.active. I found that the currently active profile should now be in config.activate.on-profile.active so I switched to that and then did a project wide search for spring.profiles.active and replaced every single instance with config.activate.on-profile.active.
I ran the project again and again got the message that it could resolve the placeholder spring.profiles.active! I can't for the life of me figure out where that placeholder is coming from since it doesn't exist anywhere in the project now according to IntelliJ, not even in the ReadMe file!
Is there some secret place I need to look to purge this old placeholder from existence?
I ran into the same issue and found a link to a github issue where a workaround was provided.
Create a class:
public class JunitProfileResolver implements ActiveProfilesResolver {
private static final String PROFILE_JUNIT = "test";
#Override
public String[] resolve(Class<?> testClass) {
System.setProperty(AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME, PROFILE_JUNIT);
return new String[] { PROFILE_JUNIT };
}
}
And use it like:
#ActiveProfiles(profiles = "test", resolver = JunitProfileResolver.class)
public class MyTest {
For me this solves the problem.
I have an SPI interface defined in a maven module A. In Module B, which is a spring-boot application, I have defined META-INF/services/<interface-named-file>.
In Module A, I am having this code.
public class NotificationResultPluginProvider {
private static NotificationResultPluginProvider notificationResultPluginProvider;
private NotificationResultPlugin notificationResultPlugin;
private NotificationResultPluginProvider() {
final ServiceLoader<NotificationResultPlugin> loader = ServiceLoader.load(NotificationResultPlugin.class);
final Iterator<NotificationResultPlugin> it = loader.iterator();
if (it.hasNext()) {
notificationResultPlugin = it.next();
}
}
public static synchronized NotificationResultPluginProvider getInstance() {
if (null == notificationResultPluginProvider) {
notificationResultPluginProvider = new NotificationResultPluginProvider();
}
return notificationResultPluginProvider;
}
public NotificationResultPlugin getNotificationResultPlugin() {
return notificationResultPlugin;
}
}
In Module B - spring boot app - I have an implementation of NotificationResultPlugin interface.
Now here is the pickle.
When I run the boot app from intellij, I see that (in below code) it.hasNext() is true and notificationResultPlugin is found. In this case my application is working as expected.
if (it.hasNext()) {
notificationResultPlugin = it.next();
}
But when I run the boot app with CLI, using a command like below (note I am deflating the jar first and then launching)
jar -xf <jar file>;
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=12341 -cp 'BOOT-INF/lib/*:BOOT-INF/classes' <main/class/fqn>;
Then it.hasNext() is false and my implementation in Module B for given SPI is not found. As a result, my application is not working as expected. I am at wits end now.
Other relevant information:
My spring boot app, is not a web app. That is I am not exposing any rest end point. I am simply using spring-kafka with minimal dependencies.
What am I missing ? Any help is appreciated.
Found solution
As they say, mind works best when you are not trying.
ServiceLoader was not able to load the SPI implementation because META-INF/services directory was not in classpath while I am starting the application. Setting the class path correctly did the trick. Ergo, this is the change i needed to do.
-cp 'BOOT-INF/lib/*:BOOT-INF/classes:.'
Notice that extra dot at the end.
It seems to me that having annotations in different order breaks my build.
Does annotations order matter?
Answers from above say, in general, annotations order should not matter. In my case it is breaking.
This is module commons
#ConditionalOnProperty(value = "calculatorEnabled", havingValue = "true")
#Component
class Calculator {
// some logic
}
#ConditionalOnBean(Calculator.class)
#Service
class CalculationService {
private final Calculator calculator;
#Autowired
public CalculationService(final Calculator calculator) {
this.calculator = calculator;
}
// some logic
}
#RequestMapping(value = "/calculations", produces = MediaType.APPLICATION_JSON_VALUE)
#ConditionalOnBean(CalculationService.class)
#RestController
class CalculationController {
}
let there be another module - advanced-calculations
which has module commons as a dependency (maven dependency).
Please, note, there are two maven modules on purpose.
So CalculationController is available in other modules that use commons dependency.
Now, let me have two tests in advanced-calculations. (Again, I decided to test CalculationController) in another module.
I know that it is better to have tests in the module that actually defines a component, but commons module was written by other team long time ago; and for now we have to use it.
I want to make sure if we update version of commons, the app should not break (API should not change). Therefore, I added integration tests for CalculationContrioller into advanced-calculation module.
#SpringBootTest(classes = [AdvancedCalculationApplication.class],properties = ["calculatorEnabled=true" ])
#AutoConfigureMockMvc
AdvancedCalculationsITTest extends Specification {
}
and
#SpringBootTest(classes = [AdvancedCalculationApplication.class],properties = ["calculatorEnabled=" ])
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
#AutoConfigureMockMvc
AdvancedCalculationsITTestsDisabled extends Specification {
}
mvn clean install fails because AdvancedCalculationsITTest fails. Error cannot autowire CalculationController because there is no candidate calculationService.
However, when I change slightly order of annotations, it works
#ConditionalOnBean(CalculationService.class)
#RequestMapping(value = "/calculations", produces = MediaType.APPLICATION_JSON_VALUE)
#RestController
class CalculationController {
}
I will update this answer, but a bit later.
For me TODO.
(I will make a demo and add a link to github and put some code examples).
Your ideas and suggestions are welcome!
I have 2 custom annotations (RUNTIME) on a method: one annotation that makes a method always to throw an exception #Exceptional, another #Catchable that always catches an exception. Let this method return void for simplicity. By placing these annotations in different order, you should get a different result.
#Catchable
#Exceptional
public void processAction() {
// There is nothing that throws an exception.
safeStatement1();
safeStatement2();
safeStatementN();
}
vs
#Exceptional
#Catchable
public void processAction() {
// There is nothing that throws an exception.
safeStatement1();
safeStatement2();
safeStatementN();
}
By having these annotations in different order, the result should be different.
I have the following settings in my spring-boot project
application.properties:
file_path=${lookup_path}
lookup-file.file_1=${file_path}/file1.lku
lookup-file.file_2=${file_path}/file2.lku
and a corresponding config class LookupFileConfiguration, and also handler class to use this configuration:
#Component
#ConfigurationProperties("lookup-file")
public class LookupFileConfiguration {
private String file_1;
private String file_2;
// getter and setter methods skipped here
}
#Component
public class MyHandler {
#Autowired
private LookupFileConfiguration files;
}
I tried to run from command line:
mvn test -Drun.arguments=--lookup_path=C:\\my-proj\target\test-classes\lookupFiles\\
But it is not working. lookup_path seems not be used in the application.properties.
If I hard-code in application.properties file
file_path=C:\\my-proj\target\test-classes\lookupFiles\\
it is working fine.
I know I can use profiles for different environments. However, my project will run test cases in Bamboo plan. So I can only use bamboo variable ${bamboo_build_working_directory} but cannot predict the directory for test. Please help with a proper solution/suggestion for such case.
Using Spring Boot 1.3.0.RELEASE
I have a couple of yaml files that describe several instances of a program. I now want to parse all those files into a List<Program> (Map, whatever), so I can later on search for the most appropriate instance for a given criteria in all the programs.
I like the approach with #ConfigurationProperties a lot, and it works good enough for a single yaml-file, but I haven't found a way yet to read all files in a directory using that method.
Current approach working for a single file:
programs/program1.yml
name: Program 1
minDays: 4
maxDays: 6
can be read by
#Configuration
#ConfigurationProperties(locations = "classpath:programs/program1.yml", ignoreUnknownFields = false)
public class ProgramProperties {
private Program test; //Program is a POJO with all the fields in the yml.
//getters+setters
I tried changing the locations to an Array listing all of my files locations = {"classpath:programs/program1.yml", "classpath:programs/program2.yml"} as well as using locations = "classpath:programs/*.yml", but that still only loads the first file (array-approach) or nothing at all (wildcard-approach).
So, my question is, what is the best way in Spring Boot to load a bunch of yaml files in a classpath-directory and parse them into a (List of) POJO, so they can be autowired in a Controller? Do I need to use Snakeyaml directly, or is there an integrated mechanism that I just haven't found yet?
EDIT:
A working approach is doing it manually:
private static final Yaml yaml = new Yaml(new Constructor(Program.class));
private static final ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
try {
for (Resource resource : resolver.getResources("/programs/*.yml")) {
Object data = yaml.load(resource.getInputStream());
programList.add((Program) data);
}
}
catch (IOException ioe) {
logger.error("failed to load resource", ioe);
}
In Spring, it is possible to load multiple configuration properties files using PropertySource annotation, but not YAML files. See section 26.6.4 in link below:
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html#boot-features-external-config-typesafe-configuration-properties
However, from your problem, it seems that you can configure all your programs in single YAML and then get all list of programs in a single list.
Sample YAML (all.yaml)
programs:
- name: A
min: 1
max: 2
- name: B
min: 3
max: 4
Config.java
#Configuration
#ConfigurationProperties(locations={"classpath:all.yaml"})
public class Config{
private List<Program> programs;
public void setPrograms(List<Program> programs) {
this.programs = programs;
}
public List<Program> getPrograms() {
return programs;
}
}
What I am currently doing, as far as I understood your question, is nearly the same.
I am having an application.yml and also profile-specific yml files, e.g. application-{profile}.yml in my src/main/resources.
In the application.yml I have defined the default profile key-values, which are partially overridden by the profile-specific yml files.
If you want to have a type-safe and well defined access of your YML key/values, then you can use the following approach:
#ConfigurationProperties
public class AppSettings {
String name; // has to be the same as the key in your yml file
// setters/getters
}
In your Spring-Boot config, you have to add the following annotations onto your config class:
#ComponentScan
#EnableAutoConfiguration
#EnableConfigurationProperties( value = { AppSettings.class, SomeOtherSettings.class } )
public class SpringContextConfig {
#Autowired
private AppSettings appSettings;
public void test() {
System.out.println(appSettings.getName());
}
}
The #Autowiring is also accessible from other Beans.
The other way around (without an extra separated and type-safe class, is to access the YML-values via #Value("${name}").
To bring it together in a short manner:
Yes, it is possible to use several YAML files for your application via Spring-profiles. You define your current active spring profile via command args, programmatically or via your system env (SPRING_PROFILES_ACTIVE=name1,name2).
Therefore you can have several application.yml files for each profile (see above).