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 043 /** 044 * The OpenAPI specification file path relative to the project's root path. 045 */ 046 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".definition", defaultValue = IO_MICRONAUT_OPENAPI_PREFIX + ".invoker", required = true) 047 protected File definitionFile; 048 049 /** 050 * The name of the package that can be used for various classes required for invocation. 051 */ 052 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".invoker.package.name", defaultValue = IO_MICRONAUT_OPENAPI_PREFIX + ".invoker", required = true) 053 protected String invokerPackageName; 054 055 /** 056 * The package name for the APIs (controller interfaces). 057 */ 058 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".api.package.name", defaultValue = IO_MICRONAUT_OPENAPI_PREFIX + ".api", required = true) 059 protected String apiPackageName; 060 061 /** 062 * The package name for the model classes. 063 */ 064 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".model.package.name", defaultValue = IO_MICRONAUT_OPENAPI_PREFIX + ".model", required = true) 065 protected String modelPackageName; 066 067 /** 068 * Whether to generate validation annotations for models and APIs. 069 */ 070 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".use.bean.validation", defaultValue = "true", required = true) 071 protected boolean useBeanValidation; 072 073 /** 074 * Flag to indicate whether to use the utils.OneOfImplementorAdditionalData related logic. 075 */ 076 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".use.one.of.interfaces", defaultValue = "true", required = true) 077 protected boolean useOneOfInterfaces; 078 079 /** 080 * Whether to use {@link java.util.Optional} for non-required model properties and API parameters. 081 */ 082 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".use.optional", defaultValue = "false", required = true) 083 protected boolean useOptional; 084 085 /** 086 * Whether to use reactor types for operation responses. 087 */ 088 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".use.reactive", defaultValue = "true", required = true) 089 protected boolean useReactive; 090 091 /** 092 * Configure the serialization library. 093 */ 094 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".serialization.framework", defaultValue = "MICRONAUT_SERDE_JACKSON", required = true) 095 protected String serializationFramework; 096 097 /** 098 * If true, the generated operation return types will be wrapped in HttpResponse. 099 */ 100 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".always.use.generate.http.response", defaultValue = "false", required = true) 101 protected boolean alwaysUseGenerateHttpResponse; 102 103 /** 104 * Wrap the operations response in HttpResponse object where non-200 HTTP status codes or additional headers are defined. 105 */ 106 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".generate.http.response.where.required", defaultValue = "false", required = true) 107 protected boolean generateHttpResponseWhereRequired; 108 109 /** 110 * If set to true, generated code will be fully compatible with KSP, but not 100% with KAPT. 111 */ 112 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".ksp", defaultValue = "false", required = true) 113 protected boolean ksp; 114 115 /** 116 * Configure the date-time format. 117 */ 118 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".date.time.format", defaultValue = "ZONED_DATETIME", required = true) 119 protected String dateTimeFormat; 120 121 /** 122 * Comma-separated values of output kinds to generate. The values are defined by the 123 * {@link MicronautCodeGeneratorEntryPoint.OutputKind} enum. 124 */ 125 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".outputs", required = true, defaultValue = "apis,models,supporting_files") 126 protected List<String> outputKinds; 127 128 /** 129 * The output directory to which all the sources will be generated. 130 */ 131 @Parameter(defaultValue = "${project.build.directory}/generated-sources/openapi", required = true) 132 protected File outputDirectory; 133 134 /** 135 * Define parameter mappings that allow using custom types for parameter binding. 136 * See {@link ParameterMapping} for details. 137 */ 138 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".parameterMappings") 139 protected List<ParameterMapping> parameterMappings; 140 141 /** 142 * Define parameter mappings that allow using custom types for parameter binding. 143 * See {@link ResponseBodyMapping} for details. 144 */ 145 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".responseBodyMappings") 146 protected List<ResponseBodyMapping> responseBodyMappings; 147 148 /** 149 * Add the schema mappings. 150 */ 151 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".schemaMapping") 152 protected Map<String, String> schemaMapping; 153 154 /** 155 * Add the import mappings. 156 */ 157 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".importMapping") 158 protected Map<String, String> importMapping; 159 160 /** 161 * Add the name mappings. 162 */ 163 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".nameMapping") 164 protected Map<String, String> nameMapping; 165 166 /** 167 * Add the type mappings. 168 */ 169 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".typeMapping") 170 protected Map<String, String> typeMapping; 171 172 /** 173 * Add the enum name mappings. 174 */ 175 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".enumNameMapping") 176 protected Map<String, String> enumNameMapping; 177 178 /** 179 * Add the model name mappings. 180 */ 181 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".modelNameMapping") 182 protected Map<String, String> modelNameMapping; 183 184 /** 185 * Add the inline schema name mappings. 186 */ 187 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".inlineSchemaNameMapping") 188 protected Map<String, String> inlineSchemaNameMapping; 189 190 /** 191 * Add the inline schema options. 192 */ 193 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".inlineSchemaOption") 194 protected Map<String, String> inlineSchemaOption; 195 196 /** 197 * Add the OpenAPI normalizer options. 198 */ 199 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".openapiNormalizer") 200 protected Map<String, String> openapiNormalizer; 201 202 /** 203 * Set the api name prefix. 204 */ 205 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".apiNamePrefix") 206 protected String apiNamePrefix; 207 208 /** 209 * Set the api name suffix. 210 */ 211 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".apiNameSuffix") 212 protected String apiNameSuffix; 213 214 /** 215 * Set the model name prefix. 216 */ 217 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".modelNamePrefix") 218 protected String modelNamePrefix; 219 220 /** 221 * Set the model name suffix. 222 */ 223 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".modelNameSuffix") 224 protected String modelNameSuffix; 225 226 /** 227 * Allows specifying the language of the generated code. 228 * 229 * @since 4.3.0 230 */ 231 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".lang", defaultValue = "java") 232 protected String lang; 233 234 /** 235 * If set to true, the generated enums check enum value with ignoring case. 236 */ 237 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".useEnumCaseInsensitive", defaultValue = "false") 238 protected boolean useEnumCaseInsensitive; 239 240 /** 241 * Additional annotations for enum type (class level annotations). 242 */ 243 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".additionalEnumTypeAnnotations") 244 protected List<String> additionalEnumTypeAnnotations; 245 246 /** 247 * Additional annotations for model type (class level annotations). 248 */ 249 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".additionalModelTypeAnnotations") 250 protected List<String> additionalModelTypeAnnotations; 251 252 /** 253 * Additional annotations for oneOf interfaces (class level annotations). 254 */ 255 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".additionalOneOfTypeAnnotations") 256 protected List<String> additionalOneOfTypeAnnotations; 257 258 /** 259 * Additional generator properties. 260 */ 261 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".additionalProperties") 262 protected Map<String, Object> additionalProperties; 263 264 /** 265 * If set to true, controller and client method will be generated with openAPI annotations. 266 */ 267 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".generateSwaggerAnnotations", defaultValue = "false") 268 protected boolean generateSwaggerAnnotations; 269 270 /** 271 * Set the implicit headers flag. 272 */ 273 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".implicitHeaders", defaultValue = "false") 274 protected boolean implicitHeaders; 275 276 /** 277 * Set the implicit headers regex. 278 */ 279 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".implicitHeadersRegex") 280 protected String implicitHeadersRegex; 281 282 /** 283 * Flag to indicate whether to use the "jakarta" or "javax" package. 284 */ 285 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".useJakartaEe", defaultValue = "true") 286 protected boolean useJakartaEe = true; 287 288 /** 289 * Sort method arguments to place required parameters before optional parameters. 290 * Default: true 291 */ 292 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".sortParamsByRequiredFlag", defaultValue = "true") 293 protected boolean sortParamsByRequiredFlag = true; 294 295 /** 296 * Skip examples defined in operations to avoid out of memory errors. 297 * Default: false 298 */ 299 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".skipOperationExample") 300 protected boolean skipOperationExample; 301 302 /** 303 * Skip sorting operations. 304 * Default: false 305 */ 306 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".skipSortingOperations") 307 protected boolean skipSortingOperations; 308 309 /** 310 * Character to use as a delimiter for the prefix. Default: '_' 311 */ 312 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".removeOperationIdPrefixDelimiter", defaultValue = "_") 313 protected String removeOperationIdPrefixDelimiter = "_"; 314 315 /** 316 * Count of delimiter for the prefix. Use -1 for last. Default: 1 317 */ 318 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".removeOperationIdPrefixCount", defaultValue = "1") 319 protected int removeOperationIdPrefixCount = 1; 320 321 /** 322 * Sort model properties to place required parameters before optional parameters. 323 */ 324 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".sortModelPropertiesByRequiredFlag", defaultValue = "true") 325 protected boolean sortModelPropertiesByRequiredFlag = true; 326 327 /** 328 * Whether to ensure parameter names are unique in an operation (rename parameters that are not). 329 */ 330 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".ensureUniqueParams", defaultValue = "true") 331 protected boolean ensureUniqueParams = true; 332 333 /** 334 * boolean, toggles whether Unicode identifiers are allowed in names or not, default is false. 335 */ 336 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".allowUnicodeIdentifiers") 337 protected boolean allowUnicodeIdentifiers; 338 339 /** 340 * Add form or body parameters to the beginning of the parameter list. 341 */ 342 @Parameter(property = MICRONAUT_OPENAPI_PREFIX + ".prependFormOrBodyParameters") 343 protected boolean prependFormOrBodyParameters; 344 345 @Parameter(defaultValue = "${project}", readonly = true) 346 protected MavenProject project; 347 348 /** 349 * Determines if this mojo must be executed. 350 * 351 * @return true if the mojo is enabled 352 */ 353 protected abstract boolean isEnabled(); 354 355 /** 356 * Configures the OpenAPI generator. When this method is called, 357 * common properties shared by all generators have already been 358 * configured, so this method should only take care of configuring 359 * the generator specific parameters. 360 * 361 * @param builder the generator configuration builder 362 */ 363 protected abstract void configureBuilder(MicronautCodeGeneratorBuilder builder) throws MojoExecutionException; 364 365 @Override 366 public final void execute() throws MojoExecutionException, MojoFailureException { 367 if (!isEnabled()) { 368 getLog().debug(this.getClass().getSimpleName() + " is disabled"); 369 return; 370 } 371 372 project.addCompileSourceRoot(outputDirectory.getAbsolutePath()); 373 var builder = MicronautCodeGeneratorEntryPoint.builder() 374 .withDefinitionFile(definitionFile.toURI()) 375 .withOutputDirectory(outputDirectory) 376 .withOutputs( 377 outputKinds.stream().map(String::toUpperCase).map(MicronautCodeGeneratorEntryPoint.OutputKind::valueOf).toList().toArray(new MicronautCodeGeneratorEntryPoint.OutputKind[0]) 378 ) 379 .withOptions(options -> options 380 .withLang(MicronautCodeGeneratorOptionsBuilder.GeneratorLanguage.valueOf(lang.toUpperCase(Locale.ENGLISH))) 381 .withApiPackage(apiPackageName) 382 .withModelPackage(modelPackageName) 383 .withInvokerPackage(invokerPackageName) 384 .withBeanValidation(useBeanValidation) 385 .withUseOneOfInterfaces(useOneOfInterfaces) 386 .withOptional(useOptional) 387 .withReactive(useReactive) 388 .withSerializationLibrary(SerializationLibraryKind.valueOf(serializationFramework.toUpperCase(Locale.ENGLISH))) 389 .withGenerateHttpResponseAlways(alwaysUseGenerateHttpResponse) 390 .withGenerateHttpResponseWhereRequired(generateHttpResponseWhereRequired) 391 .withDateTimeFormat(MicronautCodeGeneratorOptionsBuilder.DateTimeFormat.valueOf(dateTimeFormat.toUpperCase(Locale.ENGLISH))) 392 .withParameterMappings(parameterMappings.stream() 393 .map(mapping -> new io.micronaut.openapi.generator.ParameterMapping( 394 mapping.getName(), 395 io.micronaut.openapi.generator.ParameterMapping.ParameterLocation.valueOf( 396 mapping.getLocation().name() 397 ), 398 mapping.getMappedType(), 399 mapping.getMappedName(), 400 mapping.isValidated() 401 )) 402 .toList() 403 ) 404 .withResponseBodyMappings(responseBodyMappings.stream() 405 .map(mapping -> new io.micronaut.openapi.generator.ResponseBodyMapping( 406 mapping.getHeaderName(), 407 mapping.getMappedBodyType(), 408 mapping.isListWrapper(), 409 mapping.isValidated() 410 )) 411 .toList() 412 ) 413 .withSchemaMapping(schemaMapping) 414 .withImportMapping(importMapping) 415 .withNameMapping(nameMapping) 416 .withTypeMapping(typeMapping) 417 .withEnumNameMapping(enumNameMapping) 418 .withModelNameMapping(modelNameMapping) 419 .withInlineSchemaNameMapping(inlineSchemaNameMapping) 420 .withInlineSchemaOption(inlineSchemaOption) 421 .withOpenapiNormalizer(openapiNormalizer) 422 .withApiNamePrefix(apiNamePrefix != null ? apiNamePrefix : "") 423 .withApiNameSuffix(apiNameSuffix != null ? apiNameSuffix : "") 424 .withModelNamePrefix(modelNamePrefix != null ? modelNamePrefix : "") 425 .withModelNameSuffix(modelNameSuffix != null ? modelNameSuffix : "") 426 .withGenerateSwaggerAnnotations(generateSwaggerAnnotations) 427 .withImplicitHeaders(implicitHeaders) 428 .withImplicitHeadersRegex(implicitHeadersRegex != null ? implicitHeadersRegex : "") 429 .withUseEnumCaseInsensitive(useEnumCaseInsensitive) 430 .withAdditionalEnumTypeAnnotations(additionalEnumTypeAnnotations) 431 .withAdditionalModelTypeAnnotations(additionalModelTypeAnnotations) 432 .withAdditionalOneOfTypeAnnotations(additionalOneOfTypeAnnotations) 433 .withAdditionalProperties(additionalProperties) 434 435 .withUseJakartaEe(useJakartaEe) 436 .withSortParamsByRequiredFlag(sortParamsByRequiredFlag) 437 .withSkipOperationExample(skipOperationExample) 438 .withSkipSortingOperations(skipSortingOperations) 439 .withRemoveOperationIdPrefixDelimiter(removeOperationIdPrefixDelimiter) 440 .withRemoveOperationIdPrefixCount(removeOperationIdPrefixCount) 441 .withSortModelPropertiesByRequiredFlag(sortModelPropertiesByRequiredFlag) 442 .withEnsureUniqueParams(ensureUniqueParams) 443 .withAllowUnicodeIdentifiers(allowUnicodeIdentifiers) 444 .withPrependFormOrBodyParameters(prependFormOrBodyParameters) 445 ); 446 configureBuilder(builder); 447 builder.build().generate(); 448 } 449}