Skip to content

Commit 15b83df

Browse files
authored
Merge pull request #605 from Backbase/bugfix/issues/604
Issue 604: containerDefaultToNull set to false by default
2 parents d5e80d4 + db32e1a commit 15b83df

File tree

5 files changed

+179
-46
lines changed

5 files changed

+179
-46
lines changed

boat-maven-plugin/README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,20 @@ Same with `generate` but with opinionated defaults for Spring
4343
<useOptional>false</useOptional>
4444
<apiPackage>com.backbase.product.api.service.v2</apiPackage>
4545
<modelPackage>com.backbase.product.api.service.v2.model</modelPackage>
46+
<useJakartaEe>true</useJakartaEe>
47+
<useSpringBoot3>true</useSpringBoot3>
48+
<containerDefaultToNull>false</containerDefaultToNull>
49+
</configOptions>
50+
</configuration>
51+
52+
... explicit `configOptions` override default ones, e.g. in sample below `containerDefaultToNull` overrides default (i.e. `false`) with `true`
53+
54+
<configuration>
55+
<inputSpec>${project.basedir}/../api/product-service-api/src/main/resources/openapi.yaml</inputSpec>
56+
<apiPackage>com.backbase.product.api.service.v2</apiPackage>
57+
<modelPackage>com.backbase.product.api.service.v2.model</modelPackage>
58+
<configOptions>
59+
<containerDefaultToNull>true</containerDefaultToNull>
4660
</configOptions>
4761
</configuration>
4862

boat-maven-plugin/src/main/java/com/backbase/oss/boat/AbstractGenerateMojo.java

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@
22

33
import java.util.HashMap;
44
import java.util.Map;
5+
import lombok.extern.slf4j.Slf4j;
56
import org.apache.maven.plugin.MojoExecutionException;
67
import org.apache.maven.plugin.MojoFailureException;
78

