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}