Skip to content

Commit

Permalink
[Kotlin-Spring] support to Spring boot3 & jakarta extension (OpenAPIT…
Browse files Browse the repository at this point in the history
…ools#14369)

* [Kotlin] add spring boot 3 & jakarta extension support

* [kotlin-spring] readme update & modified imports

* use latest Spring Boot starter parent

* use same options as in [Java] generator

* new config kotlin-spring-boot-3

---------

Co-authored-by: jayandran sampath <[email protected]>
  • Loading branch information
parenko and JayAtOC authored Jan 31, 2023
1 parent eca9ec7 commit cffe2d0
Show file tree
Hide file tree
Showing 108 changed files with 1,992 additions and 552 deletions.
46 changes: 46 additions & 0 deletions .github/workflows/samples-kotlin-server-jdk17.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Samples Kotlin server

on:
push:
branches:
- 'samples/server/petstore/kotlin-springboot-3*/**'
pull_request:
paths:
- 'samples/server/petstore/kotlin-springboot-3*/**'

env:
GRADLE_VERSION: 7.4

jobs:
build:
name: Build Kotlin server
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
sample:
# server
- samples/server/petstore/kotlin-springboot-3
steps:
- uses: actions/checkout@v3
- uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: 17
- name: Cache maven dependencies
uses: actions/cache@v3
env:
cache-name: maven-repository
with:
path: |
~/.gradle
key: ${{ runner.os }}-${{ github.job }}-${{ env.cache-name }}-${{ hashFiles('**/pom.xml') }}
- name: Install Gradle wrapper
uses: eskatos/gradle-command-action@v2
with:
gradle-version: ${{ env.GRADLE_VERSION }}
build-root-directory: ${{ matrix.sample }}
arguments: wrapper
- name: Build
working-directory: ${{ matrix.sample }}
run: ./gradlew build -x test
13 changes: 13 additions & 0 deletions bin/configs/kotlin-spring-boot-3.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
generatorName: kotlin-spring
outputDir: samples/server/petstore/kotlin-springboot-3
library: spring-boot
inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore.yaml
templateDir: modules/openapi-generator/src/main/resources/kotlin-spring
additionalProperties:
documentationProvider: none
annotationLibrary: none
useSwaggerUI: "false"
serviceImplementation: "true"
serializableModel: "true"
beanValidations: "true"
useSpringBoot3: "true"
1 change: 1 addition & 0 deletions docs/generators/kotlin-spring.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|sourceFolder|source folder for generated code| |src/main/kotlin|
|title|server title name or client service name| |OpenAPI Kotlin Spring|
|useBeanValidation|Use BeanValidation API annotations to validate data types| |true|
|useSpringBoot3|Generate code and provide dependencies for use with Spring Boot 3.x. (Use jakarta instead of javax in imports). Enabling this option will also enable `useJakartaEe`.| |false|
|useSwaggerUI|Open the OpenApi specification in swagger-ui. Will also import and configure needed dependencies| |true|
|useTags|Whether to use tags for creating interface and controller class names| |false|

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ public enum SERIALIZATION_LIBRARY_TYPE {moshi, gson, jackson, kotlinx_serializat
public static final String MODEL_MUTABLE_DESC = "Create mutable models";
public static final String ADDITIONAL_MODEL_TYPE_ANNOTATIONS = "additionalModelTypeAnnotations";

public static final String JAVAX_PACKAGE = "javaxPackage";
public static final String USE_JAKARTA_EE = "useJakartaEe";

private final Logger LOGGER = LoggerFactory.getLogger(AbstractKotlinCodegen.class);

protected String artifactId;
Expand All @@ -69,6 +72,8 @@ public enum SERIALIZATION_LIBRARY_TYPE {moshi, gson, jackson, kotlinx_serializat
protected boolean parcelizeModels = false;
protected boolean serializableModel = false;

protected boolean useJakartaEe = false;

protected boolean nonPublicApi = false;

protected CodegenConstants.ENUM_PROPERTY_NAMING_TYPE enumPropertyNaming = CodegenConstants.ENUM_PROPERTY_NAMING_TYPE.camelCase;
Expand Down Expand Up @@ -544,6 +549,17 @@ public void processOpts() {
typeMapping.put("set", "kotlin.collections.MutableSet");
typeMapping.put("map", "kotlin.collections.MutableMap");
}

if (additionalProperties.containsKey(USE_JAKARTA_EE)) {
setUseJakartaEe(Boolean.TRUE.equals(additionalProperties.get(USE_JAKARTA_EE)));
}
additionalProperties.put(USE_JAKARTA_EE, useJakartaEe);

if (useJakartaEe) {
applyJakartaPackage();
} else {
applyJavaxPackage();
}
}

protected boolean isModelMutable() {
Expand Down Expand Up @@ -594,6 +610,10 @@ public void setSerializableModel(boolean serializableModel) {
this.serializableModel = serializableModel;
}

public void setUseJakartaEe(boolean useJakartaEe) {
this.useJakartaEe = useJakartaEe;
}

public boolean nonPublicApi() {
return nonPublicApi;
}
Expand Down Expand Up @@ -842,6 +862,14 @@ private String titleCase(final String input) {
return input.substring(0, 1).toUpperCase(Locale.ROOT) + input.substring(1);
}

protected void applyJavaxPackage() {
writePropertyBack(JAVAX_PACKAGE, "javax");
}

protected void applyJakartaPackage() {
writePropertyBack(JAVAX_PACKAGE, "jakarta");
}

@Override
protected boolean isReservedWord(String word) {
// We want case-sensitive escaping, to avoid unnecessary backtick-escaping.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ public class KotlinSpringServerCodegen extends AbstractKotlinCodegen
public static final String USE_TAGS = "useTags";
public static final String BEAN_QUALIFIERS = "beanQualifiers";

public static final String USE_SPRING_BOOT3 = "useSpringBoot3";

private String basePackage;
private String invokerPackage;
private String serverPort = "8080";
Expand All @@ -87,6 +89,8 @@ public class KotlinSpringServerCodegen extends AbstractKotlinCodegen
private boolean delegatePattern = false;
protected boolean useTags = false;
private boolean beanQualifiers = false;

protected boolean useSpringBoot3 = false;
private DocumentationProvider documentationProvider;
private AnnotationLibrary annotationLibrary;

Expand Down Expand Up @@ -156,6 +160,7 @@ public KotlinSpringServerCodegen() {
addSwitch(BEAN_QUALIFIERS, "Whether to add fully-qualifier class names as bean qualifiers in @Component and " +
"@RestController annotations. May be used to prevent bean names clash if multiple generated libraries" +
" (contexts) added to single project.", beanQualifiers);
addSwitch(USE_SPRING_BOOT3, "Generate code and provide dependencies for use with Spring Boot 3.x. (Use jakarta instead of javax in imports). Enabling this option will also enable `useJakartaEe`.", useSpringBoot3);
supportedLibraries.put(SPRING_BOOT, "Spring-boot Server application.");
setLibrary(SPRING_BOOT);

Expand All @@ -180,6 +185,7 @@ public KotlinSpringServerCodegen() {
cliOptions.add(annotationLibraryCliOption);
}
}

@Override
public DocumentationProvider getDocumentationProvider() {
return documentationProvider;
Expand Down Expand Up @@ -230,7 +236,7 @@ public List<AnnotationLibrary> supportedAnnotationLibraries() {
* @return true if the selected DocumentationProvider requires us to bootstrap swagger-ui.
*/
private boolean selectedDocumentationProviderRequiresSwaggerUiBootstrap() {
return getDocumentationProvider().equals(DocumentationProvider.SPRINGFOX) ||
return getDocumentationProvider().equals(DocumentationProvider.SPRINGFOX) ||
getDocumentationProvider().equals(DocumentationProvider.SOURCE);
}

Expand Down Expand Up @@ -315,6 +321,14 @@ public void setUseTags(boolean useTags) {
this.useTags = useTags;
}

public void setUseSpringBoot3(boolean isSpringBoot3) {
this.useSpringBoot3 = isSpringBoot3;
}

public boolean isUseSpringBoot3() {
return useSpringBoot3;
}

@Override
public void setUseBeanValidation(boolean useBeanValidation) {
this.useBeanValidation = useBeanValidation;
Expand Down Expand Up @@ -357,11 +371,11 @@ public void processOpts() {

if (null != defaultDocumentationProvider()) {
documentationProvider = DocumentationProvider.ofCliOption(
(String)additionalProperties.getOrDefault(DOCUMENTATION_PROVIDER,
(String) additionalProperties.getOrDefault(DOCUMENTATION_PROVIDER,
defaultDocumentationProvider().toCliOptValue())
);

if (! supportedDocumentationProvider().contains(documentationProvider)) {
if (!supportedDocumentationProvider().contains(documentationProvider)) {
String msg = String.format(Locale.ROOT,
"The [%s] Documentation Provider is not supported by this generator",
documentationProvider.toCliOptValue());
Expand All @@ -373,13 +387,13 @@ public void processOpts() {
documentationProvider.getPreferredAnnotationLibrary().toCliOptValue())
);

if (! supportedAnnotationLibraries().contains(annotationLibrary)) {
if (!supportedAnnotationLibraries().contains(annotationLibrary)) {
String msg = String.format(Locale.ROOT, "The Annotation Library [%s] is not supported by this generator",
annotationLibrary.toCliOptValue());
throw new IllegalArgumentException(msg);
}

if (! documentationProvider.supportedAnnotationLibraries().contains(annotationLibrary)) {
if (!documentationProvider.supportedAnnotationLibraries().contains(annotationLibrary)) {
String msg = String.format(Locale.ROOT,
"The [%s] documentation provider does not support [%s] as complementary annotation library",
documentationProvider.toCliOptValue(), annotationLibrary.toCliOptValue());
Expand Down Expand Up @@ -503,6 +517,22 @@ public void processOpts() {
this.setUseTags(Boolean.parseBoolean(additionalProperties.get(USE_TAGS).toString()));
}

if (additionalProperties.containsKey(USE_SPRING_BOOT3)) {
this.setUseSpringBoot3(convertPropertyToBoolean(USE_SPRING_BOOT3));
}
if (isUseSpringBoot3()) {
if (DocumentationProvider.SPRINGFOX.equals(getDocumentationProvider())) {
throw new IllegalArgumentException(DocumentationProvider.SPRINGFOX.getPropertyName() + " is not supported with Spring Boot > 3.x");
}
if (AnnotationLibrary.SWAGGER1.equals(getAnnotationLibrary())) {
throw new IllegalArgumentException(AnnotationLibrary.SWAGGER1.getPropertyName() + " is not supported with Spring Boot > 3.x");
}
useJakartaEe=true;
additionalProperties.put(USE_JAKARTA_EE, useJakartaEe);
applyJakartaPackage();
}
writePropertyBack(USE_SPRING_BOOT3, isUseSpringBoot3());

modelTemplateFiles.put("model.mustache", ".kt");

if (!this.interfaceOnly && this.delegatePattern) {
Expand Down Expand Up @@ -544,10 +574,18 @@ public void processOpts() {

if (library.equals(SPRING_BOOT)) {
LOGGER.info("Setup code generator for Kotlin Spring Boot");
supportingFiles.add(new SupportingFile("pom.mustache", "", "pom.xml"));
if (isUseSpringBoot3()) {
supportingFiles.add(new SupportingFile("pom-sb3.mustache", "", "pom.xml"));
} else {
supportingFiles.add(new SupportingFile("pom.mustache", "", "pom.xml"));
}

if (this.gradleBuildFile) {
supportingFiles.add(new SupportingFile("buildGradleKts.mustache", "", "build.gradle.kts"));
if (isUseSpringBoot3()) {
supportingFiles.add(new SupportingFile("buildGradle-sb3-Kts.mustache", "", "build.gradle.kts"));
} else {
supportingFiles.add(new SupportingFile("buildGradleKts.mustache", "", "build.gradle.kts"));
}
supportingFiles.add(new SupportingFile("settingsGradle.mustache", "", "settings.gradle"));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,8 @@ import org.springframework.web.context.request.NativeWebRequest
import org.springframework.beans.factory.annotation.Autowired

{{#useBeanValidation}}
import javax.validation.Valid
import javax.validation.constraints.DecimalMax
import javax.validation.constraints.DecimalMin
import javax.validation.constraints.Email
import javax.validation.constraints.Max
import javax.validation.constraints.Min
import javax.validation.constraints.NotNull
import javax.validation.constraints.Pattern
import javax.validation.constraints.Size
import {{javaxPackage}}.validation.constraints.*
import {{javaxPackage}}.validation.Valid
{{/useBeanValidation}}

{{#reactive}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,8 @@ import org.springframework.web.context.request.NativeWebRequest
import org.springframework.beans.factory.annotation.Autowired

{{#useBeanValidation}}
import javax.validation.Valid
import javax.validation.constraints.DecimalMax
import javax.validation.constraints.DecimalMin
import javax.validation.constraints.Email
import javax.validation.constraints.Max
import javax.validation.constraints.Min
import javax.validation.constraints.NotNull
import javax.validation.constraints.Pattern
import javax.validation.constraints.Size
import {{javaxPackage}}.validation.constraints.*
import {{javaxPackage}}.validation.Valid
{{/useBeanValidation}}

{{#reactive}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package {{apiPackage}}
{{^reactive}}
import org.springframework.web.context.request.NativeWebRequest

import javax.servlet.http.HttpServletResponse
import {{javaxPackage}}.servlet.http.HttpServletResponse
import java.io.IOException
{{/reactive}}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package {{apiPackage}}
import org.springframework.http.HttpStatus
import org.springframework.web.bind.annotation.ControllerAdvice
import org.springframework.web.bind.annotation.ExceptionHandler
import javax.servlet.http.HttpServletResponse
import javax.validation.ConstraintViolationException
import {{javaxPackage}}.servlet.http.HttpServletResponse
import {{javaxPackage}}.validation.ConstraintViolationException

// TODO Extend ApiException for custom exception handling, e.g. the below NotFound exception
sealed class ApiException(msg: String, val code: Int) : Exception(msg)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

group = "{{groupId}}"
version = "{{artifactVersion}}"
java.sourceCompatibility = JavaVersion.VERSION_17

repositories {
mavenCentral()
maven { url = uri("https://repo.spring.io/milestone") }
}

tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "17"
}

plugins {
val kotlinVersion = "1.7.10"
id("org.jetbrains.kotlin.jvm") version kotlinVersion
id("org.jetbrains.kotlin.plugin.jpa") version kotlinVersion
id("org.jetbrains.kotlin.plugin.spring") version kotlinVersion
id("org.springframework.boot") version "3.0.2"
id("io.spring.dependency-management") version "1.0.14.RELEASE"
}

dependencies {
{{#reactive}} val kotlinxCoroutinesVersion = "1.6.1"
{{/reactive}} implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("org.jetbrains.kotlin:kotlin-reflect"){{^reactive}}
implementation("org.springframework.boot:spring-boot-starter-web"){{/reactive}}{{#reactive}}
implementation("org.springframework.boot:spring-boot-starter-webflux")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinxCoroutinesVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:$kotlinxCoroutinesVersion"){{/reactive}}{{#springDocDocumentationProvider}}{{#useSwaggerUI}}
implementation("org.springdoc:springdoc-openapi-starter-{{#reactive}}webflux{{/reactive}}{{^reactive}}webmvc{{/reactive}}-ui:2.0.0-M5"){{/useSwaggerUI}}{{^useSwaggerUI}}
implementation("org.springdoc:springdoc-openapi-{{#reactive}}webflux{{/reactive}}{{^reactive}}webmvc{{/reactive}}-core:2.0.0-M5"){{/useSwaggerUI}}{{/springDocDocumentationProvider}}{{#springFoxDocumentationProvider}}
implementation("io.springfox:springfox-swagger2:2.9.2"){{/springFoxDocumentationProvider}}{{#useSwaggerUI}}{{^springDocDocumentationProvider}}
implementation("org.webjars:swagger-ui:4.10.3")
implementation("org.webjars:webjars-locator-core"){{/springDocDocumentationProvider}}{{/useSwaggerUI}}{{^springFoxDocumentationProvider}}{{^springDocDocumentationProvider}}{{#swagger1AnnotationLibrary}}
implementation("io.swagger:swagger-annotations:1.6.6"){{/swagger1AnnotationLibrary}}{{#swagger2AnnotationLibrary}}
implementation("io.swagger.core.v3:swagger-annotations:2.2.0"){{/swagger2AnnotationLibrary}}{{/springDocDocumentationProvider}}{{/springFoxDocumentationProvider}}

implementation("com.google.code.findbugs:jsr305:3.0.2")
implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml")
implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-xml")
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
{{#useBeanValidation}}
implementation("jakarta.validation:jakarta.validation-api"){{/useBeanValidation}}
implementation("jakarta.annotation:jakarta.annotation-api:2.1.0")

testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
testImplementation("org.springframework.boot:spring-boot-starter-test") {
exclude(module = "junit")
}
{{#reactive}}
testImplementation`("org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinxCoroutinesVersion")
{{/reactive}}
}
Loading

0 comments on commit cffe2d0

Please sign in to comment.