Spring Framework: setDefaultProfiles method does not work as expected as the "-Dspring.profiles.default" property - spring

For Spring Framework 6 having some #Configuration classes with the #Profile("default") and #Profile("prod") respectively. Exists the following scenarios:
Scenario I
ctx = new AnnotationConfigApplicationContext("com.manuel.jordan.config");
When is executed the Main class with the -Dspring.profiles.default=default,prod property, through:
System.out.println("Default Profiles");
for(String defaultProfile : ctx.getEnvironment().getDefaultProfiles()) {
System.out.println(" " + defaultProfile);
}
I can see both profiles being listed and is possible retrieve the #Bean from these #Configuration classes based with the #Profile("default") and #Profile("prod") profiles. Until here all work as expected. Therefore is confirmed that these two default profiles were applied to the Spring Application Context for the beans creation process.
Scenario II
With
ctx = new AnnotationConfigApplicationContext("com.manuel.jordan.config");
ctx.getEnvironment().setDefaultProfiles("default", "prod");
or even with
ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setDefaultProfiles("default", "prod");
ctx.scan("com.manuel.jordan.config");
ctx.refresh();
When is executed the Main class without the -Dspring.profiles.default=default,prod property, because now is used .setDefaultProfiles("default", "prod") instead, through:
System.out.println("Default Profiles");
for(String defaultProfile : ctx.getEnvironment().getDefaultProfiles()) {
System.out.println(" " + defaultProfile);
}
I can see both profiles being listed but is not possible retrieve the #Bean from these #Configuration classes, again, based with the #Profile("default") and #Profile("prod") profiles - both profiles were ignored because each one throws the NoSuchBeanDefinitionException type. Why here is failing?
Question
Why did -Dspring.profiles.default=default,prod work and .setDefaultProfiles("default", "prod") did not work?

Related

Spring Framework: Why the active profiles declared programmatically are working if the "refresh" method was not executed?

About Spring Framework 3.x and 4.x (nothing about Spring Boot ) if my memory does not fail me, I remember when the active profiles are declared programmatically then the refresh method must be called, it after of the profiles declaration, otherwise the profiles are not reflected when Spring is running its own Application Context.
It is reflected at:
How to set the active profile when creating Spring context programmatically?
Code from the shared answer
final AnnotationConfigApplicationContext appContext = new AnnotationConfigApplicationContext();
appContext.getEnvironment().setActiveProfiles( "myProfile" );
appContext.register( com.initech.ReleaserConfig.class );
appContext.refresh(); <--- must be executed after of the profiles declaration
Now with Spring Framework 6 for academic purposes I tried the following:
ConfigurableApplicationContext ctx =
new AnnotationConfigApplicationContext("com.manuel.jordan.config");
ctx.getEnvironment().setDefaultProfiles("default", "dev");
ctx.getEnvironment().setActiveProfiles("jdbc", "mysql");
As you can see the refresh method was not executed, it to demonstrate that the active profiles were ignored. But it works, through the following code it was confirmed
for(String activeProfile : ctx.getEnvironment().getActiveProfiles()) {
System.out.println(" " + activeProfile);
}
and even more, the constructor code with the String parameter of the AnnotationConfigApplicationContext is:
/**
* Create a new AnnotationConfigApplicationContext, scanning for components
* in the given packages, registering bean definitions for those components,
* and automatically refreshing the context.
* #param basePackages the packages to scan for component classes
*/
public AnnotationConfigApplicationContext(String... basePackages) {
this();
scan(basePackages);
refresh(); <--------------
}
As you can see the refresh method is executed but remember that this constructor execution happens before of the setActiveProfiles execution.
Therefore
Question
Why the active profiles declared programmatically are working if the refresh method was not executed?
Even more, like a simple plus, if is executed as:
ConfigurableApplicationContext ctx =
new AnnotationConfigApplicationContext("com.manuel.jordan.config");
ctx.getEnvironment().setDefaultProfiles("default", "dev");
ctx.getEnvironment().setActiveProfiles("jdbc", "mysql");
ctx.refresh(); <---
Arises
java.lang.IllegalStateException: GenericApplicationContext does not support multiple refresh attempts: just call 'refresh' once
at org.springframework.context.support.GenericApplicationContext.refreshBeanFactory (GenericApplicationContext.java:293)
at org.springframework.context.support.AbstractApplicationContext.obtainFreshBeanFactory (AbstractApplicationContext.java:672)
at org.springframework.context.support.AbstractApplicationContext.refresh (AbstractApplicationContext.java:554)
at com.manuel.jordan.main.Main.main (Main.java:28)
at org.codehaus.mojo.exec.ExecJavaMojo$1.run (ExecJavaMojo.java:279)
at java.lang.Thread.run (Thread.java:833)

