001/* 002 * Copyright 2017-2021 original authors 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package io.micronaut.maven.openapi; 017 018import io.micronaut.maven.AbstractMicronautMojo; 019import io.micronaut.openapi.generator.MicronautCodeGeneratorBuilder; 020import io.micronaut.openapi.generator.MicronautCodeGeneratorEntryPoint; 021import io.micronaut.openapi.generator.MicronautCodeGeneratorOptionsBuilder; 022import io.micronaut.openapi.generator.SerializationLibraryKind; 023import org.apache.maven.plugin.MojoExecutionException; 024import org.apache.maven.plugin.MojoFailureException; 025import org.apache.maven.plugins.annotations.Parameter; 026import org.apache.maven.project.MavenProject; 027 028import java.io.File; 029import java.util.List; 030import java.util.Locale; 031import java.util.Map; 032 033/** 034 * Base class for OpenAPI generator mojos. This provides the common 035 * parameters for all generators and the invoker logic. Subclasses 036 * must implement the {@link #isEnabled()} and {@link #configureBuilder(MicronautCodeGeneratorBuilder)} 037 * methods. 038 */ 039public abstract class AbstractOpenApiMojo extends AbstractMicronautMojo { 040 static final String MICRONAUT_OPENAPI_PREFIX = "micronaut.openapi"; 041 static final String IO_MICRONAUT_OPENAPI_PREFIX = "io.micronaut.openapi"; 042 static final String KOTLIN_MAVEN_UNSUPPORTED_MESSAGE = 043 "Kotlin applications built with Maven are no longer supported in Micronaut Platform 5. " 044 + "Use Java or Groovy with Maven, or use Gradle for Kotlin applications."; 045 046 /** 047 * The OpenAPI specification file path relative to the project's root path. 048 */ 049 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".definition", defaultValue = IO_MICRONAUT_OPENAPI_PREFIX + ".invoker", required = true) 050 protected File definitionFile; 051 052 /** 053 * The name of the package that can be used for various classes required for invocation. 054 */ 055 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".invoker.package.name", defaultValue = IO_MICRONAUT_OPENAPI_PREFIX + ".invoker", required = true) 056 protected String invokerPackageName; 057 058 /** 059 * The package name for the APIs (controller interfaces). 060 */ 061 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".api.package.name", defaultValue = IO_MICRONAUT_OPENAPI_PREFIX + ".api", required = true) 062 protected String apiPackageName; 063 064 /** 065 * The package name for the model classes. 066 */ 067 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".model.package.name", defaultValue = IO_MICRONAUT_OPENAPI_PREFIX + ".model", required = true) 068 protected String modelPackageName; 069 070 /** 071 * Whether to generate validation annotations for models and APIs. 072 */ 073 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".use.bean.validation", defaultValue = "true", required = true) 074 protected boolean useBeanValidation; 075 076 /** 077 * Flag to indicate whether to use the utils.OneOfImplementorAdditionalData related logic. 078 */ 079 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".use.one.of.interfaces", defaultValue = "true", required = true) 080 protected boolean useOneOfInterfaces; 081 082 /** 083 * Whether to use {@link java.util.Optional} for non-required model properties and API parameters. 084 */ 085 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".use.optional", defaultValue = "false", required = true) 086 protected boolean useOptional; 087 088 /** 089 * Whether to use reactor types for operation responses. 090 */ 091 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".use.reactive", defaultValue = "true", required = true) 092 protected boolean useReactive; 093 094 /** 095 * Configure the serialization library. 096 */ 097 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".serialization.framework", defaultValue = "MICRONAUT_SERDE_JACKSON", required = true) 098 protected String serializationFramework; 099 100 /** 101 * If true, the generated operation return types will be wrapped in HttpResponse. 102 */ 103 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".always.use.generate.http.response", defaultValue = "false", required = true) 104 protected boolean alwaysUseGenerateHttpResponse; 105 106 /** 107 * Wrap the operations response in HttpResponse object where non-200 HTTP status codes or additional headers are defined. 108 */ 109 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".generate.http.response.where.required", defaultValue = "true", required = true) 110 protected boolean generateHttpResponseWhereRequired; 111 112 /** 113 * Configure the date-time format. 114 */ 115 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".date.time.format", defaultValue = "ZONED_DATETIME", required = true) 116 protected String dateTimeFormat; 117 118 /** 119 * Comma-separated values of output kinds to generate. The values are defined by the 120 * {@link MicronautCodeGeneratorEntryPoint.OutputKind} enum. 121 */ 122 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".outputs", required = true, defaultValue = "apis,models,supporting_files") 123 protected List<String> outputKinds; 124 125 /** 126 * The output directory to which all the sources will be generated. 127 */ 128 @Parameter(defaultValue = "${project.build.directory}/generated-sources/openapi", required = true) 129 protected File outputDirectory; 130 131 /** 132 * Define parameter mappings that allow using custom types for parameter binding. 133 * See {@link ParameterMapping} for details. 134 */ 135 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".parameterMappings") 136 protected List<ParameterMapping> parameterMappings; 137 138 /** 139 * Define parameter mappings that allow using custom types for parameter binding. 140 * See {@link ResponseBodyMapping} for details. 141 */ 142 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".responseBodyMappings") 143 protected List<ResponseBodyMapping> responseBodyMappings; 144 145 /** 146 * Add the schema mappings. 147 */ 148 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".schemaMapping") 149 protected Map<String, String> schemaMapping; 150 151 /** 152 * Add the import mappings. 153 */ 154 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".importMapping") 155 protected Map<String, String> importMapping; 156 157 /** 158 * Add the name mappings. 159 */ 160 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".nameMapping") 161 protected Map<String, String> nameMapping; 162 163 /** 164 * Add the type mappings. 165 */ 166 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".typeMapping") 167 protected Map<String, String> typeMapping; 168 169 /** 170 * Add the enum name mappings. 171 */ 172 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".enumNameMapping") 173 protected Map<String, String> enumNameMapping; 174 175 /** 176 * Add the model name mappings. 177 */ 178 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".modelNameMapping") 179 protected Map<String, String> modelNameMapping; 180 181 /** 182 * Add the inline schema name mappings. 183 */ 184 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".inlineSchemaNameMapping") 185 protected Map<String, String> inlineSchemaNameMapping; 186 187 /** 188 * Add the inline schema options. 189 */ 190 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".inlineSchemaOption") 191 protected Map<String, String> inlineSchemaOption; 192 193 /** 194 * Add the OpenAPI normalizer options. 195 */ 196 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".openapiNormalizer") 197 protected Map<String, String> openapiNormalizer; 198 199 /** 200 * Set the api name prefix. 201 */ 202 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".apiNamePrefix") 203 protected String apiNamePrefix; 204 205 /** 206 * Set the api name suffix. 207 */ 208 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".apiNameSuffix") 209 protected String apiNameSuffix; 210 211 /** 212 * Set the model name prefix. 213 */ 214 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".modelNamePrefix") 215 protected String modelNamePrefix; 216 217 /** 218 * Set the model name suffix. 219 */ 220 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".modelNameSuffix") 221 protected String modelNameSuffix; 222 223 /** 224 * Allows specifying the language of the generated code. 225 * 226 * @since 4.3.0 227 */ 228 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".lang", defaultValue = "java") 229 protected String lang; 230 231 /** 232 * If set to true, the generated enums check enum value with ignoring case. 233 */ 234 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".useEnumCaseInsensitive", defaultValue = "false") 235 protected boolean useEnumCaseInsensitive; 236 237 /** 238 * Additional annotations for enum type (class level annotations). 239 */ 240 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".additionalEnumTypeAnnotations") 241 protected List<String> additionalEnumTypeAnnotations; 242 243 /** 244 * Additional annotations for model type (class level annotations). 245 */ 246 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".additionalModelTypeAnnotations") 247 protected List<String> additionalModelTypeAnnotations; 248 249 /** 250 * Additional annotations for oneOf interfaces (class level annotations). 251 */ 252 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".additionalOneOfTypeAnnotations") 253 protected List<String> additionalOneOfTypeAnnotations; 254 255 /** 256 * Additional generator properties. 257 */ 258 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".additionalProperties") 259 protected Map<String, Object> additionalProperties; 260 261 /** 262 * If set to true, controller and client method will be generated with openAPI annotations. 263 */ 264 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".generateSwaggerAnnotations", defaultValue = "false") 265 protected boolean generateSwaggerAnnotations; 266 267 /** 268 * Set the implicit headers flag. 269 */ 270 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".implicitHeaders", defaultValue = "false") 271 protected boolean implicitHeaders; 272 273 /** 274 * Set the implicit headers regex. 275 */ 276 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".implicitHeadersRegex") 277 protected String implicitHeadersRegex; 278 279 /** 280 * Flag to indicate whether to use the "jakarta" or "javax" package. 281 */ 282 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".useJakartaEe", defaultValue = "true") 283 protected boolean useJakartaEe = true; 284 285 /** 286 * Sort method arguments to place required parameters before optional parameters. 287 * Default: true 288 */ 289 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".sortParamsByRequiredFlag", defaultValue = "true") 290 protected boolean sortParamsByRequiredFlag = true; 291 292 /** 293 * Skip examples defined in operations to avoid out of memory errors. 294 * Default: false 295 */ 296 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".skipOperationExample") 297 protected boolean skipOperationExample; 298 299 /** 300 * Skip sorting operations. 301 * Default: false 302 */ 303 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".skipSortingOperations") 304 protected boolean skipSortingOperations; 305 306 /** 307 * Character to use as a delimiter for the prefix. Default: '_' 308 */ 309 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".removeOperationIdPrefixDelimiter", defaultValue = "_") 310 protected String removeOperationIdPrefixDelimiter = "_"; 311 312 /** 313 * Count of delimiter for the prefix. Use -1 for last. Default: 1 314 */ 315 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".removeOperationIdPrefixCount", defaultValue = "1") 316 protected int removeOperationIdPrefixCount = 1; 317 318 /** 319 * Sort model properties to place required parameters before optional parameters. 320 */ 321 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".sortModelPropertiesByRequiredFlag", defaultValue = "true") 322 protected boolean sortModelPropertiesByRequiredFlag = true; 323 324 /** 325 * Whether to ensure parameter names are unique in an operation (rename parameters that are not). 326 */ 327 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".ensureUniqueParams", defaultValue = "true") 328 protected boolean ensureUniqueParams = true; 329 330 /** 331 * boolean, toggles whether Unicode identifiers are allowed in names or not, default is false. 332 */ 333 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".allowUnicodeIdentifiers") 334 protected boolean allowUnicodeIdentifiers; 335 336 /** 337 * Add form or body parameters to the beginning of the parameter list. 338 */ 339 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".prependFormOrBodyParameters") 340 protected boolean prependFormOrBodyParameters; 341 342 /** 343 * Whether to generate sealed model interfaces and classes. Only for java generator. 344 * 345 * @since 4.8.0 346 */ 347 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".useSealed") 348 protected boolean useSealed; 349 350 /** 351 * If set to true, {@literal @}JsonInclude annotation will be with value ALWAYS for required properties in POJO's. 352 * 353 * @since 4.8.0 354 */ 355 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".jsonIncludeAlwaysForRequiredFields") 356 protected boolean jsonIncludeAlwaysForRequiredFields; 357 358 /** 359 * Generate or not required properties constructor. Only for java generator. 360 * 361 * @since 4.8.0 362 */ 363 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".requiredPropertiesInConstructor", defaultValue = "true") 364 protected boolean requiredPropertiesInConstructor = true; 365 366 /** 367 * If true, the generated controller interface will be without `@Controller` annotation. 368 * 369 * @since 4.8.0 370 */ 371 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".generateControllerAsAbstract") 372 protected boolean generateControllerAsAbstract; 373 374 @Parameter(defaultValue = "${project}", readonly = true) 375 protected MavenProject project; 376 377 /** 378 * Determines if this mojo must be executed. 379 * 380 * @return true if the mojo is enabled 381 */ 382 protected abstract boolean isEnabled(); 383 384 /** 385 * Configures the OpenAPI generator. When this method is called, 386 * common properties shared by all generators have already been 387 * configured, so this method should only take care of configuring 388 * the generator specific parameters. 389 * 390 * @param builder the generator configuration builder 391 */ 392 protected abstract void configureBuilder(MicronautCodeGeneratorBuilder builder) throws MojoExecutionException; 393 394 /** 395 * Validates generator-specific language support before common generator options are resolved. 396 */ 397 protected void validateLanguage() throws MojoExecutionException { 398 // Default implementation accepts the common generator language configuration. 399 } 400 401 protected final void rejectUnsupportedKotlinLanguage(String generatorType) throws MojoExecutionException { 402 if ("kotlin".equalsIgnoreCase(lang)) { 403 throw new MojoExecutionException( 404 "Kotlin OpenAPI " + generatorType + " generation is not supported by the Micronaut Maven Plugin. " 405 + KOTLIN_MAVEN_UNSUPPORTED_MESSAGE 406 ); 407 } 408 } 409 410 @Override 411 public final void execute() throws MojoExecutionException, MojoFailureException { 412 if (!isEnabled()) { 413 getLog().debug(this.getClass().getSimpleName() + " is disabled"); 414 return; 415 } 416 417 validateLanguage(); 418 project.addCompileSourceRoot(outputDirectory.getAbsolutePath()); 419 var builder = MicronautCodeGeneratorEntryPoint.builder() 420 .withDefinitionFile(definitionFile.toURI()) 421 .withOutputDirectory(outputDirectory) 422 .withOutputs( 423 outputKinds.stream().map(String::toUpperCase).map(MicronautCodeGeneratorEntryPoint.OutputKind::valueOf).toList().toArray(new MicronautCodeGeneratorEntryPoint.OutputKind[0]) 424 ) 425 .withOptions(options -> options 426 .withLang(MicronautCodeGeneratorOptionsBuilder.GeneratorLanguage.valueOf(lang.toUpperCase(Locale.ENGLISH))) 427 .withApiPackage(apiPackageName) 428 .withModelPackage(modelPackageName) 429 .withInvokerPackage(invokerPackageName) 430 .withBeanValidation(useBeanValidation) 431 .withUseOneOfInterfaces(useOneOfInterfaces) 432 .withOptional(useOptional) 433 .withReactive(useReactive) 434 .withSerializationLibrary(SerializationLibraryKind.valueOf(serializationFramework.toUpperCase(Locale.ENGLISH))) 435 .withGenerateHttpResponseAlways(alwaysUseGenerateHttpResponse) 436 .withGenerateHttpResponseWhereRequired(generateHttpResponseWhereRequired) 437 .withDateTimeFormat(MicronautCodeGeneratorOptionsBuilder.DateTimeFormat.valueOf(dateTimeFormat.toUpperCase(Locale.ENGLISH))) 438 .withParameterMappings(parameterMappings.stream() 439 .map(mapping -> new io.micronaut.openapi.generator.ParameterMapping( 440 mapping.getName(), 441 io.micronaut.openapi.generator.ParameterMapping.ParameterLocation.valueOf( 442 mapping.getLocation().name() 443 ), 444 mapping.getMappedType(), 445 mapping.getMappedName(), 446 mapping.isValidated() 447 )) 448 .toList() 449 ) 450 .withResponseBodyMappings(responseBodyMappings.stream() 451 .map(mapping -> new io.micronaut.openapi.generator.ResponseBodyMapping( 452 mapping.getHeaderName(), 453 mapping.getMappedBodyType(), 454 mapping.isListWrapper(), 455 mapping.isValidated() 456 )) 457 .toList() 458 ) 459 .withSchemaMapping(schemaMapping) 460 .withImportMapping(importMapping) 461 .withNameMapping(nameMapping) 462 .withTypeMapping(typeMapping) 463 .withEnumNameMapping(enumNameMapping) 464 .withModelNameMapping(modelNameMapping) 465 .withInlineSchemaNameMapping(inlineSchemaNameMapping) 466 .withInlineSchemaOption(inlineSchemaOption) 467 .withOpenapiNormalizer(openapiNormalizer) 468 .withApiNamePrefix(apiNamePrefix != null ? apiNamePrefix : "") 469 .withApiNameSuffix(apiNameSuffix != null ? apiNameSuffix : "") 470 .withModelNamePrefix(modelNamePrefix != null ? modelNamePrefix : "") 471 .withModelNameSuffix(modelNameSuffix != null ? modelNameSuffix : "") 472 .withGenerateSwaggerAnnotations(generateSwaggerAnnotations) 473 .withImplicitHeaders(implicitHeaders) 474 .withImplicitHeadersRegex(implicitHeadersRegex != null ? implicitHeadersRegex : "") 475 .withUseEnumCaseInsensitive(useEnumCaseInsensitive) 476 .withAdditionalEnumTypeAnnotations(additionalEnumTypeAnnotations) 477 .withAdditionalModelTypeAnnotations(additionalModelTypeAnnotations) 478 .withAdditionalOneOfTypeAnnotations(additionalOneOfTypeAnnotations) 479 .withAdditionalProperties(additionalProperties) 480 481 .withUseJakartaEe(useJakartaEe) 482 .withSortParamsByRequiredFlag(sortParamsByRequiredFlag) 483 .withSkipOperationExample(skipOperationExample) 484 .withSkipSortingOperations(skipSortingOperations) 485 .withRemoveOperationIdPrefixDelimiter(removeOperationIdPrefixDelimiter) 486 .withRemoveOperationIdPrefixCount(removeOperationIdPrefixCount) 487 .withSortModelPropertiesByRequiredFlag(sortModelPropertiesByRequiredFlag) 488 .withEnsureUniqueParams(ensureUniqueParams) 489 .withAllowUnicodeIdentifiers(allowUnicodeIdentifiers) 490 .withPrependFormOrBodyParameters(prependFormOrBodyParameters) 491 492 .withUseSealed(useSealed) 493 .withJsonIncludeAlwaysForRequiredFields(jsonIncludeAlwaysForRequiredFields) 494 .withRequiredPropertiesInConstructor(requiredPropertiesInConstructor) 495 .withGenerateControllerAsAbstract(generateControllerAsAbstract) 496 ); 497 configureBuilder(builder); 498 builder.build().generate(); 499 } 500}