9+
@Slf4j
810
public abstract class AbstractGenerateMojo extends GenerateMojo {
911

10-
public void execute(String generatorName, String library, boolean isEmbedded, boolean reactive, boolean generateSupportingFiles)
11-
throws MojoExecutionException, MojoFailureException {
12+
public void execute(String generatorName, String library, boolean isEmbedded, boolean reactive,
13+
boolean generateSupportingFiles) throws MojoExecutionException, MojoFailureException {
14+
1215
Map<String, String> options = new HashMap<>();
1316
options.put("library", library);
1417
options.put("java8", "true");
@@ -23,7 +26,7 @@ public void execute(String generatorName, String library, boolean isEmbedded, bo
2326
options.put("useOptional", "false");
2427
options.put("useJakartaEe", "true");
2528
options.put("useSpringBoot3", "true");
26-
options.put("containerDefaultToNull", "true");
29+
options.put("containerDefaultToNull", "false");
2730

2831
this.generatorName = generatorName;
2932
this.generateSupportingFiles = generateSupportingFiles;
@@ -32,12 +35,26 @@ public void execute(String generatorName, String library, boolean isEmbedded, bo
3235
this.generateModelDocumentation = !isEmbedded;
3336
this.generateModelTests = !isEmbedded;
3437
this.skipOverwrite = true;
35-
this.configOptions = options;
38+
if (this.configOptions == null) {
39+
this.configOptions = options;
40+
} else {
41+
this.configOptions = mergeOptions(options, this.configOptions);
42+
}
43+
log.debug("Using configOptions={}", this.configOptions);
3644

37-
if(isEmbedded) {
38-
this.supportingFilesToGenerate = "ApiClient.java,BeanValidationException.java,RFC3339DateFormat.java,ServerConfiguration.java,ServerVariable.java,StringUtil.java,Authentication.java,HttpBasicAuth.java,HttpBearerAuth.java,ApiKeyAuth.java,JavaTimeFormatter.java";
45+
if (isEmbedded) {
46+
this.supportingFilesToGenerate = "ApiClient.java,BeanValidationException.java,RFC3339DateFormat.java,"
47+
+ "ServerConfiguration.java,ServerVariable.java,StringUtil.java,Authentication.java,HttpBasicAuth.java,"
48+
+ "HttpBearerAuth.java,ApiKeyAuth.java,JavaTimeFormatter.java";
3949
}
4050
super.execute();
4151
}
52+
53+
private static Map<?, ?> mergeOptions(Map<String, String> defaultOptions, Map<?, ?> overrides) {
54+
var merged = new HashMap<>();
55+
merged.putAll(defaultOptions);
56+
merged.putAll(overrides);
57+
return merged;
58+
}
4259
}
4360

boat-maven-plugin/src/test/java/com/backbase/oss/boat/GenerateMojoTests.java

Lines changed: 67 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
11
package com.backbase.oss.boat;
22

3+
import static java.util.Collections.singletonMap;
4+
import static org.hamcrest.MatcherAssert.assertThat;
5+
import static org.hamcrest.Matchers.endsWith;
6+
import static org.hamcrest.Matchers.equalTo;
7+
import static org.hamcrest.Matchers.hasSize;
8+
import static org.junit.jupiter.api.Assertions.assertEquals;
9+
import static org.junit.jupiter.api.Assertions.assertNotNull;
10+
311
import com.backbase.oss.codegen.java.BoatJavaCodeGen;
412
import com.backbase.oss.codegen.java.BoatSpringCodeGen;
513
import java.io.File;
14+
import java.util.HashMap;
15+
import java.util.Map;
616
import org.apache.maven.plugin.MojoExecutionException;
717
import org.apache.maven.plugin.MojoFailureException;
818
import org.apache.maven.project.MavenProject;
@@ -12,12 +22,6 @@
1222
import org.openapitools.codegen.DefaultCodegen;
1323
import org.sonatype.plexus.build.incremental.DefaultBuildContext;
1424

15-
import static java.util.Collections.singletonMap;
16-
import static org.hamcrest.MatcherAssert.assertThat;
17-
import static org.hamcrest.Matchers.endsWith;
18-
import static org.hamcrest.Matchers.equalTo;
19-
import static org.hamcrest.Matchers.hasSize;
20-
2125
class GenerateMojoTests {
2226
private final DefaultBuildContext buildContext = new DefaultBuildContext();
2327
private final MavenProject project = new MavenProject();
@@ -91,24 +95,69 @@ void useJavaBoatForWebClientEmbedded() throws MojoExecutionException, MojoFailur
9195
assertThat(mojo.generatorName, equalTo(BoatJavaCodeGen.NAME));
9296
}
9397

94-
private <T extends GenerateMojo> T configure(T mojo, String generatorName) {
95-
mojo.buildContext = buildContext;
96-
mojo.project = project;
97-
mojo.inputSpec = "src/test/resources/oas-examples/petstore.yaml";
98-
mojo.output = new File("target/generate-mojo-tests");
99-
mojo.generatorName = generatorName;
98+
@Test
99+
void shouldApplyDefaultConfigOptionsForSpringBoot() throws MojoExecutionException, MojoFailureException {
100+
GenerateMojo mojo = configure(new GenerateSpringBootEmbeddedMojo(), null);
100101

101-
return mojo;
102+
mojo.execute();
103+
104+
Map<String, String> expectedOpts = new HashMap<>();
105+
expectedOpts.put("java8", "true");
106+
expectedOpts.put("dateLibrary", "java8");
107+
expectedOpts.put("performBeanValidation", "true");
108+
expectedOpts.put("skipDefaultInterface", "true");
109+
expectedOpts.put("interfaceOnly", "true");
110+
expectedOpts.put("useTags", "true");
111+
expectedOpts.put("useBeanValidation", "true");
112+
expectedOpts.put("useClassLevelBeanValidation", "false");
113+
expectedOpts.put("useOptional", "false");
114+
expectedOpts.put("useJakartaEe", "true");
115+
expectedOpts.put("useSpringBoot3", "true");
116+
expectedOpts.put("containerDefaultToNull", "false");
117+
118+
assertNotNull(mojo.configOptions);
119+
expectedOpts.forEach((key, value) -> {
120+
assertEquals(value, mojo.configOptions.get(key));
121+
});
122+
}
123+
124+
@Test
125+
void shouldOverrideDefaultConfigOptionsForSpringBoot() throws MojoExecutionException, MojoFailureException {
126+
GenerateMojo mojo = configure(new GenerateSpringBootEmbeddedMojo(), null);
127+
// add overrides
128+
mojo.configOptions = Map.of(
129+
"containerDefaultToNull", "true",
130+
"useOptional", "true"
131+
);
132+
133+
mojo.execute();
134+
135+
Map<String, String> expectedOpts = new HashMap<>();
136+
expectedOpts.put("java8", "true");
137+
expectedOpts.put("dateLibrary", "java8");
138+
expectedOpts.put("performBeanValidation", "true");
139+
expectedOpts.put("skipDefaultInterface", "true");
140+
expectedOpts.put("interfaceOnly", "true");
141+
expectedOpts.put("useTags", "true");
142+
expectedOpts.put("useBeanValidation", "true");
143+
expectedOpts.put("useClassLevelBeanValidation", "false");
144+
expectedOpts.put("useOptional", "true");
145+
expectedOpts.put("useJakartaEe", "true");
146+
expectedOpts.put("useSpringBoot3", "true");
147+
expectedOpts.put("containerDefaultToNull", "true");
148+
149+
assertNotNull(mojo.configOptions);
150+
expectedOpts.forEach((key, value) -> {
151+
assertEquals(value, mojo.configOptions.get(key));
152+
});
102153
}
103-
private <T extends GenerateMojo> T configureUrl(T mojo, String generatorName) {
154+
155+
private <T extends GenerateMojo> T configure(T mojo, String generatorName) {
104156
mojo.buildContext = buildContext;
105157
mojo.project = project;
106-
mojo.inputSpec = "examples/v3.0/api-with-examples.yaml";
158+
mojo.inputSpec = "src/test/resources/oas-examples/petstore.yaml";
107159
mojo.output = new File("target/generate-mojo-tests");
108160
mojo.generatorName = generatorName;
109-
110161
return mojo;
111162
}
112163
}
113-
114-

boat-scaffold/src/test/java/com/backbase/oss/codegen/java/BoatSpringTemplatesTests.java

Lines changed: 57 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import static org.hamcrest.Matchers.not;
1313
import static org.hamcrest.Matchers.nullValue;
1414
import static org.junit.jupiter.api.Assertions.assertEquals;
15-
import static org.junit.jupiter.api.Assertions.assertNull;
15+
import static org.junit.jupiter.api.Assertions.assertNotNull;
1616
import static org.junit.jupiter.api.DynamicContainer.dynamicContainer;
1717
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
1818

@@ -36,16 +36,18 @@
3636
import java.util.ArrayList;
3737
import java.util.Arrays;
3838
import java.util.List;
39-
import java.util.concurrent.atomic.AtomicReference;
4039
import java.util.function.Predicate;
4140
import java.util.regex.Pattern;
4241
import java.util.stream.IntStream;
4342
import java.util.stream.Stream;
43+
import lombok.Getter;
4444
import lombok.SneakyThrows;
4545
import lombok.extern.slf4j.Slf4j;
4646
import org.apache.commons.io.FileUtils;
47+
import org.apache.commons.lang.UnhandledException;
4748
import org.apache.maven.cli.MavenCli;
4849
import org.codehaus.plexus.classworlds.ClassWorld;
50+
import org.hamcrest.Matchers;
4951
import org.junit.jupiter.api.BeforeAll;
5052
import org.junit.jupiter.api.DynamicNode;
5153
import org.junit.jupiter.api.DynamicTest;
@@ -298,14 +300,13 @@ private void verifyGeneratedClasses(File projectDir) throws Exception {
298300
new URL[]{classesDir.toURI().toURL()},
299301
BoatSpringTemplatesTests.class.getClassLoader()
300302
);
301-
String testedModelClassName = buildTestedModelClassName();
302-
verifyModelClassSerializesAndDeserializesFromJson(classLoader, testedModelClassName);
303+
verifyReceivableRequestModelJsonConversion(classLoader);
304+
verifyMultiLineRequest(classLoader);
303305
}
304306

305-
private void verifyModelClassSerializesAndDeserializesFromJson(ClassLoader classLoader,
306-
String testedModelClassName) throws InterruptedException {
307+
private void verifyReceivableRequestModelJsonConversion(ClassLoader classLoader) throws InterruptedException {
308+
String testedModelClassName = buildReceivableRequestModelClassName();
307309
var objectMapper = new ObjectMapper();
308-
final AtomicReference<Exception> exceptionRef = new AtomicReference<>();
309310
Runnable verification = () -> {
310311
try {
311312
Class<?> modelClass = classLoader.loadClass(testedModelClassName);
@@ -332,19 +333,32 @@ private void verifyModelClassSerializesAndDeserializesFromJson(ClassLoader class
332333
assertEquals(modelClass, deserializedObject1.getClass());
333334

334335
} catch (Exception e) {
335-
log.warn("Verification error", e);
336-
exceptionRef.set(e);
336+
throw new UnhandledException(e);
337337
}
338338
};
339+
runVerification(verification, classLoader);
340+
}
339341

340-
runVerification(verification, classLoader).join();
341-
assertNull(exceptionRef.get(), "Classes verification failed");
342+
private void verifyMultiLineRequest(ClassLoader classLoader) throws InterruptedException {
343+
String testedModelClassName = buildMultiLineRequestModelClassName();
344+
Runnable verification = () -> {
345+
try {
346+
Class<?> modelClass = classLoader.loadClass(testedModelClassName);
347+
Constructor<?> constructor = modelClass.getConstructor();
348+
Object modelObject = constructor.newInstance();
349+
List<?> listProperty = (List<?>) modelClass.getDeclaredMethod("getLines").invoke(modelObject);
350+
assertNotNull(listProperty);
351+
} catch (Exception e) {
352+
throw new UnhandledException(e);
353+
}
354+
};
355+
runVerification(verification, classLoader);
342356
}
343357

344358
/**
345359
* Build proper class name for `ReceivableRequest`.
346360
*/
347-
private String buildTestedModelClassName() {
361+
private String buildReceivableRequestModelClassName() {
348362
var modelPackage = param.name.replace('-', '.') + ".model";
349363
var classNameSuffix = org.apache.commons.lang3.StringUtils.capitalize(
350364
param.name.indexOf('-') > -1
@@ -354,14 +368,21 @@ private String buildTestedModelClassName() {
354368
return modelPackage + ".ReceivableRequest" + classNameSuffix;
355369
}
356370

357-
private Thread runVerification(Runnable verification, ClassLoader classLoader) {
358-
var verificationThread = new Thread(verification);
359-
verificationThread.setName("verify-classes-" + param.name);
360-
verificationThread.setContextClassLoader(classLoader);
361-
verificationThread.setUncaughtExceptionHandler(
362-
(t1, e) -> log.error("Uncaught exception in classes verifier: ", e));
363-
verificationThread.start();
364-
return verificationThread;
371+
private String buildMultiLineRequestModelClassName() {
372+
var modelPackage = param.name.replace('-', '.') + ".model";
373+
var classNameSuffix = org.apache.commons.lang3.StringUtils.capitalize(
374+
param.name.indexOf('-') > -1
375+
? CaseFormat.LOWER_HYPHEN.to(CaseFormat.LOWER_CAMEL, param.name)
376+
: param.name
377+
);
378+
return modelPackage + ".MultiLinePaymentRequest" + classNameSuffix;
379+
}
380+
381+
private void runVerification(Runnable verification, ClassLoader classLoader) throws InterruptedException {
382+
var thread = new ExceptionInterceptingThread(verification, "verify-classes-" + param.name, classLoader);
383+
thread.start();
384+
thread.join();
385+
assertThat(thread.getUncaughtExceptions(), Matchers.empty());
365386
}
366387

367388
private boolean findPattern(String filePattern, String linePattern) {
@@ -413,9 +434,9 @@ private List<File> generateFrom(String templates, String combination) {
413434
gcf.addAdditionalProperty(BoatSpringCodeGen.USE_CLASS_LEVEL_BEAN_VALIDATION, true);
414435
gcf.addAdditionalProperty(BoatSpringCodeGen.ADD_SERVLET_REQUEST, this.param.addServletRequest);
415436
gcf.addAdditionalProperty(BoatSpringCodeGen.ADD_BINDING_RESULT,this.param.addBindingResult);
416-
if(this.param.addBindingResult){
437+
if (this.param.addBindingResult) {
417438
gcf.addAdditionalProperty(BeanValidationFeatures.USE_BEANVALIDATION, true);
418-
}else {
439+
} else {
419440
gcf.addAdditionalProperty(BeanValidationFeatures.USE_BEANVALIDATION, this.param.useBeanValidation);
420441
}
421442
gcf.addAdditionalProperty(BoatSpringCodeGen.USE_LOMBOK_ANNOTATIONS, this.param.useLombokAnnotations);
@@ -465,4 +486,18 @@ private List<File> generateFrom(String templates, String combination) {
465486

466487
return new DefaultGenerator().opts(coi).generate();
467488
}
489+
490+
private class ExceptionInterceptingThread extends Thread {
491+
@Getter
492+
private final List<Throwable> uncaughtExceptions = new ArrayList<>();
493+
494+
public ExceptionInterceptingThread(Runnable target, String name, ClassLoader classLoader) {
495+
super(target, name);
496+
setContextClassLoader(classLoader);
497+
setUncaughtExceptionHandler((t, e) -> {
498+
log.warn("Uncaught exception in classes verifier: ", e);
499+
uncaughtExceptions.add(e);
500+
});
501+
}
502+
}
468503
}

boat-scaffold/src/test/resources/boat-spring/openapi.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,21 @@ components:
9898
currencyCode:
9999
type: string
100100
description: Currency of the payment request
101+
MultiLinePaymentRequest:
102+
required:
103+
- lines
104+
type: object
105+
properties:
106+
lines:
107+
type: array
108+
description: Payment request details
109+
items:
110+
$ref: '#/components/schemas/PaymentRequestLine'
111+
PaymentRequestLine:
112+
required:
113+
- accountId
114+
type: object
115+
properties:
116+
accountId:
117+
type: string
118+
description: Unique identifier of the related Account

0 commit comments

Comments
 (0)