Howto inject Picocli parsed parameters into Spring Bean definitions?

I'm trying to use Picocli with Spring Boot 2.2 to pass command line parameters to a Spring Bean, but not sure how to structure this. For example, I have the following #Command to specify a connection username and password from the command line, however, want to use those params to define a Bean:
#Component
#CommandLine.Command
public class ClearJdoCommand extends HelpAwarePicocliCommand {
#CommandLine.Option(names={"-u", "--username"}, description = "Username to connect to MQ")
String username;
#CommandLine.Option(names={"-p", "--password"}, description = "Password to connect to MQ")
String password;
#Autowired
JMSMessagePublisherBean jmsMessagePublisher;
#Override
public void run() {
super.run();
jmsMessagePublisher.publishMessage( "Test Message");
}
}
#Configuration
public class Config {
#Bean
public InitialContext getJndiContext() throws NamingException {
// Set up the namingContext for the JNDI lookup
final Properties env = new Properties();
env.put(Context.INITIAL_CONTEXT_FACTORY, INITIAL_CONTEXT_FACTORY);
env.put(Context.PROVIDER_URL, "http-remoting://localhost:8080");
env.put(Context.SECURITY_PRINCIPAL, username);
env.put(Context.SECURITY_CREDENTIALS, password);
return new InitialContext(env);
}
#Bean
public JMSPublisherBean getJmsPublisher(InitialContext ctx){
return new JMSPublisherBean(ctx);
}
}
I'm stuck in a bit of a circular loop here. I need the command-line username/password to instantiate my JMSPublisherBean, but these are only available at runtime and not available at startup.
I have managed to get around the issue by using Lazy intialization, injecting the ClearJdoCommand bean into the Configuration bean and retrieving the JMSPublisherBean in my run() from the Spring context, but that seems like an ugly hack. Additionally, it forces all my beans to be Lazy, which is not my preference.
Is there another/better approach to accomplish this?
Second option might be to use pure PicoCli (not PicoCli spring boot starter) and let it run command; command will not be Spring bean and will only be used to validate parameters.
In its call method, Command would create SpringApplication, populate it with properties (via setDefaultProperties or using JVM System.setProperty - difference is that environment variables will overwrite default properties while system properties have higher priority).
#Override
public Integer call() {
var application = new SpringApplication(MySpringConfiguration.class);
application.setBannerMode(Mode.OFF);
System.setProperty("my.property.first", propertyFirst);
System.setProperty("my.property.second", propertySecond);
try (var context = application.run()) {
var myBean = context.getBean(MyBean.class);
myBean.run(propertyThird);
}
return 0;
}
This way, PicoCli will validate input, provide help etc. but you can control configuration of Spring Boot application. You can even use different Spring configurations for different commands. I believe this approach is more natural then passing all properties to CommandLineRunner in Spring container
One idea that may be useful is to parse the command line in 2 passes:
the first pass is just to pick up the information needed for configuration/initialization
in the second pass we pick up additional options and execute the application
To implement this, I would create a separate class that "duplicates" the options that are needed for configuration. This class would have an #Unmatched field for the remaining args, so they are ignored by picocli. For example:
class Security {
#Option(names={"-u", "--username"})
static String username;
#Option(names={"-p", "--password"}, interactive = true, arity = "0..1")
static String password;
#Unmatched List<String> ignored;
}
In the first pass, we just want to extract the username/password info, we don't want to execute the application just yet. We can use the CommandLine.parseArgs or CommandLine.populateCommand methods for that.
So, our main method can look something like this:
public static void main(String[] args) throws Exception {
// use either populateCommand or parseArgs
Security security = CommandLine.populateCommand(new Security(), args);
if (security.username == null || security.password == null) {
System.err.println("Missing required user name or password");
new CommandLine(new ClearJdoCommand()).usage(System.err);
System.exit(CommandLine.ExitCode.USAGE);
}
// remainder of your normal main method here, something like this?
System.exit(SpringApplication.exit(SpringApplication.run(MySpringApp.class, args)));
}
I would still keep (duplicate) the usage and password options in the ClearJdoCommand class, so the application can print a nice usage help message when needed.
Note that I made the fields in the Security class static.
This is a workaround (hack?) that allows us to pass information to the getJndiContext method.
#Bean
public InitialContext getJndiContext() throws NamingException {
// Set up the namingContext for the JNDI lookup
final Properties env = new Properties();
env.put(Context.INITIAL_CONTEXT_FACTORY, INITIAL_CONTEXT_FACTORY);
env.put(Context.PROVIDER_URL, "http-remoting://localhost:8080");
env.put(Context.SECURITY_PRINCIPAL, Security.username); // use info from 1st pass
env.put(Context.SECURITY_CREDENTIALS, Security.password);
return new InitialContext(env);
}
There is probably a better way to pass information to this method.
Any Spring experts willing to jump in and show us a nicer alternative?

