diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaHelidonCommonCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaHelidonCommonCodegen.java new file mode 100644 index 000000000000..53beef1e21ce --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaHelidonCommonCodegen.java @@ -0,0 +1,259 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openapitools.codegen.languages; + +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.stream.Collectors; + +import org.openapitools.codegen.CliOption; +import org.openapitools.codegen.CodegenConstants; +import org.openapitools.codegen.SupportingFile; +import org.openapitools.codegen.languages.features.BeanValidationFeatures; +import org.openapitools.codegen.languages.features.PerformBeanValidationFeatures; + +import static org.openapitools.codegen.CodegenConstants.DEVELOPER_EMAIL; +import static org.openapitools.codegen.CodegenConstants.DEVELOPER_NAME; +import static org.openapitools.codegen.CodegenConstants.DEVELOPER_ORGANIZATION; +import static org.openapitools.codegen.CodegenConstants.DEVELOPER_ORGANIZATION_URL; +import static org.openapitools.codegen.CodegenConstants.PARENT_ARTIFACT_ID; +import static org.openapitools.codegen.CodegenConstants.PARENT_GROUP_ID; +import static org.openapitools.codegen.CodegenConstants.PARENT_VERSION; +import static org.openapitools.codegen.CodegenConstants.SCM_CONNECTION; +import static org.openapitools.codegen.CodegenConstants.SCM_DEVELOPER_CONNECTION; +import static org.openapitools.codegen.CodegenConstants.SCM_URL; + +public abstract class JavaHelidonCommonCodegen extends AbstractJavaCodegen + implements BeanValidationFeatures, PerformBeanValidationFeatures { + + static final String HELIDON_MP = "mp"; + static final String HELIDON_SE = "se"; + + static final String HELIDON_NIMA = "nima"; + static final String HELIDON_NIMA_ANNOTATIONS = "nima-annotations"; + + static final String MICROPROFILE_ROOT_PACKAGE = "rootJavaEEPackage"; + static final String MICROPROFILE_ROOT_DEP_PREFIX = "x-helidon-rootJavaEEDepPrefix"; + static final String MICROPROFILE_ROOT_PACKAGE_DESC = "Root package name for Java EE"; + static final String MICROPROFILE_ROOT_PACKAGE_JAVAX = "javax"; + static final String MICROPROFILE_ROOT_PACKAGE_JAKARTA = "jakarta"; + private static final String VALIDATION_ARTIFACT_PREFIX_KEY = "x-helidon-validationArtifactPrefix"; + private static final String VALIDATION_ARTIFACT_PREFIX_JAVAX = ""; + private static final String VALIDATION_ARTIFACT_PREFIX_JAKARTA = MICROPROFILE_ROOT_PACKAGE_JAKARTA + "."; + + // for generated doc + static final String MICROPROFILE_ROOT_PACKAGE_DEFAULT = + "Helidon 2.x and earlier: " + MICROPROFILE_ROOT_PACKAGE_JAVAX + + "; Helidon 3.x and later: " + MICROPROFILE_ROOT_PACKAGE_JAKARTA; + + static final String SERIALIZATION_LIBRARY_JACKSON = "jackson"; + static final String SERIALIZATION_LIBRARY_JSONB = "jsonb"; + + public static final String HELIDON_VERSION = "helidonVersion"; + public static final String DEFAULT_HELIDON_VERSION = "3.0.1"; + static final String HELIDON_VERSION_DESC = "Helidon version for generated code"; + + static final String FULL_PROJECT = "fullProject"; + static final String FULL_PROJECT_DESC = "If set to true, it will generate all files; if set to false, " + + "it will only generate API files. If unspecified, the behavior depends on whether a project " + + "exists or not: if it does not, same as true; if it does, same as false. Note that test files " + + "are never overwritten."; + + private String helidonVersion; + private String rootJavaEEPackage; + private String rootJavaEEDepPrefix; + + public JavaHelidonCommonCodegen() { + super(); + cliOptions.add(new CliOption(HELIDON_VERSION, HELIDON_VERSION_DESC) + .defaultValue(DEFAULT_HELIDON_VERSION)); + cliOptions.add(new CliOption(MICROPROFILE_ROOT_PACKAGE, MICROPROFILE_ROOT_PACKAGE_DESC) + .defaultValue(MICROPROFILE_ROOT_PACKAGE_DEFAULT)); + cliOptions.add(new CliOption(FULL_PROJECT, FULL_PROJECT_DESC) + .defaultValue("")); // depends on project state + } + + @Override + public void processOpts() { + super.processOpts(); + + String userHelidonVersion = ""; + String userParentVersion = ""; + + if (additionalProperties.containsKey(CodegenConstants.PARENT_VERSION)) { + userParentVersion = additionalProperties.get(CodegenConstants.PARENT_VERSION).toString(); + } + + if (additionalProperties.containsKey(HELIDON_VERSION)) { + userHelidonVersion = additionalProperties.get(HELIDON_VERSION).toString(); + } + + if (!userHelidonVersion.isEmpty()) { + if (!userParentVersion.isEmpty() && !userHelidonVersion.equals(userParentVersion)) { + throw new IllegalArgumentException( + String.format(Locale.ROOT, + "Both %s and %s properties were set with different value.", + CodegenConstants.PARENT_VERSION, + HELIDON_VERSION)); + } + setHelidonVersion(userHelidonVersion); + } else if (!userParentVersion.isEmpty()) { + setHelidonVersion(userParentVersion); + } else { + setHelidonVersion(DEFAULT_HELIDON_VERSION); + } + + additionalProperties.put(HELIDON_VERSION, helidonVersion); + setEEPackageAndDependencies(helidonVersion); + } + + /** + * Remove set of options not currently used by any Helidon generator. Should be + * called during construction but only on leaf classes. + */ + protected void removeUnusedOptions() { + removeCliOptions(SCM_CONNECTION, + SCM_DEVELOPER_CONNECTION, + SCM_URL, + DEVELOPER_NAME, + DEVELOPER_ORGANIZATION, + DEVELOPER_ORGANIZATION_URL, + DEVELOPER_EMAIL, + PARENT_ARTIFACT_ID, + PARENT_VERSION, + PARENT_GROUP_ID, + DISABLE_HTML_ESCAPING); + } + + /** + * Determine whether to generate or overwrite files depending on fullProject property. + * If property is unspecified, then check if sources are already there and avoid overwriting + * modifiable files. + * + * @param modifiable list of modifiable files to be processed + * @param unmodifiable list of unmodifiable files to be processed + */ + protected void processSupportingFiles(List modifiable, List unmodifiable) { + Boolean fullProject = !additionalProperties.containsKey(FULL_PROJECT) ? null : + Boolean.parseBoolean(additionalProperties.get(FULL_PROJECT).toString()); + + if (fullProject == null && !projectFilesExist()) { // not explicitly set + supportingFiles.addAll(modifiable); + } else if (Boolean.TRUE.equals(fullProject)) { // explicitly set to true + supportingFiles.addAll(modifiable); + } + supportingFiles.addAll(unmodifiable); + } + + /** + * Check if project is already generated to determine default for the fullProject + * flag. Can be overridden in subclasses to strengthen test condition. + * + * @return outcome of test + */ + protected boolean projectFilesExist() { + return Paths.get(getOutputTestFolder()).toFile().exists(); + } + + protected String rootJavaEEPackage() { + return rootJavaEEPackage; + } + + private void setHelidonVersion(String version) { + helidonVersion = version; + setParentVersion(version); + } + + private void setEEPackageAndDependencies(String version) { + + rootJavaEEPackage = checkAndSelectRootEEPackage(version); + additionalProperties.put(MICROPROFILE_ROOT_PACKAGE, rootJavaEEPackage); + + rootJavaEEDepPrefix = checkAndSelectRootEEDepPrefix(version); + additionalProperties.put(MICROPROFILE_ROOT_DEP_PREFIX, rootJavaEEDepPrefix); + + additionalProperties.put(VALIDATION_ARTIFACT_PREFIX_KEY, + rootJavaEEDepPrefix.equals(MICROPROFILE_ROOT_PACKAGE_JAVAX) + ? VALIDATION_ARTIFACT_PREFIX_JAVAX + : VALIDATION_ARTIFACT_PREFIX_JAKARTA); + } + + private String checkAndSelectRootEEPackage(String version) { + String packagePrefixImpliedByVersion = usesJakartaPackages(version) + ? MICROPROFILE_ROOT_PACKAGE_JAKARTA + : MICROPROFILE_ROOT_PACKAGE_JAVAX; + + // Make sure any user-specified root EE package is correct for the chosen Helidon version. + if (additionalProperties.containsKey(MICROPROFILE_ROOT_PACKAGE)) { + String userRootEEPackage = additionalProperties.get(MICROPROFILE_ROOT_PACKAGE).toString(); + if (!packagePrefixImpliedByVersion.equals(userRootEEPackage)) { + throw new IllegalArgumentException( + String.format(Locale.ROOT, + "Helidon version %s uses the %s namespace but options specified '%s'", + version, + packagePrefixImpliedByVersion, + userRootEEPackage)); + } + return userRootEEPackage; + } + + // No explicit setting for the root EE package. + return packagePrefixImpliedByVersion; + } + + private String checkAndSelectRootEEDepPrefix(String version) { + String mavenDepPrefixImpliedByVersion = usesJakartaPrefix(version) + ? MICROPROFILE_ROOT_PACKAGE_JAKARTA + : MICROPROFILE_ROOT_PACKAGE_JAVAX; + + // Make sure any user-specified prefix is correct for the chosen Helidon version. + if (additionalProperties.containsKey(MICROPROFILE_ROOT_DEP_PREFIX)) { + String userMavenDepPrefix = additionalProperties.get(MICROPROFILE_ROOT_DEP_PREFIX).toString(); + if (!mavenDepPrefixImpliedByVersion.equals(userMavenDepPrefix)) { + throw new IllegalArgumentException( + String.format(Locale.ROOT, + "Helidon version %s uses the %s prefix for EE dependencies but options specified '%s'", + version, + mavenDepPrefixImpliedByVersion, + userMavenDepPrefix)); + } + return userMavenDepPrefix; + } + + // No explicit setting for the dependency prefix. + return mavenDepPrefixImpliedByVersion; + } + + private boolean usesJakartaPackages(String version) { + return !version.startsWith("2.") && !version.startsWith("1."); + } + + private boolean usesJakartaPrefix(String version) { + return !version.startsWith("1."); + } + + protected void removeCliOptions(String... opt) { + List opts = Arrays.asList(opt); + Set forRemoval = cliOptions.stream() + .filter(cliOption -> opts.contains(cliOption.getOpt())) + .collect(Collectors.toSet()); + forRemoval.forEach(cliOptions::remove); + } +} diff --git a/modules/openapi-generator/src/main/resources/java-helidon/common/README.mustache b/modules/openapi-generator/src/main/resources/java-helidon/common/README.mustache new file mode 100644 index 000000000000..961c4207f436 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/java-helidon/common/README.mustache @@ -0,0 +1,9 @@ +# {{appName}} + +{{#appDescriptionWithNewLines}} +{{{.}}} + +{{/appDescriptionWithNewLines}} + +## Overview +This project was generated using the Helidon OpenAPI Generator. diff --git a/modules/openapi-generator/src/main/resources/java-helidon/common/gitignore.mustache b/modules/openapi-generator/src/main/resources/java-helidon/common/gitignore.mustache new file mode 100644 index 000000000000..a530464afa1b --- /dev/null +++ b/modules/openapi-generator/src/main/resources/java-helidon/common/gitignore.mustache @@ -0,0 +1,21 @@ +*.class + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.ear + +# exclude jar for gradle wrapper +!gradle/wrapper/*.jar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +# build files +**/target +target +.gradle +build diff --git a/modules/openapi-generator/src/main/resources/java-helidon/common/licenseInfo.mustache b/modules/openapi-generator/src/main/resources/java-helidon/common/licenseInfo.mustache new file mode 100644 index 000000000000..be193d0c4fe8 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/java-helidon/common/licenseInfo.mustache @@ -0,0 +1,11 @@ +/** + * {{{appName}}} + * {{{appDescription}}} + * + * {{#version}}The version of the OpenAPI document: {{{.}}}{{/version}} + * {{#infoEmail}}Contact: {{{.}}}{{/infoEmail}} + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ diff --git a/modules/openapi-generator/src/main/resources/java-helidon/common/model.mustache b/modules/openapi-generator/src/main/resources/java-helidon/common/model.mustache new file mode 100644 index 000000000000..00a3c3db1315 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/java-helidon/common/model.mustache @@ -0,0 +1,23 @@ +{{>licenseInfo}} +package {{package}}; + +{{#imports}}import {{import}}; +{{/imports}} +{{#serializableModel}} +import java.io.Serializable; +{{/serializableModel}} +{{#useBeanValidation}} +import {{rootJavaEEPackage}}.validation.constraints.*; +import {{rootJavaEEPackage}}.validation.Valid; +{{/useBeanValidation}} + +{{#models}} +{{#model}} +{{#isEnum}} +{{>enumOuterClass}} +{{/isEnum}} +{{^isEnum}} +{{>pojo}} +{{/isEnum}} +{{/model}} +{{/models}} diff --git a/modules/openapi-generator/src/main/resources/java-helidon/common/pojo.mustache b/modules/openapi-generator/src/main/resources/java-helidon/common/pojo.mustache new file mode 100644 index 000000000000..944ae482956d --- /dev/null +++ b/modules/openapi-generator/src/main/resources/java-helidon/common/pojo.mustache @@ -0,0 +1,121 @@ +{{#jsonb}} +import java.lang.reflect.Type; +import {{rootJavaEEPackage}}.json.bind.annotation.JsonbTypeDeserializer; +import {{rootJavaEEPackage}}.json.bind.annotation.JsonbTypeSerializer; +import {{rootJavaEEPackage}}.json.bind.serializer.DeserializationContext; +import {{rootJavaEEPackage}}.json.bind.serializer.JsonbDeserializer; +import {{rootJavaEEPackage}}.json.bind.serializer.JsonbSerializer; +import {{rootJavaEEPackage}}.json.bind.serializer.SerializationContext; +import {{rootJavaEEPackage}}.json.stream.JsonGenerator; +import {{rootJavaEEPackage}}.json.stream.JsonParser; +import {{rootJavaEEPackage}}.json.bind.annotation.JsonbProperty; +{{#vendorExtensions.x-has-readonly-properties}} +import {{rootJavaEEPackage}}.json.bind.annotation.JsonbCreator; +{{/vendorExtensions.x-has-readonly-properties}} +{{/jsonb}} + +{{#description}} +/** + * {{{.}}} + */{{/description}} +public class {{classname}} {{#parent}}extends {{{.}}}{{/parent}} {{#vendorExtensions.x-implements}}{{#-first}}implements {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} { + +{{#vars}} + {{#isEnum}} + + {{^isContainer}} +{{>enumClass}} + {{/isContainer}} + {{#isContainer}} + {{#mostInnerItems}} +{{>enumClass}} + {{/mostInnerItems}} + {{/isContainer}} + + {{/isEnum}} + private {{{datatypeWithEnum}}} {{{name}}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}; +{{/vars}} + + /** + * Default constructor. + */ + public {{classname}}() { + // JSON-B / Jackson + } + + /** + * Create {{classname}}. + * +{{#vars}} + * @param {{name}} {{description}}{{^description}}{{name}}{{/description}} +{{/vars}} + */ + public {{classname}}( +{{#vars}} + {{{datatypeWithEnum}}} {{name}}{{^-last}}, {{/-last}} +{{/vars}} + ) { +{{#vars}} + this.{{name}} = {{name}}; +{{/vars}} + } + +{{#vars}}{{#vendorExtensions.x-has-readonly-properties}}{{#jsonb}} + @JsonbCreator + public {{classname}}( + {{#readOnlyVars}} + @JsonbProperty("{{baseName}}") {{{datatypeWithEnum}}} {{name}}{{^-last}}, {{/-last}} + {{/readOnlyVars}} + ) { + {{#readOnlyVars}} + this.{{name}} = {{name}}; + {{/readOnlyVars}} + }{{/jsonb}}{{/vendorExtensions.x-has-readonly-properties}} + + /** + {{#description}} + * {{{.}}} + {{/description}} + {{^description}} + * Get {{name}} + {{/description}} + {{#minimum}} + * minimum: {{.}} + {{/minimum}} + {{#maximum}} + * maximum: {{.}} + {{/maximum}} + * @return {{name}} + */ + public {{{datatypeWithEnum}}} {{getter}}() { + return {{name}}; + } + + public void {{setter}}({{{datatypeWithEnum}}} {{name}}) { + this.{{name}} = {{name}}; + }{{/vars}} + + /** + * Create a string representation of this pojo. + **/ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class {{classname}} {\n"); + {{#parent}}sb.append(" ").append(toIndentedString(super.toString())).append("\n");{{/parent}} + {{#vars}}sb.append(" {{name}}: ").append(toIndentedString({{name}})).append("\n"); + {{/vars}}sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private static String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } +}