org.openapitools.codegen.languages.JavaHelidonCommonCodegen Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2022, 2024 OpenAPI-Generator Contributors (https://openapi-generator.tech)
* Copyright (c) 2022, 2024 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.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URL;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.locks.ReentrantLock;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.parameters.Parameter;
import io.swagger.v3.oas.models.responses.ApiResponse;
import io.swagger.v3.oas.models.servers.Server;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.aether.util.version.GenericVersionScheme;
import org.eclipse.aether.version.InvalidVersionSpecificationException;
import org.eclipse.aether.version.Version;
import org.eclipse.aether.version.VersionConstraint;
import org.eclipse.aether.version.VersionScheme;
import org.openapitools.codegen.CliOption;
import org.openapitools.codegen.CodegenConstants;
import org.openapitools.codegen.CodegenOperation;
import org.openapitools.codegen.CodegenParameter;
import org.openapitools.codegen.CodegenProperty;
import org.openapitools.codegen.CodegenResponse;
import org.openapitools.codegen.SupportingFile;
import org.openapitools.codegen.languages.features.BeanValidationFeatures;
import org.openapitools.codegen.languages.features.PerformBeanValidationFeatures;
import org.openapitools.codegen.model.ModelMap;
import org.openapitools.codegen.model.OperationMap;
import org.openapitools.codegen.model.OperationsMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 MICROPROFILE_ROOT_PACKAGE = "rootJavaEEPackage";
static final String MICROPROFILE_ROOT_DEP_PREFIX = "x-helidon-rootJavaEEDepPrefix";
static final String X_USE_MP_TESTING = "x-helidon-useMpTesting";
static final String X_USE_SMALLRYE_JANDEX_PLUGIN = "x-helidon-useSmallRyeJandexPlugin";
static final String X_HAS_RESPONSE_PROPS = "x-helidon-hasResponseProps";
static final String X_ALL_RESPONSE_PROPS = "x-helidon-allResponseProps";
static final String X_OPTIONAL_RESPONSE_PROPS = "x-helidon-optionalResponseProps";
static final String X_REQUIRED_RESPONSE_PROPS = "x-helidon-requiredResponseProps";
static final String X_HAS_REQUIRED_RESPONSE_PROPS = "x-helidon-hasRequiredResponseProps";
static final String X_RESULT_BUILDER_NEEDS_CTOR = "x-helidon-resultBuilderNeedsCtor";
static final String X_IS_MULTIPART_FORM_PARAM = "x-helidon-isMultipartFormParam";
static final String X_HAS_RETURN_TYPE = "x-helidon-hasReturnType";
static final String X_RETURN_TYPE_EXAMPLE_VALUE = "x-helidon-exampleReturnTypeValue";
static final String X_MEDIA_SUPPORT_PACKAGE_PREFIX = "x-helidon-media-support-package-prefix";
static final String X_MEDIA_COMMON_MEDIA_TYPE_PACKAGE_PREFIX = "x-helidon-common-media-type-package-prefix";
static final String X_USE_REACTIVE = "x-helidon-use-reactive";
static final String X_USE_OPTIONAL = "x-helidon-use-optional";
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";
static final String HELIDON_ENUM_CLASS = "x-helidon-hasEnumClass";
private static final String VALIDATION_ARTIFACT_PREFIX_KEY = "x-helidon-validationArtifactPrefix";
private static final String X_PATH_SUFFIX = "x-helidon-pathSuffix";
private static final String X_FIXUP_PATH = "x-helidon-fixupPath";
private static final String VALIDATION_ARTIFACT_PREFIX_JAVAX = "";
private static final String VALIDATION_ARTIFACT_PREFIX_JAKARTA = MICROPROFILE_ROOT_PACKAGE_JAKARTA + ".";
private static final Map EXAMPLE_RETURN_VALUES = new HashMap();
private final Logger LOGGER = LoggerFactory.getLogger(JavaHelidonCommonCodegen.class);
// 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";
// Helidon 3 featured reactive style; more recent releases feature synchronous.
static final String V3_STYLE = "x-helidon-v3";
static final String HELIDON_VERSION_DESC = "Helidon complete version identifier or major version number. "
+ "The specified exact Helidon release or, if specified as a major version the latest release of that major version, "
+ " is used in the generated code.";
static final String X_HELIDON_USE_OPTIONAL = "x-helidon-useOptional";
static final String X_HELIDON_USE_OPTIONAL_DESC = "Wrap optional parameters in an Optional (Helidon 4 and later)";
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.";
static final String HELIDON_GROUP_BY = "x-helidon-groupBy";
static final String HELIDON_GROUP_BY_DESC = "Selects how to group operations into APIs";
protected String helidonVersion;
protected int helidonMajorVersion;
protected boolean useReactive;
protected boolean useOptional = true; // effective with Helidon 4
protected final GenericTypeDeclarations genericTypeDeclarations = new GenericTypeDeclarations();
protected Map> tagToOperations;
private String rootJavaEEPackage;
private String rootJavaEEDepPrefix;
private String mpTestsGroup;
private String mpTestsArtifact;
private String jandexGroup;
private String jandexArtifact;
private final Map knownHttpStatusMap;
protected GroupBy groupBy = GroupBy.DEFAULT;
protected enum GroupBy {
TAGS("tags", "Use the 'tags' settings on each operation"),
FIRST_PATH_SEGMENT("first-path-segment", "Use the first segment of the path");
static GroupBy DEFAULT = TAGS;
static final Map OPTION_VALUES = Arrays.stream(values())
.collect(HashMap::new, (map, value) -> map.put(value.groupingName, value.desc), Map::putAll);
static GroupBy from(String value) {
return Arrays.stream(values())
.filter(candidate -> candidate.groupingName.equals(value))
.findFirst()
.orElseThrow();
}
private final String groupingName;
private final String desc;
GroupBy(String groupingName, String desc) {
this.groupingName = groupingName;
this.desc = desc;
}
}
public static String defaultHelidonVersion() {
return VersionUtil.instance().defaultVersion();
}
public static String chooseVersion(String requestedVersionPrefix) {
return VersionUtil.instance().chooseVersion(requestedVersionPrefix);
}
public JavaHelidonCommonCodegen() {
super();
EXAMPLE_RETURN_VALUES.put("set", "Set");
EXAMPLE_RETURN_VALUES.put("array", "List");
EXAMPLE_RETURN_VALUES.put("map", "Map");
cliOptions.add(new CliOption(HELIDON_VERSION, HELIDON_VERSION_DESC)
.defaultValue("Highest released 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
cliOptions.add(new CliOption(X_HELIDON_USE_OPTIONAL, X_HELIDON_USE_OPTIONAL_DESC)
.defaultValue("true"));
addOption(HELIDON_GROUP_BY, HELIDON_GROUP_BY_DESC, GroupBy.DEFAULT.groupingName, GroupBy.OPTION_VALUES);
knownHttpStatusMap = loadKnownHttpStatusMap();
}
@Override
public void processOpts() {
super.processOpts();
importMapping.put("Headers", helidonMajorVersion == 3 ? "io.helidon.http.common.Headers" : "io.helidon.http.Headers");
importMapping.put("Optional", "java.util.Optional");
importMapping.put("Collectors", "java.util.stream.Collectors");
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(VersionUtil.instance().defaultVersion());
}
if (helidonMajorVersion > 3) {
importMapping.put("Status", "io.helidon.http.Status");
importMapping.put("HexFormat", "java.util.HexFormat");
}
additionalProperties.put(HELIDON_VERSION, helidonVersion);
additionalProperties.put(V3_STYLE, (helidonMajorVersion == 3));
setEEPackageAndDependencies(helidonVersion);
setMpTestDependency(helidonVersion);
setJandexPluginDependency(helidonVersion);
setMediaPackageInfo();
setUseReactive();
setUseOptional();
convertPropertyToTypeAndWriteBack(HELIDON_GROUP_BY, GroupBy::from, (value) -> this.groupBy = value);
}
@Override
public void addOperationToGroup(String tag,
String resourcePath,
Operation operation,
CodegenOperation co,
Map> operations) {
if (helidonMajorVersion <= 3) {
super.addOperationToGroup(tag, resourcePath, operation, co, operations);
return;
}
/*
By convention, the operation's baseName is assigned the name of the group (API) in which it is placed.
*/
String pathPrefixWithoutLeadingSlash = StringUtils.substringBefore(StringUtils.removeStart(resourcePath, "/"),
"/");
String groupName = (groupBy == GroupBy.TAGS)
? tag
: sanitizeName(pathPrefixWithoutLeadingSlash);
if (groupName.isEmpty()) {
groupName = "Default";
}
co.baseName = groupName;
// Invoke the superclass with our chosen group name as the "tag".
super.addOperationToGroup(groupName, resourcePath, operation, co, operations);
}
@Override
public CodegenOperation fromOperation(String path, String httpMethod, Operation operation, List servers) {
CodegenOperation op = super.fromOperation(path, httpMethod, operation, servers);
op.allParams.forEach(p -> p.vendorExtensions.put(X_IS_MULTIPART_FORM_PARAM, op.isMultipart && p.isFormParam));
op.vendorExtensions.put(X_HAS_RETURN_TYPE, op.returnType != null && !op.returnType.equals("void"));
op.vendorExtensions.put(X_RETURN_TYPE_EXAMPLE_VALUE, chooseExampleReturnTypeValue(op));
return op;
}
@Override
public CodegenParameter fromParameter(Parameter parameter, Set imports) {
CodegenParameter result = super.fromParameter(parameter, imports);
if (!result.required && helidonMajorVersion > 3) {
imports.add("Optional");
}
if (helidonMajorVersion > 3 && result.containerTypeMapped != null) {
imports.add(result.containerTypeMapped);
}
if (result.items != null && result.items.getRef() != null) {
result.vendorExtensions.put(HELIDON_ENUM_CLASS, true);
}
return result;
}
@Override
public CodegenResponse fromResponse(String responseCode, ApiResponse response) {
CodegenResponse result = super.fromResponse(responseCode, response);
result.vendorExtensions.put(X_HAS_RESPONSE_PROPS, result.hasHeaders || result.dataType != null);
List allResponseProps = new ArrayList<>(result.headers);
List requiredResponseProps = new ArrayList<>();
List optionalResponseProps = new ArrayList<>();
if (result.returnProperty != null) {
allResponseProps.add(result.returnProperty);
}
result.vendorExtensions.put(X_ALL_RESPONSE_PROPS, allResponseProps);
for (CodegenProperty responseProp : allResponseProps) {
if (responseProp.required) {
requiredResponseProps.add(responseProp);
} else {
optionalResponseProps.add(responseProp);
}
}
result.vendorExtensions.put(X_REQUIRED_RESPONSE_PROPS, requiredResponseProps);
result.vendorExtensions.put(X_OPTIONAL_RESPONSE_PROPS, optionalResponseProps);
result.vendorExtensions.put(X_HAS_REQUIRED_RESPONSE_PROPS, !allResponseProps.equals(optionalResponseProps));
result.vendorExtensions.put(X_RESULT_BUILDER_NEEDS_CTOR, result.isDefault || !requiredResponseProps.isEmpty());
return result;
}
@Override
public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List allModels) {
/*
Scan the paths of all the operations, computing the longest common prefix. Then compute and set the path suffix
for each operation.
*/
String commonPathPrefixForApi = StringUtils.getCommonPrefix(objs.getOperations().getOperation()
.stream()
.map(op -> op.path)
.map(path -> path.charAt(0) != '/' ? "/" + path : path )
.toArray(String[]::new));
String commonPathPrefix;
if (commonPathPrefixForApi.equals("/")) {
commonPathPrefix = "/";
objs.getOperations().put(X_FIXUP_PATH, "TODO - fix path or operation grouping for better performance");
} else {
int end = commonPathPrefixForApi.charAt(commonPathPrefixForApi.length() - 1) == '/'
? commonPathPrefixForApi.length() - 1
: commonPathPrefixForApi.length();
commonPathPrefix = commonPathPrefixForApi.substring(0, end);
}
objs.getOperations().setPathPrefix(commonPathPrefix);
List mismatchedOperations = new ArrayList<>();
objs.getOperations().getOperation().forEach(op -> {
if (commonPathPrefix.length() > 1) {
op.vendorExtensions.put(X_PATH_SUFFIX,
op.path.equals(commonPathPrefix) ? "/" : op.path.substring(commonPathPrefix.length()));
} else {
op.vendorExtensions.put(X_PATH_SUFFIX, op.path);
mismatchedOperations.add("operation = " + op.operationId + ", path = " + op.path);
}});
if (!mismatchedOperations.isEmpty()) {
LOGGER.warn(
"Grouping operations by tag has placed operations into API '{}' for which the generated "
+ "routing works inefficiently because the operation paths do not share a common path prefix. "
+ "Consider specifying the generator option '"
+ HELIDON_GROUP_BY
+ " {}' or changing the tag settings or path in your OpenAPI document for the following operations: {}{}",
objs.getOperations().getClassname(),
GroupBy.FIRST_PATH_SEGMENT.groupingName,
System.lineSeparator(),
mismatchedOperations);
}
return objs;
}
protected String statusDeclaration(String code) {
return "Status." + knownHttpStatusMap.getOrDefault(code, ".create(" + code + ")");
}
/**
* 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;
}
/**
* Prepares a map of predefined HTTP status code constants.
*
* Helidon uses its own HTTP status type, and the Helidon code predefines many HTTP status code constants but also allows
* ad hoc creation of other values based on the numeric status value. It's more efficient at runtime to use a constant
* if it exists.
*
* This method scans a copy of the Helidon Java file which contains the predefined constants and prepares a map
* from the string representation of the numeric code to the Helidon constant name. This table allows us, when we are
* generating the Response records for an operation, to use the Helidon predefined constant--if it exists--for the
* response code declared for an operation in the OpenAPI document.
*
* @return prepared map
*/
private HashMap loadKnownHttpStatusMap() {
try (InputStream is = getClass().getResourceAsStream("/java-helidon/common/Status.java")) {
if (is == null) {
throw new RuntimeException("Unable to locate /java-helidon/common/Status.java to discover known HTTP statuses");
}
Pattern statusPattern = Pattern.compile("public static final Status (\\w+)\\s*=\\s*new\\s*Status\\((\\d+)",
Pattern.MULTILINE);
return new Scanner(is, StandardCharsets.UTF_8)
.findAll(statusPattern)
.collect(HashMap::new,
(map, match) -> map.put(match.group(2), match.group(1)),
Map::putAll);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private void setHelidonVersion(String version) {
helidonVersion = VersionUtil.instance().chooseVersionBestMatchOrSelf(version);
setParentVersion(helidonVersion);
helidonMajorVersion = VersionUtil.majorVersion(helidonVersion);
}
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 void setMpTestDependency(String version) {
// The Helidon MP test dependency changed from io.helidon.microprofile.tests:helidon-microprofile-tests-junit5 in 3.x
// to io.helidon.microprofile.testing:helidon-microprofile-testing-junit5 in 4.x.
additionalProperties.put(X_USE_MP_TESTING, helidonMajorVersion >= 4);
}
private void setJandexPluginDependency(String version) {
// The Jandex plug-in GAV changed from org.jboss.jandex:jandex-maven-plugin in 3.x to
// io.smallrye:jandex-maven-plugin in 4.x.
additionalProperties.put(X_USE_SMALLRYE_JANDEX_PLUGIN, helidonMajorVersion >= 4);
}
private void setMediaPackageInfo() {
additionalProperties.put(X_MEDIA_SUPPORT_PACKAGE_PREFIX,
(helidonMajorVersion >= 4) ? "io.helidon.http.media" : "io.helidon.media");
additionalProperties.put(X_MEDIA_COMMON_MEDIA_TYPE_PACKAGE_PREFIX,
(helidonMajorVersion >= 4) ? "io.helidon.common.media.type" : "io.helidon.common.http");
}
private void setUseReactive() {
useReactive = (helidonMajorVersion < 4);
additionalProperties.put(X_USE_REACTIVE, useReactive);
}
private void setUseOptional() {
Object useOptionalSetting = additionalProperties.get(X_USE_OPTIONAL);
if (useOptionalSetting == null) {
useOptional = true;
} else if (useOptionalSetting instanceof Boolean) {
useOptional = (Boolean) useOptionalSetting;
} else if (useOptionalSetting instanceof String) {
useOptional = Boolean.parseBoolean((String) useOptionalSetting);
}
additionalProperties.put(X_USE_OPTIONAL, useOptional);
}
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);
}
protected String apiFolder() {
return folder(apiPackage);
}
protected String modelFolder() {
return folder(modelPackage);
}
private String folder(String packageName) {
return (sourceFolder + File.separator + packageName).replace(".", java.io.File.separator);
}
private String chooseExampleReturnTypeValue(CodegenOperation op) {
// See DefaultCodegen#handleMethodResponse to see how the various op fields related to the return type are set.
if (op.returnType == null) {
return ""; // won't be used anyway in the templates
}
if (op.returnContainer != null) {
return "java.util.Collections.empty" + EXAMPLE_RETURN_VALUES.get(op.returnContainer) + "()";
}
switch (op.returnType) {
case "Integer":
return "new Integer(0)";
case "byte[]":
return "new byte[0]";
case "Float":
return "new Float(0.0f)";
case "boolean":
return "false";
case "Long":
return "new Long(0L)";
case "Object":
return "new Object()";
case "String":
return "\"\"";
case "Boolean":
return "new Boolean(false)";
case "Double":
return "new Double(0.0d)";
default:
return "null";
}
}
public static class GenericTypeDeclaration {
@Getter private final String collectionType;
@Getter private final String baseType;
public GenericTypeDeclaration(String collectionType, String baseType) {
this.collectionType = collectionType;
this.baseType = baseType;
}
public boolean isMap() {
return collectionType.equals("Map");
}
}
/**
* Captures information about the model types for which we need GenericType declarations.
*/
public static class GenericTypeDeclarations {
protected static final String ATTR_NAME = "x-helidon-genericTypeDeclarations";
protected static final String HAS_ATTR_NAME = "x-helidon-hasGenericTypeDeclarations";
// Maps collection type (array or map) to an inner map of base type to declaration. This structure makes it easy to
// avoid duplicate declarations for the same collection and base type.
private final Map> declarations = new HashMap<>();
protected void register(OperationsMap opns) {
OperationMap ops = opns.getOperations();
ops.getOperation().stream()
.flatMap(op -> op.allParams.stream())
.filter(p -> p.isArray || p.isMap)
.forEach(p -> {
String collectionType = p.isArray ? "List" : "Map";
declarations.computeIfAbsent(collectionType, ct -> new TreeMap<>())
.computeIfAbsent(p.baseType, bt -> new GenericTypeDeclaration(collectionType, bt));
});
}
public List genericTypeDeclarations() {
return declarations.values().stream()
.flatMap(m -> m.values().stream())
.collect(Collectors.toList());
}
}
/**
* Logic for determining the Helidon versions available for user selection, either from helidon.io
* or from local preferences or from hard-coded values as a last-chance fallback.
*/
static class VersionUtil {
private static final String PREFERENCES_KEY = "/io/helidon/openapigenerators";
private static final String VERSIONS_KEY = "versions";
private static final String HELIDON_VERSIONS_URL = "https://helidon.io/cli-data/versions.xml";
private static final Duration CONNECTION_TIMEOUT = Duration.ofSeconds(10);
private static final Duration REQUEST_TIMEOUT = Duration.ofSeconds(5);
private final System.Logger LOGGER = System.getLogger(VersionUtil.class.getName());
private static final String DEFAULT_VERSIONS = "\n" +
" \n" +
" 2.6.5 \n" +
" 3.2.7 \n" +
" 4.0.9 \n" +
" \n" +
"";
private static VersionUtil INSTANCE = null;
private static ReentrantLock lock = new ReentrantLock();
static VersionUtil instance() {
lock.lock();
try {
if (INSTANCE == null) {
INSTANCE = new VersionUtil();
}
} catch (IOException | BackingStoreException ex) {
throw new RuntimeException(ex);
} finally {
lock.unlock();
}
return INSTANCE;
}
private final List versions;
private VersionUtil() throws BackingStoreException, IOException {
versions = versions();
}
String defaultVersion() {
return versions.get(versions.size() - 1);
}
static int majorVersion(String fullVersion) {
return Integer.parseUnsignedInt(fullVersion.substring(0, fullVersion.indexOf('.')));
}
/**
* Returns the version that is the "closest" match to the requested version expression from among the known releases,
* where the expression is one of the following:
*
* - a single major version number (e.g., {@code 4})
* - the full exact version to use
* - a Maven version range
*
*
* @param requestedVersion version to search for
* @return matching version
*/
String chooseVersion(String requestedVersion) {
return chooseVersion(requestedVersion, versions);
}
/**
* Returns either the best match version or, if there is none, the requested version itself to allow references to
* unpublished releases such as snapshots.
*
* @param requestedVersion version to search for
* @return either the best match or, if none, the requested version itself
*/
String chooseVersionBestMatchOrSelf(String requestedVersion) {
return chooseVersionBestMatchOrSelf(requestedVersion, versions);
}
/**
* Returns either the best match version or, if there is none, the requested version itself to allow references to
* unpublished releases such as snapshots.
*
* @param requestedVersion version to search for
* @param candidateVersions releases to consider
* @return either the best match or, if none, the requested version itself
*/
String chooseVersionBestMatchOrSelf(String requestedVersion, List candidateVersions) {
String bestMatch = chooseVersion(requestedVersion, candidateVersions);
return bestMatch != null ? bestMatch : requestedVersion;
}
/**
* Returns the version that is the "closest" match to the requested version expression from among the provided
* releases, where the expression expression is one of the following:
*
* - a single major version number (e.g., {@code 4})
* - the full exact version to use
* - a Maven version range
*
*
* @param requestedVersion version to search for
* @param candidateVersions releases to consider
* @return matching version
*/
String chooseVersion(String requestedVersion, List candidateVersions) {
VersionScheme versionScheme = new GenericVersionScheme();
// If the requested version is a single number then treat it as "highest dot release of this major version".
// Otherwise, just create a constraint from the value. That also handles the case where the requested version is
// the complete version.
VersionConstraint requestedConstraint = constraint(versionScheme, requestedVersion);
Version bestMatch = null;
for (String candidate : candidateVersions) {
Version candidateVersion;
try {
candidateVersion = versionScheme.parseVersion(candidate);
} catch (InvalidVersionSpecificationException ex) {
LOGGER.log(System.Logger.Level.WARNING, "Error parsing candidate version '" + candidate + "'", ex);
continue;
}
if (requestedConstraint.containsVersion(candidateVersion)) {
if (bestMatch == null || bestMatch.compareTo(candidateVersion) <= 0) {
bestMatch = candidateVersion;
}
}
}
// The user might have requested a legal version we cannot fully validate because of a network outage
// that prevents us from retrieving the current full list of versions, for example. In such cases return the
// requested version itself as the best match.
return bestMatch != null ? bestMatch.toString() : requestedVersion;
}
private VersionConstraint constraint(VersionScheme versionScheme, String requestedVersion) {
try {
int asSingleNumber = Integer.parseUnsignedInt(requestedVersion);
try {
return versionScheme.parseVersionConstraint(String.format(Locale.getDefault(),
"[%s,%d-alpha)",
requestedVersion,
asSingleNumber + 1));
} catch (InvalidVersionSpecificationException ex) {
throw new RuntimeException("Error preparing constraint for version expression '"
+ requestedVersion
+ "' treated as major version " + asSingleNumber,
ex);
}
} catch (NumberFormatException nfe) {
try {
return versionScheme.parseVersionConstraint(requestedVersion);
} catch (InvalidVersionSpecificationException ex) {
throw new RuntimeException("Error parsing version expression '"
+ requestedVersion
+ "' as a version constraint",
ex);
}
}
}
/**
* Retrieves the list of supported versions from the web site or, failing that, local preferences or, failing that,
* hard-coded versions.
*
* @return list of supported versions
* @throws IOException in case of error accessing the web site and reading the local file
*/
private List versions() throws IOException, BackingStoreException {
HttpClient httpClient = HttpClient.newBuilder()
.connectTimeout(CONNECTION_TIMEOUT)
.build();
try {
HttpRequest req = HttpRequest.newBuilder(URI.create(HELIDON_VERSIONS_URL))
.GET()
.timeout(REQUEST_TIMEOUT)
.build();
HttpResponse response = httpClient.send(req, HttpResponse.BodyHandlers.ofString());
Preferences versionPrefs = Preferences.userRoot().node(PREFERENCES_KEY);
String versions;
if (response.statusCode() == 200) {
versions = response.body();
// Save just-retrieved versions for later off-line use.
versionPrefs.put(VERSIONS_KEY, versions);
versionPrefs.flush();
LOGGER.log(System.Logger.Level.DEBUG, "Saved retrieved versions in preferences");
} else {
LOGGER.log(System.Logger.Level.INFO,
"Unable to retrieve versions remotely; using local preferences or hard-wired defaults");
// Try to get versions from preferences.
versions = versionPrefs.get(VERSIONS_KEY, DEFAULT_VERSIONS);
}
return extractVersions(versions);
} catch (IOException | InterruptedException e) {
// Fallback to use the local versions.xml contents.
return localDefaultVersions();
}
}
private static List localDefaultVersions() throws IOException {
URL versionsURL = VersionUtil.class.getResource("versions.xml");
if (versionsURL == null) {
return extractVersions(DEFAULT_VERSIONS);
}
File versionsFile = new File(versionsURL.getFile());
return Files.readAllLines(versionsFile.toPath());
}
private static List extractVersions(String xmlContent) {
Pattern versionPattern = Pattern.compile("]*>([^>]+) ");
Matcher matcher = versionPattern.matcher(xmlContent);
List result = new ArrayList<>();
while (matcher.find()) {
result.add(matcher.group(1));
}
return result;
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy