Skip to content

Commit e97eec9

Browse files
sam0r040timonback
andauthored
feat: default constructor of DefaultStandaloneFactory reads base packages from application properties (#1237)
* feat: default constructor of DefaultStandaloneFactory reads base packages from application properties Co-authored-by: Timon Back <[email protected]> * feat(core): improve usability of StandaloneApplication Co-authored-by: sam0r040 <[email protected]> * chore(core): minor improvements Co-authored-by: sam0r040 <[email protected]> --------- Co-authored-by: Timon Back <[email protected]>
1 parent 16c839c commit e97eec9

File tree

19 files changed

+266
-183
lines changed

19 files changed

+266
-183
lines changed

springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/scanners/classes/spring/annotations/ClassScannerUtil.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515

1616
@Slf4j
1717
public abstract class ClassScannerUtil {
18+
/**
19+
* Get classes from basePackages that match the filter
20+
* @param basePackages comma separated list of packages
21+
*/
1822
public static List<Class<?>> getClasses(String basePackages, TypeFilter filter, Environment environment) {
1923
ClassPathScanningCandidateComponentProvider provider =
2024
new ClassPathScanningCandidateComponentProvider(false, environment);
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
package io.github.springwolf.core.standalone;
3+
4+
import io.github.springwolf.core.asyncapi.AsyncApiService;
5+
import io.github.springwolf.core.configuration.properties.SpringwolfConfigConstants;
6+
import jakarta.annotation.Nullable;
7+
import lombok.NoArgsConstructor;
8+
import org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor;
9+
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
10+
import org.springframework.core.env.ConfigurableEnvironment;
11+
12+
import java.util.ArrayList;
13+
import java.util.List;
14+
15+
@NoArgsConstructor(access = lombok.AccessLevel.PRIVATE)
16+
public class DefaultStandaloneApplication implements StandaloneApplication {
17+
private final AnnotationConfigApplicationContext diContext = new AnnotationConfigApplicationContext();
18+
19+
/**
20+
* Create instance via {@link Builder#builder()}.
21+
*/
22+
private DefaultStandaloneApplication(ConfigurableEnvironment environment, List<Class<?>> standaloneConfigurations) {
23+
diContext.setEnvironment(environment);
24+
25+
standaloneConfigurations.forEach(diContext::register);
26+
// populate ConfigurationProperties beans
27+
ConfigurationPropertiesBindingPostProcessor.register(diContext);
28+
29+
diContext.refresh();
30+
this.verifyContextHasAllRequiredBeans();
31+
}
32+
33+
private void verifyContextHasAllRequiredBeans() {
34+
try {
35+
this.getAsyncApiService();
36+
} catch (Exception e) {
37+
throw new IllegalStateException("Missing required beans in Springwolf standalone context", e);
38+
}
39+
}
40+
41+
@Override
42+
public AsyncApiService getAsyncApiService() {
43+
return diContext.getBean(AsyncApiService.class);
44+
}
45+
46+
public static Builder builder() {
47+
return new Builder();
48+
}
49+
50+
/**
51+
* Create a standalone application context using spring mechanisms internally, while skipping Spring Boot auto-configurations.
52+
* <p>
53+
* By default, all Springwolf configuration classes annotated with {@link StandaloneConfiguration} will be registered in the context.
54+
* Use {@link StandaloneConfigurationDiscoverer#scan(String, ConfigurableEnvironment)} to discover configurations
55+
* <p>
56+
* Custom configuration classes or beans in custom packages can be added using {@link Builder#addScanPackage(String)},
57+
* for example to override beans or to add a {@link io.github.springwolf.core.asyncapi.AsyncApiCustomizer}
58+
*/
59+
@NoArgsConstructor(access = lombok.AccessLevel.PRIVATE)
60+
public static class Builder {
61+
62+
@Nullable
63+
private ConfigurableEnvironment environment;
64+
65+
private final List<String> scanPackages =
66+
new ArrayList<>(List.of(SpringwolfConfigConstants.SPRINGWOLF_PACKAGE));
67+
68+
public Builder setEnvironment(ConfigurableEnvironment environment) {
69+
this.environment = environment;
70+
return this;
71+
}
72+
73+
public Builder addScanPackage(String scanPackage) {
74+
return addScanPackages(List.of(scanPackage));
75+
}
76+
77+
public Builder addScanPackages(List<String> scanPackages) {
78+
this.scanPackages.addAll(scanPackages);
79+
return this;
80+
}
81+
82+
public Builder setScanPackages(List<String> scanPackages) {
83+
this.scanPackages.clear();
84+
this.scanPackages.addAll(scanPackages);
85+
return this;
86+
}
87+
88+
public DefaultStandaloneApplication buildAndStart() {
89+
ConfigurableEnvironment actualEnvironment =
90+
environment != null ? environment : StandaloneEnvironmentLoader.load();
91+
List<Class<?>> actualStandaloneConfigurations =
92+
StandaloneConfigurationDiscoverer.scan(String.join(",", scanPackages), actualEnvironment);
93+
94+
return new DefaultStandaloneApplication(actualEnvironment, actualStandaloneConfigurations);
95+
}
96+
}
97+
}

springwolf-core/src/main/java/io/github/springwolf/core/standalone/DefaultStandaloneFactory.java

Lines changed: 0 additions & 56 deletions
This file was deleted.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
package io.github.springwolf.core.standalone;
3+
4+
import io.github.springwolf.core.asyncapi.AsyncApiService;
5+
6+
/**
7+
* Standalone application instance to access {@link io.github.springwolf.asyncapi.v3.model.AsyncAPI}
8+
* without Spring runtime context.
9+
* <p>
10+
* Allows usage of Springwolf at build time, for example in unit test.
11+
*/
12+
public interface StandaloneApplication {
13+
AsyncApiService getAsyncApiService();
14+
}

springwolf-core/src/main/java/io/github/springwolf/core/standalone/StandaloneConfigurationDiscoverer.java

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,22 @@
22
package io.github.springwolf.core.standalone;
33

44
import io.github.springwolf.core.asyncapi.scanners.classes.spring.annotations.ClassScannerUtil;
5-
import io.github.springwolf.core.configuration.properties.SpringwolfConfigConstants;
65
import org.springframework.core.env.ConfigurableEnvironment;
76
import org.springframework.core.type.filter.AnnotationTypeFilter;
87
import org.springframework.core.type.filter.TypeFilter;
98

109
import java.util.List;
11-
import java.util.stream.Stream;
1210

1311
/**
1412
* Discovers classes annotated with {@link StandaloneConfiguration}.
1513
*/
1614
public class StandaloneConfigurationDiscoverer {
17-
public static List<Class<?>> scan(ConfigurableEnvironment environment) {
18-
return scan(SpringwolfConfigConstants.SPRINGWOLF_PACKAGE, environment);
19-
}
2015

21-
public static List<Class<?>> scan(String scanPackage, ConfigurableEnvironment environment) {
16+
/**
17+
* @param scanPackages comma separated list of packages
18+
*/
19+
public static List<Class<?>> scan(String scanPackages, ConfigurableEnvironment environment) {
2220
final TypeFilter filter = new AnnotationTypeFilter(StandaloneConfiguration.class);
23-
return ClassScannerUtil.getClasses(scanPackage, filter, environment);
24-
}
25-
26-
public static List<Class<?>> scanSpringwolfAnd(List<String> scanPackages, ConfigurableEnvironment environment) {
27-
return Stream.concat(
28-
scan(SpringwolfConfigConstants.SPRINGWOLF_PACKAGE, environment).stream(),
29-
scanPackages.stream().flatMap(scanPackage -> scan(scanPackage, environment).stream()))
30-
.toList();
21+
return ClassScannerUtil.getClasses(scanPackages, filter, environment);
3122
}
3223
}

springwolf-core/src/main/java/io/github/springwolf/core/standalone/StandaloneEnvironmentLoader.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public static ConfigurableEnvironment load(List<String> profiles) {
2121
environment.setActiveProfiles(profiles.toArray(new String[0]));
2222
}
2323

24+
// Load properties from application.properties
2425
ConfigDataEnvironmentPostProcessor.applyTo(environment);
2526

2627
return environment;

springwolf-core/src/main/java/io/github/springwolf/core/standalone/StandaloneFactory.java

Lines changed: 0 additions & 14 deletions
This file was deleted.

springwolf-core/src/test/java/io/github/springwolf/core/integrationtests/AsyncApiDocumentIntegrationTest.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
import io.github.springwolf.core.integrationtests.application.polymorphic.PolymorphicPayloadApplication;
1717
import io.github.springwolf.core.integrationtests.application.publisher.PublisherApplication;
1818
import io.github.springwolf.core.integrationtests.application.schema.SchemaEnumAsRefApplication;
19-
import io.github.springwolf.core.standalone.DefaultStandaloneFactory;
19+
import io.github.springwolf.core.standalone.DefaultStandaloneApplication;
2020
import org.junit.jupiter.api.Nested;
2121
import org.junit.jupiter.api.Test;
2222
import org.springframework.beans.factory.annotation.Autowired;
@@ -406,6 +406,10 @@ void shouldFindAllForGroupAll() {
406406
}
407407

408408
private AsyncApiService createStandaloneAsyncApiService(ConfigurableEnvironment environment, String basePackage) {
409-
return new DefaultStandaloneFactory(basePackage, environment).getAsyncApiService();
409+
DefaultStandaloneApplication standaloneApplication = DefaultStandaloneApplication.builder()
410+
.addScanPackage(basePackage)
411+
.setEnvironment(environment)
412+
.buildAndStart();
413+
return standaloneApplication.getAsyncApiService();
410414
}
411415
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
package io.github.springwolf.core.standalone;
3+
4+
import io.github.springwolf.core.integrationtests.application.basic.TestApplication;
5+
import org.junit.jupiter.api.Test;
6+
import org.springframework.core.env.ConfigurableEnvironment;
7+
8+
import java.util.List;
9+
10+
import static org.assertj.core.api.Assertions.assertThat;
11+
import static org.assertj.core.api.Fail.fail;
12+
13+
class DefaultStandaloneApplicationTest {
14+
15+
@Test
16+
void shouldCreateStandaloneFactoryForBasicApplicationWithBasePackeFromProperties() {
17+
// when
18+
DefaultStandaloneApplication standaloneApplication =
19+
DefaultStandaloneApplication.builder().buildAndStart();
20+
21+
// then
22+
assertThat(standaloneApplication.getAsyncApiService().getAsyncAPI()).isNotNull();
23+
assertThat(standaloneApplication
24+
.getAsyncApiService()
25+
.getAsyncAPI()
26+
.getInfo()
27+
.getTitle())
28+
.isEqualTo("Springwolf-core");
29+
}
30+
31+
@Test
32+
void shouldCreateStandaloneFactoryForBasicApplication() {
33+
// when
34+
DefaultStandaloneApplication standaloneApplication = DefaultStandaloneApplication.builder()
35+
.addScanPackage(TestApplication.class.getPackageName())
36+
.buildAndStart();
37+
38+
// then
39+
assertThat(standaloneApplication.getAsyncApiService().getAsyncAPI()).isNotNull();
40+
assertThat(standaloneApplication
41+
.getAsyncApiService()
42+
.getAsyncAPI()
43+
.getInfo()
44+
.getTitle())
45+
.isEqualTo("Springwolf-core");
46+
}
47+
48+
@Test
49+
void shouldCreateStandaloneFactoryForBasicApplicationWithProfile() {
50+
// given
51+
ConfigurableEnvironment environment = StandaloneEnvironmentLoader.load(List.of("standalone"));
52+
53+
// when
54+
DefaultStandaloneApplication standaloneApplication = DefaultStandaloneApplication.builder()
55+
.setEnvironment(environment)
56+
.buildAndStart();
57+
58+
// then
59+
assertThat(standaloneApplication.getAsyncApiService().getAsyncAPI()).isNotNull();
60+
assertThat(standaloneApplication
61+
.getAsyncApiService()
62+
.getAsyncAPI()
63+
.getInfo()
64+
.getTitle())
65+
.isEqualTo("Springwolf-core-properties");
66+
}
67+
68+
@Test
69+
void shouldThrowWhenConfigurationsAreMissing() {
70+
// when
71+
List<String> scanPackages = List.of("non.existing.package");
72+
73+
try {
74+
DefaultStandaloneApplication.builder().setScanPackages(scanPackages).buildAndStart();
75+
fail();
76+
} catch (Exception e) {
77+
assertThat(e).isInstanceOf(IllegalStateException.class);
78+
}
79+
}
80+
81+
@Test
82+
void shouldThrowWhenNoBasePackageConfigured() {
83+
// when
84+
DefaultStandaloneApplication standaloneApplication =
85+
DefaultStandaloneApplication.builder().buildAndStart();
86+
87+
// then
88+
assertThat(standaloneApplication.getAsyncApiService().getAsyncAPI()).isNotNull();
89+
assertThat(standaloneApplication
90+
.getAsyncApiService()
91+
.getAsyncAPI()
92+
.getInfo()
93+
.getTitle())
94+
.isEqualTo("Springwolf-core");
95+
}
96+
}

0 commit comments

Comments
 (0)