Problem with connection to Neo4j test container using Spring boot 2 and JUnit5

Problem with connection to Neo4j test container using Spring boot 2 and JUnit5
int test context. Container started successfully but spring.data.neo4j.uri property has a wrong default port:7687, I guess this URI must be the same when I call neo4jContainer.getBoltUrl().
Everything works fine in this case:
#Testcontainers
public class ExampleTest {
#Container
private static Neo4jContainer neo4jContainer = new Neo4jContainer()
.withAdminPassword(null); // Disable password
#Test
void testSomethingUsingBolt() {
// Retrieve the Bolt URL from the container
String boltUrl = neo4jContainer.getBoltUrl();
try (
Driver driver = GraphDatabase.driver(boltUrl, AuthTokens.none());
Session session = driver.session()
) {
long one = session.run("RETURN 1",
Collections.emptyMap()).next().get(0).asLong();
assertThat(one, is(1L));
} catch (Exception e) {
fail(e.getMessage());
}
}
}
But SessionFactory is not created for the application using autoconfiguration following to these recommendations - https://www.testcontainers.org/modules/databases/neo4j/
When I try to create own primary bean - SessionFactory in test context I get the message like this - "URI cannot be returned before the container is not loaded"
But Application runs and works perfect using autoconfiguration and neo4j started in a container, the same cannot be told about the test context
You cannot rely 100% on Spring Boot's auto configuration (for production) in this case because it will read the application.properties or use the default values for the connection.
To achieve what you want to, the key part is to create a custom (Neo4j-OGM) Configuration bean. The #DataNeo4jTest annotation is provided by the spring-boot-test-autoconfigure module.
#Testcontainers
#DataNeo4jTest
public class TestClass {
#TestConfiguration
static class Config {
#Bean
public org.neo4j.ogm.config.Configuration configuration() {
return new Configuration.Builder()
.uri(databaseServer.getBoltUrl())
.credentials("neo4j", databaseServer.getAdminPassword())
.build();
}
}
// your tests
}
For a broader explanation have a look at this blog post. Esp. the section Using with Neo4j-OGM and SDN.

Unable to generate the Spring rest docs using Cucumber

