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}