I am trying to test spring rest documentation for rest API for our services using spring cucumber jvm but end up with a null pointer exeception when I try to execute the scenario, as the framework is not able to intialize the Junit context.
Error Message:
java.lang.NullPointerException at
org.springframework.restdocs.ManualRestDocumentation.beforeO‌​peration(ManualRestD‌​ocumentation.java:90‌​) at
org.springframework.restdocs.JUnitRestDocumentation.beforeOp‌​eration(JUnitRestDoc‌​umentation.java:76)
Code:
private AppProperties props;
#Before("#rest") public void beforeScenario() {
JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation( "target/generated-snippets" );
System.out.println( "jUnitRestDocumentation " +restDocumentation );
spec = new RequestSpecBuilder().addFilter( documentationConfiguration( restDocumentation ) ).build();
System.out.println( "\n spec init .. " +restDocumentation );
}
Step definition code:
#Given("^create a rest document for VHR API$")
public void create_a_rest_document_for_VHR_API() throws Throwable {
estAssured.given( spec )
.accept( "application/json" )
.filter( document( "vhrdocument" ) ) .when()
.get( props.getVhrrequesturl() + "/vhrData/{vehicleID}", "5VW4T7AU0FM029999" ) .then().log().all();
}
You aren't using JUnitRestDocumentation as it's intended to be used. It's designed to be used as a JUnit rule which means it should be a public field annotated with #Rule:
#Rule
public JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation();
Being a rule means that JUnit will automatically call restDocumentation for each test, allowing Spring REST Docs to set up and tear down the test-specific context. The NullPointerException is occurring because restDocumentation hasn't been called in this way and, therefore, the context hasn't been set up.
You haven't described how you're using Cucumber, but if you're using it's JUnit runner you should be able to fix the problem by declaring restDocumentation as a #Rule-annotated field as shown above. If you're not using its JUnit runner, you may need to use Spring REST Docs' ManualRestDocumentation instead. The Spring REST Docs reference documentation contains a section that describes how to set up your tests when you're not using JUnit.
I had the same problem because I had multiple test class inheriting the class, in which I declared the JUnitRestDocumentation instance. My mistake was that I declared the rule using the #Rule annotation. I should have used #ClassRule and declared the instance as static.
#ClassRule
public static JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation();
it happened with test SpockFramework, and i added to pom.xml:
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-junit4</artifactId>
<scope>test</scope>
</dependency>
I had the same symptoms when migrating from RestAssured 2.x to RestAssured 3.1.1.
The codebase had a way to setup RestAssured in order to avoid repetitive ceremony for every tests :
#Rule
public JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation();
#Before
public void configure_rest_assured() {
RestAssured.port = springServerPort;
RestAssured.config = config().objectMapperConfig(
objectMapperConfig().jackson2ObjectMapperFactory((cls, charset) -> customObjectMapper)
)
...;
RestAssured.requestSpecification = new RequestSpecBuilder()
.addRequestSpecification(documentationConfiguration(docRule, ...))
...
.build();
}
This was working well, until I migrated to 3.x. The issue was that new RequestSpecBuilder() will append itself to the default static RestAssured.requestSpecification.
The first test passed, but when it finished the rule was disposed (the after part), when the second test started to ran, the Before method was chaining
the specification created for the first test (referencing the disposed rule used by the first test method)
the specification created for the second test (referencing the active rule for second test method)
And so on as new tests are ran.
But when the second test is run RestAssured invoke specification in order, e.g. the number 1, but since it was referencing a disposed rule (the beforeOperation was executed on a null context)
To fix that the code had to clear the previous specifications :
#Before
public void configure_rest_assured() {
RestAssured.port = springServerPort;
RestAssured.config = config().objectMapperConfig(
objectMapperConfig().jackson2ObjectMapperFactory((cls, charset) -> customObjectMapper)
)
...;
RestAssured.requestSpecification = null; // avoid the builder to acquire previous specs.
RestAssured.requestSpecification = new RequestSpecBuilder()
.addRequestSpecification(documentationConfiguration(docRule, ...))
...
.build();
}
For using cucumber-java-8 with spring rest docs and spring-security the following worked for me.
This is combining #AndyWilkison's answer from above but using the cucumber hooks instead of junit rules.
public class StepDefs implements En {
#Autowired
private WebApplicationContext context;
private MockMvc mockMvc;
private ManualRestDocumentation restDocumentation = new ManualRestDocumentation();
public StepDefs() {
BeforeStep((Scenario scenario) -> {
restDocumentation.beforeTest(AuthenticationStepDefs.class, scenario.getName());
mockMvc = MockMvcBuilders.webAppContextSetup(context).apply(springSecurity()).apply(documentationConfiguration(restDocumentation)).build();
});
AfterStep((Scenario scenario) -> {
restDocumentation.afterTest();
});
When("create a rest document for VHR API", () -> {
MvcResult result = mockMvc.perform(/*
your normal call here
*/).
.andDo(document("documentation")).
.andReturn();
}
}
}

What is the best alternative for #ConfigurationProperties locations?

#ConfigurationProperties locations is deprecated in Spring Boot 1.4.x and option is now removed in 1.5.x
I was using it like this: BucketTestConfig.java
For now with deprecation, I'm trying to set the system property spring.config.location for both production code and test code as an alternative.
./gradlew clean test is still failing although I set the system property.
What is the best alternative for deprecated #ConfigurationProperties locations in this case?
UPDATE:
Using SpringApplicationBuilder.properties() doesn't work in the test (BucketTestRepositoryTests).
Using SpringApplicationBuilder.listeners() doesn't work in the test (BucketTestRepositoryTests), either.
UPDATE (2nd):
There was no reason to depend on #ConfigurationProperties in my case, so I went with Yaml instead as follows: https://github.com/izeye/spring-boot-throwaway-branches/commit/a1290672dceea98706b1a258f8a17e2628ea01ee
So this question's title is invalid and this question can be deleted.
Follow this thread for more information.
Basically, this thread suggests two options
First option is to set spring.config.name to a list of the files you want to load:
new SpringApplicationBuilder(Application.class)
.properties("spring.config.name=application,mine")
.run(args);
Second options is to add listeners
new SpringApplicationBuilder(SanityCheckApplication.class)
.listeners(new LoadAdditionalProperties())
.run(args);
Content of LoadAdditionalProperties
#Component
public class LoadAdditionalProperties implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
private ResourceLoader loader = new DefaultResourceLoader();
#Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
try {
Resource resource = loader.getResource("classpath:mine.properties");
PropertySource<?> propertySource = new PropertySourcesLoader().load(resource);
event.getEnvironment().getPropertySources().addLast(propertySource);
} catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
}

Resources