From fa45b79881af2cd7972c11fe7f058b648fa15d4d Mon Sep 17 00:00:00 2001 From: "Justin \"J.R.\" Hill" Date: Thu, 27 Jul 2023 10:09:28 -0700 Subject: [PATCH 1/9] feat(java): Initialize Java SDK --- Makefile | 10 + config/clients/java/.openapi-generator-ignore | 3 + config/clients/java/CHANGELOG.md.mustache | 0 config/clients/java/config.overrides.json | 34 + config/clients/java/generator.txt | 1 + config/clients/java/template-source.json | 7 + .../java/template/.github/workflows/main.yml | 0 .../java/template/Java/ApiClient.mustache | 887 ++++++++++++++++++ .../Java/BeanValidationException.mustache | 27 + .../java/template/Java/Configuration.mustache | 30 + .../Java/CustomInstantDeserializer.mustache | 232 +++++ .../clients/java/template/Java/JSON.mustache | 539 +++++++++++ .../template/Java/JavaTimeFormatter.mustache | 53 ++ .../clients/java/template/Java/Pair.mustache | 46 + .../java/template/Java/README.mustache | 246 +++++ .../template/Java/RFC3339DateFormat.mustache | 46 + .../Java/ServerConfiguration.mustache | 58 ++ .../template/Java/ServerVariable.mustache | 23 + .../java/template/Java/StringUtil.mustache | 72 ++ .../additionalEnumTypeAnnotations.mustache | 2 + .../additionalModelTypeAnnotations.mustache | 2 + .../additionalOneOfTypeAnnotations.mustache | 2 + .../clients/java/template/Java/api.mustache | 120 +++ .../java/template/Java/apiException.mustache | 89 ++ .../java/template/Java/apiOperation.mustache | 28 + .../java/template/Java/api_doc.mustache | 108 +++ .../java/template/Java/api_test.mustache | 51 + .../template/Java/auth/ApiKeyAuth.mustache | 66 ++ .../Java/auth/Authentication.mustache | 19 + .../template/Java/auth/HttpBasicAuth.mustache | 42 + .../Java/auth/HttpBearerAuth.mustache | 49 + .../java/template/Java/auth/OAuth.mustache | 28 + .../template/Java/auth/OAuthFlow.mustache | 14 + .../template/Java/beanValidation.mustache | 18 + .../template/Java/beanValidationCore.mustache | 20 + .../Java/beanValidationQueryParams.mustache | 1 + .../java/template/Java/build.gradle.mustache | 94 ++ .../template/Java/enum_outer_doc.mustache | 7 + .../Java/generatedAnnotation.mustache | 1 + .../java/template/Java/git_push.sh.mustache | 57 ++ .../java/template/Java/gitignore.mustache | 21 + .../java/template/Java/gradle-wrapper.jar | Bin 0 -> 59536 bytes .../Java/gradle-wrapper.properties.mustache | 5 + .../template/Java/gradle.properties.mustache | 9 + .../java/template/Java/gradlew.bat.mustache | 89 ++ .../java/template/Java/gradlew.mustache | 234 +++++ .../Java/jackson_annotations.mustache | 20 + .../native/AbstractOpenApiSchema.mustache | 136 +++ .../Java/libraries/native/ApiClient.mustache | 449 +++++++++ .../libraries/native/ApiResponse.mustache | 58 ++ .../Java/libraries/native/JSON.mustache | 260 +++++ .../Java/libraries/native/README.mustache | 189 ++++ .../native/additional_properties.mustache | 45 + .../libraries/native/anyof_model.mustache | 360 +++++++ .../Java/libraries/native/api.mustache | 591 ++++++++++++ .../libraries/native/apiException.mustache | 79 ++ .../Java/libraries/native/api_doc.mustache | 280 ++++++ .../Java/libraries/native/api_test.mustache | 58 ++ .../native/generatedAnnotation.mustache | 1 + .../native/gradle.properties.mustache | 0 .../Java/libraries/native/model.mustache | 68 ++ .../Java/libraries/native/modelEnum.mustache | 117 +++ .../libraries/native/model_anyof_doc.mustache | 38 + .../Java/libraries/native/model_doc.mustache | 19 + .../libraries/native/model_oneof_doc.mustache | 38 + .../libraries/native/oneof_model.mustache | 393 ++++++++ .../Java/libraries/native/pojo.mustache | 596 ++++++++++++ .../Java/libraries/native/pom.mustache | 299 ++++++ .../Java/libraries/native/travis.mustache | 16 + .../java/template/Java/licenseInfo.mustache | 11 + .../java/template/Java/manifest.mustache | 3 + .../java/template/Java/maven.yml.mustache | 31 + .../clients/java/template/Java/model.mustache | 68 ++ .../java/template/Java/modelEnum.mustache | 117 +++ .../template/Java/modelInnerEnum.mustache | 95 ++ .../java/template/Java/model_doc.mustache | 4 + .../java/template/Java/model_test.mustache | 43 + .../template/Java/oneof_interface.mustache | 6 + .../java/template/Java/openapi.mustache | 1 + .../clients/java/template/Java/pojo.mustache | 617 ++++++++++++ .../java/template/Java/pojo_doc.mustache | 37 + .../clients/java/template/Java/pom.mustache | 359 +++++++ .../template/Java/settings.gradle.mustache | 1 + .../java/template/Java/travis.mustache | 22 + .../template/Java/typeInfoAnnotation.mustache | 17 + .../java/template/Java/xmlAnnotation.mustache | 6 + .../template/README_api_endpoints.mustache | 0 .../java/template/README_calling_api.mustache | 0 .../template/README_custom_badges.mustache | 0 .../template/README_initializing.mustache | 0 .../template/README_installation.mustache | 0 .../README_license_disclaimer.mustache | 1 + .../java/template/README_models.mustache | 1 + .../java/template/gitignore_custom.mustache | 0 94 files changed, 9040 insertions(+) create mode 100644 config/clients/java/.openapi-generator-ignore create mode 100644 config/clients/java/CHANGELOG.md.mustache create mode 100644 config/clients/java/config.overrides.json create mode 100644 config/clients/java/generator.txt create mode 100644 config/clients/java/template-source.json create mode 100644 config/clients/java/template/.github/workflows/main.yml create mode 100644 config/clients/java/template/Java/ApiClient.mustache create mode 100644 config/clients/java/template/Java/BeanValidationException.mustache create mode 100644 config/clients/java/template/Java/Configuration.mustache create mode 100644 config/clients/java/template/Java/CustomInstantDeserializer.mustache create mode 100644 config/clients/java/template/Java/JSON.mustache create mode 100644 config/clients/java/template/Java/JavaTimeFormatter.mustache create mode 100644 config/clients/java/template/Java/Pair.mustache create mode 100644 config/clients/java/template/Java/README.mustache create mode 100644 config/clients/java/template/Java/RFC3339DateFormat.mustache create mode 100644 config/clients/java/template/Java/ServerConfiguration.mustache create mode 100644 config/clients/java/template/Java/ServerVariable.mustache create mode 100644 config/clients/java/template/Java/StringUtil.mustache create mode 100644 config/clients/java/template/Java/additionalEnumTypeAnnotations.mustache create mode 100644 config/clients/java/template/Java/additionalModelTypeAnnotations.mustache create mode 100644 config/clients/java/template/Java/additionalOneOfTypeAnnotations.mustache create mode 100644 config/clients/java/template/Java/api.mustache create mode 100644 config/clients/java/template/Java/apiException.mustache create mode 100644 config/clients/java/template/Java/apiOperation.mustache create mode 100644 config/clients/java/template/Java/api_doc.mustache create mode 100644 config/clients/java/template/Java/api_test.mustache create mode 100644 config/clients/java/template/Java/auth/ApiKeyAuth.mustache create mode 100644 config/clients/java/template/Java/auth/Authentication.mustache create mode 100644 config/clients/java/template/Java/auth/HttpBasicAuth.mustache create mode 100644 config/clients/java/template/Java/auth/HttpBearerAuth.mustache create mode 100644 config/clients/java/template/Java/auth/OAuth.mustache create mode 100644 config/clients/java/template/Java/auth/OAuthFlow.mustache create mode 100644 config/clients/java/template/Java/beanValidation.mustache create mode 100644 config/clients/java/template/Java/beanValidationCore.mustache create mode 100644 config/clients/java/template/Java/beanValidationQueryParams.mustache create mode 100644 config/clients/java/template/Java/build.gradle.mustache create mode 100644 config/clients/java/template/Java/enum_outer_doc.mustache create mode 100644 config/clients/java/template/Java/generatedAnnotation.mustache create mode 100755 config/clients/java/template/Java/git_push.sh.mustache create mode 100644 config/clients/java/template/Java/gitignore.mustache create mode 100644 config/clients/java/template/Java/gradle-wrapper.jar create mode 100644 config/clients/java/template/Java/gradle-wrapper.properties.mustache create mode 100644 config/clients/java/template/Java/gradle.properties.mustache create mode 100644 config/clients/java/template/Java/gradlew.bat.mustache create mode 100755 config/clients/java/template/Java/gradlew.mustache create mode 100644 config/clients/java/template/Java/jackson_annotations.mustache create mode 100644 config/clients/java/template/Java/libraries/native/AbstractOpenApiSchema.mustache create mode 100644 config/clients/java/template/Java/libraries/native/ApiClient.mustache create mode 100644 config/clients/java/template/Java/libraries/native/ApiResponse.mustache create mode 100644 config/clients/java/template/Java/libraries/native/JSON.mustache create mode 100644 config/clients/java/template/Java/libraries/native/README.mustache create mode 100644 config/clients/java/template/Java/libraries/native/additional_properties.mustache create mode 100644 config/clients/java/template/Java/libraries/native/anyof_model.mustache create mode 100644 config/clients/java/template/Java/libraries/native/api.mustache create mode 100644 config/clients/java/template/Java/libraries/native/apiException.mustache create mode 100644 config/clients/java/template/Java/libraries/native/api_doc.mustache create mode 100644 config/clients/java/template/Java/libraries/native/api_test.mustache create mode 100644 config/clients/java/template/Java/libraries/native/generatedAnnotation.mustache create mode 100644 config/clients/java/template/Java/libraries/native/gradle.properties.mustache create mode 100644 config/clients/java/template/Java/libraries/native/model.mustache create mode 100644 config/clients/java/template/Java/libraries/native/modelEnum.mustache create mode 100644 config/clients/java/template/Java/libraries/native/model_anyof_doc.mustache create mode 100644 config/clients/java/template/Java/libraries/native/model_doc.mustache create mode 100644 config/clients/java/template/Java/libraries/native/model_oneof_doc.mustache create mode 100644 config/clients/java/template/Java/libraries/native/oneof_model.mustache create mode 100644 config/clients/java/template/Java/libraries/native/pojo.mustache create mode 100644 config/clients/java/template/Java/libraries/native/pom.mustache create mode 100644 config/clients/java/template/Java/libraries/native/travis.mustache create mode 100644 config/clients/java/template/Java/licenseInfo.mustache create mode 100644 config/clients/java/template/Java/manifest.mustache create mode 100644 config/clients/java/template/Java/maven.yml.mustache create mode 100644 config/clients/java/template/Java/model.mustache create mode 100644 config/clients/java/template/Java/modelEnum.mustache create mode 100644 config/clients/java/template/Java/modelInnerEnum.mustache create mode 100644 config/clients/java/template/Java/model_doc.mustache create mode 100644 config/clients/java/template/Java/model_test.mustache create mode 100644 config/clients/java/template/Java/oneof_interface.mustache create mode 100644 config/clients/java/template/Java/openapi.mustache create mode 100644 config/clients/java/template/Java/pojo.mustache create mode 100644 config/clients/java/template/Java/pojo_doc.mustache create mode 100644 config/clients/java/template/Java/pom.mustache create mode 100644 config/clients/java/template/Java/settings.gradle.mustache create mode 100644 config/clients/java/template/Java/travis.mustache create mode 100644 config/clients/java/template/Java/typeInfoAnnotation.mustache create mode 100644 config/clients/java/template/Java/xmlAnnotation.mustache create mode 100644 config/clients/java/template/README_api_endpoints.mustache create mode 100644 config/clients/java/template/README_calling_api.mustache create mode 100644 config/clients/java/template/README_custom_badges.mustache create mode 100644 config/clients/java/template/README_initializing.mustache create mode 100644 config/clients/java/template/README_installation.mustache create mode 100644 config/clients/java/template/README_license_disclaimer.mustache create mode 100644 config/clients/java/template/README_models.mustache create mode 100644 config/clients/java/template/gitignore_custom.mustache diff --git a/Makefile b/Makefile index 465f3283..25ec41c0 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,7 @@ GO_DOCKER_TAG = 1 DOTNET_DOCKER_TAG = 6.0 GOLINT_DOCKER_TAG = v1.51-alpine BUSYBOX_DOCKER_TAG = 1 +GRADLE_DOCKER_TAG = 8.2 PYTHON_DOCKER_TAG = 3.10 # Other config CONFIG_DIR = ${PWD}/config @@ -202,3 +203,12 @@ shellcheck: .PHONY: setup-new-sdk setup-new-sdk: ./scripts/setup_new_sdk.sh + +.PHONY: build-client-java +build-client-java: + make build-client sdk_language=java tmpdir=${TMP_DIR} + make run-in-docker sdk_language=java image=gradle:${GRADLE_DOCKER_TAG} command="/bin/sh -c 'gradle fmt'" + +.PHONY: test-client-java +test-client-java: build-client-java + # ... any custom test code ... diff --git a/config/clients/java/.openapi-generator-ignore b/config/clients/java/.openapi-generator-ignore new file mode 100644 index 00000000..0b58f4f8 --- /dev/null +++ b/config/clients/java/.openapi-generator-ignore @@ -0,0 +1,3 @@ +src/main/AndroidManifest.xml +build.sbt +.travis.yml diff --git a/config/clients/java/CHANGELOG.md.mustache b/config/clients/java/CHANGELOG.md.mustache new file mode 100644 index 00000000..e69de29b diff --git a/config/clients/java/config.overrides.json b/config/clients/java/config.overrides.json new file mode 100644 index 00000000..4ed4f6fb --- /dev/null +++ b/config/clients/java/config.overrides.json @@ -0,0 +1,34 @@ +{ + "sdkId": "java", + "gitRepoId": "java-sdk", + "packageName": "dev.openfga:openfga-sdk", + "artifactId": "openfga-sdk", + "groupId": "dev.openfga", + "apiPackage": "dev.openfga.api", + "modelPackage": "dev.openfga.api.model", + "packageVersion": "0.0.1", + "snapshotVersion": false, + "packageDescription": "Java SDK for OpenFGA", + "artifactDescription": "Java SDK for OpenFGA", + "packageDetailedDescription": "This is an autogenerated java SDK for OpenFGA. It provides a wrapper around the [OpenFGA API definition](https://openfga.dev/api).", + "developerEmail": "community@openfga.dev", + "developerName": "OpenFGA Contributors", + "developerOrganization": "OpenFGA", + "developerOrganizationUrl": "https://openfga.dev", + "licenseName": "Apache-2.0", + "licenseUrl": "https://github.com/openfga/dotnet-sdk/blob/main/LICENSE", + "scmConnection": "scm:git:git@github.com:openfga/java-sdk.git", + "scmDeveloperConnection": "scm:git:git@github.com:openfga/java-sdk.git", + "scmUrl": "https://github.com/openfga/java-sdk", + "library": "native", + "disallowAdditionalPropertiesIfNotPresent": false, + "enumUnknownDefaultCase": true, + "allowUnicodeIdentifiers": true, + "caseInsensitiveResponseHeaders": true, + "files": { + "Java/build.gradle.mustache" : { + "destinationFilename": "build.gradle", + "templateType": "SupportingFiles" + } + } +} diff --git a/config/clients/java/generator.txt b/config/clients/java/generator.txt new file mode 100644 index 00000000..f3d360b1 --- /dev/null +++ b/config/clients/java/generator.txt @@ -0,0 +1 @@ +java diff --git a/config/clients/java/template-source.json b/config/clients/java/template-source.json new file mode 100644 index 00000000..47ae7a41 --- /dev/null +++ b/config/clients/java/template-source.json @@ -0,0 +1,7 @@ +{ + "repo": "https://github.com/OpenAPITools/openapi-generator", + "branch": "master", + "commit": "90eacb685c57308e67c65e436b19d160091f91e1", + "url": "https://github.com/OpenAPITools/openapi-generator/tree/master/modules/openapi-generator/src/main/resources/Java", + "docs": "https://github.com/OpenAPITools/openapi-generator/blob/master/docs/generators/java.md" +} diff --git a/config/clients/java/template/.github/workflows/main.yml b/config/clients/java/template/.github/workflows/main.yml new file mode 100644 index 00000000..e69de29b diff --git a/config/clients/java/template/Java/ApiClient.mustache b/config/clients/java/template/Java/ApiClient.mustache new file mode 100644 index 00000000..ed5012fc --- /dev/null +++ b/config/clients/java/template/Java/ApiClient.mustache @@ -0,0 +1,887 @@ +{{>licenseInfo}} +package {{invokerPackage}}; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.*; +{{#joda}} +import com.fasterxml.jackson.datatype.joda.JodaModule; +{{/joda}} +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import java.time.OffsetDateTime; +import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider; + +import com.sun.jersey.api.client.Client; +import com.sun.jersey.api.client.ClientResponse; +import com.sun.jersey.api.client.GenericType; +import com.sun.jersey.api.client.config.DefaultClientConfig; +import com.sun.jersey.api.client.filter.GZIPContentEncodingFilter; +import com.sun.jersey.api.client.filter.LoggingFilter; +import com.sun.jersey.api.client.WebResource.Builder; + +import com.sun.jersey.multipart.FormDataMultiPart; +import com.sun.jersey.multipart.file.FileDataBodyPart; + +import {{javaxPackage}}.ws.rs.core.Cookie; +import {{javaxPackage}}.ws.rs.core.Response.Status.Family; +import {{javaxPackage}}.ws.rs.core.MediaType; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.Date; +import java.util.TimeZone; + +import java.net.URLEncoder; + +import java.io.File; +import java.io.UnsupportedEncodingException; + +import java.text.DateFormat; + +import {{invokerPackage}}.auth.Authentication; +{{#hasHttpBasicMethods}} +import {{invokerPackage}}.auth.HttpBasicAuth; +{{/hasHttpBasicMethods}} +{{#hasHttpBearerMethods}} +import {{invokerPackage}}.auth.HttpBearerAuth; +{{/hasHttpBearerMethods}} +{{#hasApiKeyMethods}} +import {{invokerPackage}}.auth.ApiKeyAuth; +{{/hasApiKeyMethods}} +{{#hasOAuthMethods}} +import {{invokerPackage}}.auth.OAuth; +{{/hasOAuthMethods}} + +{{>generatedAnnotation}} +public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { + private Map defaultHeaderMap = new HashMap(); + private Map defaultCookieMap = new HashMap(); + private String basePath = "{{{basePath}}}"; + protected List servers = new ArrayList({{#servers}}{{#-first}}Arrays.asList( +{{/-first}} new ServerConfiguration( + "{{{url}}}", + "{{{description}}}{{^description}}No description provided{{/description}}", + new HashMap(){{#variables}}{{#-first}} {{ +{{/-first}} put("{{{name}}}", new ServerVariable( + "{{{description}}}{{^description}}No description provided{{/description}}", + "{{{defaultValue}}}", + new HashSet( + {{#enumValues}} + {{#-first}} + Arrays.asList( + {{/-first}} + "{{{.}}}"{{^-last}},{{/-last}} + {{#-last}} + ) + {{/-last}} + {{/enumValues}} + ) + )); + {{#-last}} + }}{{/-last}}{{/variables}} + ){{^-last}},{{/-last}} + {{#-last}} + ){{/-last}}{{/servers}}); + protected Integer serverIndex = 0; + protected Map serverVariables = null; + private boolean debugging = false; + private int connectionTimeout = 0; + + private Client httpClient; + private ObjectMapper objectMapper; + + private Map authentications; + + private int statusCode; + private Map> responseHeaders; + + private DateFormat dateFormat; + + public ApiClient() { + objectMapper = new ObjectMapper(); + objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + objectMapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false); + objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + objectMapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING); + objectMapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING); + {{#joda}} + objectMapper.registerModule(new JodaModule()); + {{/joda}} + objectMapper.registerModule(new JavaTimeModule()); + objectMapper.setDateFormat(ApiClient.buildDefaultDateFormat()); + + dateFormat = ApiClient.buildDefaultDateFormat(); + + // Set default User-Agent. + setUserAgent("{{{httpUserAgent}}}{{^httpUserAgent}}OpenAPI-Generator/{{{artifactVersion}}}/java{{/httpUserAgent}}"); + + // Setup authentications (key: authentication name, value: authentication). + authentications = new HashMap();{{#authMethods}}{{#isBasic}}{{#isBasicBasic}} + authentications.put("{{name}}", new HttpBasicAuth());{{/isBasicBasic}}{{#isBasicBearer}} + authentications.put("{{name}}", new HttpBearerAuth("{{scheme}}"));{{/isBasicBearer}}{{/isBasic}}{{#isApiKey}} + authentications.put("{{name}}", new ApiKeyAuth({{#isKeyInHeader}}"header"{{/isKeyInHeader}}{{^isKeyInHeader}}"query"{{/isKeyInHeader}}, "{{keyParamName}}"));{{/isApiKey}}{{#isOAuth}} + authentications.put("{{name}}", new OAuth());{{/isOAuth}}{{/authMethods}} + // Prevent the authentications from being modified. + authentications = Collections.unmodifiableMap(authentications); + + rebuildHttpClient(); + } + + public static DateFormat buildDefaultDateFormat() { + return new RFC3339DateFormat(); + } + + /** + * Build the Client used to make HTTP requests with the latest settings, + * i.e. objectMapper and debugging. + * TODO: better to use the Builder Pattern? + * @return API client + */ + public ApiClient rebuildHttpClient() { + // Add the JSON serialization support to Jersey + JacksonJsonProvider jsonProvider = new JacksonJsonProvider(objectMapper); + DefaultClientConfig conf = new DefaultClientConfig(); + conf.getSingletons().add(jsonProvider); + Client client = Client.create(conf); + client.addFilter(new GZIPContentEncodingFilter({{#useGzipFeature}}true{{/useGzipFeature}}{{^useGzipFeature}}false{{/useGzipFeature}})); + if (debugging) { + client.addFilter(new LoggingFilter()); + } + this.httpClient = client; + return this; + } + + /** + * Returns the current object mapper used for JSON serialization/deserialization. + *

+ * Note: If you make changes to the object mapper, remember to set it back via + * setObjectMapper in order to trigger HTTP client rebuilding. + *

+ * @return Object mapper + */ + public ObjectMapper getObjectMapper() { + return objectMapper; + } + + public ApiClient setObjectMapper(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + // Need to rebuild the Client as it depends on object mapper. + rebuildHttpClient(); + return this; + } + + public Client getHttpClient() { + return httpClient; + } + + public ApiClient setHttpClient(Client httpClient) { + this.httpClient = httpClient; + return this; + } + + public String getBasePath() { + return basePath; + } + + public ApiClient setBasePath(String basePath) { + this.basePath = basePath; + this.serverIndex = null; + return this; + } + + public List getServers() { + return servers; + } + + public ApiClient setServers(List servers) { + this.servers = servers; + return this; + } + + public Integer getServerIndex() { + return serverIndex; + } + + public ApiClient setServerIndex(Integer serverIndex) { + this.serverIndex = serverIndex; + return this; + } + + public Map getServerVariables() { + return serverVariables; + } + + public ApiClient setServerVariables(Map serverVariables) { + this.serverVariables = serverVariables; + return this; + } + + /** + * Gets the status code of the previous request + * @return Status code + */ + public int getStatusCode() { + return statusCode; + } + + /** + * Gets the response headers of the previous request + * @return Response headers + */ + public Map> getResponseHeaders() { + return responseHeaders; + } + + /** + * Get authentications (key: authentication name, value: authentication). + * @return Map of authentication + */ + public Map getAuthentications() { + return authentications; + } + + /** + * Get authentication for the given name. + * + * @param authName The authentication name + * @return The authentication, null if not found + */ + public Authentication getAuthentication(String authName) { + return authentications.get(authName); + } + + {{#hasHttpBearerMethods}} + /** + * Helper method to set access token for the first Bearer authentication. + * @param bearerToken Bearer token + * @return API client + */ + public void setBearerToken(String bearerToken) { + for (Authentication auth : authentications.values()) { + if (auth instanceof HttpBearerAuth) { + ((HttpBearerAuth) auth).setBearerToken(bearerToken); + return; + } + } + throw new RuntimeException("No Bearer authentication configured!"); + } + + {{/hasHttpBearerMethods}} + + {{#hasHttpBasicMethods}} + /** + * Helper method to set username for the first HTTP basic authentication. + * @param username Username + */ + public void setUsername(String username) { + for (Authentication auth : authentications.values()) { + if (auth instanceof HttpBasicAuth) { + ((HttpBasicAuth) auth).setUsername(username); + return; + } + } + throw new RuntimeException("No HTTP basic authentication configured!"); + } + + /** + * Helper method to set password for the first HTTP basic authentication. + * @param password Password + */ + public void setPassword(String password) { + for (Authentication auth : authentications.values()) { + if (auth instanceof HttpBasicAuth) { + ((HttpBasicAuth) auth).setPassword(password); + return; + } + } + throw new RuntimeException("No HTTP basic authentication configured!"); + } + + {{/hasHttpBasicMethods}} + + {{#hasApiKeyMethods}} + /** + * Helper method to set API key value for the first API key authentication. + * @param apiKey the API key + */ + public void setApiKey(String apiKey) { + for (Authentication auth : authentications.values()) { + if (auth instanceof ApiKeyAuth) { + ((ApiKeyAuth) auth).setApiKey(apiKey); + return; + } + } + throw new RuntimeException("No API key authentication configured!"); + } + + /** + * Helper method to set API key prefix for the first API key authentication. + * @param apiKeyPrefix API key prefix + */ + public void setApiKeyPrefix(String apiKeyPrefix) { + for (Authentication auth : authentications.values()) { + if (auth instanceof ApiKeyAuth) { + ((ApiKeyAuth) auth).setApiKeyPrefix(apiKeyPrefix); + return; + } + } + throw new RuntimeException("No API key authentication configured!"); + } + + {{/hasApiKeyMethods}} + + {{#hasOAuthMethods}} + /** + * Helper method to set access token for the first OAuth2 authentication. + * @param accessToken Access token + */ + public void setAccessToken(String accessToken) { + for (Authentication auth : authentications.values()) { + if (auth instanceof OAuth) { + ((OAuth) auth).setAccessToken(accessToken); + return; + } + } + throw new RuntimeException("No OAuth2 authentication configured!"); + } + + {{/hasOAuthMethods}} + + /** + * Set the User-Agent header's value (by adding to the default header map). + * @param userAgent User agent + * @return API client + */ + public ApiClient setUserAgent(String userAgent) { + addDefaultHeader("User-Agent", userAgent); + return this; + } + + /** + * Add a default header. + * + * @param key The header's key + * @param value The header's value + * @return API client + */ + public ApiClient addDefaultHeader(String key, String value) { + defaultHeaderMap.put(key, value); + return this; + } + + /** + * Add a default cookie. + * + * @param key The cookie's key + * @param value The cookie's value + * @return API client + */ + public ApiClient addDefaultCookie(String key, String value) { + defaultCookieMap.put(key, value); + return this; + } + + /** + * Check that whether debugging is enabled for this API client. + * @return True if debugging is on + */ + public boolean isDebugging() { + return debugging; + } + + /** + * Enable/disable debugging for this API client. + * + * @param debugging To enable (true) or disable (false) debugging + * @return API client + */ + public ApiClient setDebugging(boolean debugging) { + this.debugging = debugging; + // Need to rebuild the Client as it depends on the value of debugging. + rebuildHttpClient(); + return this; + } + + /** + * Connect timeout (in milliseconds). + * @return Connection timeout + */ + public int getConnectTimeout() { + return connectionTimeout; + } + + /** + * Set the connect timeout (in milliseconds). + * A value of 0 means no timeout, otherwise values must be between 1 and + * {@link Integer#MAX_VALUE}. + * @param connectionTimeout Connection timeout in milliseconds + * @return API client + */ + public ApiClient setConnectTimeout(int connectionTimeout) { + this.connectionTimeout = connectionTimeout; + httpClient.setConnectTimeout(connectionTimeout); + return this; + } + + /** + * Get the date format used to parse/format date parameters. + * @return Date format + */ + public DateFormat getDateFormat() { + return dateFormat; + } + + /** + * Set the date format used to parse/format date parameters. + * @param dateFormat Date format + * @return API client + */ + public ApiClient setDateFormat(DateFormat dateFormat) { + this.dateFormat = dateFormat; + // Also set the date format for model (de)serialization with Date properties. + this.objectMapper.setDateFormat((DateFormat) dateFormat.clone()); + // Need to rebuild the Client as objectMapper changes. + rebuildHttpClient(); + return this; + } + + /** + * Parse the given string into Date object. + * @param str String + * @return Date + */ + public Date parseDate(String str) { + try { + return dateFormat.parse(str); + } catch (java.text.ParseException e) { + throw new RuntimeException(e); + } + } + + /** + * Format the given Date object into string. + * @param date Date + * @return Date in string format + */ + public String formatDate(Date date) { + return dateFormat.format(date); + } + + /** + * Format the given parameter object into string. + * @param param Object + * @return Object in string format + */ + public String parameterToString(Object param) { + if (param == null) { + return ""; + } else if (param instanceof Date) { + return formatDate((Date) param); + } {{#jsr310}}else if (param instanceof OffsetDateTime) { + return formatOffsetDateTime((OffsetDateTime) param); + } {{/jsr310}}else if (param instanceof Collection) { + StringBuilder b = new StringBuilder(); + for(Object o : (Collection)param) { + if(b.length() > 0) { + b.append(','); + } + b.append(String.valueOf(o)); + } + return b.toString(); + } else { + return String.valueOf(param); + } + } + + /** + * Formats the specified query parameter to a list containing a single {@code Pair} object. + * + * Note that {@code value} must not be a collection. + * + * @param name The name of the parameter. + * @param value The value of the parameter. + * @return A list containing a single {@code Pair} object. + */ + public List parameterToPair(String name, Object value) { + List params = new ArrayList(); + + // preconditions + if (name == null || name.isEmpty() || value == null || value instanceof Collection) return params; + + params.add(new Pair(name, parameterToString(value))); + return params; + } + + /** + * Formats the specified collection query parameters to a list of {@code Pair} objects. + * + * Note that the values of each of the returned Pair objects are percent-encoded. + * + * @param collectionFormat The collection format of the parameter. + * @param name The name of the parameter. + * @param value The value of the parameter. + * @return A list of {@code Pair} objects. + */ + public List parameterToPairs(String collectionFormat, String name, Collection value) { + List params = new ArrayList(); + + // preconditions + if (name == null || name.isEmpty() || value == null) { + return params; + } + + // create the params based on the collection format + if ("multi".equals(collectionFormat)) { + for (Object item : value) { + params.add(new Pair(name, escapeString(parameterToString(item)))); + } + return params; + } + + // collectionFormat is assumed to be "csv" by default + String delimiter = ","; + + // escape all delimiters except commas, which are URI reserved + // characters + if ("ssv".equals(collectionFormat)) { + delimiter = escapeString(" "); + } else if ("tsv".equals(collectionFormat)) { + delimiter = escapeString("\t"); + } else if ("pipes".equals(collectionFormat)) { + delimiter = escapeString("|"); + } + + StringBuilder sb = new StringBuilder() ; + for (Object item : value) { + sb.append(delimiter); + sb.append(escapeString(parameterToString(item))); + } + + params.add(new Pair(name, sb.substring(delimiter.length()))); + + return params; + } + + /** + * Check if the given MIME is a JSON MIME. + * JSON MIME examples: + * application/json + * application/json; charset=UTF8 + * APPLICATION/JSON + * application/vnd.company+json + * @param mime MIME + * @return True if MIME type is boolean + */ + public boolean isJsonMime(String mime) { + String jsonMime = "(?i)^(application/json|[^;/ \t]+/[^;/ \t]+[+]json)[ \t]*(;.*)?$"; + return mime != null && (mime.matches(jsonMime) || mime.equals("*/*")); + } + + /** + * Select the Accept header's value from the given accepts array: + * if JSON exists in the given array, use it; + * otherwise use all of them (joining into a string) + * + * @param accepts The accepts array to select from + * @return The Accept header to use. If the given array is empty, + * null will be returned (not to set the Accept header explicitly). + */ + public String selectHeaderAccept(String[] accepts) { + if (accepts.length == 0) { + return null; + } + for (String accept : accepts) { + if (isJsonMime(accept)) { + return accept; + } + } + return StringUtil.join(accepts, ","); + } + + /** + * Select the Content-Type header's value from the given array: + * if JSON exists in the given array, use it; + * otherwise use the first one of the array. + * + * @param contentTypes The Content-Type array to select from + * @return The Content-Type header to use. If the given array is empty, + * or matches "any", JSON will be used. + */ + public String selectHeaderContentType(String[] contentTypes) { + if (contentTypes.length == 0 || contentTypes[0].equals("*/*")) { + return "application/json"; + } + for (String contentType : contentTypes) { + if (isJsonMime(contentType)) { + return contentType; + } + } + return contentTypes[0]; + } + + /** + * Escape the given string to be used as URL query value. + * @param str String + * @return Escaped string + */ + public String escapeString(String str) { + try { + return URLEncoder.encode(str, "utf8").replaceAll("\\+", "%20"); + } catch (UnsupportedEncodingException e) { + return str; + } + } + + /** + * Serialize the given Java object into string according the given + * Content-Type (only JSON is supported for now). + * @param obj Object + * @param contentType Content type + * @param formParams Form parameters + * @return Object + * @throws ApiException API exception + */ + public Object serialize(Object obj, String contentType, Map formParams) throws ApiException { + if (contentType.startsWith("multipart/form-data")) { + FormDataMultiPart mp = new FormDataMultiPart(); + for (Entry param: formParams.entrySet()) { + if( param.getValue() instanceof List && !( ( List ) param.getValue() ).isEmpty() + && ( ( List ) param.getValue() ).get( 0 ) instanceof File ) { + @SuppressWarnings( "unchecked" ) + List files = ( List ) param.getValue(); + for( File file : files ) { + mp.bodyPart( new FileDataBodyPart( param.getKey(), file, MediaType.APPLICATION_OCTET_STREAM_TYPE ) ); + } + } else if (param.getValue() instanceof File) { + File file = (File) param.getValue(); + mp.bodyPart(new FileDataBodyPart(param.getKey(), file, MediaType.APPLICATION_OCTET_STREAM_TYPE)); + } else { + mp.field(param.getKey(), parameterToString(param.getValue()), MediaType.MULTIPART_FORM_DATA_TYPE); + } + } + return mp; + } else if (contentType.startsWith("application/x-www-form-urlencoded")) { + return this.getXWWWFormUrlencodedParams(formParams); + } else { + // We let Jersey attempt to serialize the body + return obj; + } + } + + /** + * Build full URL by concatenating base path, the given sub path and query parameters. + * + * @param path The sub path + * @param queryParams The query parameters + * @param collectionQueryParams The collection query parameters + * @return The full URL + */ + private String buildUrl(String path, List queryParams, List collectionQueryParams) { + String baseURL; + if (serverIndex != null) { + if (serverIndex < 0 || serverIndex >= servers.size()) { + throw new ArrayIndexOutOfBoundsException(String.format( + "Invalid index %d when selecting the host settings. Must be less than %d", serverIndex, servers.size() + )); + } + baseURL = servers.get(serverIndex).URL(serverVariables); + } else { + baseURL = basePath; + } + + final StringBuilder url = new StringBuilder(); + url.append(baseURL).append(path); + + if (queryParams != null && !queryParams.isEmpty()) { + // support (constant) query string in `path`, e.g. "/posts?draft=1" + String prefix = path.contains("?") ? "&" : "?"; + for (Pair param : queryParams) { + if (param.getValue() != null) { + if (prefix != null) { + url.append(prefix); + prefix = null; + } else { + url.append("&"); + } + String value = parameterToString(param.getValue()); + url.append(escapeString(param.getName())).append("=").append(escapeString(value)); + } + } + } + + if (collectionQueryParams != null && !collectionQueryParams.isEmpty()) { + String prefix = url.toString().contains("?") ? "&" : "?"; + for (Pair param : collectionQueryParams) { + if (param.getValue() != null) { + if (prefix != null) { + url.append(prefix); + prefix = null; + } else { + url.append("&"); + } + String value = parameterToString(param.getValue()); + // collection query parameter value already escaped as part of parameterToPairs + url.append(escapeString(param.getName())).append("=").append(value); + } + } + } + + return url.toString(); + } + + private ClientResponse getAPIResponse(String path, String method, List queryParams, List collectionQueryParams, Object body, Map headerParams, Map cookieParams, Map formParams, String accept, String contentType, String[] authNames) throws ApiException { + if (body != null && !formParams.isEmpty()) { + throw new ApiException(500, "Cannot have body and form params"); + } + + updateParamsForAuth(authNames, queryParams, headerParams, cookieParams); + + final String url = buildUrl(path, queryParams, collectionQueryParams); + Builder builder; + if (accept == null) { + builder = httpClient.resource(url).getRequestBuilder(); + } else { + builder = httpClient.resource(url).accept(accept); + } + + for (Entry keyValue : headerParams.entrySet()) { + builder = builder.header(keyValue.getKey(), keyValue.getValue()); + } + for (Map.Entry keyValue : defaultHeaderMap.entrySet()) { + if (!headerParams.containsKey(keyValue.getKey())) { + builder = builder.header(keyValue.getKey(), keyValue.getValue()); + } + } + + for (Entry keyValue : cookieParams.entrySet()) { + builder = builder.cookie(new Cookie(keyValue.getKey(), keyValue.getValue())); + } + for (Map.Entry keyValue : defaultCookieMap.entrySet()) { + if (!cookieParams.containsKey(keyValue.getKey())) { + builder = builder.cookie(new Cookie(keyValue.getKey(), keyValue.getValue())); + } + } + + ClientResponse response = null; + + if ("GET".equals(method)) { + response = (ClientResponse) builder.get(ClientResponse.class); + } else if ("POST".equals(method)) { + response = builder.type(contentType).post(ClientResponse.class, serialize(body, contentType, formParams)); + } else if ("PUT".equals(method)) { + response = builder.type(contentType).put(ClientResponse.class, serialize(body, contentType, formParams)); + } else if ("DELETE".equals(method)) { + response = builder.type(contentType).delete(ClientResponse.class, serialize(body, contentType, formParams)); + } else if ("PATCH".equals(method)) { + response = builder.type(contentType).header("X-HTTP-Method-Override", "PATCH").post(ClientResponse.class, serialize(body, contentType, formParams)); + } else if ("HEAD".equals(method)) { + response = builder.head(); + } else { + throw new ApiException(500, "unknown method type " + method); + } + return response; + } + + /** + * Invoke API by sending HTTP request with the given options. + * + * @param Type + * @param path The sub-path of the HTTP URL + * @param method The request method, one of "GET", "POST", "PUT", and "DELETE" + * @param queryParams The query parameters + * @param collectionQueryParams The collection query parameters + * @param body The request body object - if it is not binary, otherwise null + * @param headerParams The header parameters + * @param cookieParams The cookie parameters + * @param formParams The form parameters + * @param accept The request's Accept header + * @param contentType The request's Content-Type header + * @param authNames The authentications to apply + * @param returnType Return type + * @return The response body in type of string + * @throws ApiException API exception + */ + public T invokeAPI(String path, String method, List queryParams, List collectionQueryParams, Object body, Map headerParams, Map cookieParams, Map formParams, String accept, String contentType, String[] authNames, GenericType returnType) throws ApiException { + + ClientResponse response = getAPIResponse(path, method, queryParams, collectionQueryParams, body, headerParams, cookieParams, formParams, accept, contentType, authNames); + + statusCode = response.getStatusInfo().getStatusCode(); + responseHeaders = response.getHeaders(); + + if(response.getStatusInfo().getStatusCode() == ClientResponse.Status.NO_CONTENT.getStatusCode()) { + return null; + } else if (response.getStatusInfo().getFamily() == Family.SUCCESSFUL) { + if (returnType == null) + return null; + else + return response.getEntity(returnType); + } else { + String message = "error"; + String respBody = null; + if (response.hasEntity()) { + try { + respBody = response.getEntity(String.class); + message = respBody; + } catch (RuntimeException e) { + // e.printStackTrace(); + } + } + throw new ApiException( + response.getStatusInfo().getStatusCode(), + message, + response.getHeaders(), + respBody); + } + } + + /** + * Update query and header parameters based on authentication settings. + * + * @param authNames The authentications to apply + * @param queryParams Query parameters + * @param headerParams Header parameters + * @param cookieParams Cookie parameters + */ + private void updateParamsForAuth(String[] authNames, List queryParams, Map headerParams, Map cookieParams) { + for (String authName : authNames) { + Authentication auth = authentications.get(authName); + if (auth == null) throw new RuntimeException("Authentication undefined: " + authName); + auth.applyToParams(queryParams, headerParams, cookieParams); + } + } + + /** + * Encode the given form parameters as request body. + * @param formParams Form parameters + * @return HTTP form encoded parameters + */ + private String getXWWWFormUrlencodedParams(Map formParams) { + StringBuilder formParamBuilder = new StringBuilder(); + + for (Entry param : formParams.entrySet()) { + String valueStr = parameterToString(param.getValue()); + try { + formParamBuilder.append(URLEncoder.encode(param.getKey(), "utf8")) + .append("=") + .append(URLEncoder.encode(valueStr, "utf8")); + formParamBuilder.append("&"); + } catch (UnsupportedEncodingException e) { + // move on to next + } + } + + String encodedFormParams = formParamBuilder.toString(); + if (encodedFormParams.endsWith("&")) { + encodedFormParams = encodedFormParams.substring(0, encodedFormParams.length() - 1); + } + + return encodedFormParams; + } +} diff --git a/config/clients/java/template/Java/BeanValidationException.mustache b/config/clients/java/template/Java/BeanValidationException.mustache new file mode 100644 index 00000000..acdaef19 --- /dev/null +++ b/config/clients/java/template/Java/BeanValidationException.mustache @@ -0,0 +1,27 @@ +package {{invokerPackage}}; + +import java.util.Set; + +import {{javaxPackage}}.validation.ConstraintViolation; +import {{javaxPackage}}.validation.ValidationException; + +public class BeanValidationException extends ValidationException { + /** + * + */ + private static final long serialVersionUID = -5294733947409491364L; + Set> violations; + + public BeanValidationException(Set> violations) { + this.violations = violations; + } + + public Set> getViolations() { + return violations; + } + + public void setViolations(Set> violations) { + this.violations = violations; + } + +} diff --git a/config/clients/java/template/Java/Configuration.mustache b/config/clients/java/template/Java/Configuration.mustache new file mode 100644 index 00000000..8e9720e3 --- /dev/null +++ b/config/clients/java/template/Java/Configuration.mustache @@ -0,0 +1,30 @@ +{{>licenseInfo}} + +package {{invokerPackage}}; + +{{>generatedAnnotation}} +public class Configuration { + public static final String VERSION = "{{{artifactVersion}}}"; + + private static ApiClient defaultApiClient = new ApiClient(); + + /** + * Get the default API client, which would be used when creating API + * instances without providing an API client. + * + * @return Default API client + */ + public static ApiClient getDefaultApiClient() { + return defaultApiClient; + } + + /** + * Set the default API client, which would be used when creating API + * instances without providing an API client. + * + * @param apiClient API client + */ + public static void setDefaultApiClient(ApiClient apiClient) { + defaultApiClient = apiClient; + } +} diff --git a/config/clients/java/template/Java/CustomInstantDeserializer.mustache b/config/clients/java/template/Java/CustomInstantDeserializer.mustache new file mode 100644 index 00000000..5ebea810 --- /dev/null +++ b/config/clients/java/template/Java/CustomInstantDeserializer.mustache @@ -0,0 +1,232 @@ +package {{invokerPackage}}; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonTokenId; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.datatype.threetenbp.DecimalUtils; +import com.fasterxml.jackson.datatype.threetenbp.deser.ThreeTenDateTimeDeserializerBase; +import com.fasterxml.jackson.datatype.threetenbp.function.BiFunction; +import com.fasterxml.jackson.datatype.threetenbp.function.Function; +import org.threeten.bp.DateTimeException; +import org.threeten.bp.DateTimeUtils; +import org.threeten.bp.Instant; +import org.threeten.bp.OffsetDateTime; +import org.threeten.bp.ZoneId; +import org.threeten.bp.ZonedDateTime; +import org.threeten.bp.format.DateTimeFormatter; +import org.threeten.bp.temporal.Temporal; +import org.threeten.bp.temporal.TemporalAccessor; + +import java.io.IOException; +import java.math.BigDecimal; + +/** + * Deserializer for ThreeTen temporal {@link Instant}s, {@link OffsetDateTime}, and {@link ZonedDateTime}s. + * Adapted from the jackson threetenbp InstantDeserializer to add support for deserializing rfc822 format. + * + * @author Nick Williams + */ +public class CustomInstantDeserializer + extends ThreeTenDateTimeDeserializerBase { + private static final long serialVersionUID = 1L; + + public static final CustomInstantDeserializer INSTANT = new CustomInstantDeserializer( + Instant.class, DateTimeFormatter.ISO_INSTANT, + new Function() { + @Override + public Instant apply(TemporalAccessor temporalAccessor) { + return Instant.from(temporalAccessor); + } + }, + new Function() { + @Override + public Instant apply(FromIntegerArguments a) { + return Instant.ofEpochMilli(a.value); + } + }, + new Function() { + @Override + public Instant apply(FromDecimalArguments a) { + return Instant.ofEpochSecond(a.integer, a.fraction); + } + }, + null + ); + + public static final CustomInstantDeserializer OFFSET_DATE_TIME = new CustomInstantDeserializer( + OffsetDateTime.class, DateTimeFormatter.ISO_OFFSET_DATE_TIME, + new Function() { + @Override + public OffsetDateTime apply(TemporalAccessor temporalAccessor) { + return OffsetDateTime.from(temporalAccessor); + } + }, + new Function() { + @Override + public OffsetDateTime apply(FromIntegerArguments a) { + return OffsetDateTime.ofInstant(Instant.ofEpochMilli(a.value), a.zoneId); + } + }, + new Function() { + @Override + public OffsetDateTime apply(FromDecimalArguments a) { + return OffsetDateTime.ofInstant(Instant.ofEpochSecond(a.integer, a.fraction), a.zoneId); + } + }, + new BiFunction() { + @Override + public OffsetDateTime apply(OffsetDateTime d, ZoneId z) { + return d.withOffsetSameInstant(z.getRules().getOffset(d.toLocalDateTime())); + } + } + ); + + public static final CustomInstantDeserializer ZONED_DATE_TIME = new CustomInstantDeserializer( + ZonedDateTime.class, DateTimeFormatter.ISO_ZONED_DATE_TIME, + new Function() { + @Override + public ZonedDateTime apply(TemporalAccessor temporalAccessor) { + return ZonedDateTime.from(temporalAccessor); + } + }, + new Function() { + @Override + public ZonedDateTime apply(FromIntegerArguments a) { + return ZonedDateTime.ofInstant(Instant.ofEpochMilli(a.value), a.zoneId); + } + }, + new Function() { + @Override + public ZonedDateTime apply(FromDecimalArguments a) { + return ZonedDateTime.ofInstant(Instant.ofEpochSecond(a.integer, a.fraction), a.zoneId); + } + }, + new BiFunction() { + @Override + public ZonedDateTime apply(ZonedDateTime zonedDateTime, ZoneId zoneId) { + return zonedDateTime.withZoneSameInstant(zoneId); + } + } + ); + + protected final Function fromMilliseconds; + + protected final Function fromNanoseconds; + + protected final Function parsedToValue; + + protected final BiFunction adjust; + + protected CustomInstantDeserializer(Class supportedType, + DateTimeFormatter parser, + Function parsedToValue, + Function fromMilliseconds, + Function fromNanoseconds, + BiFunction adjust) { + super(supportedType, parser); + this.parsedToValue = parsedToValue; + this.fromMilliseconds = fromMilliseconds; + this.fromNanoseconds = fromNanoseconds; + this.adjust = adjust == null ? new BiFunction() { + @Override + public T apply(T t, ZoneId zoneId) { + return t; + } + } : adjust; + } + + @SuppressWarnings("unchecked") + protected CustomInstantDeserializer(CustomInstantDeserializer base, DateTimeFormatter f) { + super((Class) base.handledType(), f); + parsedToValue = base.parsedToValue; + fromMilliseconds = base.fromMilliseconds; + fromNanoseconds = base.fromNanoseconds; + adjust = base.adjust; + } + + @Override + protected JsonDeserializer withDateFormat(DateTimeFormatter dtf) { + if (dtf == _formatter) { + return this; + } + return new CustomInstantDeserializer(this, dtf); + } + + @Override + public T deserialize(JsonParser parser, DeserializationContext context) throws IOException { + //NOTE: Timestamps contain no timezone info, and are always in configured TZ. Only + //string values have to be adjusted to the configured TZ. + switch (parser.getCurrentTokenId()) { + case JsonTokenId.ID_NUMBER_FLOAT: { + BigDecimal value = parser.getDecimalValue(); + long seconds = value.longValue(); + int nanoseconds = DecimalUtils.extractNanosecondDecimal(value, seconds); + return fromNanoseconds.apply(new FromDecimalArguments( + seconds, nanoseconds, getZone(context))); + } + + case JsonTokenId.ID_NUMBER_INT: { + long timestamp = parser.getLongValue(); + if (context.isEnabled(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS)) { + return this.fromNanoseconds.apply(new FromDecimalArguments( + timestamp, 0, this.getZone(context) + )); + } + return this.fromMilliseconds.apply(new FromIntegerArguments( + timestamp, this.getZone(context) + )); + } + + case JsonTokenId.ID_STRING: { + String string = parser.getText().trim(); + if (string.length() == 0) { + return null; + } + if (string.endsWith("+0000")) { + string = string.substring(0, string.length() - 5) + "Z"; + } + T value; + try { + TemporalAccessor acc = _formatter.parse(string); + value = parsedToValue.apply(acc); + if (context.isEnabled(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)) { + return adjust.apply(value, this.getZone(context)); + } + } catch (DateTimeException e) { + throw _peelDTE(e); + } + return value; + } + } + throw context.mappingException("Expected type float, integer, or string."); + } + + private ZoneId getZone(DeserializationContext context) { + // Instants are always in UTC, so don't waste compute cycles + return (_valueClass == Instant.class) ? null : DateTimeUtils.toZoneId(context.getTimeZone()); + } + + private static class FromIntegerArguments { + public final long value; + public final ZoneId zoneId; + + private FromIntegerArguments(long value, ZoneId zoneId) { + this.value = value; + this.zoneId = zoneId; + } + } + + private static class FromDecimalArguments { + public final long integer; + public final int fraction; + public final ZoneId zoneId; + + private FromDecimalArguments(long integer, int fraction, ZoneId zoneId) { + this.integer = integer; + this.fraction = fraction; + this.zoneId = zoneId; + } + } +} diff --git a/config/clients/java/template/Java/JSON.mustache b/config/clients/java/template/Java/JSON.mustache new file mode 100644 index 00000000..1d0a8138 --- /dev/null +++ b/config/clients/java/template/Java/JSON.mustache @@ -0,0 +1,539 @@ +{{>licenseInfo}} + +package {{invokerPackage}}; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonParseException; +import com.google.gson.TypeAdapter; +import com.google.gson.internal.bind.util.ISO8601Utils; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import com.google.gson.JsonElement; +import io.gsonfire.GsonFireBuilder; +import io.gsonfire.TypeSelector; +{{#joda}} +import org.joda.time.DateTime; +import org.joda.time.LocalDate; +import org.joda.time.format.DateTimeFormatter; +import org.joda.time.format.DateTimeFormatterBuilder; +import org.joda.time.format.ISODateTimeFormat; +{{/joda}} + +{{#models.0}} +import {{modelPackage}}.*; +{{/models.0}} +import okio.ByteString; + +import java.io.IOException; +import java.io.StringReader; +import java.lang.reflect.Type; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.ParsePosition; +import java.time.LocalDate; +import java.time.OffsetDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Date; +import java.util.Locale; +import java.util.Map; +import java.util.HashMap; + +public class JSON { + private Gson gson; + private boolean isLenientOnJson = false; + private DateTypeAdapter dateTypeAdapter = new DateTypeAdapter(); + private SqlDateTypeAdapter sqlDateTypeAdapter = new SqlDateTypeAdapter(); + {{#joda}} + private DateTimeTypeAdapter dateTimeTypeAdapter = new DateTimeTypeAdapter(); + private LocalDateTypeAdapter localDateTypeAdapter = new LocalDateTypeAdapter(); + {{/joda}} + {{#jsr310}} + private OffsetDateTimeTypeAdapter offsetDateTimeTypeAdapter = new OffsetDateTimeTypeAdapter(); + private LocalDateTypeAdapter localDateTypeAdapter = new LocalDateTypeAdapter(); + {{/jsr310}} + private ByteArrayAdapter byteArrayAdapter = new ByteArrayAdapter(); + + @SuppressWarnings("unchecked") + public static GsonBuilder createGson() { + GsonFireBuilder fireBuilder = new GsonFireBuilder() + {{#models}} + {{#model}} + {{#discriminator}} + .registerTypeSelector({{classname}}.class, new TypeSelector<{{classname}}>() { + @Override + public Class getClassForElement(JsonElement readElement) { + Map classByDiscriminatorValue = new HashMap(); + {{#mappedModels}} + classByDiscriminatorValue.put("{{mappingName}}"{{^discriminatorCaseSensitive}}.toUpperCase(Locale.ROOT){{/discriminatorCaseSensitive}}, {{modelName}}.class); + {{/mappedModels}} + classByDiscriminatorValue.put("{{name}}"{{^discriminatorCaseSensitive}}.toUpperCase(Locale.ROOT){{/discriminatorCaseSensitive}}, {{classname}}.class); + return getClassByDiscriminator(classByDiscriminatorValue, + getDiscriminatorValue(readElement, "{{{propertyBaseName}}}")); + } + }) + {{/discriminator}} + {{/model}} + {{/models}} + ; + GsonBuilder builder = fireBuilder.createGsonBuilder(); + {{#disableHtmlEscaping}} + builder.disableHtmlEscaping(); + {{/disableHtmlEscaping}} + return builder; + } + + private static String getDiscriminatorValue(JsonElement readElement, String discriminatorField) { + JsonElement element = readElement.getAsJsonObject().get(discriminatorField); + if (null == element) { + throw new IllegalArgumentException("missing discriminator field: <" + discriminatorField + ">"); + } + return element.getAsString(); + } + + /** + * Returns the Java class that implements the OpenAPI schema for the specified discriminator value. + * + * @param classByDiscriminatorValue The map of discriminator values to Java classes. + * @param discriminatorValue The value of the OpenAPI discriminator in the input data. + * @return The Java class that implements the OpenAPI schema + */ + private static Class getClassByDiscriminator(Map classByDiscriminatorValue, String discriminatorValue) { + Class clazz = (Class) classByDiscriminatorValue.get(discriminatorValue{{^discriminatorCaseSensitive}}.toUpperCase(Locale.ROOT){{/discriminatorCaseSensitive}}); + if (null == clazz) { + throw new IllegalArgumentException("cannot determine model class of name: <" + discriminatorValue + ">"); + } + return clazz; + } + + public JSON() { + gson = createGson() + .registerTypeAdapter(Date.class, dateTypeAdapter) + .registerTypeAdapter(java.sql.Date.class, sqlDateTypeAdapter) + {{#joda}} + .registerTypeAdapter(DateTime.class, dateTimeTypeAdapter) + .registerTypeAdapter(LocalDate.class, localDateTypeAdapter) + {{/joda}} + {{#jsr310}} + .registerTypeAdapter(OffsetDateTime.class, offsetDateTimeTypeAdapter) + .registerTypeAdapter(LocalDate.class, localDateTypeAdapter) + {{/jsr310}} + .registerTypeAdapter(byte[].class, byteArrayAdapter) + .create(); + } + + /** + * Get Gson. + * + * @return Gson + */ + public Gson getGson() { + return gson; + } + + /** + * Set Gson. + * + * @param gson Gson + * @return JSON + */ + public JSON setGson(Gson gson) { + this.gson = gson; + return this; + } + + /** + * Configure the parser to be liberal in what it accepts. + * + * @param lenientOnJson Set it to true to ignore some syntax errors + * @return JSON + * @see https://www.javadoc.io/doc/com.google.code.gson/gson/2.8.5/com/google/gson/stream/JsonReader.html + */ + public JSON setLenientOnJson(boolean lenientOnJson) { + isLenientOnJson = lenientOnJson; + return this; + } + + /** + * Serialize the given Java object into JSON string. + * + * @param obj Object + * @return String representation of the JSON + */ + public String serialize(Object obj) { + return gson.toJson(obj); + } + + /** + * Deserialize the given JSON string to Java object. + * + * @param Type + * @param body The JSON string + * @param returnType The type to deserialize into + * @return The deserialized Java object + */ + @SuppressWarnings("unchecked") + public T deserialize(String body, Type returnType) { + try { + if (isLenientOnJson) { + JsonReader jsonReader = new JsonReader(new StringReader(body)); + // see https://www.javadoc.io/doc/com.google.code.gson/gson/2.8.5/com/google/gson/stream/JsonReader.html + jsonReader.setLenient(true); + return gson.fromJson(jsonReader, returnType); + } else { + return gson.fromJson(body, returnType); + } + } catch (JsonParseException e) { + // Fallback processing when failed to parse JSON form response body: + // return the response body string directly for the String return type; + if (returnType.equals(String.class)) { + return (T) body; + } else { + throw (e); + } + } + } + + /** + * Gson TypeAdapter for Byte Array type + */ + public class ByteArrayAdapter extends TypeAdapter { + + @Override + public void write(JsonWriter out, byte[] value) throws IOException { + if (value == null) { + out.nullValue(); + } else { + out.value(ByteString.of(value).base64()); + } + } + + @Override + public byte[] read(JsonReader in) throws IOException { + switch (in.peek()) { + case NULL: + in.nextNull(); + return null; + default: + String bytesAsBase64 = in.nextString(); + ByteString byteString = ByteString.decodeBase64(bytesAsBase64); + return byteString.toByteArray(); + } + } + } + + {{#joda}} + /** + * Gson TypeAdapter for Joda DateTime type + */ + public static class DateTimeTypeAdapter extends TypeAdapter { + + private DateTimeFormatter formatter; + + public DateTimeTypeAdapter() { + this(new DateTimeFormatterBuilder() + .append(ISODateTimeFormat.dateTime().getPrinter(), ISODateTimeFormat.dateOptionalTimeParser().getParser()) + .toFormatter()); + } + + public DateTimeTypeAdapter(DateTimeFormatter formatter) { + this.formatter = formatter; + } + + public void setFormat(DateTimeFormatter dateFormat) { + this.formatter = dateFormat; + } + + @Override + public void write(JsonWriter out, DateTime date) throws IOException { + if (date == null) { + out.nullValue(); + } else { + out.value(formatter.print(date)); + } + } + + @Override + public DateTime read(JsonReader in) throws IOException { + switch (in.peek()) { + case NULL: + in.nextNull(); + return null; + default: + String date = in.nextString(); + return formatter.parseDateTime(date); + } + } + } + + /** + * Gson TypeAdapter for Joda LocalDate type + */ + public class LocalDateTypeAdapter extends TypeAdapter { + + private DateTimeFormatter formatter; + + public LocalDateTypeAdapter() { + this(ISODateTimeFormat.date()); + } + + public LocalDateTypeAdapter(DateTimeFormatter formatter) { + this.formatter = formatter; + } + + public void setFormat(DateTimeFormatter dateFormat) { + this.formatter = dateFormat; + } + + @Override + public void write(JsonWriter out, LocalDate date) throws IOException { + if (date == null) { + out.nullValue(); + } else { + out.value(formatter.print(date)); + } + } + + @Override + public LocalDate read(JsonReader in) throws IOException { + switch (in.peek()) { + case NULL: + in.nextNull(); + return null; + default: + String date = in.nextString(); + return formatter.parseLocalDate(date); + } + } + } + + public JSON setDateTimeFormat(DateTimeFormatter dateFormat) { + dateTimeTypeAdapter.setFormat(dateFormat); + return this; + } + + public JSON setLocalDateFormat(DateTimeFormatter dateFormat) { + localDateTypeAdapter.setFormat(dateFormat); + return this; + } + + {{/joda}} + {{#jsr310}} + /** + * Gson TypeAdapter for JSR310 OffsetDateTime type + */ + public static class OffsetDateTimeTypeAdapter extends TypeAdapter { + + private DateTimeFormatter formatter; + + public OffsetDateTimeTypeAdapter() { + this(DateTimeFormatter.ISO_OFFSET_DATE_TIME); + } + + public OffsetDateTimeTypeAdapter(DateTimeFormatter formatter) { + this.formatter = formatter; + } + + public void setFormat(DateTimeFormatter dateFormat) { + this.formatter = dateFormat; + } + + @Override + public void write(JsonWriter out, OffsetDateTime date) throws IOException { + if (date == null) { + out.nullValue(); + } else { + out.value(formatter.format(date)); + } + } + + @Override + public OffsetDateTime read(JsonReader in) throws IOException { + switch (in.peek()) { + case NULL: + in.nextNull(); + return null; + default: + String date = in.nextString(); + if (date.endsWith("+0000")) { + date = date.substring(0, date.length()-5) + "Z"; + } + return OffsetDateTime.parse(date, formatter); + } + } + } + + /** + * Gson TypeAdapter for JSR310 LocalDate type + */ + public class LocalDateTypeAdapter extends TypeAdapter { + + private DateTimeFormatter formatter; + + public LocalDateTypeAdapter() { + this(DateTimeFormatter.ISO_LOCAL_DATE); + } + + public LocalDateTypeAdapter(DateTimeFormatter formatter) { + this.formatter = formatter; + } + + public void setFormat(DateTimeFormatter dateFormat) { + this.formatter = dateFormat; + } + + @Override + public void write(JsonWriter out, LocalDate date) throws IOException { + if (date == null) { + out.nullValue(); + } else { + out.value(formatter.format(date)); + } + } + + @Override + public LocalDate read(JsonReader in) throws IOException { + switch (in.peek()) { + case NULL: + in.nextNull(); + return null; + default: + String date = in.nextString(); + return LocalDate.parse(date, formatter); + } + } + } + + public JSON setOffsetDateTimeFormat(DateTimeFormatter dateFormat) { + offsetDateTimeTypeAdapter.setFormat(dateFormat); + return this; + } + + public JSON setLocalDateFormat(DateTimeFormatter dateFormat) { + localDateTypeAdapter.setFormat(dateFormat); + return this; + } + + {{/jsr310}} + /** + * Gson TypeAdapter for java.sql.Date type + * If the dateFormat is null, a simple "yyyy-MM-dd" format will be used + * (more efficient than SimpleDateFormat). + */ + public static class SqlDateTypeAdapter extends TypeAdapter { + + private DateFormat dateFormat; + + public SqlDateTypeAdapter() {} + + public SqlDateTypeAdapter(DateFormat dateFormat) { + this.dateFormat = dateFormat; + } + + public void setFormat(DateFormat dateFormat) { + this.dateFormat = dateFormat; + } + + @Override + public void write(JsonWriter out, java.sql.Date date) throws IOException { + if (date == null) { + out.nullValue(); + } else { + String value; + if (dateFormat != null) { + value = dateFormat.format(date); + } else { + value = date.toString(); + } + out.value(value); + } + } + + @Override + public java.sql.Date read(JsonReader in) throws IOException { + switch (in.peek()) { + case NULL: + in.nextNull(); + return null; + default: + String date = in.nextString(); + try { + if (dateFormat != null) { + return new java.sql.Date(dateFormat.parse(date).getTime()); + } + return new java.sql.Date(ISO8601Utils.parse(date, new ParsePosition(0)).getTime()); + } catch (ParseException e) { + throw new JsonParseException(e); + } + } + } + } + + /** + * Gson TypeAdapter for java.util.Date type + * If the dateFormat is null, ISO8601Utils will be used. + */ + public static class DateTypeAdapter extends TypeAdapter { + + private DateFormat dateFormat; + + public DateTypeAdapter() {} + + public DateTypeAdapter(DateFormat dateFormat) { + this.dateFormat = dateFormat; + } + + public void setFormat(DateFormat dateFormat) { + this.dateFormat = dateFormat; + } + + @Override + public void write(JsonWriter out, Date date) throws IOException { + if (date == null) { + out.nullValue(); + } else { + String value; + if (dateFormat != null) { + value = dateFormat.format(date); + } else { + value = ISO8601Utils.format(date, true); + } + out.value(value); + } + } + + @Override + public Date read(JsonReader in) throws IOException { + try { + switch (in.peek()) { + case NULL: + in.nextNull(); + return null; + default: + String date = in.nextString(); + try { + if (dateFormat != null) { + return dateFormat.parse(date); + } + return ISO8601Utils.parse(date, new ParsePosition(0)); + } catch (ParseException e) { + throw new JsonParseException(e); + } + } + } catch (IllegalArgumentException e) { + throw new JsonParseException(e); + } + } + } + + public JSON setDateFormat(DateFormat dateFormat) { + dateTypeAdapter.setFormat(dateFormat); + return this; + } + + public JSON setSqlDateFormat(DateFormat dateFormat) { + sqlDateTypeAdapter.setFormat(dateFormat); + return this; + } + +} diff --git a/config/clients/java/template/Java/JavaTimeFormatter.mustache b/config/clients/java/template/Java/JavaTimeFormatter.mustache new file mode 100644 index 00000000..f3fb34e5 --- /dev/null +++ b/config/clients/java/template/Java/JavaTimeFormatter.mustache @@ -0,0 +1,53 @@ +{{>licenseInfo}} +package {{invokerPackage}}; + +import java.time.OffsetDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; + +/** + * Class that add parsing/formatting support for Java 8+ {@code OffsetDateTime} class. + * It's generated for java clients when {@code AbstractJavaCodegen#dateLibrary} specified as {@code java8}. + */ +{{>generatedAnnotation}} +public class JavaTimeFormatter { + + private DateTimeFormatter offsetDateTimeFormatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME; + + /** + * Get the date format used to parse/format {@code OffsetDateTime} parameters. + * @return DateTimeFormatter + */ + public DateTimeFormatter getOffsetDateTimeFormatter() { + return offsetDateTimeFormatter; + } + + /** + * Set the date format used to parse/format {@code OffsetDateTime} parameters. + * @param offsetDateTimeFormatter {@code DateTimeFormatter} + */ + public void setOffsetDateTimeFormatter(DateTimeFormatter offsetDateTimeFormatter) { + this.offsetDateTimeFormatter = offsetDateTimeFormatter; + } + + /** + * Parse the given string into {@code OffsetDateTime} object. + * @param str String + * @return {@code OffsetDateTime} + */ + public OffsetDateTime parseOffsetDateTime(String str) { + try { + return OffsetDateTime.parse(str, offsetDateTimeFormatter); + } catch (DateTimeParseException e) { + throw new RuntimeException(e); + } + } + /** + * Format the given {@code OffsetDateTime} object into string. + * @param offsetDateTime {@code OffsetDateTime} + * @return {@code OffsetDateTime} in string format + */ + public String formatOffsetDateTime(OffsetDateTime offsetDateTime) { + return offsetDateTimeFormatter.format(offsetDateTime); + } +} diff --git a/config/clients/java/template/Java/Pair.mustache b/config/clients/java/template/Java/Pair.mustache new file mode 100644 index 00000000..ed767ce7 --- /dev/null +++ b/config/clients/java/template/Java/Pair.mustache @@ -0,0 +1,46 @@ +{{>licenseInfo}} + +package {{invokerPackage}}; + +{{>generatedAnnotation}} +public class Pair { + private String name = ""; + private String value = ""; + + public Pair (String name, String value) { + setName(name); + setValue(value); + } + + private void setName(String name) { + if (!isValidString(name)) { + return; + } + + this.name = name; + } + + private void setValue(String value) { + if (!isValidString(value)) { + return; + } + + this.value = value; + } + + public String getName() { + return this.name; + } + + public String getValue() { + return this.value; + } + + private boolean isValidString(String arg) { + if (arg == null) { + return false; + } + + return true; + } +} diff --git a/config/clients/java/template/Java/README.mustache b/config/clients/java/template/Java/README.mustache new file mode 100644 index 00000000..1b2d1299 --- /dev/null +++ b/config/clients/java/template/Java/README.mustache @@ -0,0 +1,246 @@ +# {{artifactId}} + +{{appName}} + +- API version: {{appVersion}} +{{^hideGenerationTimestamp}} + +- Build date: {{generatedDate}} +{{/hideGenerationTimestamp}} + +{{{appDescriptionWithNewLines}}} + +{{#infoUrl}} + For more information, please visit [{{{infoUrl}}}]({{{infoUrl}}}) +{{/infoUrl}} + +*Automatically generated by the [OpenAPI Generator](https://openapi-generator.tech)* + +## Requirements + +Building the API client library requires: + +1. Java 1.8+ +{{#jersey2}} +2. Maven (3.8.3+)/Gradle (7.2+) +{{/jersey2}} +{{^jersey2}} +2. Maven/Gradle +{{/jersey2}} + +## Installation + +To install the API client library to your local Maven repository, simply execute: + +```shell +mvn clean install +``` + +To deploy it to a remote Maven repository instead, configure the settings of the repository and execute: + +```shell +mvn clean deploy +``` + +Refer to the [OSSRH Guide](http://central.sonatype.org/pages/ossrh-guide.html) for more information. + +### Maven users + +Add this dependency to your project's POM: + +```xml + + {{{groupId}}} + {{{artifactId}}} + {{{artifactVersion}}} + compile + +``` + +### Gradle users + +Add this dependency to your project's build file: + +```groovy + repositories { + mavenCentral() // Needed if the '{{{artifactId}}}' jar has been published to maven central. + mavenLocal() // Needed if the '{{{artifactId}}}' jar has been published to the local maven repo. + } + + dependencies { + implementation "{{{groupId}}}:{{{artifactId}}}:{{{artifactVersion}}}" + } +``` + +### Others + +At first generate the JAR by executing: + +```shell +mvn clean package +``` + +Then manually install the following JARs: + +- `target/{{{artifactId}}}-{{{artifactVersion}}}.jar` +- `target/lib/*.jar` + +{{#jersey2}} +## Usage + +To add a HTTP proxy for the API client, use `ClientConfig`: +```java +{{#apiInfo}}{{#apis}}{{#-first}}{{#operations}}{{#operation}}{{#-first}} +import org.glassfish.jersey.apache.connector.ApacheConnectorProvider; +import org.glassfish.jersey.client.ClientConfig; +import org.glassfish.jersey.client.ClientProperties; +import {{{invokerPackage}}}.*; +import {{{package}}}.{{{classname}}}; + +... + +ApiClient defaultClient = Configuration.getDefaultApiClient(); +ClientConfig clientConfig = defaultClient.getClientConfig(); +clientConfig.connectorProvider(new ApacheConnectorProvider()); +clientConfig.property(ClientProperties.PROXY_URI, "http://proxy_url_here"); +clientConfig.property(ClientProperties.PROXY_USERNAME, "proxy_username"); +clientConfig.property(ClientProperties.PROXY_PASSWORD, "proxy_password"); +defaultClient.setClientConfig(clientConfig); + +{{{classname}}} apiInstance = new {{{classname}}}(defaultClient); +{{/-first}}{{/operation}}{{/operations}}{{/-first}}{{/apis}}{{/apiInfo}} +``` + +{{/jersey2}} +## Getting Started + +Please follow the [installation](#installation) instruction and execute the following Java code: + +```java +{{#apiInfo}}{{#apis}}{{#-first}}{{#operations}}{{#operation}}{{#-first}} +import {{{invokerPackage}}}.*; +import {{{invokerPackage}}}.auth.*; +import {{{modelPackage}}}.*; +import {{{package}}}.{{{classname}}}; + +public class {{{classname}}}Example { + + public static void main(String[] args) { + ApiClient defaultClient = Configuration.getDefaultApiClient(); + defaultClient.setBasePath("{{{basePath}}}"); + {{#hasAuthMethods}}{{#authMethods}}{{#isBasic}}{{#isBasicBasic}} + // Configure HTTP basic authorization: {{{name}}} + HttpBasicAuth {{{name}}} = (HttpBasicAuth) defaultClient.getAuthentication("{{{name}}}"); + {{{name}}}.setUsername("YOUR USERNAME"); + {{{name}}}.setPassword("YOUR PASSWORD");{{/isBasicBasic}}{{#isBasicBearer}} + // Configure HTTP bearer authorization: {{{name}}} + HttpBearerAuth {{{name}}} = (HttpBearerAuth) defaultClient.getAuthentication("{{{name}}}"); + {{{name}}}.setBearerToken("BEARER TOKEN");{{/isBasicBearer}}{{/isBasic}}{{#isApiKey}} + // Configure API key authorization: {{{name}}} + ApiKeyAuth {{{name}}} = (ApiKeyAuth) defaultClient.getAuthentication("{{{name}}}"); + {{{name}}}.setApiKey("YOUR API KEY"); + // Uncomment the following line to set a prefix for the API key, e.g. "Token" (defaults to null) + //{{{name}}}.setApiKeyPrefix("Token");{{/isApiKey}}{{#isOAuth}} + // Configure OAuth2 access token for authorization: {{{name}}} + OAuth {{{name}}} = (OAuth) defaultClient.getAuthentication("{{{name}}}"); + {{{name}}}.setAccessToken("YOUR ACCESS TOKEN");{{/isOAuth}}{{#isHttpSignature}} + // Configure HTTP signature authorization: {{{name}}} + HttpSignatureAuth {{{name}}} = (HttpSignatureAuth) defaultClient.getAuthentication("{{{name}}}"); + // All the HTTP signature parameters below should be customized to your environment. + // Configure the keyId + {{{name}}}.setKeyId("YOUR KEY ID"); + // Configure the signature algorithm + {{{name}}}.setSigningAlgorithm(SigningAlgorithm.HS2019); + // Configure the specific cryptographic algorithm + {{{name}}}.setAlgorithm(Algorithm.ECDSA_SHA256); + // Configure the cryptographic algorithm parameters, if applicable + {{{name}}}.setAlgorithmParameterSpec(null); + // Set the cryptographic digest algorithm. + {{{name}}}.setDigestAlgorithm("SHA-256"); + // Set the HTTP headers that should be included in the HTTP signature. + {{{name}}}.setHeaders(Arrays.asList("date", "host")); + // Set the private key used to sign the HTTP messages + {{{name}}}.setPrivateKey();{{/isHttpSignature}} + {{/authMethods}} + {{/hasAuthMethods}} + + {{{classname}}} apiInstance = new {{{classname}}}(defaultClient); + {{#allParams}} + {{{dataType}}} {{{paramName}}} = {{{example}}}; // {{{dataType}}} | {{{description}}} + {{/allParams}} + try { + {{#returnType}}{{{.}}} result = {{/returnType}}apiInstance.{{{operationId}}}({{#allParams}}{{{paramName}}}{{^-last}}, {{/-last}}{{/allParams}});{{#returnType}} + System.out.println(result);{{/returnType}} + } catch (ApiException e) { + System.err.println("Exception when calling {{{classname}}}#{{{operationId}}}"); + System.err.println("Status code: " + e.getCode()); + System.err.println("Reason: " + e.getResponseBody()); + System.err.println("Response headers: " + e.getResponseHeaders()); + e.printStackTrace(); + } + } +} +{{/-first}}{{/operation}}{{/operations}}{{/-first}}{{/apis}}{{/apiInfo}} +``` + +## Documentation for API Endpoints + +All URIs are relative to *{{basePath}}* + +Class | Method | HTTP request | Description +------------ | ------------- | ------------- | ------------- +{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}*{{classname}}* | [**{{operationId}}**]({{apiDocPath}}{{classname}}.md#{{operationId}}) | **{{httpMethod}}** {{commonPath}}{{path}} | {{summary}} +{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}} + +## Documentation for Models + +{{#models}}{{#model}} - [{{classname}}]({{modelDocPath}}{{classname}}.md) +{{/model}}{{/models}} + + +## Documentation for Authorization + +{{^authMethods}}Endpoints do not require authorization.{{/authMethods}} +{{#hasAuthMethods}}Authentication schemes defined for the API:{{/hasAuthMethods}} +{{#authMethods}} + +### {{name}} + +{{#isApiKey}} + +- **Type**: API key +- **API key parameter name**: {{keyParamName}} +- **Location**: {{#isKeyInQuery}}URL query string{{/isKeyInQuery}}{{#isKeyInHeader}}HTTP header{{/isKeyInHeader}} +{{/isApiKey}} +{{#isBasicBasic}} + +- **Type**: HTTP basic authentication +{{/isBasicBasic}} +{{#isBasicBearer}} + +- **Type**: HTTP Bearer Token authentication{{#bearerFormat}} ({{{.}}}){{/bearerFormat}} +{{/isBasicBearer}} +{{#isHttpSignature}} + +- **Type**: HTTP signature authentication +{{/isHttpSignature}} +{{#isOAuth}} + +- **Type**: OAuth +- **Flow**: {{flow}} +- **Authorization URL**: {{authorizationUrl}} +- **Scopes**: {{^scopes}}N/A{{/scopes}} +{{#scopes}} - {{scope}}: {{description}} +{{/scopes}} +{{/isOAuth}} + +{{/authMethods}} + +## Recommendation + +It's recommended to create an instance of `ApiClient` per thread in a multithreaded environment to avoid any potential issues. + +## Author + +{{#apiInfo}}{{#apis}}{{#-last}}{{infoEmail}} +{{/-last}}{{/apis}}{{/apiInfo}} diff --git a/config/clients/java/template/Java/RFC3339DateFormat.mustache b/config/clients/java/template/Java/RFC3339DateFormat.mustache new file mode 100644 index 00000000..311616a4 --- /dev/null +++ b/config/clients/java/template/Java/RFC3339DateFormat.mustache @@ -0,0 +1,46 @@ +{{>licenseInfo}} +package {{invokerPackage}}; + +import com.fasterxml.jackson.databind.util.StdDateFormat; + +import java.text.DateFormat; +import java.text.FieldPosition; +import java.text.ParsePosition; +import java.util.Date; +import java.text.DecimalFormat; +import java.util.GregorianCalendar; +import java.util.TimeZone; + +public class RFC3339DateFormat extends DateFormat { + private static final long serialVersionUID = 1L; + private static final TimeZone TIMEZONE_Z = TimeZone.getTimeZone("UTC"); + + private final StdDateFormat fmt = new StdDateFormat() + .withTimeZone(TIMEZONE_Z) + .withColonInTimeZone(true); + + public RFC3339DateFormat() { + this.calendar = new GregorianCalendar(); + this.numberFormat = new DecimalFormat(); + } + + @Override + public Date parse(String source) { + return parse(source, new ParsePosition(0)); + } + + @Override + public Date parse(String source, ParsePosition pos) { + return fmt.parse(source, pos); + } + + @Override + public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition) { + return fmt.format(date, toAppendTo, fieldPosition); + } + + @Override + public Object clone() { + return super.clone(); + } +} \ No newline at end of file diff --git a/config/clients/java/template/Java/ServerConfiguration.mustache b/config/clients/java/template/Java/ServerConfiguration.mustache new file mode 100644 index 00000000..8b4cf59f --- /dev/null +++ b/config/clients/java/template/Java/ServerConfiguration.mustache @@ -0,0 +1,58 @@ +package {{invokerPackage}}; + +import java.util.Map; + +/** + * Representing a Server configuration. + */ +public class ServerConfiguration { + public String URL; + public String description; + public Map variables; + + /** + * @param URL A URL to the target host. + * @param description A description of the host designated by the URL. + * @param variables A map between a variable name and its value. The value is used for substitution in the server's URL template. + */ + public ServerConfiguration(String URL, String description, Map variables) { + this.URL = URL; + this.description = description; + this.variables = variables; + } + + /** + * Format URL template using given variables. + * + * @param variables A map between a variable name and its value. + * @return Formatted URL. + */ + public String URL(Map variables) { + String url = this.URL; + + // go through variables and replace placeholders + for (Map.Entry variable: this.variables.entrySet()) { + String name = variable.getKey(); + ServerVariable serverVariable = variable.getValue(); + String value = serverVariable.defaultValue; + + if (variables != null && variables.containsKey(name)) { + value = variables.get(name); + if (serverVariable.enumValues.size() > 0 && !serverVariable.enumValues.contains(value)) { + throw new IllegalArgumentException("The variable " + name + " in the server URL has invalid value " + value + "."); + } + } + url = url.replace("{" + name + "}", value); + } + return url; + } + + /** + * Format URL template using default server variables. + * + * @return Formatted URL. + */ + public String URL() { + return URL(null); + } +} diff --git a/config/clients/java/template/Java/ServerVariable.mustache b/config/clients/java/template/Java/ServerVariable.mustache new file mode 100644 index 00000000..1978b1eb --- /dev/null +++ b/config/clients/java/template/Java/ServerVariable.mustache @@ -0,0 +1,23 @@ +package {{invokerPackage}}; + +import java.util.HashSet; + +/** + * Representing a Server Variable for server URL template substitution. + */ +public class ServerVariable { + public String description; + public String defaultValue; + public HashSet enumValues = null; + + /** + * @param description A description for the server variable. + * @param defaultValue The default value to use for substitution. + * @param enumValues An enumeration of string values to be used if the substitution options are from a limited set. + */ + public ServerVariable(String description, String defaultValue, HashSet enumValues) { + this.description = description; + this.defaultValue = defaultValue; + this.enumValues = enumValues; + } +} diff --git a/config/clients/java/template/Java/StringUtil.mustache b/config/clients/java/template/Java/StringUtil.mustache new file mode 100644 index 00000000..e3d5d6e9 --- /dev/null +++ b/config/clients/java/template/Java/StringUtil.mustache @@ -0,0 +1,72 @@ +{{>licenseInfo}} + +package {{invokerPackage}}; + +import java.util.Collection; +import java.util.Iterator; + +{{>generatedAnnotation}} +public class StringUtil { + /** + * Check if the given array contains the given value (with case-insensitive comparison). + * + * @param array The array + * @param value The value to search + * @return true if the array contains the value + */ + public static boolean containsIgnoreCase(String[] array, String value) { + for (String str : array) { + if (value == null && str == null) { + return true; + } + if (value != null && value.equalsIgnoreCase(str)) { + return true; + } + } + return false; + } + + /** + * Join an array of strings with the given separator. + *

+ * Note: This might be replaced by utility method from commons-lang or guava someday + * if one of those libraries is added as dependency. + *

+ * + * @param array The array of strings + * @param separator The separator + * @return the resulting string + */ + public static String join(String[] array, String separator) { + int len = array.length; + if (len == 0) { + return ""; + } + + StringBuilder out = new StringBuilder(); + out.append(array[0]); + for (int i = 1; i < len; i++) { + out.append(separator).append(array[i]); + } + return out.toString(); + } + + /** + * Join a list of strings with the given separator. + * + * @param list The list of strings + * @param separator The separator + * @return the resulting string + */ + public static String join(Collection list, String separator) { + Iterator iterator = list.iterator(); + StringBuilder out = new StringBuilder(); + if (iterator.hasNext()) { + out.append(iterator.next()); + } + while (iterator.hasNext()) { + out.append(separator).append(iterator.next()); + } + return out.toString(); + } +} diff --git a/config/clients/java/template/Java/additionalEnumTypeAnnotations.mustache b/config/clients/java/template/Java/additionalEnumTypeAnnotations.mustache new file mode 100644 index 00000000..aa524798 --- /dev/null +++ b/config/clients/java/template/Java/additionalEnumTypeAnnotations.mustache @@ -0,0 +1,2 @@ +{{#additionalEnumTypeAnnotations}}{{{.}}} +{{/additionalEnumTypeAnnotations}} \ No newline at end of file diff --git a/config/clients/java/template/Java/additionalModelTypeAnnotations.mustache b/config/clients/java/template/Java/additionalModelTypeAnnotations.mustache new file mode 100644 index 00000000..f4871c02 --- /dev/null +++ b/config/clients/java/template/Java/additionalModelTypeAnnotations.mustache @@ -0,0 +1,2 @@ +{{#additionalModelTypeAnnotations}}{{{.}}} +{{/additionalModelTypeAnnotations}} \ No newline at end of file diff --git a/config/clients/java/template/Java/additionalOneOfTypeAnnotations.mustache b/config/clients/java/template/Java/additionalOneOfTypeAnnotations.mustache new file mode 100644 index 00000000..283f8f91 --- /dev/null +++ b/config/clients/java/template/Java/additionalOneOfTypeAnnotations.mustache @@ -0,0 +1,2 @@ +{{#additionalOneOfTypeAnnotations}}{{{.}}} +{{/additionalOneOfTypeAnnotations}} \ No newline at end of file diff --git a/config/clients/java/template/Java/api.mustache b/config/clients/java/template/Java/api.mustache new file mode 100644 index 00000000..98f6d0cb --- /dev/null +++ b/config/clients/java/template/Java/api.mustache @@ -0,0 +1,120 @@ +{{>licenseInfo}} +package {{package}}; + +import com.sun.jersey.api.client.GenericType; + +import {{invokerPackage}}.ApiException; +import {{invokerPackage}}.ApiClient; +import {{invokerPackage}}.Configuration; +import {{modelPackage}}.*; +import {{invokerPackage}}.Pair; + +{{#imports}}import {{import}}; +{{/imports}} + + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +{{>generatedAnnotation}} +{{#operations}} +public class {{classname}} { + private ApiClient apiClient; + + public {{classname}}() { + this(Configuration.getDefaultApiClient()); + } + + public {{classname}}(ApiClient apiClient) { + this.apiClient = apiClient; + } + + public ApiClient getApiClient() { + return apiClient; + } + + public void setApiClient(ApiClient apiClient) { + this.apiClient = apiClient; + } + + {{#operation}} + /** + * {{summary}} + * {{notes}} + {{#allParams}} + * @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{^isContainer}}{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/isContainer}}{{/required}} + {{/allParams}} + {{#returnType}} + * @return {{.}} + {{/returnType}} + * @throws ApiException if fails to make API call + {{#isDeprecated}} + * @deprecated + {{/isDeprecated}} + {{#externalDocs}} + * {{description}} + * @see {{summary}} Documentation + {{/externalDocs}} + */ + {{#isDeprecated}} + @Deprecated + {{/isDeprecated}} + public {{#returnType}}{{{.}}} {{/returnType}}{{^returnType}}void {{/returnType}}{{operationId}}({{#allParams}}{{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws ApiException { + Object localVarPostBody = {{#bodyParam}}{{paramName}}{{/bodyParam}}{{^bodyParam}}null{{/bodyParam}}; + {{#allParams}}{{#required}} + // verify the required parameter '{{paramName}}' is set + if ({{paramName}} == null) { + throw new ApiException(400, "Missing the required parameter '{{paramName}}' when calling {{operationId}}"); + } + {{/required}}{{/allParams}} + // create path and map variables + String localVarPath = "{{{path}}}"{{#pathParams}} + .replaceAll("\\{" + "{{baseName}}" + "\\}", apiClient.escapeString({{{paramName}}}.toString())){{/pathParams}}; + + // query params + List localVarQueryParams = new ArrayList(); + List localVarCollectionQueryParams = new ArrayList(); + Map localVarHeaderParams = new HashMap(); + Map localVarCookieParams = new HashMap(); + Map localVarFormParams = new HashMap(); + + {{#queryParams}} + {{#collectionFormat}}localVarCollectionQueryParams.addAll(apiClient.parameterToPairs("{{{.}}}", {{/collectionFormat}}{{^collectionFormat}}localVarQueryParams.addAll(apiClient.parameterToPair({{/collectionFormat}}"{{baseName}}", {{paramName}})); + {{/queryParams}} + + {{#headerParams}}if ({{paramName}} != null) + localVarHeaderParams.put("{{baseName}}", apiClient.parameterToString({{paramName}})); + {{/headerParams}} + + {{#cookieParams}}if ({{paramName}} != null) + localVarCookieParams.put("{{baseName}}", apiClient.parameterToString({{paramName}})); + {{/cookieParams}} + + {{#formParams}}if ({{paramName}} != null) + localVarFormParams.put("{{baseName}}", {{paramName}}); + {{/formParams}} + + final String[] localVarAccepts = { + {{#produces}}"{{{mediaType}}}"{{^-last}}, {{/-last}}{{/produces}} + }; + final String localVarAccept = apiClient.selectHeaderAccept(localVarAccepts); + + final String[] localVarContentTypes = { + {{#consumes}}"{{{mediaType}}}"{{^-last}}, {{/-last}}{{/consumes}} + }; + final String localVarContentType = apiClient.selectHeaderContentType(localVarContentTypes); + + String[] localVarAuthNames = new String[] { {{#authMethods}}"{{name}}"{{^-last}}, {{/-last}}{{/authMethods}} }; + + {{#returnType}} + GenericType<{{{returnType}}}> localVarReturnType = new GenericType<{{{returnType}}}>() {}; + return apiClient.invokeAPI(localVarPath, "{{httpMethod}}", localVarQueryParams, localVarCollectionQueryParams, localVarPostBody, localVarHeaderParams, localVarCookieParams, localVarFormParams, localVarAccept, localVarContentType, localVarAuthNames, localVarReturnType); + {{/returnType}}{{^returnType}} + apiClient.invokeAPI(localVarPath, "{{httpMethod}}", localVarQueryParams, localVarCollectionQueryParams, localVarPostBody, localVarHeaderParams, localVarCookieParams, localVarFormParams, localVarAccept, localVarContentType, localVarAuthNames, null); + {{/returnType}} + } + {{/operation}} +} +{{/operations}} diff --git a/config/clients/java/template/Java/apiException.mustache b/config/clients/java/template/Java/apiException.mustache new file mode 100644 index 00000000..31d2a807 --- /dev/null +++ b/config/clients/java/template/Java/apiException.mustache @@ -0,0 +1,89 @@ +{{>licenseInfo}} + +package {{invokerPackage}}; + +import java.util.Map; +import java.util.List; + +{{>generatedAnnotation}} +public class ApiException extends{{#useRuntimeException}} RuntimeException {{/useRuntimeException}}{{^useRuntimeException}} Exception {{/useRuntimeException}}{ + private int code = 0; + private Map> responseHeaders = null; + private String responseBody = null; + + public ApiException() {} + + public ApiException(Throwable throwable) { + super(throwable); + } + + public ApiException(String message) { + super(message); + } + + public ApiException(String message, Throwable throwable, int code, Map> responseHeaders, String responseBody) { + super(message, throwable); + this.code = code; + this.responseHeaders = responseHeaders; + this.responseBody = responseBody; + } + + public ApiException(String message, int code, Map> responseHeaders, String responseBody) { + this(message, (Throwable) null, code, responseHeaders, responseBody); + } + + public ApiException(String message, Throwable throwable, int code, Map> responseHeaders) { + this(message, throwable, code, responseHeaders, null); + } + + public ApiException(int code, Map> responseHeaders, String responseBody) { + this("Response Code: " + code + " Response Body: " + responseBody, (Throwable) null, code, responseHeaders, responseBody); + } + + public ApiException(int code, String message) { + super(message); + this.code = code; + } + + public ApiException(int code, String message, Map> responseHeaders, String responseBody) { + this(code, message); + this.responseHeaders = responseHeaders; + this.responseBody = responseBody; + } + + /** + * Get the HTTP status code. + * + * @return HTTP status code + */ + public int getCode() { + return code; + } + + /** + * Get the HTTP response headers. + * + * @return A map of list of string + */ + public Map> getResponseHeaders() { + return responseHeaders; + } + + /** + * Get the HTTP response body. + * + * @return Response body in the form of string + */ + public String getResponseBody() { + return responseBody; + } + + @Override + public String toString() { + return "ApiException{" + + "code=" + code + + ", responseHeaders=" + responseHeaders + + ", responseBody='" + responseBody + '\'' + + '}'; + } +} diff --git a/config/clients/java/template/Java/apiOperation.mustache b/config/clients/java/template/Java/apiOperation.mustache new file mode 100644 index 00000000..97adb0ea --- /dev/null +++ b/config/clients/java/template/Java/apiOperation.mustache @@ -0,0 +1,28 @@ +{{>licenseInfo}} +package {{invokerPackage}}; + +import io.swagger.v3.oas.models.Operation; + +public class ApiOperation { + private final String path; + private final String method; + private final Operation operation; + + public ApiOperation(String path, String method, Operation operation) { + this.path = path; + this.method = method; + this.operation = operation; + } + + public Operation getOperation() { + return operation; + } + + public String getPath() { + return path; + } + + public String getMethod() { + return method; + } +} diff --git a/config/clients/java/template/Java/api_doc.mustache b/config/clients/java/template/Java/api_doc.mustache new file mode 100644 index 00000000..3d320395 --- /dev/null +++ b/config/clients/java/template/Java/api_doc.mustache @@ -0,0 +1,108 @@ +# {{classname}}{{#description}} + +{{.}}{{/description}} + +All URIs are relative to *{{basePath}}* + +| Method | HTTP request | Description | +|------------- | ------------- | -------------| +{{#operations}}{{#operation}}| [**{{operationId}}**]({{classname}}.md#{{operationId}}) | **{{httpMethod}}** {{commonPath}}{{path}} | {{summary}} | +{{/operation}}{{/operations}} + +{{#operations}} +{{#operation}} + +## {{operationId}} + +> {{#returnType}}{{.}} {{/returnType}}{{operationId}}({{#allParams}}{{{paramName}}}{{^-last}}, {{/-last}}{{/allParams}}) + +{{summary}}{{#notes}} + +{{.}}{{/notes}} + +### Example + +```java +// Import classes: +import {{{invokerPackage}}}.ApiClient; +import {{{invokerPackage}}}.ApiException; +import {{{invokerPackage}}}.Configuration;{{#hasAuthMethods}} +import {{{invokerPackage}}}.auth.*;{{/hasAuthMethods}} +import {{{invokerPackage}}}.models.*; +import {{{package}}}.{{{classname}}}; + +public class Example { + public static void main(String[] args) { + ApiClient defaultClient = Configuration.getDefaultApiClient(); + defaultClient.setBasePath("{{{basePath}}}"); + {{#hasAuthMethods}} + {{#authMethods}}{{#isBasic}}{{#isBasicBasic}} + // Configure HTTP basic authorization: {{{name}}} + HttpBasicAuth {{{name}}} = (HttpBasicAuth) defaultClient.getAuthentication("{{{name}}}"); + {{{name}}}.setUsername("YOUR USERNAME"); + {{{name}}}.setPassword("YOUR PASSWORD");{{/isBasicBasic}}{{#isBasicBearer}} + // Configure HTTP bearer authorization: {{{name}}} + HttpBearerAuth {{{name}}} = (HttpBearerAuth) defaultClient.getAuthentication("{{{name}}}"); + {{{name}}}.setBearerToken("BEARER TOKEN");{{/isBasicBearer}}{{/isBasic}}{{#isApiKey}} + // Configure API key authorization: {{{name}}} + ApiKeyAuth {{{name}}} = (ApiKeyAuth) defaultClient.getAuthentication("{{{name}}}"); + {{{name}}}.setApiKey("YOUR API KEY"); + // Uncomment the following line to set a prefix for the API key, e.g. "Token" (defaults to null) + //{{{name}}}.setApiKeyPrefix("Token");{{/isApiKey}}{{#isOAuth}} + // Configure OAuth2 access token for authorization: {{{name}}} + OAuth {{{name}}} = (OAuth) defaultClient.getAuthentication("{{{name}}}"); + {{{name}}}.setAccessToken("YOUR ACCESS TOKEN");{{/isOAuth}} + {{/authMethods}} + {{/hasAuthMethods}} + + {{{classname}}} apiInstance = new {{{classname}}}(defaultClient); + {{#allParams}} + {{{dataType}}} {{{paramName}}} = {{{example}}}; // {{{dataType}}} | {{{description}}} + {{/allParams}} + try { + {{#returnType}}{{{.}}} result = {{/returnType}}apiInstance.{{{operationId}}}({{#allParams}}{{{paramName}}}{{^-last}}, {{/-last}}{{/allParams}});{{#returnType}} + System.out.println(result);{{/returnType}} + } catch (ApiException e) { + System.err.println("Exception when calling {{{classname}}}#{{{operationId}}}"); + System.err.println("Status code: " + e.getCode()); + System.err.println("Reason: " + e.getResponseBody()); + System.err.println("Response headers: " + e.getResponseHeaders()); + e.printStackTrace(); + } + } +} +``` + +### Parameters + +{{^allParams}}This endpoint does not need any parameter.{{/allParams}}{{#allParams}}{{#-last}} +| Name | Type | Description | Notes | +|------------- | ------------- | ------------- | -------------|{{/-last}}{{/allParams}} +{{#allParams}}| **{{paramName}}** | {{#isPrimitiveType}}**{{dataType}}**{{/isPrimitiveType}}{{^isPrimitiveType}}{{#isFile}}**{{dataType}}**{{/isFile}}{{^isFile}}[**{{dataType}}**]({{baseType}}.md){{/isFile}}{{/isPrimitiveType}}| {{description}} |{{^required}} [optional]{{/required}}{{^isContainer}}{{#defaultValue}} [default to {{.}}]{{/defaultValue}}{{/isContainer}}{{#allowableValues}} [enum: {{#values}}{{{.}}}{{^-last}}, {{/-last}}{{/values}}]{{/allowableValues}} | +{{/allParams}} + +### Return type + +{{#returnType}}{{#returnTypeIsPrimitive}}**{{returnType}}**{{/returnTypeIsPrimitive}}{{^returnTypeIsPrimitive}}[**{{returnType}}**]({{returnBaseType}}.md){{/returnTypeIsPrimitive}}{{/returnType}}{{^returnType}}null (empty response body){{/returnType}} + +### Authorization + +{{^authMethods}}No authorization required{{/authMethods}}{{#authMethods}}[{{name}}](../README.md#{{name}}){{^-last}}, {{/-last}}{{/authMethods}} + +### HTTP request headers + +- **Content-Type**: {{#consumes}}{{{mediaType}}}{{^-last}}, {{/-last}}{{/consumes}}{{^consumes}}Not defined{{/consumes}} +- **Accept**: {{#produces}}{{{mediaType}}}{{^-last}}, {{/-last}}{{/produces}}{{^produces}}Not defined{{/produces}} + +{{#responses.0}} + +### HTTP response details +| Status code | Description | Response headers | +|-------------|-------------|------------------| +{{#responses}} +| **{{code}}** | {{message}} | {{#headers}} * {{baseName}} - {{description}}
{{/headers}}{{^headers.0}} - {{/headers.0}} | +{{/responses}} +{{/responses.0}} + +{{/operation}} +{{/operations}} diff --git a/config/clients/java/template/Java/api_test.mustache b/config/clients/java/template/Java/api_test.mustache new file mode 100644 index 00000000..a579d741 --- /dev/null +++ b/config/clients/java/template/Java/api_test.mustache @@ -0,0 +1,51 @@ +{{>licenseInfo}} + +package {{package}}; + +import {{invokerPackage}}.ApiException; +{{#imports}}import {{import}}; +{{/imports}} +import org.junit.Test; +import org.junit.Ignore; +import org.junit.Assert; + +import java.time.LocalDate; +import java.time.OffsetDateTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * API tests for {{classname}} + */ +public class {{classname}}Test { + + private final {{classname}} api = new {{classname}}(); + + {{#operations}} + {{#operation}} + /** + {{#summary}} + * {{.}} + * + {{/summary}} + {{#notes}} + * {{.}} + * + {{/notes}} + * @throws ApiException + * if the Api call fails + */ + @Test + public void {{operationId}}Test() throws ApiException { + //{{#allParams}} + //{{{dataType}}} {{paramName}} = null; + //{{/allParams}} + //{{#returnType}}{{{.}}} response = {{/returnType}}api.{{operationId}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}); + + // TODO: test validations + } + {{/operation}} + {{/operations}} +} diff --git a/config/clients/java/template/Java/auth/ApiKeyAuth.mustache b/config/clients/java/template/Java/auth/ApiKeyAuth.mustache new file mode 100644 index 00000000..991ae235 --- /dev/null +++ b/config/clients/java/template/Java/auth/ApiKeyAuth.mustache @@ -0,0 +1,66 @@ +{{>licenseInfo}} + +package {{invokerPackage}}.auth; + +import {{invokerPackage}}.Pair; + +import java.util.Map; +import java.util.List; + +{{>generatedAnnotation}} +public class ApiKeyAuth implements Authentication { + private final String location; + private final String paramName; + + private String apiKey; + private String apiKeyPrefix; + + public ApiKeyAuth(String location, String paramName) { + this.location = location; + this.paramName = paramName; + } + + public String getLocation() { + return location; + } + + public String getParamName() { + return paramName; + } + + public String getApiKey() { + return apiKey; + } + + public void setApiKey(String apiKey) { + this.apiKey = apiKey; + } + + public String getApiKeyPrefix() { + return apiKeyPrefix; + } + + public void setApiKeyPrefix(String apiKeyPrefix) { + this.apiKeyPrefix = apiKeyPrefix; + } + + @Override + public void applyToParams(List queryParams, Map headerParams, Map cookieParams) { + if (apiKey == null) { + return; + } + String value; + if (apiKeyPrefix != null) { + value = apiKeyPrefix + " " + apiKey; + } else { + value = apiKey; + } + if ("query".equals(location)) { + queryParams.add(new Pair(paramName, value)); + } else if ("header".equals(location)) { + headerParams.put(paramName, value); + } else if ("cookie".equals(location)) { + cookieParams.put(paramName, value); + } + } +} diff --git a/config/clients/java/template/Java/auth/Authentication.mustache b/config/clients/java/template/Java/auth/Authentication.mustache new file mode 100644 index 00000000..033de98d --- /dev/null +++ b/config/clients/java/template/Java/auth/Authentication.mustache @@ -0,0 +1,19 @@ +{{>licenseInfo}} + +package {{invokerPackage}}.auth; + +import {{invokerPackage}}.Pair; + +import java.util.Map; +import java.util.List; + +public interface Authentication { + /** + * Apply authentication settings to header and query params. + * + * @param queryParams List of query parameters + * @param headerParams Map of header parameters + * @param cookieParams Map of cookie parameters + */ + void applyToParams(List queryParams, Map headerParams, Map cookieParams); +} diff --git a/config/clients/java/template/Java/auth/HttpBasicAuth.mustache b/config/clients/java/template/Java/auth/HttpBasicAuth.mustache new file mode 100644 index 00000000..b5c72de6 --- /dev/null +++ b/config/clients/java/template/Java/auth/HttpBasicAuth.mustache @@ -0,0 +1,42 @@ +{{>licenseInfo}} + +package {{invokerPackage}}.auth; + +import {{invokerPackage}}.Pair; + +import java.util.Base64; +import java.nio.charset.StandardCharsets; + +import java.util.Map; +import java.util.List; + +{{>generatedAnnotation}} +public class HttpBasicAuth implements Authentication { + private String username; + private String password; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + @Override + public void applyToParams(List queryParams, Map headerParams, Map cookieParams) { + if (username == null && password == null) { + return; + } + String str = (username == null ? "" : username) + ":" + (password == null ? "" : password); + headerParams.put("Authorization", "Basic " + Base64.getEncoder().encodeToString(str.getBytes(StandardCharsets.UTF_8))); + } +} diff --git a/config/clients/java/template/Java/auth/HttpBearerAuth.mustache b/config/clients/java/template/Java/auth/HttpBearerAuth.mustache new file mode 100644 index 00000000..322281f8 --- /dev/null +++ b/config/clients/java/template/Java/auth/HttpBearerAuth.mustache @@ -0,0 +1,49 @@ +{{>licenseInfo}} + +package {{invokerPackage}}.auth; + +import {{invokerPackage}}.Pair; + +import java.util.Map; +import java.util.List; + +{{>generatedAnnotation}} +public class HttpBearerAuth implements Authentication { + private final String scheme; + private String bearerToken; + + public HttpBearerAuth(String scheme) { + this.scheme = scheme; + } + + /** + * Gets the token, which together with the scheme, will be sent as the value of the Authorization header. + * + * @return The bearer token + */ + public String getBearerToken() { + return bearerToken; + } + + /** + * Sets the token, which together with the scheme, will be sent as the value of the Authorization header. + * + * @param bearerToken The bearer token to send in the Authorization header + */ + public void setBearerToken(String bearerToken) { + this.bearerToken = bearerToken; + } + + @Override + public void applyToParams(List queryParams, Map headerParams, Map cookieParams) { + if(bearerToken == null) { + return; + } + + headerParams.put("Authorization", (scheme != null ? upperCaseBearer(scheme) + " " : "") + bearerToken); + } + + private static String upperCaseBearer(String scheme) { + return ("bearer".equalsIgnoreCase(scheme)) ? "Bearer" : scheme; + } +} diff --git a/config/clients/java/template/Java/auth/OAuth.mustache b/config/clients/java/template/Java/auth/OAuth.mustache new file mode 100644 index 00000000..5cb98e7c --- /dev/null +++ b/config/clients/java/template/Java/auth/OAuth.mustache @@ -0,0 +1,28 @@ +{{>licenseInfo}} + +package {{invokerPackage}}.auth; + +import {{invokerPackage}}.Pair; + +import java.util.Map; +import java.util.List; + +{{>generatedAnnotation}} +public class OAuth implements Authentication { + private String accessToken; + + public String getAccessToken() { + return accessToken; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + @Override + public void applyToParams(List queryParams, Map headerParams, Map cookieParams) { + if (accessToken != null) { + headerParams.put("Authorization", "Bearer " + accessToken); + } + } +} diff --git a/config/clients/java/template/Java/auth/OAuthFlow.mustache b/config/clients/java/template/Java/auth/OAuthFlow.mustache new file mode 100644 index 00000000..7a425266 --- /dev/null +++ b/config/clients/java/template/Java/auth/OAuthFlow.mustache @@ -0,0 +1,14 @@ +{{>licenseInfo}} + +package {{invokerPackage}}.auth; + +/** + * OAuth flows that are supported by this client + */ +{{>generatedAnnotation}} +public enum OAuthFlow { + ACCESS_CODE, //called authorizationCode in OpenAPI 3.0 + IMPLICIT, + PASSWORD, + APPLICATION //called clientCredentials in OpenAPI 3.0 +} diff --git a/config/clients/java/template/Java/beanValidation.mustache b/config/clients/java/template/Java/beanValidation.mustache new file mode 100644 index 00000000..47f7109e --- /dev/null +++ b/config/clients/java/template/Java/beanValidation.mustache @@ -0,0 +1,18 @@ +{{#required}} +{{^isReadOnly}} + @NotNull +{{/isReadOnly}} +{{/required}} +{{#isContainer}} +{{^isPrimitiveType}} +{{^isEnum}} + @Valid +{{/isEnum}} +{{/isPrimitiveType}} +{{/isContainer}} +{{^isContainer}} +{{^isPrimitiveType}} + @Valid +{{/isPrimitiveType}} +{{/isContainer}} +{{>beanValidationCore}} \ No newline at end of file diff --git a/config/clients/java/template/Java/beanValidationCore.mustache b/config/clients/java/template/Java/beanValidationCore.mustache new file mode 100644 index 00000000..ff4ffe26 --- /dev/null +++ b/config/clients/java/template/Java/beanValidationCore.mustache @@ -0,0 +1,20 @@ +{{#pattern}}{{^isByteArray}} @Pattern(regexp="{{{pattern}}}"){{/isByteArray}}{{/pattern}}{{! +minLength && maxLength set +}}{{#minLength}}{{#maxLength}} @Size(min={{minLength}},max={{maxLength}}){{/maxLength}}{{/minLength}}{{! +minLength set, maxLength not +}}{{#minLength}}{{^maxLength}} @Size(min={{minLength}}){{/maxLength}}{{/minLength}}{{! +minLength not set, maxLength set +}}{{^minLength}}{{#maxLength}} @Size(max={{.}}){{/maxLength}}{{/minLength}}{{! +@Size: minItems && maxItems set +}}{{#minItems}}{{#maxItems}} @Size(min={{minItems}},max={{maxItems}}){{/maxItems}}{{/minItems}}{{! +@Size: minItems set, maxItems not +}}{{#minItems}}{{^maxItems}} @Size(min={{minItems}}){{/maxItems}}{{/minItems}}{{! +@Size: minItems not set && maxItems set +}}{{^minItems}}{{#maxItems}} @Size(max={{.}}){{/maxItems}}{{/minItems}}{{! +check for integer or long / all others=decimal type with @Decimal* +isInteger set +}}{{#isInteger}}{{#minimum}} @Min({{.}}){{/minimum}}{{#maximum}} @Max({{.}}){{/maximum}}{{/isInteger}}{{! +isLong set +}}{{#isLong}}{{#minimum}} @Min({{.}}L){{/minimum}}{{#maximum}} @Max({{.}}L){{/maximum}}{{/isLong}}{{! +Not Integer, not Long => we have a decimal value! +}}{{^isInteger}}{{^isLong}}{{#minimum}} @DecimalMin({{#exclusiveMinimum}}value={{/exclusiveMinimum}}"{{minimum}}"{{#exclusiveMinimum}},inclusive=false{{/exclusiveMinimum}}){{/minimum}}{{#maximum}} @DecimalMax({{#exclusiveMaximum}}value={{/exclusiveMaximum}}"{{maximum}}"{{#exclusiveMaximum}},inclusive=false{{/exclusiveMaximum}}){{/maximum}}{{/isLong}}{{/isInteger}} \ No newline at end of file diff --git a/config/clients/java/template/Java/beanValidationQueryParams.mustache b/config/clients/java/template/Java/beanValidationQueryParams.mustache new file mode 100644 index 00000000..0f99bffd --- /dev/null +++ b/config/clients/java/template/Java/beanValidationQueryParams.mustache @@ -0,0 +1 @@ +{{#required}} @NotNull {{/required}}{{>beanValidationCore}} \ No newline at end of file diff --git a/config/clients/java/template/Java/build.gradle.mustache b/config/clients/java/template/Java/build.gradle.mustache new file mode 100644 index 00000000..43d789d0 --- /dev/null +++ b/config/clients/java/template/Java/build.gradle.mustache @@ -0,0 +1,94 @@ +plugins { + id 'java' + id 'maven-publish' + id 'idea' + id 'eclipse' + id 'com.diffplug.spotless' version '6.+' +} + +group = '{{groupId}}' +version = '{{artifactVersion}}' + +repositories { + mavenCentral() +} + +publishing { + publications { + maven(MavenPublication) { + artifactId = '{{artifactId}}' + from components.java + } + } +} + +java { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + + withJavadocJar() + withSourcesJar() +} + +ext { + {{#swagger1AnnotationLibrary}} + swagger_annotations_version = "1.6.9" + {{/swagger1AnnotationLibrary}} + {{#swagger2AnnotationLibrary}} + swagger_annotations_version = "2.2.9" + {{/swagger2AnnotationLibrary}} + jackson_version = "2.14.1" + jakarta_annotation_version = "1.3.5" + junit_version = "4.13.2" + {{#hasFormParamsInSpec}} + httpmime_version = "4.5.13" + {{/hasFormParamsInSpec}} +} + +dependencies { + {{#swagger1AnnotationLibrary}} + implementation "io.swagger:swagger-annotations:$swagger_annotations_version" + {{/swagger1AnnotationLibrary}} + {{#swagger2AnnotationLibrary}} + implementation "io.swagger.core.v3:swagger-annotations:$swagger_annotations_version" + {{/swagger2AnnotationLibrary}} + implementation "com.google.code.findbugs:jsr305:3.0.2" + implementation "com.fasterxml.jackson.core:jackson-core:$jackson_version" + implementation "com.fasterxml.jackson.core:jackson-annotations:$jackson_version" + implementation "com.fasterxml.jackson.core:jackson-databind:$jackson_version" + implementation "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jackson_version" + implementation "org.openapitools:jackson-databind-nullable:0.2.1" + implementation "jakarta.annotation:jakarta.annotation-api:$jakarta_annotation_version" + {{#hasFormParamsInSpec}} + implementation "org.apache.httpcomponents:httpmime:$httpmime_version" + {{/hasFormParamsInSpec}} + testImplementation "junit:junit:$junit_version" +} + +// Use spotless plugin to automatically format code, remove unused import, etc +// To apply changes directly to the file, run `gradlew spotlessApply` +// Ref: https://github.com/diffplug/spotless/tree/main/plugin-gradle +spotless { + // comment out below to run spotless as part of the `check` task + enforceCheck false + format 'misc', { + // define the files (e.g. '*.gradle', '*.md') to apply `misc` to + target '.gitignore' + // define the steps to apply to those files + trimTrailingWhitespace() + indentWithSpaces() // Takes an integer argument if you don't like 4 + endWithNewline() + } + java { + palantirJavaFormat() + removeUnusedImports() + importOrder() + } +} + +// Use spotless plugin to automatically format code, remove unused import, etc +// To apply changes directly to the file, run `gradlew spotlessApply` +// Ref: https://github.com/diffplug/spotless/tree/main/plugin-gradle +tasks.register('fmt') { + dependsOn 'spotlessApply' +} diff --git a/config/clients/java/template/Java/enum_outer_doc.mustache b/config/clients/java/template/Java/enum_outer_doc.mustache new file mode 100644 index 00000000..20c512aa --- /dev/null +++ b/config/clients/java/template/Java/enum_outer_doc.mustache @@ -0,0 +1,7 @@ +# {{classname}} + +## Enum + +{{#allowableValues}}{{#enumVars}} +* `{{name}}` (value: `{{{value}}}`) +{{/enumVars}}{{/allowableValues}} diff --git a/config/clients/java/template/Java/generatedAnnotation.mustache b/config/clients/java/template/Java/generatedAnnotation.mustache new file mode 100644 index 00000000..f408f319 --- /dev/null +++ b/config/clients/java/template/Java/generatedAnnotation.mustache @@ -0,0 +1 @@ +@{{javaxPackage}}.annotation.Generated(value = "{{generatorClass}}"{{^hideGenerationTimestamp}}, date = "{{generatedDate}}"{{/hideGenerationTimestamp}}) \ No newline at end of file diff --git a/config/clients/java/template/Java/git_push.sh.mustache b/config/clients/java/template/Java/git_push.sh.mustache new file mode 100755 index 00000000..0e3776ae --- /dev/null +++ b/config/clients/java/template/Java/git_push.sh.mustache @@ -0,0 +1,57 @@ +#!/bin/sh +# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/ +# +# Usage example: /bin/sh ./git_push.sh wing328 openapi-petstore-perl "minor update" "gitlab.com" + +git_user_id=$1 +git_repo_id=$2 +release_note=$3 +git_host=$4 + +if [ "$git_host" = "" ]; then + git_host="{{{gitHost}}}" + echo "[INFO] No command line input provided. Set \$git_host to $git_host" +fi + +if [ "$git_user_id" = "" ]; then + git_user_id="{{{gitUserId}}}" + echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id" +fi + +if [ "$git_repo_id" = "" ]; then + git_repo_id="{{{gitRepoId}}}" + echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id" +fi + +if [ "$release_note" = "" ]; then + release_note="{{{releaseNote}}}" + echo "[INFO] No command line input provided. Set \$release_note to $release_note" +fi + +# Initialize the local directory as a Git repository +git init + +# Adds the files in the local repository and stages them for commit. +git add . + +# Commits the tracked changes and prepares them to be pushed to a remote repository. +git commit -m "$release_note" + +# Sets the new remote +git_remote=$(git remote) +if [ "$git_remote" = "" ]; then # git remote not defined + + if [ "$GIT_TOKEN" = "" ]; then + echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment." + git remote add origin https://${git_host}/${git_user_id}/${git_repo_id}.git + else + git remote add origin https://${git_user_id}:"${GIT_TOKEN}"@${git_host}/${git_user_id}/${git_repo_id}.git + fi + +fi + +git pull origin master + +# Pushes (Forces) the changes in the local repository up to the remote repository +echo "Git pushing to https://${git_host}/${git_user_id}/${git_repo_id}.git" +git push origin master 2>&1 | grep -v 'To https' diff --git a/config/clients/java/template/Java/gitignore.mustache b/config/clients/java/template/Java/gitignore.mustache new file mode 100644 index 00000000..a530464a --- /dev/null +++ b/config/clients/java/template/Java/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/config/clients/java/template/Java/gradle-wrapper.jar b/config/clients/java/template/Java/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..7454180f2ae8848c63b8b4dea2cb829da983f2fa GIT binary patch literal 59536 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 QuEJmfE|#`Dqx|px0L@K;Y5)KL literal 0 HcmV?d00001 diff --git a/config/clients/java/template/Java/gradle-wrapper.properties.mustache b/config/clients/java/template/Java/gradle-wrapper.properties.mustache new file mode 100644 index 00000000..a1f2792d --- /dev/null +++ b/config/clients/java/template/Java/gradle-wrapper.properties.mustache @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-all.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/config/clients/java/template/Java/gradle.properties.mustache b/config/clients/java/template/Java/gradle.properties.mustache new file mode 100644 index 00000000..095ab31d --- /dev/null +++ b/config/clients/java/template/Java/gradle.properties.mustache @@ -0,0 +1,9 @@ +# This file is automatically generated by OpenAPI Generator (https://github.com/openAPITools/openapi-generator). +# To include other gradle properties as part of the code generation process, please use the `gradleProperties` option. +# +# Gradle properties reference: https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties +# For example, uncomment below to build for Android +#target = android +{{#gradleProperties}} +{{{.}}} +{{/gradleProperties}} diff --git a/config/clients/java/template/Java/gradlew.bat.mustache b/config/clients/java/template/Java/gradlew.bat.mustache new file mode 100644 index 00000000..6a68175e --- /dev/null +++ b/config/clients/java/template/Java/gradlew.bat.mustache @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS=-Dfile.encoding=UTF-8 "-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/config/clients/java/template/Java/gradlew.mustache b/config/clients/java/template/Java/gradlew.mustache new file mode 100755 index 00000000..005bcde0 --- /dev/null +++ b/config/clients/java/template/Java/gradlew.mustache @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# 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. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='-Dfile.encoding=UTF-8 "-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/config/clients/java/template/Java/jackson_annotations.mustache b/config/clients/java/template/Java/jackson_annotations.mustache new file mode 100644 index 00000000..c7413447 --- /dev/null +++ b/config/clients/java/template/Java/jackson_annotations.mustache @@ -0,0 +1,20 @@ +{{! + If this is map and items are nullable, make sure that nulls are included. + To determine what JsonInclude.Include method to use, consider the following: + * If the field is required, always include it, even if it is null. + * Else use custom behaviour, IOW use whatever is defined on the object mapper + }} + @JsonProperty(JSON_PROPERTY_{{nameInSnakeCase}}) + @JsonInclude({{#isMap}}{{#items.isNullable}}content = JsonInclude.Include.ALWAYS, {{/items.isNullable}}{{/isMap}}value = JsonInclude.Include.{{#required}}ALWAYS{{/required}}{{^required}}USE_DEFAULTS{{/required}}) + {{#withXml}} + {{^isContainer}} + @JacksonXmlProperty({{#isXmlAttribute}}isAttribute = true, {{/isXmlAttribute}}{{#xmlNamespace}}namespace="{{.}}", {{/xmlNamespace}}localName = "{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}") + {{/isContainer}} + {{#isContainer}} + {{#xmlName}} + // xmlName={{.}} + {{/xmlName}} + @JacksonXmlProperty({{#xmlNamespace}}namespace="{{.}}", {{/xmlNamespace}}localName = "{{#xmlName}}{{xmlName}}{{/xmlName}}{{^xmlName}}{{#xmlName}}{{xmlName}}{{/xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}{{/xmlName}}") + @JacksonXmlElementWrapper(useWrapping = {{isXmlWrapped}}{{#xmlNamespace}}, namespace="{{.}}"{{/xmlNamespace}}{{#isXmlWrapped}}, localName = "{{#xmlName}}{{xmlName}}{{/xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}"{{/isXmlWrapped}}) + {{/isContainer}} + {{/withXml}} \ No newline at end of file diff --git a/config/clients/java/template/Java/libraries/native/AbstractOpenApiSchema.mustache b/config/clients/java/template/Java/libraries/native/AbstractOpenApiSchema.mustache new file mode 100644 index 00000000..f196a13e --- /dev/null +++ b/config/clients/java/template/Java/libraries/native/AbstractOpenApiSchema.mustache @@ -0,0 +1,136 @@ +{{>licenseInfo}} + +package {{modelPackage}}; + +import java.util.Objects; +import java.lang.reflect.Type; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * Abstract class for oneOf,anyOf schemas defined in OpenAPI spec + */ +{{>generatedAnnotation}} +public abstract class AbstractOpenApiSchema { + + // store the actual instance of the schema/object + private Object instance; + + // is nullable + private Boolean isNullable; + + // schema type (e.g. oneOf, anyOf) + private final String schemaType; + + public AbstractOpenApiSchema(String schemaType, Boolean isNullable) { + this.schemaType = schemaType; + this.isNullable = isNullable; + } + + /** + * Get the list of oneOf/anyOf composed schemas allowed to be stored in this object + * + * @return an instance of the actual schema/object + */ + public abstract Map> getSchemas(); + + /** + * Get the actual instance + * + * @return an instance of the actual schema/object + */ + @JsonValue + public Object getActualInstance() {return instance;} + + /** + * Set the actual instance + * + * @param instance the actual instance of the schema/object + */ + public void setActualInstance(Object instance) {this.instance = instance;} + + /** + * Get the instant recursively when the schemas defined in oneOf/anyof happen to be oneOf/anyOf schema as well + * + * @return an instance of the actual schema/object + */ + public Object getActualInstanceRecursively() { + return getActualInstanceRecursively(this); + } + + private Object getActualInstanceRecursively(AbstractOpenApiSchema object) { + if (object.getActualInstance() == null) { + return null; + } else if (object.getActualInstance() instanceof AbstractOpenApiSchema) { + return getActualInstanceRecursively((AbstractOpenApiSchema)object.getActualInstance()); + } else { + return object.getActualInstance(); + } + } + + /** + * Get the schema type (e.g. anyOf, oneOf) + * + * @return the schema type + */ + public String getSchemaType() { + return schemaType; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class ").append(getClass()).append(" {\n"); + sb.append(" instance: ").append(toIndentedString(instance)).append("\n"); + sb.append(" isNullable: ").append(toIndentedString(isNullable)).append("\n"); + sb.append(" schemaType: ").append(toIndentedString(schemaType)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + AbstractOpenApiSchema a = (AbstractOpenApiSchema) o; + return Objects.equals(this.instance, a.instance) && + Objects.equals(this.isNullable, a.isNullable) && + Objects.equals(this.schemaType, a.schemaType); + } + + @Override + public int hashCode() { + return Objects.hash(instance, isNullable, schemaType); + } + + /** + * Is nullable + * + * @return true if it's nullable + */ + public Boolean isNullable() { + if (Boolean.TRUE.equals(isNullable)) { + return Boolean.TRUE; + } else { + return Boolean.FALSE; + } + } + +{{>libraries/native/additional_properties}} + +} diff --git a/config/clients/java/template/Java/libraries/native/ApiClient.mustache b/config/clients/java/template/Java/libraries/native/ApiClient.mustache new file mode 100644 index 00000000..14add046 --- /dev/null +++ b/config/clients/java/template/Java/libraries/native/ApiClient.mustache @@ -0,0 +1,449 @@ +{{>licenseInfo}} +package {{invokerPackage}}; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +{{#openApiNullable}} +import org.openapitools.jackson.nullable.JsonNullableModule; +{{/openApiNullable}} + +import java.io.InputStream; +import java.net.URI; +import java.net.URLEncoder; +import java.net.http.HttpClient; +import java.net.http.HttpConnectTimeoutException; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.time.OffsetDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.StringJoiner; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * Configuration and utility class for API clients. + * + *

This class can be constructed and modified, then used to instantiate the + * various API classes. The API classes use the settings in this class to + * configure themselves, but otherwise do not store a link to this class.

+ * + *

This class is mutable and not synchronized, so it is not thread-safe. + * The API classes generated from this are immutable and thread-safe.

+ * + *

The setter methods of this class return the current object to facilitate + * a fluent style of configuration.

+ */ +{{>generatedAnnotation}} +public class ApiClient { + + private HttpClient.Builder builder; + private ObjectMapper mapper; + private String scheme; + private String host; + private int port; + private String basePath; + private Consumer interceptor; + private Consumer> responseInterceptor; + private Consumer> asyncResponseInterceptor; + private Duration readTimeout; + private Duration connectTimeout; + + private static String valueToString(Object value) { + if (value == null) { + return ""; + } + if (value instanceof OffsetDateTime) { + return ((OffsetDateTime) value).format(DateTimeFormatter.ISO_OFFSET_DATE_TIME); + } + return value.toString(); + } + + /** + * URL encode a string in the UTF-8 encoding. + * + * @param s String to encode. + * @return URL-encoded representation of the input string. + */ + public static String urlEncode(String s) { + return URLEncoder.encode(s, UTF_8).replaceAll("\\+", "%20"); + } + + /** + * Convert a URL query name/value parameter to a list of encoded {@link Pair} + * objects. + * + *

The value can be null, in which case an empty list is returned.

+ * + * @param name The query name parameter. + * @param value The query value, which may not be a collection but may be + * null. + * @return A singleton list of the {@link Pair} objects representing the input + * parameters, which is encoded for use in a URL. If the value is null, an + * empty list is returned. + */ + public static List parameterToPairs(String name, Object value) { + if (name == null || name.isEmpty() || value == null) { + return Collections.emptyList(); + } + return Collections.singletonList(new Pair(urlEncode(name), urlEncode(valueToString(value)))); + } + + /** + * Convert a URL query name/collection parameter to a list of encoded + * {@link Pair} objects. + * + * @param collectionFormat The swagger collectionFormat string (csv, tsv, etc). + * @param name The query name parameter. + * @param values A collection of values for the given query name, which may be + * null. + * @return A list of {@link Pair} objects representing the input parameters, + * which is encoded for use in a URL. If the values collection is null, an + * empty list is returned. + */ + public static List parameterToPairs( + String collectionFormat, String name, Collection values) { + if (name == null || name.isEmpty() || values == null || values.isEmpty()) { + return Collections.emptyList(); + } + + // get the collection format (default: csv) + String format = collectionFormat == null || collectionFormat.isEmpty() ? "csv" : collectionFormat; + + // create the params based on the collection format + if ("multi".equals(format)) { + return values.stream() + .map(value -> new Pair(urlEncode(name), urlEncode(valueToString(value)))) + .collect(Collectors.toList()); + } + + String delimiter; + switch(format) { + case "csv": + delimiter = urlEncode(","); + break; + case "ssv": + delimiter = urlEncode(" "); + break; + case "tsv": + delimiter = urlEncode("\t"); + break; + case "pipes": + delimiter = urlEncode("|"); + break; + default: + throw new IllegalArgumentException("Illegal collection format: " + collectionFormat); + } + + StringJoiner joiner = new StringJoiner(delimiter); + for (Object value : values) { + joiner.add(urlEncode(valueToString(value))); + } + + return Collections.singletonList(new Pair(urlEncode(name), joiner.toString())); + } + + /** + * Create an instance of ApiClient. + */ + public ApiClient() { + this.builder = createDefaultHttpClientBuilder(); + this.mapper = createDefaultObjectMapper(); + updateBaseUri(getDefaultBaseUri()); + interceptor = null; + readTimeout = null; + connectTimeout = null; + responseInterceptor = null; + asyncResponseInterceptor = null; + } + + /** + * Create an instance of ApiClient. + * + * @param builder Http client builder. + * @param mapper Object mapper. + * @param baseUri Base URI + */ + public ApiClient(HttpClient.Builder builder, ObjectMapper mapper, String baseUri) { + this.builder = builder; + this.mapper = mapper; + updateBaseUri(baseUri != null ? baseUri : getDefaultBaseUri()); + interceptor = null; + readTimeout = null; + connectTimeout = null; + responseInterceptor = null; + asyncResponseInterceptor = null; + } + + protected ObjectMapper createDefaultObjectMapper() { + ObjectMapper mapper = new ObjectMapper(); + mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + mapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false); + mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + mapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING); + mapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING); + mapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE); + mapper.registerModule(new JavaTimeModule()); + {{#openApiNullable}} + mapper.registerModule(new JsonNullableModule()); + {{/openApiNullable}} + return mapper; + } + + protected String getDefaultBaseUri() { + return "{{{basePath}}}"; + } + + protected HttpClient.Builder createDefaultHttpClientBuilder() { + return HttpClient.newBuilder(); + } + + public void updateBaseUri(String baseUri) { + URI uri = URI.create(baseUri); + scheme = uri.getScheme(); + host = uri.getHost(); + port = uri.getPort(); + basePath = uri.getRawPath(); + } + + /** + * Set a custom {@link HttpClient.Builder} object to use when creating the + * {@link HttpClient} that is used by the API client. + * + * @param builder Custom client builder. + * @return This object. + */ + public ApiClient setHttpClientBuilder(HttpClient.Builder builder) { + this.builder = builder; + return this; + } + + /** + * Get an {@link HttpClient} based on the current {@link HttpClient.Builder}. + * + *

The returned object is immutable and thread-safe.

+ * + * @return The HTTP client. + */ + public HttpClient getHttpClient() { + return builder.build(); + } + + /** + * Set a custom {@link ObjectMapper} to serialize and deserialize the request + * and response bodies. + * + * @param mapper Custom object mapper. + * @return This object. + */ + public ApiClient setObjectMapper(ObjectMapper mapper) { + this.mapper = mapper; + return this; + } + + /** + * Get a copy of the current {@link ObjectMapper}. + * + * @return A copy of the current object mapper. + */ + public ObjectMapper getObjectMapper() { + return mapper.copy(); + } + + /** + * Set a custom host name for the target service. + * + * @param host The host name of the target service. + * @return This object. + */ + public ApiClient setHost(String host) { + this.host = host; + return this; + } + + /** + * Set a custom port number for the target service. + * + * @param port The port of the target service. Set this to -1 to reset the + * value to the default for the scheme. + * @return This object. + */ + public ApiClient setPort(int port) { + this.port = port; + return this; + } + + /** + * Set a custom base path for the target service, for example '/v2'. + * + * @param basePath The base path against which the rest of the path is + * resolved. + * @return This object. + */ + public ApiClient setBasePath(String basePath) { + this.basePath = basePath; + return this; + } + + /** + * Get the base URI to resolve the endpoint paths against. + * + * @return The complete base URI that the rest of the API parameters are + * resolved against. + */ + public String getBaseUri() { + return scheme + "://" + host + (port == -1 ? "" : ":" + port) + basePath; + } + + /** + * Set a custom scheme for the target service, for example 'https'. + * + * @param scheme The scheme of the target service + * @return This object. + */ + public ApiClient setScheme(String scheme){ + this.scheme = scheme; + return this; + } + + /** + * Set a custom request interceptor. + * + *

A request interceptor is a mechanism for altering each request before it + * is sent. After the request has been fully configured but not yet built, the + * request builder is passed into this function for further modification, + * after which it is sent out.

+ * + *

This is useful for altering the requests in a custom manner, such as + * adding headers. It could also be used for logging and monitoring.

+ * + * @param interceptor A function invoked before creating each request. A value + * of null resets the interceptor to a no-op. + * @return This object. + */ + public ApiClient setRequestInterceptor(Consumer interceptor) { + this.interceptor = interceptor; + return this; + } + + /** + * Get the custom interceptor. + * + * @return The custom interceptor that was set, or null if there isn't any. + */ + public Consumer getRequestInterceptor() { + return interceptor; + } + + /** + * Set a custom response interceptor. + * + *

This is useful for logging, monitoring or extraction of header variables

+ * + * @param interceptor A function invoked before creating each request. A value + * of null resets the interceptor to a no-op. + * @return This object. + */ + public ApiClient setResponseInterceptor(Consumer> interceptor) { + this.responseInterceptor = interceptor; + return this; + } + + /** + * Get the custom response interceptor. + * + * @return The custom interceptor that was set, or null if there isn't any. + */ + public Consumer> getResponseInterceptor() { + return responseInterceptor; + } + + /** + * Set a custom async response interceptor. Use this interceptor when asyncNative is set to 'true'. + * + *

This is useful for logging, monitoring or extraction of header variables

+ * + * @param interceptor A function invoked before creating each request. A value + * of null resets the interceptor to a no-op. + * @return This object. + */ + public ApiClient setAsyncResponseInterceptor(Consumer> interceptor) { + this.asyncResponseInterceptor = interceptor; + return this; + } + + /** + * Get the custom async response interceptor. Use this interceptor when asyncNative is set to 'true'. + * + * @return The custom interceptor that was set, or null if there isn't any. + */ + public Consumer> getAsyncResponseInterceptor() { + return asyncResponseInterceptor; + } + + /** + * Set the read timeout for the http client. + * + *

This is the value used by default for each request, though it can be + * overridden on a per-request basis with a request interceptor.

+ * + * @param readTimeout The read timeout used by default by the http client. + * Setting this value to null resets the timeout to an + * effectively infinite value. + * @return This object. + */ + public ApiClient setReadTimeout(Duration readTimeout) { + this.readTimeout = readTimeout; + return this; + } + + /** + * Get the read timeout that was set. + * + * @return The read timeout, or null if no timeout was set. Null represents + * an infinite wait time. + */ + public Duration getReadTimeout() { + return readTimeout; + } + /** + * Sets the connect timeout (in milliseconds) for the http client. + * + *

In the case where a new connection needs to be established, if + * the connection cannot be established within the given {@code + * duration}, then {@link HttpClient#send(HttpRequest,BodyHandler) + * HttpClient::send} throws an {@link HttpConnectTimeoutException}, or + * {@link HttpClient#sendAsync(HttpRequest,BodyHandler) + * HttpClient::sendAsync} completes exceptionally with an + * {@code HttpConnectTimeoutException}. If a new connection does not + * need to be established, for example if a connection can be reused + * from a previous request, then this timeout duration has no effect. + * + * @param connectTimeout connection timeout in milliseconds + * + * @return This object. + */ + public ApiClient setConnectTimeout(Duration connectTimeout) { + this.connectTimeout = connectTimeout; + this.builder.connectTimeout(connectTimeout); + return this; + } + + /** + * Get connection timeout (in milliseconds). + * + * @return Timeout in milliseconds + */ + public Duration getConnectTimeout() { + return connectTimeout; + } +} diff --git a/config/clients/java/template/Java/libraries/native/ApiResponse.mustache b/config/clients/java/template/Java/libraries/native/ApiResponse.mustache new file mode 100644 index 00000000..1e277319 --- /dev/null +++ b/config/clients/java/template/Java/libraries/native/ApiResponse.mustache @@ -0,0 +1,58 @@ +{{>licenseInfo}} + +package {{invokerPackage}}; + +import java.util.List; +import java.util.Map; +{{#caseInsensitiveResponseHeaders}} +import java.util.Map.Entry; +import java.util.TreeMap; +{{/caseInsensitiveResponseHeaders}} + +/** + * API response returned by API call. + * + * @param The type of data that is deserialized from response body + */ +public class ApiResponse { + final private int statusCode; + final private Map> headers; + final private T data; + + /** + * @param statusCode The status code of HTTP response + * @param headers The headers of HTTP response + */ + public ApiResponse(int statusCode, Map> headers) { + this(statusCode, headers, null); + } + + /** + * @param statusCode The status code of HTTP response + * @param headers The headers of HTTP response + * @param data The object deserialized from response bod + */ + public ApiResponse(int statusCode, Map> headers, T data) { + this.statusCode = statusCode; + {{#caseInsensitiveResponseHeaders}} + Map> responseHeaders = new TreeMap>(String.CASE_INSENSITIVE_ORDER); + for(Entry> entry : headers.entrySet()){ + responseHeaders.put(entry.getKey().toLowerCase(), entry.getValue()); + } + {{/caseInsensitiveResponseHeaders}} + this.headers = {{#caseInsensitiveResponseHeaders}}responseHeaders{{/caseInsensitiveResponseHeaders}}{{^caseInsensitiveResponseHeaders}}headers{{/caseInsensitiveResponseHeaders}}; + this.data = data; + } + + public int getStatusCode() { + return statusCode; + } + + public Map> getHeaders() { + return headers; + } + + public T getData() { + return data; + } +} diff --git a/config/clients/java/template/Java/libraries/native/JSON.mustache b/config/clients/java/template/Java/libraries/native/JSON.mustache new file mode 100644 index 00000000..a13bf2fd --- /dev/null +++ b/config/clients/java/template/Java/libraries/native/JSON.mustache @@ -0,0 +1,260 @@ +package {{invokerPackage}}; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.*; +{{#openApiNullable}} +import org.openapitools.jackson.nullable.JsonNullableModule; +{{/openApiNullable}} +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +{{#joda}} +import com.fasterxml.jackson.datatype.joda.JodaModule; +{{/joda}} +{{#models.0}} +import {{modelPackage}}.*; +{{/models.0}} + +import java.text.DateFormat; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +{{>generatedAnnotation}} +public class JSON { + private ObjectMapper mapper; + + public JSON() { + mapper = new ObjectMapper(); + mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + mapper.configure(MapperFeature.ALLOW_COERCION_OF_SCALARS, false); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true); + mapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, true); + mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + mapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING); + mapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING); + mapper.setDateFormat(new RFC3339DateFormat()); + mapper.registerModule(new JavaTimeModule()); + {{#joda}} + mapper.registerModule(new JodaModule()); + {{/joda}} + {{#openApiNullable}} + JsonNullableModule jnm = new JsonNullableModule(); + mapper.registerModule(jnm); + {{/openApiNullable}} + } + + /** + * Set the date format for JSON (de)serialization with Date properties. + * + * @param dateFormat Date format + */ + public void setDateFormat(DateFormat dateFormat) { + mapper.setDateFormat(dateFormat); + } + + /** + * Get the object mapper + * + * @return object mapper + */ + public ObjectMapper getMapper() { return mapper; } + + /** + * Returns the target model class that should be used to deserialize the input data. + * The discriminator mappings are used to determine the target model class. + * + * @param node The input data. + * @param modelClass The class that contains the discriminator mappings. + * + * @return the target model class. + */ + public static Class getClassForElement(JsonNode node, Class modelClass) { + ClassDiscriminatorMapping cdm = modelDiscriminators.get(modelClass); + if (cdm != null) { + return cdm.getClassForElement(node, new HashSet>()); + } + return null; + } + + /** + * Helper class to register the discriminator mappings. + */ + private static class ClassDiscriminatorMapping { + // The model class name. + Class modelClass; + // The name of the discriminator property. + String discriminatorName; + // The discriminator mappings for a model class. + Map> discriminatorMappings; + + // Constructs a new class discriminator. + ClassDiscriminatorMapping(Class cls, String propertyName, Map> mappings) { + modelClass = cls; + discriminatorName = propertyName; + discriminatorMappings = new HashMap>(); + if (mappings != null) { + discriminatorMappings.putAll(mappings); + } + } + + // Return the name of the discriminator property for this model class. + String getDiscriminatorPropertyName() { + return discriminatorName; + } + + // Return the discriminator value or null if the discriminator is not + // present in the payload. + String getDiscriminatorValue(JsonNode node) { + // Determine the value of the discriminator property in the input data. + if (discriminatorName != null) { + // Get the value of the discriminator property, if present in the input payload. + node = node.get(discriminatorName); + if (node != null && node.isValueNode()) { + String discrValue = node.asText(); + if (discrValue != null) { + return discrValue; + } + } + } + return null; + } + + /** + * Returns the target model class that should be used to deserialize the input data. + * This function can be invoked for anyOf/oneOf composed models with discriminator mappings. + * The discriminator mappings are used to determine the target model class. + * + * @param node The input data. + * @param visitedClasses The set of classes that have already been visited. + * + * @return the target model class. + */ + Class getClassForElement(JsonNode node, Set> visitedClasses) { + if (visitedClasses.contains(modelClass)) { + // Class has already been visited. + return null; + } + // Determine the value of the discriminator property in the input data. + String discrValue = getDiscriminatorValue(node); + if (discrValue == null) { + return null; + } + Class cls = discriminatorMappings.get(discrValue); + // It may not be sufficient to return this cls directly because that target class + // may itself be a composed schema, possibly with its own discriminator. + visitedClasses.add(modelClass); + for (Class childClass : discriminatorMappings.values()) { + ClassDiscriminatorMapping childCdm = modelDiscriminators.get(childClass); + if (childCdm == null) { + continue; + } + if (!discriminatorName.equals(childCdm.discriminatorName)) { + discrValue = getDiscriminatorValue(node); + if (discrValue == null) { + continue; + } + } + if (childCdm != null) { + // Recursively traverse the discriminator mappings. + Class childDiscr = childCdm.getClassForElement(node, visitedClasses); + if (childDiscr != null) { + return childDiscr; + } + } + } + return cls; + } + } + + /** + * Returns true if inst is an instance of modelClass in the OpenAPI model hierarchy. + * + * The Java class hierarchy is not implemented the same way as the OpenAPI model hierarchy, + * so it's not possible to use the instanceof keyword. + * + * @param modelClass A OpenAPI model class. + * @param inst The instance object. + * @param visitedClasses The set of classes that have already been visited. + * + * @return true if inst is an instance of modelClass in the OpenAPI model hierarchy. + */ + public static boolean isInstanceOf(Class modelClass, Object inst, Set> visitedClasses) { + if (modelClass.isInstance(inst)) { + // This handles the 'allOf' use case with single parent inheritance. + return true; + } + if (visitedClasses.contains(modelClass)) { + // This is to prevent infinite recursion when the composed schemas have + // a circular dependency. + return false; + } + visitedClasses.add(modelClass); + + // Traverse the oneOf/anyOf composed schemas. + Map> descendants = modelDescendants.get(modelClass); + if (descendants != null) { + for (Class childType : descendants.values()) { + if (isInstanceOf(childType, inst, visitedClasses)) { + return true; + } + } + } + return false; + } + + /** + * A map of discriminators for all model classes. + */ + private static Map, ClassDiscriminatorMapping> modelDiscriminators = new HashMap<>(); + + /** + * A map of oneOf/anyOf descendants for each model class. + */ + private static Map, Map>> modelDescendants = new HashMap<>(); + + /** + * Register a model class discriminator. + * + * @param modelClass the model class + * @param discriminatorPropertyName the name of the discriminator property + * @param mappings a map with the discriminator mappings. + */ + public static void registerDiscriminator(Class modelClass, String discriminatorPropertyName, Map> mappings) { + ClassDiscriminatorMapping m = new ClassDiscriminatorMapping(modelClass, discriminatorPropertyName, mappings); + modelDiscriminators.put(modelClass, m); + } + + /** + * Register the oneOf/anyOf descendants of the modelClass. + * + * @param modelClass the model class + * @param descendants a map of oneOf/anyOf descendants. + */ + public static void registerDescendants(Class modelClass, Map> descendants) { + modelDescendants.put(modelClass, descendants); + } + + private static JSON json; + + static { + json = new JSON(); + } + + /** + * Get the default JSON instance. + * + * @return the default JSON instance + */ + public static JSON getDefault() { + return json; + } + + /** + * Set the default JSON instance. + * + * @param json JSON instance to be used + */ + public static void setDefault(JSON json) { + JSON.json = json; + } +} diff --git a/config/clients/java/template/Java/libraries/native/README.mustache b/config/clients/java/template/Java/libraries/native/README.mustache new file mode 100644 index 00000000..0d183007 --- /dev/null +++ b/config/clients/java/template/Java/libraries/native/README.mustache @@ -0,0 +1,189 @@ +# {{artifactId}} + +{{appName}} + +- API version: {{appVersion}} +{{^hideGenerationTimestamp}} + +- Build date: {{generatedDate}} +{{/hideGenerationTimestamp}} + +{{{appDescriptionWithNewLines}}} + +{{#infoUrl}} + For more information, please visit [{{{infoUrl}}}]({{{infoUrl}}}) +{{/infoUrl}} + +*Automatically generated by the [OpenAPI Generator](https://openapi-generator.tech)* + +## Requirements + +Building the API client library requires: + +1. Java 11+ +2. Maven/Gradle + +## Installation + +To install the API client library to your local Maven repository, simply execute: + +```shell +mvn clean install +``` + +To deploy it to a remote Maven repository instead, configure the settings of the repository and execute: + +```shell +mvn clean deploy +``` + +Refer to the [OSSRH Guide](http://central.sonatype.org/pages/ossrh-guide.html) for more information. + +### Maven users + +Add this dependency to your project's POM: + +```xml + + {{{groupId}}} + {{{artifactId}}} + {{{artifactVersion}}} + compile + +``` + +### Gradle users + +Add this dependency to your project's build file: + +```groovy +compile "{{{groupId}}}:{{{artifactId}}}:{{{artifactVersion}}}" +``` + +### Others + +At first generate the JAR by executing: + +```shell +mvn clean package +``` + +Then manually install the following JARs: + +- `target/{{{artifactId}}}-{{{artifactVersion}}}.jar` +- `target/lib/*.jar` + +## Getting Started + +Please follow the [installation](#installation) instruction and execute the following Java code: + +```java +{{#apiInfo}}{{#apis}}{{#-first}}{{#operations}}{{#operation}}{{#-first}} +import {{{invokerPackage}}}.*; +import {{{modelPackage}}}.*; +import {{{package}}}.{{{classname}}};{{#vendorExtensions.x-group-parameters}} +import {{{package}}}.{{{classname}}}.*;{{/vendorExtensions.x-group-parameters}} +{{#asyncNative}} +import java.util.concurrent.CompletableFuture; +{{/asyncNative}} + +public class {{{classname}}}Example { + + public static void main(String[] args) { + ApiClient defaultClient = Configuration.getDefaultApiClient(); + // Configure clients using the `defaultClient` object, such as + // overriding the host and port, timeout, etc. + {{{classname}}} apiInstance = new {{{classname}}}(defaultClient); + {{#allParams}} + {{{dataType}}} {{{paramName}}} = {{{example}}}; // {{{dataType}}} | {{{description}}} + {{/allParams}} + try { + {{^vendorExtensions.x-group-parameters}} + {{#returnType}}{{#asyncNative}}CompletableFuture<{{{returnType}}}>{{/asyncNative}}{{^asyncNative}}{{{returnType}}}{{/asyncNative}} result = {{/returnType}}{{^returnType}}{{#asyncNative}}CompletableFuture result = {{/asyncNative}}{{/returnType}}apiInstance.{{{operationId}}}({{#allParams}}{{{paramName}}}{{^-last}}, {{/-last}}{{/allParams}}); + {{/vendorExtensions.x-group-parameters}} + {{#vendorExtensions.x-group-parameters}} + {{#hasParams}} + API{{operationId}}Request request = API{{operationId}}Request.newBuilder(){{#allParams}} + .{{paramName}}({{paramName}}){{/allParams}} + .build(); + {{/hasParams}} + {{#returnType}}{{#asyncNative}}CompletableFuture<{{{returnType}}}>{{/asyncNative}}{{^asyncNative}}{{{returnType}}}{{/asyncNative}} result = {{/returnType}}{{^returnType}}{{#asyncNative}}CompletableFuture result = {{/asyncNative}}{{/returnType}}apiInstance.{{operationId}}({{#hasParams}}request{{/hasParams}}); + {{/vendorExtensions.x-group-parameters}} + {{#returnType}} + System.out.println(result{{#asyncNative}}.get(){{/asyncNative}}); + {{/returnType}} + } catch (ApiException e) { + System.err.println("Exception when calling {{{classname}}}#{{{operationId}}}"); + System.err.println("Status code: " + e.getCode()); + System.err.println("Reason: " + e.getResponseBody()); + System.err.println("Response headers: " + e.getResponseHeaders()); + e.printStackTrace(); + } + } +} +{{/-first}}{{/operation}}{{/operations}}{{/-first}}{{/apis}}{{/apiInfo}} +``` + +## Documentation for API Endpoints + +All URIs are relative to *{{basePath}}* + +Class | Method | HTTP request | Description +------------ | ------------- | ------------- | ------------- +{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}*{{classname}}* | [**{{operationId}}**]({{apiDocPath}}{{classname}}.md#{{operationId}}) | **{{httpMethod}}** {{path}} | {{summary}} +*{{classname}}* | [**{{operationId}}WithHttpInfo**]({{apiDocPath}}{{classname}}.md#{{operationId}}WithHttpInfo) | **{{httpMethod}}** {{path}} | {{summary}} +{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}} + +## Documentation for Models + +{{#models}}{{#model}} - [{{classname}}]({{modelDocPath}}{{classname}}.md) +{{/model}}{{/models}} + + +## Documentation for Authorization + +{{^authMethods}}Endpoints do not require authorization.{{/authMethods}} +{{#hasAuthMethods}}Authentication schemes defined for the API:{{/hasAuthMethods}} +{{#authMethods}} + +### {{name}} + +{{#isApiKey}} + +- **Type**: API key +- **API key parameter name**: {{keyParamName}} +- **Location**: {{#isKeyInQuery}}URL query string{{/isKeyInQuery}}{{#isKeyInHeader}}HTTP header{{/isKeyInHeader}} +{{/isApiKey}} +{{#isBasicBasic}} + +- **Type**: HTTP basic authentication +{{/isBasicBasic}} +{{#isBasicBearer}} + +- **Type**: HTTP Bearer Token authentication{{#bearerFormat}} ({{{.}}}){{/bearerFormat}} +{{/isBasicBearer}} +{{#isHttpSignature}} + +- **Type**: HTTP signature authentication +{{/isHttpSignature}} +{{#isOAuth}} + +- **Type**: OAuth +- **Flow**: {{flow}} +- **Authorization URL**: {{authorizationUrl}} +- **Scopes**: {{^scopes}}N/A{{/scopes}} +{{#scopes}} - {{scope}}: {{description}} +{{/scopes}} +{{/isOAuth}} + +{{/authMethods}} + +## Recommendation + +It's recommended to create an instance of `ApiClient` per thread in a multithreaded environment to avoid any potential issues. +However, the instances of the api clients created from the `ApiClient` are thread-safe and can be re-used. + +## Author + +{{#apiInfo}}{{#apis}}{{#-last}}{{infoEmail}} +{{/-last}}{{/apis}}{{/apiInfo}} diff --git a/config/clients/java/template/Java/libraries/native/additional_properties.mustache b/config/clients/java/template/Java/libraries/native/additional_properties.mustache new file mode 100644 index 00000000..8e718279 --- /dev/null +++ b/config/clients/java/template/Java/libraries/native/additional_properties.mustache @@ -0,0 +1,45 @@ +{{#additionalPropertiesType}} + /** + * A container for additional, undeclared properties. + * This is a holder for any undeclared properties as specified with + * the 'additionalProperties' keyword in the OAS document. + */ + private Map additionalProperties; + + /** + * Set the additional (undeclared) property with the specified name and value. + * If the property does not already exist, create it otherwise replace it. + * @param key the name of the property + * @param value the value of the property + * @return self reference + */ + @JsonAnySetter + public {{classname}} putAdditionalProperty(String key, {{{.}}} value) { + if (this.additionalProperties == null) { + this.additionalProperties = new HashMap(); + } + this.additionalProperties.put(key, value); + return this; + } + + /** + * Return the additional (undeclared) properties. + * @return the additional (undeclared) properties + */ + @JsonAnyGetter + public Map getAdditionalProperties() { + return additionalProperties; + } + + /** + * Return the additional (undeclared) property with the specified name. + * @param key the name of the property + * @return the additional (undeclared) property with the specified name + */ + public {{{.}}} getAdditionalProperty(String key) { + if (this.additionalProperties == null) { + return null; + } + return this.additionalProperties.get(key); + } +{{/additionalPropertiesType}} diff --git a/config/clients/java/template/Java/libraries/native/anyof_model.mustache b/config/clients/java/template/Java/libraries/native/anyof_model.mustache new file mode 100644 index 00000000..2097e183 --- /dev/null +++ b/config/clients/java/template/Java/libraries/native/anyof_model.mustache @@ -0,0 +1,360 @@ +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import {{invokerPackage}}.JSON; + +{{>additionalModelTypeAnnotations}}{{>generatedAnnotation}}{{>xmlAnnotation}} +@JsonDeserialize(using={{classname}}.{{classname}}Deserializer.class) +@JsonSerialize(using = {{classname}}.{{classname}}Serializer.class) +public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-implements}}, {{{.}}}{{/vendorExtensions.x-implements}} { + private static final Logger log = Logger.getLogger({{classname}}.class.getName()); + + public static class {{classname}}Serializer extends StdSerializer<{{classname}}> { + public {{classname}}Serializer(Class<{{classname}}> t) { + super(t); + } + + public {{classname}}Serializer() { + this(null); + } + + @Override + public void serialize({{classname}} value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException { + jgen.writeObject(value.getActualInstance()); + } + } + + public static class {{classname}}Deserializer extends StdDeserializer<{{classname}}> { + public {{classname}}Deserializer() { + this({{classname}}.class); + } + + public {{classname}}Deserializer(Class vc) { + super(vc); + } + + @Override + public {{classname}} deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { + JsonNode tree = jp.readValueAsTree(); + + Object deserialized = null; + {{#discriminator}} + Class cls = JSON.getClassForElement(tree, {{classname}}.class); + if (cls != null) { + // When the OAS schema includes a discriminator, use the discriminator value to + // discriminate the anyOf schemas. + // Get the discriminator mapping value to get the class. + deserialized = tree.traverse(jp.getCodec()).readValueAs(cls); + {{classname}} ret = new {{classname}}(); + ret.setActualInstance(deserialized); + return ret; + } + {{/discriminator}} + {{#anyOf}} + // deserialize {{{.}}} + try { + deserialized = tree.traverse(jp.getCodec()).readValueAs({{{.}}}.class); + {{classname}} ret = new {{classname}}(); + ret.setActualInstance(deserialized); + return ret; + } catch (Exception e) { + // deserialization failed, continue, log to help debugging + log.log(Level.FINER, "Input data does not match '{{classname}}'", e); + } + + {{/anyOf}} + throw new IOException(String.format("Failed deserialization for {{classname}}: no match found")); + } + + /** + * Handle deserialization of the 'null' value. + */ + @Override + public {{classname}} getNullValue(DeserializationContext ctxt) throws JsonMappingException { + {{#isNullable}} + return null; + {{/isNullable}} + {{^isNullable}} + throw new JsonMappingException(ctxt.getParser(), "{{classname}} cannot be null"); + {{/isNullable}} + } + } + + // store a list of schema names defined in anyOf + public static final Map> schemas = new HashMap>(); + + public {{classname}}() { + super("anyOf", {{#isNullable}}Boolean.TRUE{{/isNullable}}{{^isNullable}}Boolean.FALSE{{/isNullable}}); + } +{{> libraries/native/additional_properties }} + {{#additionalPropertiesType}} + /** + * Return true if this {{name}} object is equal to o. + */ + @Override + public boolean equals(Object o) { + return super.equals(o) && Objects.equals(this.additionalProperties, (({{classname}})o).additionalProperties); + } + + @Override + public int hashCode() { + return Objects.hash(getActualInstance(), isNullable(), getSchemaType(), additionalProperties); + } + {{/additionalPropertiesType}} + {{#anyOf}} + public {{classname}}({{{.}}} o) { + super("anyOf", {{#isNullable}}Boolean.TRUE{{/isNullable}}{{^isNullable}}Boolean.FALSE{{/isNullable}}); + setActualInstance(o); + } + + {{/anyOf}} + static { + {{#anyOf}} + schemas.put("{{{.}}}", {{{.}}}.class); + {{/anyOf}} + JSON.registerDescendants({{classname}}.class, Collections.unmodifiableMap(schemas)); + {{#discriminator}} + // Initialize and register the discriminator mappings. + Map> mappings = new HashMap>(); + {{#mappedModels}} + mappings.put("{{mappingName}}", {{modelName}}.class); + {{/mappedModels}} + mappings.put("{{name}}", {{classname}}.class); + JSON.registerDiscriminator({{classname}}.class, "{{propertyBaseName}}", mappings); + {{/discriminator}} + } + + @Override + public Map> getSchemas() { + return {{classname}}.schemas; + } + + /** + * Set the instance that matches the anyOf child schema, check + * the instance parameter is valid against the anyOf child schemas: + * {{#anyOf}}{{{.}}}{{^-last}}, {{/-last}}{{/anyOf}} + * + * It could be an instance of the 'anyOf' schemas. + * The anyOf child schemas may themselves be a composed schema (allOf, anyOf, anyOf). + */ + @Override + public void setActualInstance(Object instance) { + {{#isNullable}} + if (instance == null) { + super.setActualInstance(instance); + return; + } + + {{/isNullable}} + {{#anyOf}} + if (JSON.isInstanceOf({{{.}}}.class, instance, new HashSet>())) { + super.setActualInstance(instance); + return; + } + + {{/anyOf}} + throw new RuntimeException("Invalid instance type. Must be {{#anyOf}}{{{.}}}{{^-last}}, {{/-last}}{{/anyOf}}"); + } + + /** + * Get the actual instance, which can be the following: + * {{#anyOf}}{{{.}}}{{^-last}}, {{/-last}}{{/anyOf}} + * + * @return The actual instance ({{#anyOf}}{{{.}}}{{^-last}}, {{/-last}}{{/anyOf}}) + */ + @Override + public Object getActualInstance() { + return super.getActualInstance(); + } + + {{#anyOf}} + /** + * Get the actual instance of `{{{.}}}`. If the actual instance is not `{{{.}}}`, + * the ClassCastException will be thrown. + * + * @return The actual instance of `{{{.}}}` + * @throws ClassCastException if the instance is not `{{{.}}}` + */ + public {{{.}}} get{{{.}}}() throws ClassCastException { + return ({{{.}}})super.getActualInstance(); + } + + {{/anyOf}} + +{{#supportUrlQuery}} + + /** + * Convert the instance into URL query string. + * + * @return URL query string + */ + public String toUrlQueryString() { + return toUrlQueryString(null); + } + + /** + * Convert the instance into URL query string. + * + * @param prefix prefix of the query string + * @return URL query string + */ + public String toUrlQueryString(String prefix) { + String suffix = ""; + String containerSuffix = ""; + String containerPrefix = ""; + if (prefix == null) { + // style=form, explode=true, e.g. /pet?name=cat&type=manx + prefix = ""; + } else { + // deepObject style e.g. /pet?id[name]=cat&id[type]=manx + prefix = prefix + "["; + suffix = "]"; + containerSuffix = "]"; + containerPrefix = "["; + } + + StringJoiner joiner = new StringJoiner("&"); + + {{#composedSchemas.oneOf}} + if (getActualInstance() instanceof {{{dataType}}}) { + {{#isArray}} + {{#items.isPrimitiveType}} + {{#uniqueItems}} + if (getActualInstance() != null) { + int i = 0; + for ({{{items.dataType}}} _item : ({{{dataType}}})getActualInstance()) { + joiner.add(String.format("%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, i, containerSuffix), + URLEncoder.encode(String.valueOf(_item), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + i++; + } + {{/uniqueItems}} + {{^uniqueItems}} + if (getActualInstance() != null) { + for (int i = 0; i < (({{{dataType}}})getActualInstance()).size(); i++) { + joiner.add(String.format("%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, i, containerSuffix), + URLEncoder.encode(String.valueOf(getActualInstance().get(i)), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + } + {{/uniqueItems}} + {{/items.isPrimitiveType}} + {{^items.isPrimitiveType}} + {{#items.isModel}} + {{#uniqueItems}} + if (getActualInstance() != null) { + int i = 0; + for ({{{items.dataType}}} _item : ({{{dataType}}})getActualInstance()) { + if (_item != null) { + joiner.add(_item.toUrlQueryString(String.format("%s{{baseName}}%s%s", prefix, suffix, + "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, i, containerSuffix)))); + } + } + i++; + } + {{/uniqueItems}} + {{^uniqueItems}} + if (getActualInstance() != null) { + for (int i = 0; i < (({{{dataType}}})getActualInstance()).size(); i++) { + if ((({{{dataType}}})getActualInstance()).get(i) != null) { + joiner.add((({{{items.dataType}}})getActualInstance()).get(i).toUrlQueryString(String.format("%s{{baseName}}%s%s", prefix, suffix, + "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, i, containerSuffix)))); + } + } + } + {{/uniqueItems}} + {{/items.isModel}} + {{^items.isModel}} + {{#uniqueItems}} + if (getActualInstance() != null) { + int i = 0; + for ({{{items.dataType}}} _item : ({{{dataType}}})getActualInstance()) { + if (_item != null) { + joiner.add(String.format("%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, i, containerSuffix), + URLEncoder.encode(String.valueOf(_item), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + i++; + } + } + {{/uniqueItems}} + {{^uniqueItems}} + if (getActualInstance() != null) { + for (int i = 0; i < (({{{dataType}}})getActualInstance()).size(); i++) { + if (getActualInstance().get(i) != null) { + joiner.add(String.format("%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, i, containerSuffix), + URLEncoder.encode(String.valueOf((({{{dataType}}})getActualInstance()).get(i)), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + } + } + {{/uniqueItems}} + {{/items.isModel}} + {{/items.isPrimitiveType}} + {{/isArray}} + {{^isArray}} + {{#isMap}} + {{#items.isPrimitiveType}} + if (getActualInstance() != null) { + for (String _key : (({{{dataType}}})getActualInstance()).keySet()) { + joiner.add(String.format("%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, _key, containerSuffix), + getActualInstance().get(_key), URLEncoder.encode(String.valueOf((({{{dataType}}})getActualInstance()).get(_key)), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + } + {{/items.isPrimitiveType}} + {{^items.isPrimitiveType}} + if (getActualInstance() != null) { + for (String _key : (({{{dataType}}})getActualInstance()).keySet()) { + if ((({{{dataType}}})getActualInstance()).get(_key) != null) { + joiner.add((({{{items.dataType}}})getActualInstance()).get(_key).toUrlQueryString(String.format("%s{{baseName}}%s%s", prefix, suffix, + "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, _key, containerSuffix)))); + } + } + } + {{/items.isPrimitiveType}} + {{/isMap}} + {{^isMap}} + {{#isPrimitiveType}} + if (getActualInstance() != null) { + joiner.add(String.format("%s{{{baseName}}}%s=%s", prefix, suffix, URLEncoder.encode(String.valueOf(getActualInstance()), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + {{/isPrimitiveType}} + {{^isPrimitiveType}} + {{#isModel}} + if (getActualInstance() != null) { + joiner.add((({{{dataType}}})getActualInstance()).toUrlQueryString(prefix + "{{{baseName}}}" + suffix)); + } + {{/isModel}} + {{^isModel}} + if (getActualInstance() != null) { + joiner.add(String.format("%s{{{baseName}}}%s=%s", prefix, suffix, URLEncoder.encode(String.valueOf(getActualInstance()), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + {{/isModel}} + {{/isPrimitiveType}} + {{/isMap}} + {{/isArray}} + return joiner.toString(); + } + {{/composedSchemas.oneOf}} + return null; + } +{{/supportUrlQuery}} + +} diff --git a/config/clients/java/template/Java/libraries/native/api.mustache b/config/clients/java/template/Java/libraries/native/api.mustache new file mode 100644 index 00000000..526e5652 --- /dev/null +++ b/config/clients/java/template/Java/libraries/native/api.mustache @@ -0,0 +1,591 @@ +{{>licenseInfo}} +package {{package}}; + +import {{invokerPackage}}.ApiClient; +import {{invokerPackage}}.ApiException; +import {{invokerPackage}}.ApiResponse; +import {{invokerPackage}}.Pair; + +{{#imports}} +import {{import}}; +{{/imports}} + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +{{#hasFormParamsInSpec}} +import org.apache.http.HttpEntity; +import org.apache.http.NameValuePair; +import org.apache.http.entity.mime.MultipartEntityBuilder; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.client.entity.UrlEncodedFormEntity; + +{{/hasFormParamsInSpec}} +import java.io.InputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.net.http.HttpRequest; +import java.nio.channels.Channels; +import java.nio.channels.Pipe; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; + +import java.util.ArrayList; +import java.util.StringJoiner; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; +{{#asyncNative}} + +import java.util.concurrent.CompletableFuture; +{{/asyncNative}} + +{{>generatedAnnotation}} +{{#operations}} +public class {{classname}} { + private final HttpClient memberVarHttpClient; + private final ObjectMapper memberVarObjectMapper; + private final String memberVarBaseUri; + private final Consumer memberVarInterceptor; + private final Duration memberVarReadTimeout; + private final Consumer> memberVarResponseInterceptor; + private final Consumer> memberVarAsyncResponseInterceptor; + + public {{classname}}() { + this(new ApiClient()); + } + + public {{classname}}(ApiClient apiClient) { + memberVarHttpClient = apiClient.getHttpClient(); + memberVarObjectMapper = apiClient.getObjectMapper(); + memberVarBaseUri = apiClient.getBaseUri(); + memberVarInterceptor = apiClient.getRequestInterceptor(); + memberVarReadTimeout = apiClient.getReadTimeout(); + memberVarResponseInterceptor = apiClient.getResponseInterceptor(); + memberVarAsyncResponseInterceptor = apiClient.getAsyncResponseInterceptor(); + } + {{#asyncNative}} + + private ApiException getApiException(String operationId, HttpResponse response) { + String message = formatExceptionMessage(operationId, response.statusCode(), response.body()); + return new ApiException(response.statusCode(), message, response.headers(), response.body()); + } + {{/asyncNative}} + {{^asyncNative}} + + protected ApiException getApiException(String operationId, HttpResponse response) throws IOException { + String body = response.body() == null ? null : new String(response.body().readAllBytes()); + String message = formatExceptionMessage(operationId, response.statusCode(), body); + return new ApiException(response.statusCode(), message, response.headers(), body); + } + {{/asyncNative}} + + private String formatExceptionMessage(String operationId, int statusCode, String body) { + if (body == null || body.isEmpty()) { + body = "[no body]"; + } + return operationId + " call failed with: " + statusCode + " - " + body; + } + + {{#operation}} + {{#vendorExtensions.x-group-parameters}} + {{#hasParams}} + /** + * {{summary}} + * {{notes}} + * @param apiRequest {@link API{{operationId}}Request} + {{#returnType}} + * @return {{#asyncNative}}CompletableFuture<{{/asyncNative}}{{returnType}}{{#asyncNative}}>{{/asyncNative}} + {{/returnType}} + {{^returnType}} + {{#asyncNative}} + * @return CompletableFuture<Void> + {{/asyncNative}} + {{/returnType}} + * @throws ApiException if fails to make API call + {{#isDeprecated}} + * @deprecated + {{/isDeprecated}} + {{#externalDocs}} + * {{description}} + * @see {{summary}} Documentation + {{/externalDocs}} + */ + {{#isDeprecated}} + @Deprecated + {{/isDeprecated}} + public {{#returnType}}{{#asyncNative}}CompletableFuture<{{{returnType}}}>{{/asyncNative}}{{^asyncNative}}{{{returnType}}}{{/asyncNative}}{{/returnType}}{{^returnType}}{{#asyncNative}}CompletableFuture{{/asyncNative}}{{^asyncNative}}void{{/asyncNative}}{{/returnType}} {{operationId}}(API{{operationId}}Request apiRequest) throws ApiException { + {{#allParams}} + {{{dataType}}} {{paramName}} = apiRequest.{{paramName}}(); + {{/allParams}} + {{#returnType}}return {{/returnType}}{{^returnType}}{{#asyncNative}}return {{/asyncNative}}{{/returnType}}{{operationId}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}); + } + + /** + * {{summary}} + * {{notes}} + * @param apiRequest {@link API{{operationId}}Request} + * @return {{#asyncNative}}CompletableFuture<{{/asyncNative}}ApiResponse<{{returnType}}{{^returnType}}Void{{/returnType}}>{{#asyncNative}}>{{/asyncNative}} + * @throws ApiException if fails to make API call + {{#isDeprecated}} + * @deprecated + {{/isDeprecated}} + {{#externalDocs}} + * {{description}} + * @see {{summary}} Documentation + {{/externalDocs}} + */ + {{#isDeprecated}} + @Deprecated + {{/isDeprecated}} + public {{#asyncNative}}CompletableFuture<{{/asyncNative}}ApiResponse<{{{returnType}}}{{^returnType}}Void{{/returnType}}>{{#asyncNative}}>{{/asyncNative}} {{operationId}}WithHttpInfo(API{{operationId}}Request apiRequest) throws ApiException { + {{#allParams}} + {{{dataType}}} {{paramName}} = apiRequest.{{paramName}}(); + {{/allParams}} + return {{operationId}}WithHttpInfo({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}); + } + + {{/hasParams}} + {{/vendorExtensions.x-group-parameters}} + /** + * {{summary}} + * {{notes}} + {{#allParams}} + * @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{^isContainer}}{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/isContainer}}{{/required}} + {{/allParams}} + {{#returnType}} + * @return {{#asyncNative}}CompletableFuture<{{/asyncNative}}{{returnType}}{{#asyncNative}}>{{/asyncNative}} + {{/returnType}} + {{^returnType}} + {{#asyncNative}} + * @return CompletableFuture<Void> + {{/asyncNative}} + {{/returnType}} + * @throws ApiException if fails to make API call + {{#isDeprecated}} + * @deprecated + {{/isDeprecated}} + {{#externalDocs}} + * {{description}} + * @see {{summary}} Documentation + {{/externalDocs}} + */ + {{#isDeprecated}} + @Deprecated + {{/isDeprecated}} + public {{#returnType}}{{#asyncNative}}CompletableFuture<{{{returnType}}}>{{/asyncNative}}{{^asyncNative}}{{{returnType}}}{{/asyncNative}}{{/returnType}}{{^returnType}}{{#asyncNative}}CompletableFuture{{/asyncNative}}{{^asyncNative}}void{{/asyncNative}}{{/returnType}} {{operationId}}({{#allParams}}{{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws ApiException { + {{^asyncNative}} + {{#returnType}}ApiResponse<{{{.}}}> localVarResponse = {{/returnType}}{{operationId}}WithHttpInfo({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}); + {{#returnType}} + return localVarResponse.getData(); + {{/returnType}} + {{/asyncNative}} + {{#asyncNative}} + try { + HttpRequest.Builder localVarRequestBuilder = {{operationId}}RequestBuilder({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}); + return memberVarHttpClient.sendAsync( + localVarRequestBuilder.build(), + HttpResponse.BodyHandlers.ofString()).thenComposeAsync(localVarResponse -> { + if (localVarResponse.statusCode()/ 100 != 2) { + return CompletableFuture.failedFuture(getApiException("{{operationId}}", localVarResponse)); + } + {{#returnType}} + try { + String responseBody = localVarResponse.body(); + return CompletableFuture.completedFuture( + responseBody == null || responseBody.isBlank() ? null : memberVarObjectMapper.readValue(responseBody, new TypeReference<{{{returnType}}}>() {}) + ); + } catch (IOException e) { + return CompletableFuture.failedFuture(new ApiException(e)); + } + {{/returnType}} + {{^returnType}} + return CompletableFuture.completedFuture(null); + {{/returnType}} + }); + } + catch (ApiException e) { + return CompletableFuture.failedFuture(e); + } + {{/asyncNative}} + } + + /** + * {{summary}} + * {{notes}} + {{#allParams}} + * @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{^isContainer}}{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/isContainer}}{{/required}} + {{/allParams}} + * @return {{#asyncNative}}CompletableFuture<{{/asyncNative}}ApiResponse<{{returnType}}{{^returnType}}Void{{/returnType}}>{{#asyncNative}}>{{/asyncNative}} + * @throws ApiException if fails to make API call + {{#isDeprecated}} + * @deprecated + {{/isDeprecated}} + {{#externalDocs}} + * {{description}} + * @see {{summary}} Documentation + {{/externalDocs}} + */ + {{#isDeprecated}} + @Deprecated + {{/isDeprecated}} + public {{#asyncNative}}CompletableFuture<{{/asyncNative}}ApiResponse<{{{returnType}}}{{^returnType}}Void{{/returnType}}>{{#asyncNative}}>{{/asyncNative}} {{operationId}}WithHttpInfo({{#allParams}}{{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws ApiException { + {{^asyncNative}} + HttpRequest.Builder localVarRequestBuilder = {{operationId}}RequestBuilder({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}); + try { + HttpResponse localVarResponse = memberVarHttpClient.send( + localVarRequestBuilder.build(), + HttpResponse.BodyHandlers.ofInputStream()); + if (memberVarResponseInterceptor != null) { + memberVarResponseInterceptor.accept(localVarResponse); + } + try { + if (localVarResponse.statusCode()/ 100 != 2) { + throw getApiException("{{operationId}}", localVarResponse); + } + {{#vendorExtensions.x-java-text-plain-string}} + // for plain text response + if (localVarResponse.headers().map().containsKey("Content-Type") && + "text/plain".equalsIgnoreCase(localVarResponse.headers().map().get("Content-Type").get(0).split(";")[0].trim())) { + java.util.Scanner s = new java.util.Scanner(localVarResponse.body()).useDelimiter("\\A"); + String responseBodyText = s.hasNext() ? s.next() : ""; + return new ApiResponse( + localVarResponse.statusCode(), + localVarResponse.headers().map(), + responseBodyText + ); + } else { + throw new RuntimeException("Error! The response Content-Type is supposed to be `text/plain` but it's not: " + localVarResponse); + } + {{/vendorExtensions.x-java-text-plain-string}} + {{^vendorExtensions.x-java-text-plain-string}} + return new ApiResponse<{{{returnType}}}{{^returnType}}Void{{/returnType}}>( + localVarResponse.statusCode(), + localVarResponse.headers().map(), + {{#returnType}} + localVarResponse.body() == null ? null : memberVarObjectMapper.readValue(localVarResponse.body(), new TypeReference<{{{returnType}}}>() {}) // closes the InputStream + {{/returnType}} + {{^returnType}} + null + {{/returnType}} + ); + {{/vendorExtensions.x-java-text-plain-string}} + } finally { + {{^returnType}} + // Drain the InputStream + while (localVarResponse.body().read() != -1) { + // Ignore + } + localVarResponse.body().close(); + {{/returnType}} + } + } catch (IOException e) { + throw new ApiException(e); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new ApiException(e); + } + {{/asyncNative}} + {{#asyncNative}} + try { + HttpRequest.Builder localVarRequestBuilder = {{operationId}}RequestBuilder({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}); + return memberVarHttpClient.sendAsync( + localVarRequestBuilder.build(), + HttpResponse.BodyHandlers.ofString()).thenComposeAsync(localVarResponse -> { + if (memberVarAsyncResponseInterceptor != null) { + memberVarAsyncResponseInterceptor.accept(localVarResponse); + } + if (localVarResponse.statusCode()/ 100 != 2) { + return CompletableFuture.failedFuture(getApiException("{{operationId}}", localVarResponse)); + } + {{#returnType}} + try { + String responseBody = localVarResponse.body(); + return CompletableFuture.completedFuture( + new ApiResponse<{{{returnType}}}>( + localVarResponse.statusCode(), + localVarResponse.headers().map(), + responseBody == null || responseBody.isBlank() ? null : memberVarObjectMapper.readValue(responseBody, new TypeReference<{{{returnType}}}>() {})) + ); + } catch (IOException e) { + return CompletableFuture.failedFuture(new ApiException(e)); + } + {{/returnType}} + {{^returnType}} + return CompletableFuture.completedFuture( + new ApiResponse(localVarResponse.statusCode(), localVarResponse.headers().map(), null) + ); + {{/returnType}} + } + ); + } + catch (ApiException e) { + return CompletableFuture.failedFuture(e); + } + {{/asyncNative}} + } + + private HttpRequest.Builder {{operationId}}RequestBuilder({{#allParams}}{{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws ApiException { + {{#allParams}} + {{#required}} + // verify the required parameter '{{paramName}}' is set + if ({{paramName}} == null) { + throw new ApiException(400, "Missing the required parameter '{{paramName}}' when calling {{operationId}}"); + } + {{/required}} + {{/allParams}} + + HttpRequest.Builder localVarRequestBuilder = HttpRequest.newBuilder(); + + {{! Switch delimiters for baseName so we can write constants like "{query}" }} + String localVarPath = "{{{path}}}"{{#pathParams}} + .replace({{=<% %>=}}"{<%baseName%>}"<%={{ }}=%>, ApiClient.urlEncode({{{paramName}}}.toString())){{/pathParams}}; + + {{#hasQueryParams}} + List localVarQueryParams = new ArrayList<>(); + StringJoiner localVarQueryStringJoiner = new StringJoiner("&"); + String localVarQueryParameterBaseName; + {{#queryParams}} + localVarQueryParameterBaseName = "{{{baseName}}}"; + {{#collectionFormat}} + localVarQueryParams.addAll(ApiClient.parameterToPairs("{{{collectionFormat}}}", "{{baseName}}", {{paramName}})); + {{/collectionFormat}} + {{^collectionFormat}} + {{#isDeepObject}} + if ({{paramName}} != null) { + {{#isArray}} + for (int i=0; i < {{paramName}}.size(); i++) { + localVarQueryStringJoiner.add({{paramName}}.get(i).toUrlQueryString(String.format("{{baseName}}[%d]", i))); + } + {{/isArray}} + {{^isArray}} + localVarQueryStringJoiner.add({{paramName}}.toUrlQueryString("{{baseName}}")); + {{/isArray}} + } + {{/isDeepObject}} + {{^isDeepObject}} + {{#isExplode}} + {{#hasVars}} + {{#vars}} + {{#isArray}} + localVarQueryParams.addAll(ApiClient.parameterToPairs("multi", "{{baseName}}", {{paramName}}.{{getter}}())); + {{/isArray}} + {{^isArray}} + localVarQueryParams.addAll(ApiClient.parameterToPairs("{{baseName}}", {{paramName}}.{{getter}}())); + {{/isArray}} + {{/vars}} + {{/hasVars}} + {{^hasVars}} + {{#isModel}} + localVarQueryStringJoiner.add({{paramName}}.toUrlQueryString()); + {{/isModel}} + {{^isModel}} + localVarQueryParams.addAll(ApiClient.parameterToPairs("{{baseName}}", {{paramName}})); + {{/isModel}} + {{/hasVars}} + {{/isExplode}} + {{^isExplode}} + localVarQueryParams.addAll(ApiClient.parameterToPairs("{{baseName}}", {{paramName}})); + {{/isExplode}} + {{/isDeepObject}} + {{/collectionFormat}} + {{/queryParams}} + + if (!localVarQueryParams.isEmpty() || localVarQueryStringJoiner.length() != 0) { + StringJoiner queryJoiner = new StringJoiner("&"); + localVarQueryParams.forEach(p -> queryJoiner.add(p.getName() + '=' + p.getValue())); + if (localVarQueryStringJoiner.length() != 0) { + queryJoiner.add(localVarQueryStringJoiner.toString()); + } + localVarRequestBuilder.uri(URI.create(memberVarBaseUri + localVarPath + '?' + queryJoiner.toString())); + } else { + localVarRequestBuilder.uri(URI.create(memberVarBaseUri + localVarPath)); + } + {{/hasQueryParams}} + {{^hasQueryParams}} + localVarRequestBuilder.uri(URI.create(memberVarBaseUri + localVarPath)); + {{/hasQueryParams}} + + {{#headerParams}} + if ({{paramName}} != null) { + localVarRequestBuilder.header("{{baseName}}", {{paramName}}.toString()); + } + {{/headerParams}} + {{#bodyParam}} + localVarRequestBuilder.header("Content-Type", "{{#hasConsumes}}{{#consumes}}{{#-first}}{{mediaType}}{{/-first}}{{/consumes}}{{/hasConsumes}}{{#hasConsumes}}{{^consumes}}application/json{{/consumes}}{{/hasConsumes}}{{^hasConsumes}}application/json{{/hasConsumes}}"); + {{/bodyParam}} + localVarRequestBuilder.header("Accept", "{{#hasProduces}}{{#produces}}{{mediaType}}{{^-last}}, {{/-last}}{{/produces}}{{/hasProduces}}{{#hasProduces}}{{^produces}}application/json{{/produces}}{{/hasProduces}}{{^hasProduces}}application/json{{/hasProduces}}"); + + {{#bodyParam}} + {{#isString}} + localVarRequestBuilder.method("{{httpMethod}}", HttpRequest.BodyPublishers.ofString({{paramName}})); + {{/isString}} + {{^isString}} + try { + byte[] localVarPostBody = memberVarObjectMapper.writeValueAsBytes({{paramName}}); + localVarRequestBuilder.method("{{httpMethod}}", HttpRequest.BodyPublishers.ofByteArray(localVarPostBody)); + } catch (IOException e) { + throw new ApiException(e); + } + {{/isString}} + {{/bodyParam}} + {{^bodyParam}} + {{#hasFormParams}} + {{#isMultipart}} + MultipartEntityBuilder multiPartBuilder = MultipartEntityBuilder.create(); + boolean hasFiles = false; + {{#formParams}} + {{#isArray}} + for (int i=0; i < {{paramName}}.size(); i++) { + {{#isFile}} + multiPartBuilder.addBinaryBody("{{{baseName}}}", {{paramName}}.get(i)); + hasFiles = true; + {{/isFile}} + {{^isFile}} + multiPartBuilder.addTextBody("{{{baseName}}}", {{paramName}}.get(i).toString()); + {{/isFile}} + } + {{/isArray}} + {{^isArray}} + {{#isFile}} + multiPartBuilder.addBinaryBody("{{{baseName}}}", {{paramName}}); + hasFiles = true; + {{/isFile}} + {{^isFile}} + multiPartBuilder.addTextBody("{{{baseName}}}", {{paramName}}.toString()); + {{/isFile}} + {{/isArray}} + {{/formParams}} + HttpEntity entity = multiPartBuilder.build(); + HttpRequest.BodyPublisher formDataPublisher; + if (hasFiles) { + Pipe pipe; + try { + pipe = Pipe.open(); + } catch (IOException e) { + throw new RuntimeException(e); + } + new Thread(() -> { + try (OutputStream outputStream = Channels.newOutputStream(pipe.sink())) { + entity.writeTo(outputStream); + } catch (IOException e) { + e.printStackTrace(); + } + }).start(); + formDataPublisher = HttpRequest.BodyPublishers.ofInputStream(() -> Channels.newInputStream(pipe.source())); + } else { + ByteArrayOutputStream formOutputStream = new ByteArrayOutputStream(); + try { + entity.writeTo(formOutputStream); + } catch (IOException e) { + throw new RuntimeException(e); + } + formDataPublisher = HttpRequest.BodyPublishers + .ofInputStream(() -> new ByteArrayInputStream(formOutputStream.toByteArray())); + } + localVarRequestBuilder + .header("Content-Type", entity.getContentType().getValue()) + .method("{{httpMethod}}", formDataPublisher); + {{/isMultipart}} + {{^isMultipart}} + List formValues = new ArrayList<>(); + {{#formParams}} + {{#isArray}} + for (int i=0; i < {{paramName}}.size(); i++) { + if ({{paramName}}.get(i) != null) { + formValues.add(new BasicNameValuePair("{{{baseName}}}", {{paramName}}.get(i).toString())); + } + } + {{/isArray}} + {{^isArray}} + if ({{paramName}} != null) { + formValues.add(new BasicNameValuePair("{{{baseName}}}", {{paramName}}.toString())); + } + {{/isArray}} + {{/formParams}} + HttpEntity entity = new UrlEncodedFormEntity(formValues, java.nio.charset.StandardCharsets.UTF_8); + ByteArrayOutputStream formOutputStream = new ByteArrayOutputStream(); + try { + entity.writeTo(formOutputStream); + } catch (IOException e) { + throw new RuntimeException(e); + } + localVarRequestBuilder + .header("Content-Type", entity.getContentType().getValue()) + .method("{{httpMethod}}", HttpRequest.BodyPublishers + .ofInputStream(() -> new ByteArrayInputStream(formOutputStream.toByteArray()))); + {{/isMultipart}} + {{/hasFormParams}} + {{^hasFormParams}} + localVarRequestBuilder.method("{{httpMethod}}", HttpRequest.BodyPublishers.noBody()); + {{/hasFormParams}} + {{/bodyParam}} + if (memberVarReadTimeout != null) { + localVarRequestBuilder.timeout(memberVarReadTimeout); + } + if (memberVarInterceptor != null) { + memberVarInterceptor.accept(localVarRequestBuilder); + } + return localVarRequestBuilder; + } + {{#vendorExtensions.x-group-parameters}} + {{#hasParams}} + + public static final class API{{operationId}}Request { + {{#requiredParams}} + private {{{dataType}}} {{paramName}}; // {{description}} (required) + {{/requiredParams}} + {{#optionalParams}} + private {{{dataType}}} {{paramName}}; // {{description}} (optional{{^isContainer}}{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/isContainer}} + {{/optionalParams}} + + private API{{operationId}}Request(Builder builder) { + {{#requiredParams}} + this.{{paramName}} = builder.{{paramName}}; + {{/requiredParams}} + {{#optionalParams}} + this.{{paramName}} = builder.{{paramName}}; + {{/optionalParams}} + } + {{#allParams}} + public {{{dataType}}} {{paramName}}() { + return {{paramName}}; + } + {{/allParams}} + public static Builder newBuilder() { + return new Builder(); + } + + public static class Builder { + {{#requiredParams}} + private {{{dataType}}} {{paramName}}; + {{/requiredParams}} + {{#optionalParams}} + private {{{dataType}}} {{paramName}}; + {{/optionalParams}} + + {{#allParams}} + public Builder {{paramName}}({{{dataType}}} {{paramName}}) { + this.{{paramName}} = {{paramName}}; + return this; + } + {{/allParams}} + public API{{operationId}}Request build() { + return new API{{operationId}}Request(this); + } + } + } + + {{/hasParams}} + {{/vendorExtensions.x-group-parameters}} + {{/operation}} +} +{{/operations}} diff --git a/config/clients/java/template/Java/libraries/native/apiException.mustache b/config/clients/java/template/Java/libraries/native/apiException.mustache new file mode 100644 index 00000000..3d0eee4b --- /dev/null +++ b/config/clients/java/template/Java/libraries/native/apiException.mustache @@ -0,0 +1,79 @@ +{{>licenseInfo}} + +package {{invokerPackage}}; + +import java.net.http.HttpHeaders; + +{{>generatedAnnotation}} +public class ApiException extends{{#useRuntimeException}} RuntimeException {{/useRuntimeException}}{{^useRuntimeException}} Exception {{/useRuntimeException}}{ + private int code = 0; + private HttpHeaders responseHeaders = null; + private String responseBody = null; + + public ApiException() {} + + public ApiException(Throwable throwable) { + super(throwable); + } + + public ApiException(String message) { + super(message); + } + + public ApiException(String message, Throwable throwable, int code, HttpHeaders responseHeaders, String responseBody) { + super(message, throwable); + this.code = code; + this.responseHeaders = responseHeaders; + this.responseBody = responseBody; + } + + public ApiException(String message, int code, HttpHeaders responseHeaders, String responseBody) { + this(message, (Throwable) null, code, responseHeaders, responseBody); + } + + public ApiException(String message, Throwable throwable, int code, HttpHeaders responseHeaders) { + this(message, throwable, code, responseHeaders, null); + } + + public ApiException(int code, HttpHeaders responseHeaders, String responseBody) { + this((String) null, (Throwable) null, code, responseHeaders, responseBody); + } + + public ApiException(int code, String message) { + super(message); + this.code = code; + } + + public ApiException(int code, String message, HttpHeaders responseHeaders, String responseBody) { + this(code, message); + this.responseHeaders = responseHeaders; + this.responseBody = responseBody; + } + + /** + * Get the HTTP status code. + * + * @return HTTP status code + */ + public int getCode() { + return code; + } + + /** + * Get the HTTP response headers. + * + * @return Headers as an HttpHeaders object + */ + public HttpHeaders getResponseHeaders() { + return responseHeaders; + } + + /** + * Get the HTTP response body. + * + * @return Response body in the form of string + */ + public String getResponseBody() { + return responseBody; + } +} diff --git a/config/clients/java/template/Java/libraries/native/api_doc.mustache b/config/clients/java/template/Java/libraries/native/api_doc.mustache new file mode 100644 index 00000000..2b125e1f --- /dev/null +++ b/config/clients/java/template/Java/libraries/native/api_doc.mustache @@ -0,0 +1,280 @@ +# {{classname}}{{#description}} + +{{.}}{{/description}} + +All URIs are relative to *{{basePath}}* + +| Method | HTTP request | Description | +|------------- | ------------- | -------------| +{{#operations}}{{#operation}}| [**{{operationId}}**]({{classname}}.md#{{operationId}}) | **{{httpMethod}}** {{path}} | {{summary}} | +| [**{{operationId}}WithHttpInfo**]({{classname}}.md#{{operationId}}WithHttpInfo) | **{{httpMethod}}** {{path}} | {{summary}} | +{{/operation}}{{/operations}} + +{{#operations}} +{{#operation}} + +## {{operationId}} + +{{^vendorExtensions.x-group-parameters}} +> {{#returnType}}{{#asyncNative}}CompletableFuture<{{{returnType}}}>{{/asyncNative}}{{^asyncNative}}{{{returnType}}}{{/asyncNative}}{{/returnType}}{{^returnType}}{{#asyncNative}}CompletableFuture{{/asyncNative}}{{^asyncNative}}void{{/asyncNative}}{{/returnType}} {{operationId}}({{#allParams}}{{{paramName}}}{{^-last}}, {{/-last}}{{/allParams}}) +{{/vendorExtensions.x-group-parameters}} +{{#vendorExtensions.x-group-parameters}} +> {{#returnType}}{{#asyncNative}}CompletableFuture<{{{returnType}}}>{{/asyncNative}}{{^asyncNative}}{{{returnType}}}{{/asyncNative}}{{/returnType}}{{^returnType}}{{#asyncNative}}CompletableFuture{{/asyncNative}}{{^asyncNative}}void{{/asyncNative}}{{/returnType}} {{operationId}}({{#hasParams}}{{operationId}}Request{{/hasParams}}) +{{/vendorExtensions.x-group-parameters}} + +{{summary}}{{#notes}} + +{{.}}{{/notes}} + +### Example + +```java +// Import classes: +import {{{invokerPackage}}}.ApiClient; +import {{{invokerPackage}}}.ApiException; +import {{{invokerPackage}}}.Configuration;{{#hasAuthMethods}} +import {{{invokerPackage}}}.auth.*;{{/hasAuthMethods}} +import {{{invokerPackage}}}.models.*; +import {{{package}}}.{{{classname}}};{{#vendorExtensions.x-group-parameters}} +import {{{package}}}.{{{classname}}}.*;{{/vendorExtensions.x-group-parameters}} +{{#asyncNative}} +import java.util.concurrent.CompletableFuture; +{{/asyncNative}} + +public class Example { + public static void main(String[] args) { + ApiClient defaultClient = Configuration.getDefaultApiClient(); + defaultClient.setBasePath("{{{basePath}}}"); + {{#hasAuthMethods}} + {{#authMethods}}{{#isBasic}}{{#isBasicBasic}} + // Configure HTTP basic authorization: {{{name}}} + HttpBasicAuth {{{name}}} = (HttpBasicAuth) defaultClient.getAuthentication("{{{name}}}"); + {{{name}}}.setUsername("YOUR USERNAME"); + {{{name}}}.setPassword("YOUR PASSWORD");{{/isBasicBasic}}{{#isBasicBearer}} + // Configure HTTP bearer authorization: {{{name}}} + HttpBearerAuth {{{name}}} = (HttpBearerAuth) defaultClient.getAuthentication("{{{name}}}"); + {{{name}}}.setBearerToken("BEARER TOKEN");{{/isBasicBearer}}{{/isBasic}}{{#isApiKey}} + // Configure API key authorization: {{{name}}} + ApiKeyAuth {{{name}}} = (ApiKeyAuth) defaultClient.getAuthentication("{{{name}}}"); + {{{name}}}.setApiKey("YOUR API KEY"); + // Uncomment the following line to set a prefix for the API key, e.g. "Token" (defaults to null) + //{{{name}}}.setApiKeyPrefix("Token");{{/isApiKey}}{{#isOAuth}} + // Configure OAuth2 access token for authorization: {{{name}}} + OAuth {{{name}}} = (OAuth) defaultClient.getAuthentication("{{{name}}}"); + {{{name}}}.setAccessToken("YOUR ACCESS TOKEN");{{/isOAuth}} + {{/authMethods}} + {{/hasAuthMethods}} + + {{{classname}}} apiInstance = new {{{classname}}}(defaultClient); + {{#allParams}} + {{{dataType}}} {{{paramName}}} = {{{example}}}; // {{{dataType}}} | {{{description}}} + {{/allParams}} + try { + {{^vendorExtensions.x-group-parameters}} + {{#returnType}}{{#asyncNative}}CompletableFuture<{{{returnType}}}>{{/asyncNative}}{{^asyncNative}}{{{returnType}}}{{/asyncNative}} result = {{/returnType}}{{^returnType}}{{#asyncNative}}CompletableFuture result = {{/asyncNative}}{{/returnType}}apiInstance.{{{operationId}}}({{#allParams}}{{{paramName}}}{{^-last}}, {{/-last}}{{/allParams}}); + {{/vendorExtensions.x-group-parameters}} + {{#vendorExtensions.x-group-parameters}} + {{#hasParams}} + API{{operationId}}Request request = API{{operationId}}Request.newBuilder(){{#allParams}} + .{{paramName}}({{paramName}}){{/allParams}} + .build(); + {{/hasParams}} + {{#returnType}}{{#asyncNative}}CompletableFuture<{{{returnType}}}>{{/asyncNative}}{{^asyncNative}}{{{returnType}}}{{/asyncNative}} result = {{/returnType}}{{^returnType}}{{#asyncNative}}CompletableFuture result = {{/asyncNative}}{{/returnType}}apiInstance.{{operationId}}({{#hasParams}}request{{/hasParams}}); + {{/vendorExtensions.x-group-parameters}} + {{#returnType}} + System.out.println(result{{#asyncNative}}.get(){{/asyncNative}}); + {{/returnType}} + } catch (ApiException e) { + System.err.println("Exception when calling {{{classname}}}#{{{operationId}}}"); + System.err.println("Status code: " + e.getCode()); + System.err.println("Reason: " + e.getResponseBody()); + System.err.println("Response headers: " + e.getResponseHeaders()); + e.printStackTrace(); + } + } +} +``` + +### Parameters + +{{^allParams}}This endpoint does not need any parameter.{{/allParams}}{{^vendorExtensions.x-group-parameters}}{{#allParams}}{{#-last}} +| Name | Type | Description | Notes | +|------------- | ------------- | ------------- | -------------|{{/-last}}{{/allParams}} +{{#allParams}}| **{{paramName}}** | {{#isPrimitiveType}}**{{dataType}}**{{/isPrimitiveType}}{{^isPrimitiveType}}{{#isFile}}**{{dataType}}**{{/isFile}}{{^isFile}}[**{{dataType}}**]({{baseType}}.md){{/isFile}}{{/isPrimitiveType}}| {{description}} |{{^required}} [optional]{{/required}}{{^isContainer}}{{#defaultValue}} [default to {{.}}]{{/defaultValue}}{{/isContainer}}{{#allowableValues}} [enum: {{#values}}{{{.}}}{{^-last}}, {{/-last}}{{/values}}]{{/allowableValues}} | +{{/allParams}} +{{/vendorExtensions.x-group-parameters}} +{{#vendorExtensions.x-group-parameters}} +{{#hasParams}} +| Name | Type | Description | Notes | +|------------- | ------------- | ------------- | -------------| +| {{operationId}}Request | [**API{{operationId}}Request**]({{classname}}.md#API{{operationId}}Request)|-|-|{{/hasParams}} +{{/vendorExtensions.x-group-parameters}} + +### Return type + +{{#returnType}}{{#asyncNative}}CompletableFuture<{{/asyncNative}}{{#returnTypeIsPrimitive}}**{{returnType}}**{{/returnTypeIsPrimitive}}{{^returnTypeIsPrimitive}}[**{{returnType}}**]({{returnBaseType}}.md){{/returnTypeIsPrimitive}}{{#asyncNative}}>{{/asyncNative}}{{/returnType}} +{{^returnType}}{{#asyncNative}}CompletableFuture{{/asyncNative}}{{^asyncNative}}null{{/asyncNative}} (empty response body){{/returnType}} + +### Authorization + +{{^authMethods}}No authorization required{{/authMethods}}{{#authMethods}}[{{name}}](../README.md#{{name}}){{^-last}}, {{/-last}}{{/authMethods}} + +### HTTP request headers + +- **Content-Type**: {{#consumes}}{{{mediaType}}}{{^-last}}, {{/-last}}{{/consumes}}{{^consumes}}Not defined{{/consumes}} +- **Accept**: {{#produces}}{{{mediaType}}}{{^-last}}, {{/-last}}{{/produces}}{{^produces}}Not defined{{/produces}} + +{{#responses.0}} +### HTTP response details +| Status code | Description | Response headers | +|-------------|-------------|------------------| +{{#responses}} +| **{{code}}** | {{message}} | {{#headers}} * {{baseName}} - {{description}}
{{/headers}}{{^headers.0}} - {{/headers.0}} | +{{/responses}} +{{/responses.0}} + +## {{operationId}}WithHttpInfo + +{{^vendorExtensions.x-group-parameters}} +> {{#asyncNative}}CompletableFuture<{{/asyncNative}}ApiResponse<{{{returnType}}}{{^returnType}}Void{{/returnType}}>{{#asyncNative}}>{{/asyncNative}} {{operationId}} {{operationId}}WithHttpInfo({{#allParams}}{{{paramName}}}{{^-last}}, {{/-last}}{{/allParams}}) +{{/vendorExtensions.x-group-parameters}} +{{#vendorExtensions.x-group-parameters}} +> {{#asyncNative}}CompletableFuture<{{/asyncNative}}ApiResponse<{{{returnType}}}{{^returnType}}Void{{/returnType}}>{{#asyncNative}}>{{/asyncNative}} {{operationId}} {{operationId}}WithHttpInfo({{#hasParams}}{{operationId}}Request{{/hasParams}}) +{{/vendorExtensions.x-group-parameters}} + +{{summary}}{{#notes}} + +{{.}}{{/notes}} + +### Example + +```java +// Import classes: +import {{{invokerPackage}}}.ApiClient; +import {{{invokerPackage}}}.ApiException; +import {{{invokerPackage}}}.ApiResponse; +import {{{invokerPackage}}}.Configuration;{{#hasAuthMethods}} +import {{{invokerPackage}}}.auth.*;{{/hasAuthMethods}} +import {{{invokerPackage}}}.models.*; +import {{{package}}}.{{{classname}}};{{#vendorExtensions.x-group-parameters}} +import {{{package}}}.{{{classname}}}.*;{{/vendorExtensions.x-group-parameters}} +{{#asyncNative}} +import java.util.concurrent.CompletableFuture; +{{/asyncNative}} + +public class Example { + public static void main(String[] args) { + ApiClient defaultClient = Configuration.getDefaultApiClient(); + defaultClient.setBasePath("{{{basePath}}}"); + {{#hasAuthMethods}} + {{#authMethods}}{{#isBasic}}{{#isBasicBasic}} + // Configure HTTP basic authorization: {{{name}}} + HttpBasicAuth {{{name}}} = (HttpBasicAuth) defaultClient.getAuthentication("{{{name}}}"); + {{{name}}}.setUsername("YOUR USERNAME"); + {{{name}}}.setPassword("YOUR PASSWORD");{{/isBasicBasic}}{{#isBasicBearer}} + // Configure HTTP bearer authorization: {{{name}}} + HttpBearerAuth {{{name}}} = (HttpBearerAuth) defaultClient.getAuthentication("{{{name}}}"); + {{{name}}}.setBearerToken("BEARER TOKEN");{{/isBasicBearer}}{{/isBasic}}{{#isApiKey}} + // Configure API key authorization: {{{name}}} + ApiKeyAuth {{{name}}} = (ApiKeyAuth) defaultClient.getAuthentication("{{{name}}}"); + {{{name}}}.setApiKey("YOUR API KEY"); + // Uncomment the following line to set a prefix for the API key, e.g. "Token" (defaults to null) + //{{{name}}}.setApiKeyPrefix("Token");{{/isApiKey}}{{#isOAuth}} + // Configure OAuth2 access token for authorization: {{{name}}} + OAuth {{{name}}} = (OAuth) defaultClient.getAuthentication("{{{name}}}"); + {{{name}}}.setAccessToken("YOUR ACCESS TOKEN");{{/isOAuth}} + {{/authMethods}} + {{/hasAuthMethods}} + + {{{classname}}} apiInstance = new {{{classname}}}(defaultClient); + {{#allParams}} + {{{dataType}}} {{{paramName}}} = {{{example}}}; // {{{dataType}}} | {{{description}}} + {{/allParams}} + try { + {{^vendorExtensions.x-group-parameters}} + {{#asyncNative}}CompletableFuture<{{/asyncNative}}ApiResponse<{{{returnType}}}{{^returnType}}Void{{/returnType}}>{{#asyncNative}}>{{/asyncNative}} response = apiInstance.{{{operationId}}}WithHttpInfo({{#allParams}}{{{paramName}}}{{^-last}}, {{/-last}}{{/allParams}}); + {{/vendorExtensions.x-group-parameters}} + {{#vendorExtensions.x-group-parameters}} + {{#hasParams}} + API{{operationId}}Request request = API{{operationId}}Request.newBuilder(){{#allParams}} + .{{paramName}}({{paramName}}){{/allParams}} + .build(); + {{/hasParams}} + {{#asyncNative}}CompletableFuture<{{/asyncNative}}ApiResponse<{{{returnType}}}{{^returnType}}Void{{/returnType}}>{{#asyncNative}}>{{/asyncNative}} response = apiInstance.{{{operationId}}}WithHttpInfo({{#hasParams}}request{{/hasParams}}); + {{/vendorExtensions.x-group-parameters}} + System.out.println("Status code: " + response{{#asyncNative}}.get(){{/asyncNative}}.getStatusCode()); + System.out.println("Response headers: " + response{{#asyncNative}}.get(){{/asyncNative}}.getHeaders()); + {{#returnType}} + System.out.println("Response body: " + response{{#asyncNative}}.get(){{/asyncNative}}.getData()); + {{/returnType}} + {{#asyncNative}} + } catch (InterruptedException | ExecutionException e) { + ApiException apiException = (ApiException)e.getCause(); + System.err.println("Exception when calling {{{classname}}}#{{{operationId}}}"); + System.err.println("Status code: " + apiException.getCode()); + System.err.println("Response headers: " + apiException.getResponseHeaders()); + System.err.println("Reason: " + apiException.getResponseBody()); + e.printStackTrace(); + {{/asyncNative}} + } catch (ApiException e) { + System.err.println("Exception when calling {{{classname}}}#{{{operationId}}}"); + System.err.println("Status code: " + e.getCode()); + System.err.println("Response headers: " + e.getResponseHeaders()); + System.err.println("Reason: " + e.getResponseBody()); + e.printStackTrace(); + } + } +} +``` + +### Parameters + +{{^allParams}}This endpoint does not need any parameter.{{/allParams}}{{^vendorExtensions.x-group-parameters}}{{#allParams}}{{#-last}} +| Name | Type | Description | Notes | +|------------- | ------------- | ------------- | -------------|{{/-last}}{{/allParams}} +{{#allParams}}| **{{paramName}}** | {{#isPrimitiveType}}**{{dataType}}**{{/isPrimitiveType}}{{^isPrimitiveType}}{{#isFile}}**{{dataType}}**{{/isFile}}{{^isFile}}[**{{dataType}}**]({{baseType}}.md){{/isFile}}{{/isPrimitiveType}}| {{description}} |{{^required}} [optional]{{/required}}{{^isContainer}}{{#defaultValue}} [default to {{.}}]{{/defaultValue}}{{/isContainer}}{{#allowableValues}} [enum: {{#values}}{{{.}}}{{^-last}}, {{/-last}}{{/values}}]{{/allowableValues}} | +{{/allParams}} +{{/vendorExtensions.x-group-parameters}} +{{#vendorExtensions.x-group-parameters}} +{{#hasParams}} +| Name | Type | Description | Notes | +|------------- | ------------- | ------------- | -------------| +| {{operationId}}Request | [**API{{operationId}}Request**]({{classname}}.md#API{{operationId}}Request)|-|-|{{/hasParams}} +{{/vendorExtensions.x-group-parameters}} + +### Return type + +{{#returnType}}{{#asyncNative}}CompletableFuture<{{/asyncNative}}ApiResponse<{{#returnTypeIsPrimitive}}**{{returnType}}**{{/returnTypeIsPrimitive}}{{^returnTypeIsPrimitive}}[**{{returnType}}**]({{returnBaseType}}.md){{/returnTypeIsPrimitive}}>{{#asyncNative}}>{{/asyncNative}}{{/returnType}} +{{^returnType}}{{#asyncNative}}CompletableFuture<{{/asyncNative}}ApiResponse{{#asyncNative}}>{{/asyncNative}}{{/returnType}} + +### Authorization + +{{^authMethods}}No authorization required{{/authMethods}}{{#authMethods}}[{{name}}](../README.md#{{name}}){{^-last}}, {{/-last}}{{/authMethods}} + +### HTTP request headers + +- **Content-Type**: {{#consumes}}{{{mediaType}}}{{^-last}}, {{/-last}}{{/consumes}}{{^consumes}}Not defined{{/consumes}} +- **Accept**: {{#produces}}{{{mediaType}}}{{^-last}}, {{/-last}}{{/produces}}{{^produces}}Not defined{{/produces}} + +{{#responses.0}} +### HTTP response details +| Status code | Description | Response headers | +|-------------|-------------|------------------| +{{#responses}} +| **{{code}}** | {{message}} | {{#headers}} * {{baseName}} - {{description}}
{{/headers}}{{^headers.0}} - {{/headers.0}} | +{{/responses}} +{{/responses.0}} +{{#vendorExtensions.x-group-parameters}}{{#hasParams}} + + +## API{{operationId}}Request +### Properties +{{#allParams}}{{#-last}} +| Name | Type | Description | Notes | +| ------------- | ------------- | ------------- | -------------|{{/-last}}{{/allParams}} +{{#allParams}}| **{{paramName}}** | {{#isPrimitiveType}}**{{dataType}}**{{/isPrimitiveType}}{{^isPrimitiveType}}{{#isFile}}**{{dataType}}**{{/isFile}}{{^isFile}}[**{{dataType}}**]({{baseType}}.md){{/isFile}}{{/isPrimitiveType}} | {{description}} |{{^required}} [optional]{{/required}}{{^isContainer}}{{#defaultValue}} [default to {{.}}]{{/defaultValue}}{{/isContainer}}{{#allowableValues}} [enum: {{#values}}{{{.}}}{{^-last}}, {{/-last}}{{/values}}]{{/allowableValues}} | +{{/allParams}} + +{{/hasParams}}{{/vendorExtensions.x-group-parameters}} +{{/operation}} +{{/operations}} diff --git a/config/clients/java/template/Java/libraries/native/api_test.mustache b/config/clients/java/template/Java/libraries/native/api_test.mustache new file mode 100644 index 00000000..dc56ce78 --- /dev/null +++ b/config/clients/java/template/Java/libraries/native/api_test.mustache @@ -0,0 +1,58 @@ +{{>licenseInfo}} + +package {{package}}; + +import {{invokerPackage}}.ApiException; +{{#imports}}import {{import}}; +{{/imports}} +import org.junit.Test; +import org.junit.Ignore; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +{{#asyncNative}} +import java.util.concurrent.CompletableFuture; +{{/asyncNative}} + +/** + * API tests for {{classname}} + */ +@Ignore +public class {{classname}}Test { + + private final {{classname}} api = new {{classname}}(); + + {{#operations}}{{#operation}} + /** + * {{summary}} + * + * {{notes}} + * + * @throws ApiException + * if the Api call fails + */ + @Test + public void {{operationId}}Test() throws ApiException { + {{#allParams}} + {{{dataType}}} {{paramName}} = null; + {{/allParams}} + {{^vendorExtensions.x-group-parameters}} + {{#returnType}}{{#asyncNative}}CompletableFuture<{{{returnType}}}>{{/asyncNative}}{{^asyncNative}}{{{returnType}}}{{/asyncNative}} response = {{/returnType}} + {{^returnType}}{{#asyncNative}}CompletableFuture response = {{/asyncNative}}{{/returnType}}api.{{operationId}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}); + {{/vendorExtensions.x-group-parameters}} + {{#vendorExtensions.x-group-parameters}}{{#hasParams}} + {{classname}}.API{{operationId}}Request request = {{classname}}.API{{operationId}}Request.newBuilder(){{#allParams}} + .{{paramName}}({{paramName}}){{/allParams}} + .build();{{/hasParams}} + {{#returnType}}{{#asyncNative}}CompletableFuture<{{{returnType}}}>{{/asyncNative}}{{^asyncNative}}{{{returnType}}}{{/asyncNative}} response = {{/returnType}} + {{^returnType}}{{#asyncNative}}CompletableFuture response = {{/asyncNative}}{{/returnType}}api.{{operationId}}({{#hasParams}}request{{/hasParams}}); + {{/vendorExtensions.x-group-parameters}} + + // TODO: test validations + } + {{/operation}}{{/operations}} +} diff --git a/config/clients/java/template/Java/libraries/native/generatedAnnotation.mustache b/config/clients/java/template/Java/libraries/native/generatedAnnotation.mustache new file mode 100644 index 00000000..f408f319 --- /dev/null +++ b/config/clients/java/template/Java/libraries/native/generatedAnnotation.mustache @@ -0,0 +1 @@ +@{{javaxPackage}}.annotation.Generated(value = "{{generatorClass}}"{{^hideGenerationTimestamp}}, date = "{{generatedDate}}"{{/hideGenerationTimestamp}}) \ No newline at end of file diff --git a/config/clients/java/template/Java/libraries/native/gradle.properties.mustache b/config/clients/java/template/Java/libraries/native/gradle.properties.mustache new file mode 100644 index 00000000..e69de29b diff --git a/config/clients/java/template/Java/libraries/native/model.mustache b/config/clients/java/template/Java/libraries/native/model.mustache new file mode 100644 index 00000000..40d8a5a4 --- /dev/null +++ b/config/clients/java/template/Java/libraries/native/model.mustache @@ -0,0 +1,68 @@ +{{>licenseInfo}} + +package {{package}}; + +{{#useReflectionEqualsHashCode}} +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +{{/useReflectionEqualsHashCode}} +{{#models}} +{{#model}} +{{#additionalPropertiesType}} +import java.util.Map; +import java.util.HashMap; +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +{{/additionalPropertiesType}} +{{/model}} +{{/models}} +{{#supportUrlQuery}} +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.StringJoiner; +{{/supportUrlQuery}} +import java.util.Objects; +import java.util.Arrays; +import java.util.Map; +import java.util.HashMap; +{{#imports}} +import {{import}}; +{{/imports}} +{{#serializableModel}} +import java.io.Serializable; +{{/serializableModel}} +{{#jackson}} +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +{{#withXml}} +import com.fasterxml.jackson.dataformat.xml.annotation.*; +{{/withXml}} +{{#vendorExtensions.x-has-readonly-properties}} +import com.fasterxml.jackson.annotation.JsonCreator; +{{/vendorExtensions.x-has-readonly-properties}} +{{/jackson}} +{{#withXml}} +import {{javaxPackage}}.xml.bind.annotation.*; +{{/withXml}} +{{#parcelableModel}} +import android.os.Parcelable; +import android.os.Parcel; +{{/parcelableModel}} +{{#useBeanValidation}} +import {{javaxPackage}}.validation.constraints.*; +import {{javaxPackage}}.validation.Valid; +{{/useBeanValidation}} +{{#performBeanValidation}} +import org.hibernate.validator.constraints.*; +{{/performBeanValidation}} + +{{#models}} +{{#model}} +{{#oneOf}} +{{#-first}} +import com.fasterxml.jackson.core.type.TypeReference; +{{/-first}} +{{/oneOf}} + +{{#isEnum}}{{>modelEnum}}{{/isEnum}}{{^isEnum}}{{#oneOf}}{{#-first}}{{>oneof_model}}{{/-first}}{{/oneOf}}{{^oneOf}}{{#anyOf}}{{#-first}}{{>anyof_model}}{{/-first}}{{/anyOf}}{{^anyOf}}{{>pojo}}{{/anyOf}}{{/oneOf}}{{/isEnum}} +{{/model}} +{{/models}} diff --git a/config/clients/java/template/Java/libraries/native/modelEnum.mustache b/config/clients/java/template/Java/libraries/native/modelEnum.mustache new file mode 100644 index 00000000..b4cd2f41 --- /dev/null +++ b/config/clients/java/template/Java/libraries/native/modelEnum.mustache @@ -0,0 +1,117 @@ +{{#jackson}} +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +{{/jackson}} +{{#gson}} +import java.io.IOException; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +{{/gson}} + +/** + * {{description}}{{^description}}Gets or Sets {{{name}}}{{/description}} + */ +{{#gson}} +@JsonAdapter({{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}.Adapter.class) +{{/gson}} +{{#jsonb}} +@JsonbTypeSerializer({{datatypeWithEnum}}.Serializer.class) +@JsonbTypeDeserializer({{datatypeWithEnum}}.Deserializer.class) +{{/jsonb}} +{{>additionalEnumTypeAnnotations}}public enum {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} { + {{#allowableValues}}{{#enumVars}} + {{#enumDescription}} + /** + * {{.}} + */ + {{/enumDescription}} + {{#withXml}} + @XmlEnumValue({{#isInteger}}"{{/isInteger}}{{#isDouble}}"{{/isDouble}}{{#isLong}}"{{/isLong}}{{#isFloat}}"{{/isFloat}}{{{value}}}{{#isInteger}}"{{/isInteger}}{{#isDouble}}"{{/isDouble}}{{#isLong}}"{{/isLong}}{{#isFloat}}"{{/isFloat}}) + {{/withXml}} + {{{name}}}({{{value}}}){{^-last}}, + {{/-last}}{{#-last}};{{/-last}}{{/enumVars}}{{/allowableValues}} + + private {{{dataType}}} value; + + {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}({{{dataType}}} value) { + this.value = value; + } + +{{#jackson}} + @JsonValue +{{/jackson}} + public {{{dataType}}} getValue() { + return value; + } + + @Override + public String toString() { + return String.valueOf(value); + } + +{{#jackson}} + @JsonCreator +{{/jackson}} + public static {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} fromValue({{{dataType}}} value) { + for ({{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} b : {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}.values()) { + if (b.value.equals(value)) { + return b; + } + } + {{#isNullable}}return null;{{/isNullable}}{{^isNullable}}{{#enumUnknownDefaultCase}}{{#allowableValues}}{{#enumVars}}{{#-last}}return {{{name}}};{{/-last}}{{/enumVars}}{{/allowableValues}}{{/enumUnknownDefaultCase}}{{^enumUnknownDefaultCase}}throw new IllegalArgumentException("Unexpected value '" + value + "'");{{/enumUnknownDefaultCase}}{{/isNullable}} + } +{{#supportUrlQuery}} + + /** + * Convert the instance into URL query string. + * + * @param prefix prefix of the query string + * @return URL query string + */ + public String toUrlQueryString(String prefix) { + if (prefix == null) { + prefix = ""; + } + + return String.format("%s=%s", prefix, this.toString()); + } +{{/supportUrlQuery}} + +{{#gson}} + + public static class Adapter extends TypeAdapter<{{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}> { + @Override + public void write(final JsonWriter jsonWriter, final {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} enumeration) throws IOException { + jsonWriter.value(enumeration.getValue()); + } + + @Override + public {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} read(final JsonReader jsonReader) throws IOException { + {{^isNumber}}{{{dataType}}}{{/isNumber}}{{#isNumber}}String{{/isNumber}} value = jsonReader.{{#isNumber}}nextString(){{/isNumber}}{{#isInteger}}nextInt(){{/isInteger}}{{^isNumber}}{{^isInteger}}next{{{dataType}}}(){{/isInteger}}{{/isNumber}}; + return {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}.fromValue({{#isNumber}}new BigDecimal({{/isNumber}}value{{#isNumber}}){{/isNumber}}); + } + } +{{/gson}} +{{#jsonb}} + public static final class Deserializer implements JsonbDeserializer<{{datatypeWithEnum}}> { + @Override + public {{datatypeWithEnum}} deserialize(JsonParser parser, DeserializationContext ctx, Type rtType) { + for ({{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} b : {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}.values()) { + if (String.valueOf(b.value).equals(parser.getString())) { + return b; + } + } + {{#useNullForUnknownEnumValue}}return null;{{/useNullForUnknownEnumValue}}{{^useNullForUnknownEnumValue}}throw new IllegalArgumentException("Unexpected value '" + parser.getString() + "'");{{/useNullForUnknownEnumValue}} + } + } + + public static final class Serializer implements JsonbSerializer<{{datatypeWithEnum}}> { + @Override + public void serialize({{datatypeWithEnum}} obj, JsonGenerator generator, SerializationContext ctx) { + generator.write(obj.value); + } + } +{{/jsonb}} +} diff --git a/config/clients/java/template/Java/libraries/native/model_anyof_doc.mustache b/config/clients/java/template/Java/libraries/native/model_anyof_doc.mustache new file mode 100644 index 00000000..e360aa56 --- /dev/null +++ b/config/clients/java/template/Java/libraries/native/model_anyof_doc.mustache @@ -0,0 +1,38 @@ +# {{classname}} + +{{#description}} +{{&description}} + +{{/description}} +## anyOf schemas +{{#anyOf}} +* [{{{.}}}]({{{.}}}.md) +{{/anyOf}} + +{{#isNullable}} +NOTE: this class is nullable. + +{{/isNullable}} +## Example +```java +// Import classes: +import {{{package}}}.{{{classname}}}; +{{#anyOf}} +import {{{package}}}.{{{.}}}; +{{/anyOf}} + +public class Example { + public static void main(String[] args) { + {{classname}} example{{classname}} = new {{classname}}(); + {{#anyOf}} + + // create a new {{{.}}} + {{{.}}} example{{{.}}} = new {{{.}}}(); + // set {{{classname}}} to {{{.}}} + example{{classname}}.setActualInstance(example{{{.}}}); + // to get back the {{{.}}} set earlier + {{{.}}} test{{{.}}} = ({{{.}}}) example{{classname}}.getActualInstance(); + {{/anyOf}} + } +} +``` diff --git a/config/clients/java/template/Java/libraries/native/model_doc.mustache b/config/clients/java/template/Java/libraries/native/model_doc.mustache new file mode 100644 index 00000000..be1aedcf --- /dev/null +++ b/config/clients/java/template/Java/libraries/native/model_doc.mustache @@ -0,0 +1,19 @@ +{{#models}}{{#model}} + +{{#isEnum}} +{{>enum_outer_doc}} +{{/isEnum}} +{{^isEnum}} +{{^oneOf.isEmpty}} +{{>model_oneof_doc}} +{{/oneOf.isEmpty}} +{{^anyOf.isEmpty}} +{{>model_anyof_doc}} +{{/anyOf.isEmpty}} +{{^anyOf}} +{{^oneOf}} +{{>pojo_doc}} +{{/oneOf}} +{{/anyOf}} +{{/isEnum}} +{{/model}}{{/models}} diff --git a/config/clients/java/template/Java/libraries/native/model_oneof_doc.mustache b/config/clients/java/template/Java/libraries/native/model_oneof_doc.mustache new file mode 100644 index 00000000..5fff76c9 --- /dev/null +++ b/config/clients/java/template/Java/libraries/native/model_oneof_doc.mustache @@ -0,0 +1,38 @@ +# {{classname}} + +{{#description}} +{{&description}} + +{{/description}} +## oneOf schemas +{{#oneOf}} +* [{{{.}}}]({{{.}}}.md) +{{/oneOf}} + +{{#isNullable}} +NOTE: this class is nullable. + +{{/isNullable}} +## Example +```java +// Import classes: +import {{{package}}}.{{{classname}}}; +{{#oneOf}} +import {{{package}}}.{{{.}}}; +{{/oneOf}} + +public class Example { + public static void main(String[] args) { + {{classname}} example{{classname}} = new {{classname}}(); + {{#oneOf}} + + // create a new {{{.}}} + {{{.}}} example{{{.}}} = new {{{.}}}(); + // set {{{classname}}} to {{{.}}} + example{{classname}}.setActualInstance(example{{{.}}}); + // to get back the {{{.}}} set earlier + {{{.}}} test{{{.}}} = ({{{.}}}) example{{classname}}.getActualInstance(); + {{/oneOf}} + } +} +``` diff --git a/config/clients/java/template/Java/libraries/native/oneof_model.mustache b/config/clients/java/template/Java/libraries/native/oneof_model.mustache new file mode 100644 index 00000000..8000994f --- /dev/null +++ b/config/clients/java/template/Java/libraries/native/oneof_model.mustache @@ -0,0 +1,393 @@ +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import {{invokerPackage}}.JSON; + +{{>additionalModelTypeAnnotations}}{{>generatedAnnotation}}{{>xmlAnnotation}} +@JsonDeserialize(using = {{classname}}.{{classname}}Deserializer.class) +@JsonSerialize(using = {{classname}}.{{classname}}Serializer.class) +public class {{classname}} extends AbstractOpenApiSchema{{#vendorExtensions.x-implements}}, {{{.}}}{{/vendorExtensions.x-implements}} { + private static final Logger log = Logger.getLogger({{classname}}.class.getName()); + + public static class {{classname}}Serializer extends StdSerializer<{{classname}}> { + public {{classname}}Serializer(Class<{{classname}}> t) { + super(t); + } + + public {{classname}}Serializer() { + this(null); + } + + @Override + public void serialize({{classname}} value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException { + jgen.writeObject(value.getActualInstance()); + } + } + + public static class {{classname}}Deserializer extends StdDeserializer<{{classname}}> { + public {{classname}}Deserializer() { + this({{classname}}.class); + } + + public {{classname}}Deserializer(Class vc) { + super(vc); + } + + @Override + public {{classname}} deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { + JsonNode tree = jp.readValueAsTree(); + Object deserialized = null; + {{#useOneOfDiscriminatorLookup}} + {{#discriminator}} + {{classname}} new{{classname}} = new {{classname}}(); + Map result2 = tree.traverse(jp.getCodec()).readValueAs(new TypeReference>() {}); + String discriminatorValue = (String)result2.get("{{{propertyBaseName}}}"); + switch (discriminatorValue) { + {{#mappedModels}} + case "{{{mappingName}}}": + deserialized = tree.traverse(jp.getCodec()).readValueAs({{{modelName}}}.class); + new{{classname}}.setActualInstance(deserialized); + return new{{classname}}; + {{/mappedModels}} + default: + log.log(Level.WARNING, String.format("Failed to lookup discriminator value `%s` for {{classname}}. Possible values:{{#mappedModels}} {{{mappingName}}}{{/mappedModels}}", discriminatorValue)); + } + + {{/discriminator}} + {{/useOneOfDiscriminatorLookup}} + boolean typeCoercion = ctxt.isEnabled(MapperFeature.ALLOW_COERCION_OF_SCALARS); + int match = 0; + JsonToken token = tree.traverse(jp.getCodec()).nextToken(); + {{#oneOf}} + // deserialize {{{.}}} + try { + boolean attemptParsing = true; + // ensure that we respect type coercion as set on the client ObjectMapper + if ({{{.}}}.class.equals(Integer.class) || {{{.}}}.class.equals(Long.class) || {{{.}}}.class.equals(Float.class) || {{{.}}}.class.equals(Double.class) || {{{.}}}.class.equals(Boolean.class) || {{{.}}}.class.equals(String.class)) { + attemptParsing = typeCoercion; + if (!attemptParsing) { + attemptParsing |= (({{{.}}}.class.equals(Integer.class) || {{{.}}}.class.equals(Long.class)) && token == JsonToken.VALUE_NUMBER_INT); + attemptParsing |= (({{{.}}}.class.equals(Float.class) || {{{.}}}.class.equals(Double.class)) && token == JsonToken.VALUE_NUMBER_FLOAT); + attemptParsing |= ({{{.}}}.class.equals(Boolean.class) && (token == JsonToken.VALUE_FALSE || token == JsonToken.VALUE_TRUE)); + attemptParsing |= ({{{.}}}.class.equals(String.class) && token == JsonToken.VALUE_STRING); + {{#isNullable}} + attemptParsing |= (token == JsonToken.VALUE_NULL); + {{/isNullable}} + } + } + if (attemptParsing) { + deserialized = tree.traverse(jp.getCodec()).readValueAs({{{.}}}.class); + // TODO: there is no validation against JSON schema constraints + // (min, max, enum, pattern...), this does not perform a strict JSON + // validation, which means the 'match' count may be higher than it should be. + match++; + log.log(Level.FINER, "Input data matches schema '{{{.}}}'"); + } + } catch (Exception e) { + // deserialization failed, continue + log.log(Level.FINER, "Input data does not match schema '{{{.}}}'", e); + } + + {{/oneOf}} + if (match == 1) { + {{classname}} ret = new {{classname}}(); + ret.setActualInstance(deserialized); + return ret; + } + throw new IOException(String.format("Failed deserialization for {{classname}}: %d classes match result, expected 1", match)); + } + + /** + * Handle deserialization of the 'null' value. + */ + @Override + public {{classname}} getNullValue(DeserializationContext ctxt) throws JsonMappingException { + {{#isNullable}} + return null; + {{/isNullable}} + {{^isNullable}} + throw new JsonMappingException(ctxt.getParser(), "{{classname}} cannot be null"); + {{/isNullable}} + } + } + + // store a list of schema names defined in oneOf + public static final Map> schemas = new HashMap<>(); + + public {{classname}}() { + super("oneOf", {{#isNullable}}Boolean.TRUE{{/isNullable}}{{^isNullable}}Boolean.FALSE{{/isNullable}}); + } +{{> libraries/native/additional_properties }} + {{#additionalPropertiesType}} + /** + * Return true if this {{name}} object is equal to o. + */ + @Override + public boolean equals(Object o) { + return super.equals(o) && Objects.equals(this.additionalProperties, (({{classname}})o).additionalProperties); + } + + @Override + public int hashCode() { + return Objects.hash(getActualInstance(), isNullable(), getSchemaType(), additionalProperties); + } + {{/additionalPropertiesType}} + {{#oneOf}} + public {{classname}}({{{.}}} o) { + super("oneOf", {{#isNullable}}Boolean.TRUE{{/isNullable}}{{^isNullable}}Boolean.FALSE{{/isNullable}}); + setActualInstance(o); + } + + {{/oneOf}} + static { + {{#oneOf}} + schemas.put("{{{.}}}", {{{.}}}.class); + {{/oneOf}} + JSON.registerDescendants({{classname}}.class, Collections.unmodifiableMap(schemas)); + {{#discriminator}} + // Initialize and register the discriminator mappings. + Map> mappings = new HashMap>(); + {{#mappedModels}} + mappings.put("{{mappingName}}", {{modelName}}.class); + {{/mappedModels}} + mappings.put("{{name}}", {{classname}}.class); + JSON.registerDiscriminator({{classname}}.class, "{{propertyBaseName}}", mappings); + {{/discriminator}} + } + + @Override + public Map> getSchemas() { + return {{classname}}.schemas; + } + + /** + * Set the instance that matches the oneOf child schema, check + * the instance parameter is valid against the oneOf child schemas: + * {{#oneOf}}{{{.}}}{{^-last}}, {{/-last}}{{/oneOf}} + * + * It could be an instance of the 'oneOf' schemas. + * The oneOf child schemas may themselves be a composed schema (allOf, anyOf, oneOf). + */ + @Override + public void setActualInstance(Object instance) { + {{#isNullable}} + if (instance == null) { + super.setActualInstance(instance); + return; + } + + {{/isNullable}} + {{#oneOf}} + if (JSON.isInstanceOf({{{.}}}.class, instance, new HashSet>())) { + super.setActualInstance(instance); + return; + } + + {{/oneOf}} + throw new RuntimeException("Invalid instance type. Must be {{#oneOf}}{{{.}}}{{^-last}}, {{/-last}}{{/oneOf}}"); + } + + /** + * Get the actual instance, which can be the following: + * {{#oneOf}}{{{.}}}{{^-last}}, {{/-last}}{{/oneOf}} + * + * @return The actual instance ({{#oneOf}}{{{.}}}{{^-last}}, {{/-last}}{{/oneOf}}) + */ + @Override + public Object getActualInstance() { + return super.getActualInstance(); + } + + {{#oneOf}} + /** + * Get the actual instance of `{{{.}}}`. If the actual instance is not `{{{.}}}`, + * the ClassCastException will be thrown. + * + * @return The actual instance of `{{{.}}}` + * @throws ClassCastException if the instance is not `{{{.}}}` + */ + public {{{.}}} get{{{.}}}() throws ClassCastException { + return ({{{.}}})super.getActualInstance(); + } + + {{/oneOf}} + +{{#supportUrlQuery}} + + /** + * Convert the instance into URL query string. + * + * @return URL query string + */ + public String toUrlQueryString() { + return toUrlQueryString(null); + } + + /** + * Convert the instance into URL query string. + * + * @param prefix prefix of the query string + * @return URL query string + */ + public String toUrlQueryString(String prefix) { + String suffix = ""; + String containerSuffix = ""; + String containerPrefix = ""; + if (prefix == null) { + // style=form, explode=true, e.g. /pet?name=cat&type=manx + prefix = ""; + } else { + // deepObject style e.g. /pet?id[name]=cat&id[type]=manx + prefix = prefix + "["; + suffix = "]"; + containerSuffix = "]"; + containerPrefix = "["; + } + + StringJoiner joiner = new StringJoiner("&"); + + {{#composedSchemas.oneOf}} + if (getActualInstance() instanceof {{{dataType}}}) { + {{#isArray}} + {{#items.isPrimitiveType}} + {{#uniqueItems}} + if (getActualInstance() != null) { + int i = 0; + for ({{{items.dataType}}} _item : ({{{dataType}}})getActualInstance()) { + joiner.add(String.format("%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, i, containerSuffix), + URLEncoder.encode(String.valueOf(_item), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + i++; + } + {{/uniqueItems}} + {{^uniqueItems}} + if (getActualInstance() != null) { + for (int i = 0; i < (({{{dataType}}})getActualInstance()).size(); i++) { + joiner.add(String.format("%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, i, containerSuffix), + URLEncoder.encode(String.valueOf(getActualInstance().get(i)), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + } + {{/uniqueItems}} + {{/items.isPrimitiveType}} + {{^items.isPrimitiveType}} + {{#items.isModel}} + {{#uniqueItems}} + if (getActualInstance() != null) { + int i = 0; + for ({{{items.dataType}}} _item : ({{{dataType}}})getActualInstance()) { + if (_item != null) { + joiner.add(_item.toUrlQueryString(String.format("%s{{baseName}}%s%s", prefix, suffix, + "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, i, containerSuffix)))); + } + } + i++; + } + {{/uniqueItems}} + {{^uniqueItems}} + if (getActualInstance() != null) { + for (int i = 0; i < (({{{dataType}}})getActualInstance()).size(); i++) { + if ((({{{dataType}}})getActualInstance()).get(i) != null) { + joiner.add((({{{items.dataType}}})getActualInstance()).get(i).toUrlQueryString(String.format("%s{{baseName}}%s%s", prefix, suffix, + "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, i, containerSuffix)))); + } + } + } + {{/uniqueItems}} + {{/items.isModel}} + {{^items.isModel}} + {{#uniqueItems}} + if (getActualInstance() != null) { + int i = 0; + for ({{{items.dataType}}} _item : ({{{dataType}}})getActualInstance()) { + if (_item != null) { + joiner.add(String.format("%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, i, containerSuffix), + URLEncoder.encode(String.valueOf(_item), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + i++; + } + } + {{/uniqueItems}} + {{^uniqueItems}} + if (getActualInstance() != null) { + for (int i = 0; i < (({{{dataType}}})getActualInstance()).size(); i++) { + if (getActualInstance().get(i) != null) { + joiner.add(String.format("%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, i, containerSuffix), + URLEncoder.encode(String.valueOf((({{{dataType}}})getActualInstance()).get(i)), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + } + } + {{/uniqueItems}} + {{/items.isModel}} + {{/items.isPrimitiveType}} + {{/isArray}} + {{^isArray}} + {{#isMap}} + {{#items.isPrimitiveType}} + if (getActualInstance() != null) { + for (String _key : (({{{dataType}}})getActualInstance()).keySet()) { + joiner.add(String.format("%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, _key, containerSuffix), + getActualInstance().get(_key), URLEncoder.encode(String.valueOf((({{{dataType}}})getActualInstance()).get(_key)), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + } + {{/items.isPrimitiveType}} + {{^items.isPrimitiveType}} + if (getActualInstance() != null) { + for (String _key : (({{{dataType}}})getActualInstance()).keySet()) { + if ((({{{dataType}}})getActualInstance()).get(_key) != null) { + joiner.add((({{{items.dataType}}})getActualInstance()).get(_key).toUrlQueryString(String.format("%s{{baseName}}%s%s", prefix, suffix, + "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, _key, containerSuffix)))); + } + } + } + {{/items.isPrimitiveType}} + {{/isMap}} + {{^isMap}} + {{#isPrimitiveType}} + if (getActualInstance() != null) { + joiner.add(String.format("%s{{{baseName}}}%s=%s", prefix, suffix, URLEncoder.encode(String.valueOf(getActualInstance()), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + {{/isPrimitiveType}} + {{^isPrimitiveType}} + {{#isModel}} + if (getActualInstance() != null) { + joiner.add((({{{dataType}}})getActualInstance()).toUrlQueryString(prefix + "{{{baseName}}}" + suffix)); + } + {{/isModel}} + {{^isModel}} + if (getActualInstance() != null) { + joiner.add(String.format("%s{{{baseName}}}%s=%s", prefix, suffix, URLEncoder.encode(String.valueOf(getActualInstance()), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + {{/isModel}} + {{/isPrimitiveType}} + {{/isMap}} + {{/isArray}} + return joiner.toString(); + } + {{/composedSchemas.oneOf}} + return null; + } +{{/supportUrlQuery}} + +} diff --git a/config/clients/java/template/Java/libraries/native/pojo.mustache b/config/clients/java/template/Java/libraries/native/pojo.mustache new file mode 100644 index 00000000..a0c77d53 --- /dev/null +++ b/config/clients/java/template/Java/libraries/native/pojo.mustache @@ -0,0 +1,596 @@ +{{#discriminator}} +import {{invokerPackage}}.JSON; +{{/discriminator}} +/** + * {{description}}{{^description}}{{classname}}{{/description}}{{#isDeprecated}} + * @deprecated{{/isDeprecated}} + */{{#isDeprecated}} +@Deprecated{{/isDeprecated}} +{{#swagger1AnnotationLibrary}} +{{#description}} +@ApiModel(description = "{{{.}}}") +{{/description}} +{{/swagger1AnnotationLibrary}} +{{#swagger2AnnotationLibrary}} +{{#description}} +@Schema(description = "{{{.}}}") +{{/description}} +{{/swagger2AnnotationLibrary}} +{{#jackson}} +@JsonPropertyOrder({ +{{#vars}} + {{classname}}.JSON_PROPERTY_{{nameInSnakeCase}}{{^-last}},{{/-last}} +{{/vars}} +}) +{{/jackson}} +{{>additionalModelTypeAnnotations}}{{>generatedAnnotation}}{{#discriminator}}{{>typeInfoAnnotation}}{{/discriminator}}{{>xmlAnnotation}} +{{#vendorExtensions.x-class-extra-annotation}} +{{{vendorExtensions.x-class-extra-annotation}}} +{{/vendorExtensions.x-class-extra-annotation}} +public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtensions.x-implements}}{{#-first}}implements {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{#-last}} {{/-last}}{{/vendorExtensions.x-implements}}{ +{{#serializableModel}} + private static final long serialVersionUID = 1L; + +{{/serializableModel}} + {{#vars}} + {{#isEnum}} + {{^isContainer}} + {{^vendorExtensions.x-enum-as-string}} +{{>modelInnerEnum}} + {{/vendorExtensions.x-enum-as-string}} + {{/isContainer}} + {{#isContainer}} + {{#mostInnerItems}} +{{>modelInnerEnum}} + {{/mostInnerItems}} + {{/isContainer}} + {{/isEnum}} + {{#gson}} + public static final String SERIALIZED_NAME_{{nameInSnakeCase}} = "{{baseName}}"; + {{/gson}} + {{#jackson}} + public static final String JSON_PROPERTY_{{nameInSnakeCase}} = "{{baseName}}"; + {{/jackson}} + {{#withXml}} + {{#isXmlAttribute}} + @XmlAttribute(name = "{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}") + {{/isXmlAttribute}} + {{^isXmlAttribute}} + {{^isContainer}} + @XmlElement({{#xmlNamespace}}namespace="{{.}}", {{/xmlNamespace}}name = "{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}") + {{/isContainer}} + {{#isContainer}} + // Is a container wrapped={{isXmlWrapped}} + {{#items}} + // items.name={{name}} items.baseName={{baseName}} items.xmlName={{xmlName}} items.xmlNamespace={{xmlNamespace}} + // items.example={{example}} items.type={{dataType}} + @XmlElement({{#xmlNamespace}}namespace="{{.}}", {{/xmlNamespace}}name = "{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}") + {{/items}} + {{#isXmlWrapped}} + @XmlElementWrapper({{#xmlNamespace}}namespace="{{.}}", {{/xmlNamespace}}name = "{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}") + {{/isXmlWrapped}} + {{/isContainer}} + {{/isXmlAttribute}} + {{/withXml}} + {{#gson}} + @SerializedName(SERIALIZED_NAME_{{nameInSnakeCase}}) + {{/gson}} + {{#vendorExtensions.x-field-extra-annotation}} + {{{vendorExtensions.x-field-extra-annotation}}} + {{/vendorExtensions.x-field-extra-annotation}} + {{#vendorExtensions.x-is-jackson-optional-nullable}} + {{#isContainer}} + private JsonNullable<{{{datatypeWithEnum}}}> {{name}} = JsonNullable.<{{{datatypeWithEnum}}}>undefined(); + {{/isContainer}} + {{^isContainer}} + private JsonNullable<{{{datatypeWithEnum}}}> {{name}} = JsonNullable.<{{{datatypeWithEnum}}}>{{#defaultValue}}of({{{.}}}){{/defaultValue}}{{^defaultValue}}undefined(){{/defaultValue}}; + {{/isContainer}} + {{/vendorExtensions.x-is-jackson-optional-nullable}} + {{^vendorExtensions.x-is-jackson-optional-nullable}} + private {{{datatypeWithEnum}}} {{name}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}; + {{/vendorExtensions.x-is-jackson-optional-nullable}} + + {{/vars}} + public {{classname}}() { {{#parent}}{{#parcelableModel}} + super();{{/parcelableModel}}{{/parent}}{{#gson}}{{#discriminator}} + this.{{{discriminatorName}}} = this.getClass().getSimpleName();{{/discriminator}}{{/gson}} + }{{#vendorExtensions.x-has-readonly-properties}}{{^withXml}} + + {{#jackson}}@JsonCreator{{/jackson}} + public {{classname}}( + {{#readOnlyVars}} + @JsonProperty(JSON_PROPERTY_{{nameInSnakeCase}}) {{{datatypeWithEnum}}} {{name}}{{^-last}}, {{/-last}} + {{/readOnlyVars}} + ) { + this(); + {{#readOnlyVars}} + this.{{name}} = {{#vendorExtensions.x-is-jackson-optional-nullable}}{{name}} == null ? JsonNullable.<{{{datatypeWithEnum}}}>undefined() : JsonNullable.of({{name}}){{/vendorExtensions.x-is-jackson-optional-nullable}}{{^vendorExtensions.x-is-jackson-optional-nullable}}{{name}}{{/vendorExtensions.x-is-jackson-optional-nullable}}; + {{/readOnlyVars}} + }{{/withXml}}{{/vendorExtensions.x-has-readonly-properties}} + {{#vars}} + + {{^isReadOnly}} + {{#vendorExtensions.x-enum-as-string}} + public static final Set {{{nameInSnakeCase}}}_VALUES = new HashSet<>(Arrays.asList( + {{#allowableValues}}{{#enumVars}}{{{value}}}{{^-last}}, {{/-last}}{{/enumVars}}{{/allowableValues}} + )); + + {{/vendorExtensions.x-enum-as-string}} + public {{classname}} {{name}}({{{datatypeWithEnum}}} {{name}}) { + {{#vendorExtensions.x-enum-as-string}} + if (!{{{nameInSnakeCase}}}_VALUES.contains({{name}})) { + throw new IllegalArgumentException({{name}} + " is invalid. Possible values for {{name}}: " + String.join(", ", {{{nameInSnakeCase}}}_VALUES)); + } + + {{/vendorExtensions.x-enum-as-string}} + {{#vendorExtensions.x-is-jackson-optional-nullable}} + this.{{name}} = JsonNullable.<{{{datatypeWithEnum}}}>of({{name}}); + {{/vendorExtensions.x-is-jackson-optional-nullable}} + {{^vendorExtensions.x-is-jackson-optional-nullable}} + this.{{name}} = {{name}}; + {{/vendorExtensions.x-is-jackson-optional-nullable}} + return this; + } + {{#isArray}} + + public {{classname}} add{{nameInCamelCase}}Item({{{items.datatypeWithEnum}}} {{name}}Item) { + {{#vendorExtensions.x-is-jackson-optional-nullable}} + if (this.{{name}} == null || !this.{{name}}.isPresent()) { + this.{{name}} = JsonNullable.<{{{datatypeWithEnum}}}>of({{{defaultValue}}}{{^defaultValue}}new {{#uniqueItems}}LinkedHashSet{{/uniqueItems}}{{^uniqueItems}}ArrayList{{/uniqueItems}}<>(){{/defaultValue}}); + } + try { + this.{{name}}.get().add({{name}}Item); + } catch (java.util.NoSuchElementException e) { + // this can never happen, as we make sure above that the value is present + } + return this; + {{/vendorExtensions.x-is-jackson-optional-nullable}} + {{^vendorExtensions.x-is-jackson-optional-nullable}} + if (this.{{name}} == null) { + this.{{name}} = {{{defaultValue}}}{{^defaultValue}}new {{#uniqueItems}}LinkedHashSet{{/uniqueItems}}{{^uniqueItems}}ArrayList{{/uniqueItems}}<>(){{/defaultValue}}; + } + this.{{name}}.add({{name}}Item); + return this; + {{/vendorExtensions.x-is-jackson-optional-nullable}} + } + {{/isArray}} + {{#isMap}} + + public {{classname}} put{{nameInCamelCase}}Item(String key, {{{items.datatypeWithEnum}}} {{name}}Item) { + {{#vendorExtensions.x-is-jackson-optional-nullable}} + if (this.{{name}} == null || !this.{{name}}.isPresent()) { + this.{{name}} = JsonNullable.<{{{datatypeWithEnum}}}>of({{{defaultValue}}}{{^defaultValue}}new HashMap<>(){{/defaultValue}}); + } + try { + this.{{name}}.get().put(key, {{name}}Item); + } catch (java.util.NoSuchElementException e) { + // this can never happen, as we make sure above that the value is present + } + return this; + {{/vendorExtensions.x-is-jackson-optional-nullable}} + {{^vendorExtensions.x-is-jackson-optional-nullable}} + if (this.{{name}} == null) { + this.{{name}} = {{{defaultValue}}}{{^defaultValue}}new HashMap<>(){{/defaultValue}}; + } + this.{{name}}.put(key, {{name}}Item); + return this; + {{/vendorExtensions.x-is-jackson-optional-nullable}} + } + {{/isMap}} + + {{/isReadOnly}} + /** + {{#description}} + * {{.}} + {{/description}} + {{^description}} + * Get {{name}} + {{/description}} + {{#minimum}} + * minimum: {{.}} + {{/minimum}} + {{#maximum}} + * maximum: {{.}} + {{/maximum}} + * @return {{name}} + {{#deprecated}} + * @deprecated + {{/deprecated}} + **/ +{{#deprecated}} + @Deprecated +{{/deprecated}} +{{#required}} +{{#isNullable}} + @{{javaxPackage}}.annotation.Nullable +{{/isNullable}} +{{^isNullable}} + @{{javaxPackage}}.annotation.Nonnull +{{/isNullable}} +{{/required}} +{{^required}} + @{{javaxPackage}}.annotation.Nullable +{{/required}} +{{#useBeanValidation}} +{{>beanValidation}} +{{/useBeanValidation}} +{{#swagger1AnnotationLibrary}} + @ApiModelProperty({{#example}}example = "{{{.}}}", {{/example}}{{#required}}required = {{required}}, {{/required}}value = "{{{description}}}") +{{/swagger1AnnotationLibrary}} +{{#swagger2AnnotationLibrary}} + @Schema({{#example}}example = "{{{.}}}", {{/example}}requiredMode = {{#required}}Schema.RequiredMode.REQUIRED{{/required}}{{^required}}Schema.RequiredMode.NOT_REQUIRED{{/required}}, description = "{{{description}}}") +{{/swagger2AnnotationLibrary}} +{{#vendorExtensions.x-extra-annotation}} + {{{vendorExtensions.x-extra-annotation}}} +{{/vendorExtensions.x-extra-annotation}} +{{#vendorExtensions.x-is-jackson-optional-nullable}} + {{!unannotated, Jackson would pick this up automatically and add it *in addition* to the _JsonNullable getter field}} + @JsonIgnore +{{/vendorExtensions.x-is-jackson-optional-nullable}} +{{^vendorExtensions.x-is-jackson-optional-nullable}}{{#jackson}}{{> jackson_annotations}}{{/jackson}}{{/vendorExtensions.x-is-jackson-optional-nullable}} + public {{{datatypeWithEnum}}} {{getter}}() { + {{#vendorExtensions.x-is-jackson-optional-nullable}} + {{#isReadOnly}}{{! A readonly attribute doesn't have setter => jackson will set null directly if explicitly returned by API, so make sure we have an empty JsonNullable}} + if ({{name}} == null) { + {{name}} = JsonNullable.<{{{datatypeWithEnum}}}>{{#defaultValue}}of({{{.}}}){{/defaultValue}}{{^defaultValue}}undefined(){{/defaultValue}}; + } + {{/isReadOnly}} + return {{name}}.orElse(null); + {{/vendorExtensions.x-is-jackson-optional-nullable}} + {{^vendorExtensions.x-is-jackson-optional-nullable}} + return {{name}}; + {{/vendorExtensions.x-is-jackson-optional-nullable}} + } + + {{#vendorExtensions.x-is-jackson-optional-nullable}} +{{> jackson_annotations}} + public JsonNullable<{{{datatypeWithEnum}}}> {{getter}}_JsonNullable() { + return {{name}}; + } + {{/vendorExtensions.x-is-jackson-optional-nullable}}{{#vendorExtensions.x-is-jackson-optional-nullable}} + @JsonProperty(JSON_PROPERTY_{{nameInSnakeCase}}) + {{#isReadOnly}}private{{/isReadOnly}}{{^isReadOnly}}public{{/isReadOnly}} void {{setter}}_JsonNullable(JsonNullable<{{{datatypeWithEnum}}}> {{name}}) { + {{! For getters/setters that have name differing from attribute name, we must include setter (albeit private) for jackson to be able to set the attribute}} + this.{{name}} = {{name}}; + } + {{/vendorExtensions.x-is-jackson-optional-nullable}} + + {{^isReadOnly}} +{{#vendorExtensions.x-setter-extra-annotation}} {{{vendorExtensions.x-setter-extra-annotation}}} +{{/vendorExtensions.x-setter-extra-annotation}}{{#jackson}}{{^vendorExtensions.x-is-jackson-optional-nullable}}{{> jackson_annotations}}{{/vendorExtensions.x-is-jackson-optional-nullable}}{{/jackson}} public void {{setter}}({{{datatypeWithEnum}}} {{name}}) { + {{#vendorExtensions.x-enum-as-string}} + if (!{{{nameInSnakeCase}}}_VALUES.contains({{name}})) { + throw new IllegalArgumentException({{name}} + " is invalid. Possible values for {{name}}: " + String.join(", ", {{{nameInSnakeCase}}}_VALUES)); + } + + {{/vendorExtensions.x-enum-as-string}} + {{#vendorExtensions.x-is-jackson-optional-nullable}} + this.{{name}} = JsonNullable.<{{{datatypeWithEnum}}}>of({{name}}); + {{/vendorExtensions.x-is-jackson-optional-nullable}} + {{^vendorExtensions.x-is-jackson-optional-nullable}} + this.{{name}} = {{name}}; + {{/vendorExtensions.x-is-jackson-optional-nullable}} + } + {{/isReadOnly}} + + {{/vars}} +{{>libraries/native/additional_properties}} + {{#parent}} + {{#allVars}} + {{#isOverridden}} + @Override + public {{classname}} {{name}}({{{datatypeWithEnum}}} {{name}}) { + {{#vendorExtensions.x-is-jackson-optional-nullable}} + this.{{setter}}(JsonNullable.<{{{datatypeWithEnum}}}>of({{name}})); + {{/vendorExtensions.x-is-jackson-optional-nullable}} + {{^vendorExtensions.x-is-jackson-optional-nullable}} + this.{{setter}}({{name}}); + {{/vendorExtensions.x-is-jackson-optional-nullable}} + return this; + } + + {{/isOverridden}} + {{/allVars}} + {{/parent}} + /** + * Return true if this {{name}} object is equal to o. + */ + @Override + public boolean equals(Object o) { + {{#useReflectionEqualsHashCode}} + return EqualsBuilder.reflectionEquals(this, o, false, null, true); + {{/useReflectionEqualsHashCode}} + {{^useReflectionEqualsHashCode}} + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + }{{#hasVars}} + {{classname}} {{classVarName}} = ({{classname}}) o; + return {{#vars}}{{#vendorExtensions.x-is-jackson-optional-nullable}}equalsNullable(this.{{name}}, {{classVarName}}.{{name}}){{/vendorExtensions.x-is-jackson-optional-nullable}}{{^vendorExtensions.x-is-jackson-optional-nullable}}{{#isByteArray}}Arrays{{/isByteArray}}{{^isByteArray}}Objects{{/isByteArray}}.equals(this.{{name}}, {{classVarName}}.{{name}}){{/vendorExtensions.x-is-jackson-optional-nullable}}{{^-last}} && + {{/-last}}{{/vars}}{{#additionalPropertiesType}}&& + Objects.equals(this.additionalProperties, {{classVarName}}.additionalProperties){{/additionalPropertiesType}}{{#parent}} && + super.equals(o){{/parent}};{{/hasVars}}{{^hasVars}} + return {{#parent}}super.equals(o){{/parent}}{{^parent}}true{{/parent}};{{/hasVars}} + {{/useReflectionEqualsHashCode}} + }{{#vendorExtensions.x-jackson-optional-nullable-helpers}} + + private static boolean equalsNullable(JsonNullable a, JsonNullable b) { + return a == b || (a != null && b != null && a.isPresent() && b.isPresent() && Objects.deepEquals(a.get(), b.get())); + }{{/vendorExtensions.x-jackson-optional-nullable-helpers}} + + @Override + public int hashCode() { + {{#useReflectionEqualsHashCode}} + return HashCodeBuilder.reflectionHashCode(this); + {{/useReflectionEqualsHashCode}} + {{^useReflectionEqualsHashCode}} + return Objects.hash({{#vars}}{{#vendorExtensions.x-is-jackson-optional-nullable}}hashCodeNullable({{name}}){{/vendorExtensions.x-is-jackson-optional-nullable}}{{^vendorExtensions.x-is-jackson-optional-nullable}}{{^isByteArray}}{{name}}{{/isByteArray}}{{#isByteArray}}Arrays.hashCode({{name}}){{/isByteArray}}{{/vendorExtensions.x-is-jackson-optional-nullable}}{{^-last}}, {{/-last}}{{/vars}}{{#parent}}{{#hasVars}}, {{/hasVars}}super.hashCode(){{/parent}}{{#additionalPropertiesType}}, additionalProperties{{/additionalPropertiesType}}); + {{/useReflectionEqualsHashCode}} + }{{#vendorExtensions.x-jackson-optional-nullable-helpers}} + + private static int hashCodeNullable(JsonNullable a) { + if (a == null) { + return 1; + } + return a.isPresent() ? Arrays.deepHashCode(new Object[]{a.get()}) : 31; + }{{/vendorExtensions.x-jackson-optional-nullable-helpers}} + + @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}} + {{#additionalPropertiesType}} + sb.append(" additionalProperties: ").append(toIndentedString(additionalProperties)).append("\n"); + {{/additionalPropertiesType}} + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } +{{#supportUrlQuery}} + + /** + * Convert the instance into URL query string. + * + * @return URL query string + */ + public String toUrlQueryString() { + return toUrlQueryString(null); + } + + /** + * Convert the instance into URL query string. + * + * @param prefix prefix of the query string + * @return URL query string + */ + public String toUrlQueryString(String prefix) { + String suffix = ""; + String containerSuffix = ""; + String containerPrefix = ""; + if (prefix == null) { + // style=form, explode=true, e.g. /pet?name=cat&type=manx + prefix = ""; + } else { + // deepObject style e.g. /pet?id[name]=cat&id[type]=manx + prefix = prefix + "["; + suffix = "]"; + containerSuffix = "]"; + containerPrefix = "["; + } + + StringJoiner joiner = new StringJoiner("&"); + + {{#allVars}} + // add `{{baseName}}` to the URL query string + {{#isArray}} + {{#items.isPrimitiveType}} + {{#uniqueItems}} + if ({{getter}}() != null) { + int i = 0; + for ({{{items.dataType}}} _item : {{getter}}()) { + joiner.add(String.format("%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, i, containerSuffix), + URLEncoder.encode(String.valueOf(_item), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + i++; + } + {{/uniqueItems}} + {{^uniqueItems}} + if ({{getter}}() != null) { + for (int i = 0; i < {{getter}}().size(); i++) { + joiner.add(String.format("%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, i, containerSuffix), + URLEncoder.encode(String.valueOf({{getter}}().get(i)), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + } + {{/uniqueItems}} + {{/items.isPrimitiveType}} + {{^items.isPrimitiveType}} + {{#items.isModel}} + {{#uniqueItems}} + if ({{getter}}() != null) { + int i = 0; + for ({{{items.dataType}}} _item : {{getter}}()) { + if (_item != null) { + joiner.add(_item.toUrlQueryString(String.format("%s{{baseName}}%s%s", prefix, suffix, + "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, i, containerSuffix)))); + } + } + i++; + } + {{/uniqueItems}} + {{^uniqueItems}} + if ({{getter}}() != null) { + for (int i = 0; i < {{getter}}().size(); i++) { + if ({{getter}}().get(i) != null) { + joiner.add({{getter}}().get(i).toUrlQueryString(String.format("%s{{baseName}}%s%s", prefix, suffix, + "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, i, containerSuffix)))); + } + } + } + {{/uniqueItems}} + {{/items.isModel}} + {{^items.isModel}} + {{#uniqueItems}} + if ({{getter}}() != null) { + int i = 0; + for ({{{items.dataType}}} _item : {{getter}}()) { + if (_item != null) { + joiner.add(String.format("%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, i, containerSuffix), + URLEncoder.encode(String.valueOf(_item), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + i++; + } + } + {{/uniqueItems}} + {{^uniqueItems}} + if ({{getter}}() != null) { + for (int i = 0; i < {{getter}}().size(); i++) { + if ({{getter}}().get(i) != null) { + joiner.add(String.format("%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, i, containerSuffix), + URLEncoder.encode(String.valueOf({{getter}}().get(i)), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + } + } + {{/uniqueItems}} + {{/items.isModel}} + {{/items.isPrimitiveType}} + {{/isArray}} + {{^isArray}} + {{#isMap}} + {{^items.isModel}} + if ({{getter}}() != null) { + for (String _key : {{getter}}().keySet()) { + joiner.add(String.format("%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, _key, containerSuffix), + {{getter}}().get(_key), URLEncoder.encode(String.valueOf({{getter}}().get(_key)), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + } + {{/items.isModel}} + {{#items.isModel}} + if ({{getter}}() != null) { + for (String _key : {{getter}}().keySet()) { + if ({{getter}}().get(_key) != null) { + joiner.add({{getter}}().get(_key).toUrlQueryString(String.format("%s{{baseName}}%s%s", prefix, suffix, + "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, _key, containerSuffix)))); + } + } + } + {{/items.isModel}} + {{/isMap}} + {{^isMap}} + {{#isPrimitiveType}} + if ({{getter}}() != null) { + joiner.add(String.format("%s{{{baseName}}}%s=%s", prefix, suffix, URLEncoder.encode(String.valueOf({{{getter}}}()), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + {{/isPrimitiveType}} + {{^isPrimitiveType}} + {{#isModel}} + if ({{getter}}() != null) { + joiner.add({{getter}}().toUrlQueryString(prefix + "{{{baseName}}}" + suffix)); + } + {{/isModel}} + {{^isModel}} + if ({{getter}}() != null) { + joiner.add(String.format("%s{{{baseName}}}%s=%s", prefix, suffix, URLEncoder.encode(String.valueOf({{{getter}}}()), StandardCharsets.UTF_8).replaceAll("\\+", "%20"))); + } + {{/isModel}} + {{/isPrimitiveType}} + {{/isMap}} + {{/isArray}} + + {{/allVars}} + return joiner.toString(); + } +{{/supportUrlQuery}} +{{#parcelableModel}} + + public void writeToParcel(Parcel out, int flags) { +{{#model}} +{{#isArray}} + out.writeList(this); +{{/isArray}} +{{^isArray}} +{{#parent}} + super.writeToParcel(out, flags); +{{/parent}} +{{#vars}} + out.writeValue({{name}}); +{{/vars}} +{{/isArray}} +{{/model}} + } + + {{classname}}(Parcel in) { +{{#isArray}} + in.readTypedList(this, {{arrayModelType}}.CREATOR); +{{/isArray}} +{{^isArray}} +{{#parent}} + super(in); +{{/parent}} +{{#vars}} +{{#isPrimitiveType}} + {{name}} = ({{{datatypeWithEnum}}})in.readValue(null); +{{/isPrimitiveType}} +{{^isPrimitiveType}} + {{name}} = ({{{datatypeWithEnum}}})in.readValue({{complexType}}.class.getClassLoader()); +{{/isPrimitiveType}} +{{/vars}} +{{/isArray}} + } + + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator<{{classname}}> CREATOR = new Parcelable.Creator<{{classname}}>() { + public {{classname}} createFromParcel(Parcel in) { +{{#model}} +{{#isArray}} + {{classname}} result = new {{classname}}(); + result.addAll(in.readArrayList({{arrayModelType}}.class.getClassLoader())); + return result; +{{/isArray}} +{{^isArray}} + return new {{classname}}(in); +{{/isArray}} +{{/model}} + } + public {{classname}}[] newArray(int size) { + return new {{classname}}[size]; + } + }; +{{/parcelableModel}} +{{#discriminator}} +static { + // Initialize and register the discriminator mappings. + Map> mappings = new HashMap>(); + {{#mappedModels}} + mappings.put("{{mappingName}}", {{modelName}}.class); + {{/mappedModels}} + mappings.put("{{name}}", {{classname}}.class); + JSON.registerDiscriminator({{classname}}.class, "{{propertyBaseName}}", mappings); +} +{{/discriminator}} +} diff --git a/config/clients/java/template/Java/libraries/native/pom.mustache b/config/clients/java/template/Java/libraries/native/pom.mustache new file mode 100644 index 00000000..fb3ca69c --- /dev/null +++ b/config/clients/java/template/Java/libraries/native/pom.mustache @@ -0,0 +1,299 @@ + + 4.0.0 + {{groupId}} + {{artifactId}} + jar + {{artifactId}} + {{artifactVersion}} + {{artifactUrl}} + {{artifactDescription}} + + {{scmConnection}} + {{scmDeveloperConnection}} + {{scmUrl}} + +{{#parentOverridden}} + + {{{parentGroupId}}} + {{{parentArtifactId}}} + {{{parentVersion}}} + +{{/parentOverridden}} + + + + {{licenseName}} + {{licenseUrl}} + repo + + + + + + {{developerName}} + {{developerEmail}} + {{developerOrganization}} + {{developerOrganizationUrl}} + + + + + + + maven-enforcer-plugin + 3.1.0 + + + enforce-maven + + enforce + + + + + 3 + + + 11 + + + + + + + + maven-surefire-plugin + 3.0.0-M7 + + + conf/log4j.properties + + -Xms512m -Xmx1500m + methods + 10 + + + + maven-dependency-plugin + 3.3.0 + + + package + + copy-dependencies + + + ${project.build.directory}/lib + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.3.0 + + + + test-jar + + + + + + + + maven-compiler-plugin + 3.10.1 + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.4.1 + + + attach-javadocs + + jar + + + + + + maven-source-plugin + 3.2.1 + + + attach-sources + + jar-no-fork + + + + + + + com.diffplug.spotless + spotless-maven-plugin + ${spotless.version} + + + + + + + .gitignore + + + + + + true + 4 + + + + + + + + + + 1.8 + + true + + + + + + + + + + + + sign-artifacts + + + + maven-gpg-plugin + 3.0.1 + + + sign-artifacts + verify + + sign + + + + + + + + + + + {{#swagger1AnnotationLibrary}} + + io.swagger + swagger-annotations + ${swagger-annotations-version} + + {{/swagger1AnnotationLibrary}} + {{#swagger2AnnotationLibrary}} + + io.swagger.core.v3 + swagger-annotations + ${swagger-annotations-version} + + {{/swagger2AnnotationLibrary}} + + + + com.fasterxml.jackson.core + jackson-core + ${jackson-version} + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson-version} + + + com.fasterxml.jackson.core + jackson-databind + ${jackson-version} + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + ${jackson-version} + + + org.openapitools + jackson-databind-nullable + ${jackson-databind-nullable-version} + + + + + com.google.code.findbugs + jsr305 + 3.0.2 + + + jakarta.annotation + jakarta.annotation-api + ${jakarta-annotation-version} + provided + + {{#hasFormParamsInSpec}} + + org.apache.httpcomponents + httpmime + ${httpmime-version} + + {{/hasFormParamsInSpec}} + + + + junit + junit + ${junit-version} + test + + + + + UTF-8 + {{#swagger1AnnotationLibrary}} + 1.6.9 + {{/swagger1AnnotationLibrary}} + {{#swagger2AnnotationLibrary}} + 2.2.9 + {{/swagger2AnnotationLibrary}} + 11 + 11 + 2.14.1 + 0.2.6 + {{#useJakartaEe}} + 2.1.1 + {{/useJakartaEe}} + {{^useJakartaEe}} + 1.3.5 + {{/useJakartaEe}} + {{#hasFormParamsInSpec}} + 4.5.13 + {{/hasFormParamsInSpec}} + 4.13.2 + 2.27.2 + + diff --git a/config/clients/java/template/Java/libraries/native/travis.mustache b/config/clients/java/template/Java/libraries/native/travis.mustache new file mode 100644 index 00000000..c9464747 --- /dev/null +++ b/config/clients/java/template/Java/libraries/native/travis.mustache @@ -0,0 +1,16 @@ +# +# Generated by: https://openapi-generator.tech +# +language: java +jdk: + - oraclejdk11 +before_install: + # ensure gradlew has proper permission + - chmod a+x ./gradlew +script: + # test using maven + - mvn test + # uncomment below to test using gradle + # - gradle test + # uncomment below to test using sbt + # - sbt test diff --git a/config/clients/java/template/Java/licenseInfo.mustache b/config/clients/java/template/Java/licenseInfo.mustache new file mode 100644 index 00000000..c66209f2 --- /dev/null +++ b/config/clients/java/template/Java/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/config/clients/java/template/Java/manifest.mustache b/config/clients/java/template/Java/manifest.mustache new file mode 100644 index 00000000..f44bd07d --- /dev/null +++ b/config/clients/java/template/Java/manifest.mustache @@ -0,0 +1,3 @@ + + + diff --git a/config/clients/java/template/Java/maven.yml.mustache b/config/clients/java/template/Java/maven.yml.mustache new file mode 100644 index 00000000..f3c4733c --- /dev/null +++ b/config/clients/java/template/Java/maven.yml.mustache @@ -0,0 +1,31 @@ +# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time +# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven +# +# This file is auto-generated by OpenAPI Generator (https://openapi-generator.tech) + +name: Java CI with Maven + +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] + +jobs: + build: + name: Build {{{appName}}} + runs-on: ubuntu-latest + strategy: + matrix: + java: [ '8' ] + steps: + - uses: actions/checkout@v2 + - name: Set up JDK + uses: actions/setup-java@v2 + with: + {{=< >=}} + java-version: ${{ matrix.java }} + distribution: 'temurin' + cache: maven + - name: Build with Maven + run: mvn -B package --no-transfer-progress --file pom.xml diff --git a/config/clients/java/template/Java/model.mustache b/config/clients/java/template/Java/model.mustache new file mode 100644 index 00000000..55e6678d --- /dev/null +++ b/config/clients/java/template/Java/model.mustache @@ -0,0 +1,68 @@ +{{>licenseInfo}} + +package {{package}}; + +{{#useReflectionEqualsHashCode}} +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +{{/useReflectionEqualsHashCode}} +import java.util.Objects; +import java.util.Arrays; +{{#imports}} +import {{import}}; +{{/imports}} +{{#serializableModel}} +import java.io.Serializable; +{{/serializableModel}} +{{#jackson}} +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.annotation.JsonTypeName; +{{#withXml}} +import com.fasterxml.jackson.dataformat.xml.annotation.*; +{{/withXml}} +{{#vendorExtensions.x-has-readonly-properties}} +import com.fasterxml.jackson.annotation.JsonCreator; +{{/vendorExtensions.x-has-readonly-properties}} +{{/jackson}} +{{#withXml}} +import {{javaxPackage}}.xml.bind.annotation.*; +import {{javaxPackage}}.xml.bind.annotation.adapters.*; +import io.github.threetenjaxb.core.*; +{{/withXml}} +{{#jsonb}} +import java.lang.reflect.Type; +import {{javaxPackage}}.json.bind.annotation.JsonbTypeDeserializer; +import {{javaxPackage}}.json.bind.annotation.JsonbTypeSerializer; +import {{javaxPackage}}.json.bind.serializer.DeserializationContext; +import {{javaxPackage}}.json.bind.serializer.JsonbDeserializer; +import {{javaxPackage}}.json.bind.serializer.JsonbSerializer; +import {{javaxPackage}}.json.bind.serializer.SerializationContext; +import {{javaxPackage}}.json.stream.JsonGenerator; +import {{javaxPackage}}.json.stream.JsonParser; +import {{javaxPackage}}.json.bind.annotation.JsonbProperty; +{{#vendorExtensions.x-has-readonly-properties}} +import {{javaxPackage}}.json.bind.annotation.JsonbCreator; +{{/vendorExtensions.x-has-readonly-properties}} +{{/jsonb}} +{{#parcelableModel}} +import android.os.Parcelable; +import android.os.Parcel; +{{/parcelableModel}} +{{#useBeanValidation}} +import {{javaxPackage}}.validation.constraints.*; +import {{javaxPackage}}.validation.Valid; +{{/useBeanValidation}} +{{#performBeanValidation}} +import org.hibernate.validator.constraints.*; +{{/performBeanValidation}} +{{#supportUrlQuery}} +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.StringJoiner; +{{/supportUrlQuery}} + +{{#models}} +{{#model}} +{{#isEnum}}{{>modelEnum}}{{/isEnum}}{{^isEnum}}{{#vendorExtensions.x-is-one-of-interface}}{{>oneof_interface}}{{/vendorExtensions.x-is-one-of-interface}}{{^vendorExtensions.x-is-one-of-interface}}{{>pojo}}{{/vendorExtensions.x-is-one-of-interface}}{{/isEnum}} +{{/model}} +{{/models}} diff --git a/config/clients/java/template/Java/modelEnum.mustache b/config/clients/java/template/Java/modelEnum.mustache new file mode 100644 index 00000000..54d90dae --- /dev/null +++ b/config/clients/java/template/Java/modelEnum.mustache @@ -0,0 +1,117 @@ +{{#jackson}} +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +{{/jackson}} +{{#gson}} +import java.io.IOException; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +{{/gson}} + +/** + * {{description}}{{^description}}Gets or Sets {{{name}}}{{/description}} + */ +{{#gson}} +@JsonAdapter({{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}.Adapter.class) +{{/gson}} +{{#jsonb}} +@JsonbTypeSerializer({{datatypeWithEnum}}.Serializer.class) +@JsonbTypeDeserializer({{datatypeWithEnum}}.Deserializer.class) +{{/jsonb}} +{{>additionalEnumTypeAnnotations}}public enum {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} { + {{#allowableValues}}{{#enumVars}} + {{#enumDescription}} + /** + * {{.}} + */ + {{/enumDescription}} + {{#withXml}} + @XmlEnumValue({{#isInteger}}"{{/isInteger}}{{#isDouble}}"{{/isDouble}}{{#isLong}}"{{/isLong}}{{#isFloat}}"{{/isFloat}}{{{value}}}{{#isInteger}}"{{/isInteger}}{{#isDouble}}"{{/isDouble}}{{#isLong}}"{{/isLong}}{{#isFloat}}"{{/isFloat}}) + {{/withXml}} + {{{name}}}({{{value}}}){{^-last}}, + {{/-last}}{{#-last}};{{/-last}}{{/enumVars}}{{/allowableValues}} + + private {{{dataType}}} value; + + {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}({{{dataType}}} value) { + this.value = value; + } + +{{#jackson}} + @JsonValue +{{/jackson}} + public {{{dataType}}} getValue() { + return value; + } + + @Override + public String toString() { + return String.valueOf(value); + } + +{{#jackson}} + @JsonCreator +{{/jackson}} + public static {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} fromValue({{{dataType}}} value) { + for ({{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} b : {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}.values()) { + if (b.value.{{^isString}}equals{{/isString}}{{#isString}}{{#useEnumCaseInsensitive}}equalsIgnoreCase{{/useEnumCaseInsensitive}}{{^useEnumCaseInsensitive}}equals{{/useEnumCaseInsensitive}}{{/isString}}(value)) { + return b; + } + } + {{#isNullable}}return null;{{/isNullable}}{{^isNullable}}{{#enumUnknownDefaultCase}}{{#allowableValues}}{{#enumVars}}{{#-last}}return {{{name}}};{{/-last}}{{/enumVars}}{{/allowableValues}}{{/enumUnknownDefaultCase}}{{^enumUnknownDefaultCase}}throw new IllegalArgumentException("Unexpected value '" + value + "'");{{/enumUnknownDefaultCase}}{{/isNullable}} + } +{{#gson}} + + public static class Adapter extends TypeAdapter<{{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}> { + @Override + public void write(final JsonWriter jsonWriter, final {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} enumeration) throws IOException { + jsonWriter.value(enumeration.getValue()); + } + + @Override + public {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} read(final JsonReader jsonReader) throws IOException { + {{^isNumber}}{{{dataType}}}{{/isNumber}}{{#isNumber}}String{{/isNumber}} value = jsonReader.{{#isNumber}}nextString(){{/isNumber}}{{#isInteger}}nextInt(){{/isInteger}}{{^isNumber}}{{^isInteger}}next{{{dataType}}}(){{/isInteger}}{{/isNumber}}; + return {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}.fromValue({{#isNumber}}new BigDecimal({{/isNumber}}value{{#isNumber}}){{/isNumber}}); + } + } +{{/gson}} +{{#jsonb}} + + public static final class Deserializer implements JsonbDeserializer<{{datatypeWithEnum}}> { + @Override + public {{datatypeWithEnum}} deserialize(JsonParser parser, DeserializationContext ctx, Type rtType) { + for ({{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} b : {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}.values()) { + if (String.valueOf(b.value).equals(parser.getString())) { + return b; + } + } + {{#useNullForUnknownEnumValue}}return null;{{/useNullForUnknownEnumValue}}{{^useNullForUnknownEnumValue}}throw new IllegalArgumentException("Unexpected value '" + parser.getString() + "'");{{/useNullForUnknownEnumValue}} + } + } + + public static final class Serializer implements JsonbSerializer<{{datatypeWithEnum}}> { + @Override + public void serialize({{datatypeWithEnum}} obj, JsonGenerator generator, SerializationContext ctx) { + generator.write(obj.value); + } + } +{{/jsonb}} +{{#supportUrlQuery}} + + /** + * Convert the instance into URL query string. + * + * @param prefix prefix of the query string + * @return URL query string + */ + public String toUrlQueryString(String prefix) { + if (prefix == null) { + prefix = ""; + } + + return String.format("%s=%s", prefix, this.toString()); + } +{{/supportUrlQuery}} +} diff --git a/config/clients/java/template/Java/modelInnerEnum.mustache b/config/clients/java/template/Java/modelInnerEnum.mustache new file mode 100644 index 00000000..43ad2986 --- /dev/null +++ b/config/clients/java/template/Java/modelInnerEnum.mustache @@ -0,0 +1,95 @@ + /** + * {{description}}{{^description}}Gets or Sets {{{name}}}{{/description}} + */ +{{#gson}} + @JsonAdapter({{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}.Adapter.class) +{{/gson}} +{{#jsonb}} + @JsonbTypeSerializer({{datatypeWithEnum}}.Serializer.class) + @JsonbTypeDeserializer({{datatypeWithEnum}}.Deserializer.class) +{{/jsonb}} +{{#withXml}} + @XmlType(name="{{datatypeWithEnum}}") + @XmlEnum({{dataType}}.class) +{{/withXml}} + {{>additionalEnumTypeAnnotations}}public enum {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}} { + {{#allowableValues}} + {{#enumVars}} + {{#enumDescription}} + /** + * {{.}} + */ + {{/enumDescription}} + {{#withXml}} + @XmlEnumValue({{#isInteger}}"{{/isInteger}}{{#isDouble}}"{{/isDouble}}{{#isLong}}"{{/isLong}}{{#isFloat}}"{{/isFloat}}{{{value}}}{{#isInteger}}"{{/isInteger}}{{#isDouble}}"{{/isDouble}}{{#isLong}}"{{/isLong}}{{#isFloat}}"{{/isFloat}}) + {{/withXml}} + {{{name}}}({{{value}}}){{^-last}}, + {{/-last}}{{#-last}};{{/-last}} + {{/enumVars}} + {{/allowableValues}} + + private {{{dataType}}} value; + + {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}({{{dataType}}} value) { + this.value = value; + } + +{{#jackson}} + @JsonValue +{{/jackson}} + public {{{dataType}}} getValue() { + return value; + } + + @Override + public String toString() { + return String.valueOf(value); + } + +{{#jackson}} + @JsonCreator +{{/jackson}} + public static {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} fromValue({{{dataType}}} value) { + for ({{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} b : {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}.values()) { + if (b.value.{{^isString}}equals{{/isString}}{{#isString}}{{#useEnumCaseInsensitive}}equalsIgnoreCase{{/useEnumCaseInsensitive}}{{^useEnumCaseInsensitive}}equals{{/useEnumCaseInsensitive}}{{/isString}}(value)) { + return b; + } + } + {{#isNullable}}return null;{{/isNullable}}{{^isNullable}}{{#enumUnknownDefaultCase}}{{#allowableValues}}{{#enumVars}}{{#-last}}return {{{name}}};{{/-last}}{{/enumVars}}{{/allowableValues}}{{/enumUnknownDefaultCase}}{{^enumUnknownDefaultCase}}throw new IllegalArgumentException("Unexpected value '" + value + "'");{{/enumUnknownDefaultCase}}{{/isNullable}} + } +{{#gson}} + + public static class Adapter extends TypeAdapter<{{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}> { + @Override + public void write(final JsonWriter jsonWriter, final {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}} enumeration) throws IOException { + jsonWriter.value(enumeration.getValue()); + } + + @Override + public {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}} read(final JsonReader jsonReader) throws IOException { + {{^isNumber}}{{{dataType}}}{{/isNumber}}{{#isNumber}}String{{/isNumber}} value = {{#isFloat}}(float){{/isFloat}} jsonReader.{{#isNumber}}nextString(){{/isNumber}}{{#isInteger}}nextInt(){{/isInteger}}{{^isNumber}}{{^isInteger}}{{#isFloat}}nextDouble{{/isFloat}}{{^isFloat}}next{{{dataType}}}{{/isFloat}}(){{/isInteger}}{{/isNumber}}; + return {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}.fromValue({{#isNumber}}new BigDecimal({{/isNumber}}value{{#isNumber}}){{/isNumber}}); + } + } +{{/gson}} +{{#jsonb}} + public static final class Deserializer implements JsonbDeserializer<{{datatypeWithEnum}}> { + @Override + public {{datatypeWithEnum}} deserialize(JsonParser parser, DeserializationContext ctx, Type rtType) { + for ({{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} b : {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}.values()) { + if (String.valueOf(b.value).equals(parser.getString())) { + return b; + } + } + {{#useNullForUnknownEnumValue}}return null;{{/useNullForUnknownEnumValue}}{{^useNullForUnknownEnumValue}}throw new IllegalArgumentException("Unexpected value '" + parser.getString() + "'");{{/useNullForUnknownEnumValue}} + } + } + + public static final class Serializer implements JsonbSerializer<{{datatypeWithEnum}}> { + @Override + public void serialize({{datatypeWithEnum}} obj, JsonGenerator generator, SerializationContext ctx) { + generator.write(obj.value); + } + } +{{/jsonb}} + } diff --git a/config/clients/java/template/Java/model_doc.mustache b/config/clients/java/template/Java/model_doc.mustache new file mode 100644 index 00000000..9a7fe146 --- /dev/null +++ b/config/clients/java/template/Java/model_doc.mustache @@ -0,0 +1,4 @@ +{{#models}}{{#model}} + +{{#isEnum}}{{>enum_outer_doc}}{{/isEnum}}{{^isEnum}}{{>pojo_doc}}{{/isEnum}} +{{/model}}{{/models}} diff --git a/config/clients/java/template/Java/model_test.mustache b/config/clients/java/template/Java/model_test.mustache new file mode 100644 index 00000000..d5c03d1e --- /dev/null +++ b/config/clients/java/template/Java/model_test.mustache @@ -0,0 +1,43 @@ +{{>licenseInfo}} + +package {{package}}; + +{{#imports}}import {{import}}; +{{/imports}} +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +/** + * Model tests for {{classname}} + */ +public class {{classname}}Test { + {{#models}} + {{#model}} + {{^vendorExtensions.x-is-one-of-interface}} + {{^isEnum}} + private final {{classname}} model = new {{classname}}(); + + {{/isEnum}} + /** + * Model tests for {{classname}} + */ + @Test + public void test{{classname}}() { + // TODO: test {{classname}} + } + + {{#allVars}} + /** + * Test the property '{{name}}' + */ + @Test + public void {{name}}Test() { + // TODO: test {{name}} + } + + {{/allVars}} + {{/vendorExtensions.x-is-one-of-interface}} + {{/model}} + {{/models}} +} diff --git a/config/clients/java/template/Java/oneof_interface.mustache b/config/clients/java/template/Java/oneof_interface.mustache new file mode 100644 index 00000000..d6727727 --- /dev/null +++ b/config/clients/java/template/Java/oneof_interface.mustache @@ -0,0 +1,6 @@ +{{>additionalOneOfTypeAnnotations}}{{>generatedAnnotation}}{{>typeInfoAnnotation}}{{>xmlAnnotation}} +public interface {{classname}} {{#vendorExtensions.x-implements}}{{#-first}}extends {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} { + {{#discriminator}} + public {{propertyType}} {{propertyGetter}}(); + {{/discriminator}} +} diff --git a/config/clients/java/template/Java/openapi.mustache b/config/clients/java/template/Java/openapi.mustache new file mode 100644 index 00000000..34fbb53f --- /dev/null +++ b/config/clients/java/template/Java/openapi.mustache @@ -0,0 +1 @@ +{{{openapi-yaml}}} diff --git a/config/clients/java/template/Java/pojo.mustache b/config/clients/java/template/Java/pojo.mustache new file mode 100644 index 00000000..20f18dab --- /dev/null +++ b/config/clients/java/template/Java/pojo.mustache @@ -0,0 +1,617 @@ +/** + * {{description}}{{^description}}{{classname}}{{/description}}{{#isDeprecated}} + * @deprecated{{/isDeprecated}} + */{{#isDeprecated}} +@Deprecated{{/isDeprecated}} +{{#swagger1AnnotationLibrary}} +{{#description}} +@ApiModel(description = "{{{.}}}") +{{/description}} +{{/swagger1AnnotationLibrary}} +{{#swagger2AnnotationLibrary}} +{{#description}} +@Schema(description = "{{{.}}}") +{{/description}} +{{/swagger2AnnotationLibrary}} +{{#jackson}} +@JsonPropertyOrder({ +{{#vars}} + {{classname}}.JSON_PROPERTY_{{nameInSnakeCase}}{{^-last}},{{/-last}} +{{/vars}} +}) +{{#isClassnameSanitized}} +{{^hasDiscriminatorWithNonEmptyMapping}} +@JsonTypeName("{{name}}") +{{/hasDiscriminatorWithNonEmptyMapping}} +{{/isClassnameSanitized}} +{{/jackson}} +{{>additionalModelTypeAnnotations}}{{>generatedAnnotation}}{{#discriminator}}{{>typeInfoAnnotation}}{{/discriminator}}{{>xmlAnnotation}} +{{#vendorExtensions.x-class-extra-annotation}} +{{{vendorExtensions.x-class-extra-annotation}}} +{{/vendorExtensions.x-class-extra-annotation}} +public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtensions.x-implements}}{{#-first}}implements {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{#-last}} {{/-last}}{{/vendorExtensions.x-implements}}{ +{{#serializableModel}} + private static final long serialVersionUID = 1L; + +{{/serializableModel}} + {{#vars}} + {{#isEnum}} + {{^isContainer}} +{{>modelInnerEnum}} + {{/isContainer}} + {{#isContainer}} + {{#mostInnerItems}} +{{>modelInnerEnum}} + {{/mostInnerItems}} + {{/isContainer}} + {{/isEnum}} + {{#gson}} + public static final String SERIALIZED_NAME_{{nameInSnakeCase}} = "{{baseName}}"; + {{/gson}} + {{#jackson}} + public static final String JSON_PROPERTY_{{nameInSnakeCase}} = "{{baseName}}"; + {{/jackson}} + {{#withXml}} + {{#isXmlAttribute}} + @XmlAttribute(name = "{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}") + {{/isXmlAttribute}} + {{^isXmlAttribute}} + {{^isContainer}} + @XmlElement({{#xmlNamespace}}namespace="{{.}}", {{/xmlNamespace}}name = "{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}") + {{/isContainer}} + {{#isContainer}} + // Is a container wrapped={{isXmlWrapped}} + {{#items}} + // items.name={{name}} items.baseName={{baseName}} items.xmlName={{xmlName}} items.xmlNamespace={{xmlNamespace}} + // items.example={{example}} items.type={{dataType}} + @XmlElement({{#xmlNamespace}}namespace="{{.}}", {{/xmlNamespace}}name = "{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}") + {{/items}} + {{#isXmlWrapped}} + @XmlElementWrapper({{#xmlNamespace}}namespace="{{.}}", {{/xmlNamespace}}name = "{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}") + {{/isXmlWrapped}} + {{/isContainer}} + {{#isDateTime}} + @XmlJavaTypeAdapter(OffsetDateTimeXmlAdapter.class) + {{/isDateTime}} + {{/isXmlAttribute}} + {{/withXml}} + {{#gson}} + @SerializedName(SERIALIZED_NAME_{{nameInSnakeCase}}) + {{/gson}} + {{#vendorExtensions.x-field-extra-annotation}} + {{{vendorExtensions.x-field-extra-annotation}}} + {{/vendorExtensions.x-field-extra-annotation}} + {{#vendorExtensions.x-is-jackson-optional-nullable}} + {{#isContainer}} + private JsonNullable<{{{datatypeWithEnum}}}> {{name}} = JsonNullable.<{{{datatypeWithEnum}}}>undefined(); + {{/isContainer}} + {{^isContainer}} + private JsonNullable<{{{datatypeWithEnum}}}> {{name}} = JsonNullable.<{{{datatypeWithEnum}}}>{{#defaultValue}}of({{{.}}}){{/defaultValue}}{{^defaultValue}}undefined(){{/defaultValue}}; + {{/isContainer}} + {{/vendorExtensions.x-is-jackson-optional-nullable}} + {{^vendorExtensions.x-is-jackson-optional-nullable}} + {{#isContainer}} + private {{{datatypeWithEnum}}} {{name}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}; + {{/isContainer}} + {{^isContainer}} + {{#isDiscriminator}}protected{{/isDiscriminator}}{{^isDiscriminator}}private{{/isDiscriminator}} {{{datatypeWithEnum}}} {{name}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}; + {{/isContainer}} + {{/vendorExtensions.x-is-jackson-optional-nullable}} + + {{/vars}} + public {{classname}}() { + {{#parent}} + {{#parcelableModel}} + super();{{/parcelableModel}} + {{/parent}} + {{#gson}} + {{#discriminator}} + {{#discriminator.isEnum}} + this.{{{discriminatorName}}} = this.getClass().getSimpleName(); + {{/discriminator.isEnum}} + {{/discriminator}} + {{/gson}} + } + {{#vendorExtensions.x-has-readonly-properties}} + {{^withXml}} + + {{#jsonb}}@JsonbCreator{{/jsonb}}{{#jackson}}@JsonCreator{{/jackson}} + public {{classname}}( + {{#readOnlyVars}} + {{#jsonb}}@JsonbProperty(value = "{{baseName}}"{{^required}}, nullable = true{{/required}}){{/jsonb}}{{#jackson}}@JsonProperty(JSON_PROPERTY_{{nameInSnakeCase}}){{/jackson}} {{{datatypeWithEnum}}} {{name}}{{^-last}}, {{/-last}} + {{/readOnlyVars}} + ) { + this(); + {{#readOnlyVars}} + this.{{name}} = {{#vendorExtensions.x-is-jackson-optional-nullable}}{{name}} == null ? JsonNullable.<{{{datatypeWithEnum}}}>undefined() : JsonNullable.of({{name}}){{/vendorExtensions.x-is-jackson-optional-nullable}}{{^vendorExtensions.x-is-jackson-optional-nullable}}{{name}}{{/vendorExtensions.x-is-jackson-optional-nullable}}; + {{/readOnlyVars}} + } + {{/withXml}} + {{/vendorExtensions.x-has-readonly-properties}} + {{#vars}} + + {{^isReadOnly}} + public {{classname}} {{name}}({{{datatypeWithEnum}}} {{name}}) { + {{#vendorExtensions.x-is-jackson-optional-nullable}}this.{{name}} = JsonNullable.<{{{datatypeWithEnum}}}>of({{name}});{{/vendorExtensions.x-is-jackson-optional-nullable}} + {{^vendorExtensions.x-is-jackson-optional-nullable}}this.{{name}} = {{name}};{{/vendorExtensions.x-is-jackson-optional-nullable}} + return this; + } + {{#isArray}} + + public {{classname}} add{{nameInCamelCase}}Item({{{items.datatypeWithEnum}}} {{name}}Item) { + {{#vendorExtensions.x-is-jackson-optional-nullable}} + if (this.{{name}} == null || !this.{{name}}.isPresent()) { + this.{{name}} = JsonNullable.<{{{datatypeWithEnum}}}>of({{{defaultValue}}}{{^defaultValue}}new {{#uniqueItems}}LinkedHashSet{{/uniqueItems}}{{^uniqueItems}}ArrayList{{/uniqueItems}}<>(){{/defaultValue}}); + } + try { + this.{{name}}.get().add({{name}}Item); + } catch (java.util.NoSuchElementException e) { + // this can never happen, as we make sure above that the value is present + } + return this; + {{/vendorExtensions.x-is-jackson-optional-nullable}} + {{^vendorExtensions.x-is-jackson-optional-nullable}} + if (this.{{name}} == null) { + this.{{name}} = {{{defaultValue}}}{{^defaultValue}}new {{#uniqueItems}}LinkedHashSet{{/uniqueItems}}{{^uniqueItems}}ArrayList{{/uniqueItems}}<>(){{/defaultValue}}; + } + this.{{name}}.add({{name}}Item); + return this; + {{/vendorExtensions.x-is-jackson-optional-nullable}} + } + {{/isArray}} + {{#isMap}} + + public {{classname}} put{{nameInCamelCase}}Item(String key, {{{items.datatypeWithEnum}}} {{name}}Item) { + {{#vendorExtensions.x-is-jackson-optional-nullable}} + if (this.{{name}} == null || !this.{{name}}.isPresent()) { + this.{{name}} = JsonNullable.<{{{datatypeWithEnum}}}>of({{{defaultValue}}}{{^defaultValue}}new HashMap<>(){{/defaultValue}}); + } + try { + this.{{name}}.get().put(key, {{name}}Item); + } catch (java.util.NoSuchElementException e) { + // this can never happen, as we make sure above that the value is present + } + return this; + {{/vendorExtensions.x-is-jackson-optional-nullable}} + {{^vendorExtensions.x-is-jackson-optional-nullable}} + {{^required}} + if (this.{{name}} == null) { + this.{{name}} = {{{defaultValue}}}{{^defaultValue}}new HashMap<>(){{/defaultValue}}; + } + {{/required}} + this.{{name}}.put(key, {{name}}Item); + return this; + {{/vendorExtensions.x-is-jackson-optional-nullable}} + } + {{/isMap}} + + {{/isReadOnly}} + /** + {{#description}} + * {{.}} + {{/description}} + {{^description}} + * Get {{name}} + {{/description}} + {{#minimum}} + * minimum: {{.}} + {{/minimum}} + {{#maximum}} + * maximum: {{.}} + {{/maximum}} + * @return {{name}} + {{#deprecated}} + * @deprecated + {{/deprecated}} + **/ +{{#deprecated}} + @Deprecated +{{/deprecated}} +{{#required}} +{{#isNullable}} + @{{javaxPackage}}.annotation.Nullable +{{/isNullable}} +{{^isNullable}} + @{{javaxPackage}}.annotation.Nonnull +{{/isNullable}} +{{/required}} +{{^required}} + @{{javaxPackage}}.annotation.Nullable +{{/required}} +{{#jsonb}} + @JsonbProperty("{{baseName}}") +{{/jsonb}} +{{#useBeanValidation}} +{{>beanValidation}} +{{/useBeanValidation}} +{{#swagger1AnnotationLibrary}} + @ApiModelProperty({{#example}}example = "{{{.}}}", {{/example}}{{#required}}required = {{required}}, {{/required}}value = "{{{description}}}") +{{/swagger1AnnotationLibrary}} +{{#swagger2AnnotationLibrary}} + @Schema({{#example}}example = "{{{.}}}", {{/example}}requiredMode = {{#required}}Schema.RequiredMode.REQUIRED{{/required}}{{^required}}Schema.RequiredMode.NOT_REQUIRED{{/required}}, description = "{{{description}}}") +{{/swagger2AnnotationLibrary}} +{{#vendorExtensions.x-extra-annotation}} + {{{vendorExtensions.x-extra-annotation}}} +{{/vendorExtensions.x-extra-annotation}} +{{#vendorExtensions.x-is-jackson-optional-nullable}} + {{!unannotated, Jackson would pick this up automatically and add it *in addition* to the _JsonNullable getter field}} + @JsonIgnore +{{/vendorExtensions.x-is-jackson-optional-nullable}} +{{^vendorExtensions.x-is-jackson-optional-nullable}}{{#jackson}}{{> jackson_annotations}}{{/jackson}}{{/vendorExtensions.x-is-jackson-optional-nullable}} + public {{{datatypeWithEnum}}} {{getter}}() { + {{#vendorExtensions.x-is-jackson-optional-nullable}} + {{#isReadOnly}}{{! A readonly attribute doesn't have setter => jackson will set null directly if explicitly returned by API, so make sure we have an empty JsonNullable}} + if ({{name}} == null) { + {{name}} = JsonNullable.<{{{datatypeWithEnum}}}>{{#defaultValue}}of({{{.}}}){{/defaultValue}}{{^defaultValue}}undefined(){{/defaultValue}}; + } + {{/isReadOnly}} + return {{name}}.orElse(null); + {{/vendorExtensions.x-is-jackson-optional-nullable}} + {{^vendorExtensions.x-is-jackson-optional-nullable}} + return {{name}}; + {{/vendorExtensions.x-is-jackson-optional-nullable}} + } + + {{#vendorExtensions.x-is-jackson-optional-nullable}} +{{> jackson_annotations}} + public JsonNullable<{{{datatypeWithEnum}}}> {{getter}}_JsonNullable() { + return {{name}}; + } + {{/vendorExtensions.x-is-jackson-optional-nullable}}{{#vendorExtensions.x-is-jackson-optional-nullable}} + @JsonProperty(JSON_PROPERTY_{{nameInSnakeCase}}) + {{#isReadOnly}}private{{/isReadOnly}}{{^isReadOnly}}public{{/isReadOnly}} void {{setter}}_JsonNullable(JsonNullable<{{{datatypeWithEnum}}}> {{name}}) { + {{! For getters/setters that have name differing from attribute name, we must include setter (albeit private) for jackson to be able to set the attribute}} + this.{{name}} = {{name}}; + } + {{/vendorExtensions.x-is-jackson-optional-nullable}} + + {{^isReadOnly}} +{{#vendorExtensions.x-setter-extra-annotation}} {{{vendorExtensions.x-setter-extra-annotation}}} +{{/vendorExtensions.x-setter-extra-annotation}}{{#jackson}}{{^vendorExtensions.x-is-jackson-optional-nullable}}{{> jackson_annotations}}{{/vendorExtensions.x-is-jackson-optional-nullable}}{{/jackson}} public void {{setter}}({{{datatypeWithEnum}}} {{name}}) { + {{#vendorExtensions.x-is-jackson-optional-nullable}} + this.{{name}} = JsonNullable.<{{{datatypeWithEnum}}}>of({{name}}); + {{/vendorExtensions.x-is-jackson-optional-nullable}} + {{^vendorExtensions.x-is-jackson-optional-nullable}} + this.{{name}} = {{name}}; + {{/vendorExtensions.x-is-jackson-optional-nullable}} + } + {{/isReadOnly}} + + {{/vars}} + {{#parent}} + {{#allVars}} + {{#isOverridden}} + @Override + public {{classname}} {{name}}({{{datatypeWithEnum}}} {{name}}) { + {{#vendorExtensions.x-is-jackson-optional-nullable}} + this.{{setter}}(JsonNullable.<{{{datatypeWithEnum}}}>of({{name}})); + {{/vendorExtensions.x-is-jackson-optional-nullable}} + {{^vendorExtensions.x-is-jackson-optional-nullable}} + this.{{setter}}({{name}}); + {{/vendorExtensions.x-is-jackson-optional-nullable}} + return this; + } + + {{/isOverridden}} + {{/allVars}} + {{/parent}} + @Override + public boolean equals(Object o) { + {{#useReflectionEqualsHashCode}} + return EqualsBuilder.reflectionEquals(this, o, false, null, true); + {{/useReflectionEqualsHashCode}} + {{^useReflectionEqualsHashCode}} + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + }{{#hasVars}} + {{classname}} {{classVarName}} = ({{classname}}) o; + return {{#vars}}{{#vendorExtensions.x-is-jackson-optional-nullable}}equalsNullable(this.{{name}}, {{classVarName}}.{{name}}){{/vendorExtensions.x-is-jackson-optional-nullable}}{{^vendorExtensions.x-is-jackson-optional-nullable}}{{#isByteArray}}Arrays{{/isByteArray}}{{^isByteArray}}Objects{{/isByteArray}}.equals(this.{{name}}, {{classVarName}}.{{name}}){{/vendorExtensions.x-is-jackson-optional-nullable}}{{^-last}} && + {{/-last}}{{/vars}}{{#parent}} && + super.equals(o){{/parent}};{{/hasVars}}{{^hasVars}} + return {{#parent}}super.equals(o){{/parent}}{{^parent}}true{{/parent}};{{/hasVars}} + {{/useReflectionEqualsHashCode}} + }{{#vendorExtensions.x-jackson-optional-nullable-helpers}} + + private static boolean equalsNullable(JsonNullable a, JsonNullable b) { + return a == b || (a != null && b != null && a.isPresent() && b.isPresent() && Objects.deepEquals(a.get(), b.get())); + }{{/vendorExtensions.x-jackson-optional-nullable-helpers}} + + @Override + public int hashCode() { + {{#useReflectionEqualsHashCode}} + return HashCodeBuilder.reflectionHashCode(this); + {{/useReflectionEqualsHashCode}} + {{^useReflectionEqualsHashCode}} + return Objects.hash({{#vars}}{{#vendorExtensions.x-is-jackson-optional-nullable}}hashCodeNullable({{name}}){{/vendorExtensions.x-is-jackson-optional-nullable}}{{^vendorExtensions.x-is-jackson-optional-nullable}}{{^isByteArray}}{{name}}{{/isByteArray}}{{#isByteArray}}Arrays.hashCode({{name}}){{/isByteArray}}{{/vendorExtensions.x-is-jackson-optional-nullable}}{{^-last}}, {{/-last}}{{/vars}}{{#parent}}{{#hasVars}}, {{/hasVars}}super.hashCode(){{/parent}}); + {{/useReflectionEqualsHashCode}} + }{{#vendorExtensions.x-jackson-optional-nullable-helpers}} + + private static int hashCodeNullable(JsonNullable a) { + if (a == null) { + return 1; + } + return a.isPresent() ? Arrays.deepHashCode(new Object[]{a.get()}) : 31; + }{{/vendorExtensions.x-jackson-optional-nullable-helpers}} + + @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{{#jsonb}} static{{/jsonb}} String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } +{{#supportUrlQuery}} + + /** + * Convert the instance into URL query string. + * + * @return URL query string + */ + public String toUrlQueryString() { + return toUrlQueryString(null); + } + + /** + * Convert the instance into URL query string. + * + * @param prefix prefix of the query string + * @return URL query string + */ + public String toUrlQueryString(String prefix) { + String suffix = ""; + String containerSuffix = ""; + String containerPrefix = ""; + if (prefix == null) { + // style=form, explode=true, e.g. /pet?name=cat&type=manx + prefix = ""; + } else { + // deepObject style e.g. /pet?id[name]=cat&id[type]=manx + prefix = prefix + "["; + suffix = "]"; + containerSuffix = "]"; + containerPrefix = "["; + } + + StringJoiner joiner = new StringJoiner("&"); + + {{#allVars}} + // add `{{baseName}}` to the URL query string + {{#isArray}} + {{#items.isPrimitiveType}} + {{#uniqueItems}} + if ({{getter}}() != null) { + int i = 0; + for ({{{items.dataType}}} _item : {{getter}}()) { + try { + joiner.add(String.format("%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, i, containerSuffix), + URLEncoder.encode(String.valueOf(_item), "UTF-8").replaceAll("\\+", "%20"))); + } catch (UnsupportedEncodingException e) { + // Should never happen, UTF-8 is always supported + throw new RuntimeException(e); + } + } + i++; + } + {{/uniqueItems}} + {{^uniqueItems}} + if ({{getter}}() != null) { + for (int i = 0; i < {{getter}}().size(); i++) { + try { + joiner.add(String.format("%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, i, containerSuffix), + URLEncoder.encode(String.valueOf({{getter}}().get(i)), "UTF-8").replaceAll("\\+", "%20"))); + } catch (UnsupportedEncodingException e) { + // Should never happen, UTF-8 is always supported + throw new RuntimeException(e); + } + } + } + {{/uniqueItems}} + {{/items.isPrimitiveType}} + {{^items.isPrimitiveType}} + {{#items.isModel}} + {{#uniqueItems}} + if ({{getter}}() != null) { + int i = 0; + for ({{{items.dataType}}} _item : {{getter}}()) { + if (_item != null) { + joiner.add(_item.toUrlQueryString(String.format("%s{{baseName}}%s%s", prefix, suffix, + "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, i, containerSuffix)))); + } + } + i++; + } + {{/uniqueItems}} + {{^uniqueItems}} + if ({{getter}}() != null) { + for (int i = 0; i < {{getter}}().size(); i++) { + if ({{getter}}().get(i) != null) { + joiner.add({{getter}}().get(i).toUrlQueryString(String.format("%s{{baseName}}%s%s", prefix, suffix, + "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, i, containerSuffix)))); + } + } + } + {{/uniqueItems}} + {{/items.isModel}} + {{^items.isModel}} + {{#uniqueItems}} + if ({{getter}}() != null) { + int i = 0; + for ({{{items.dataType}}} _item : {{getter}}()) { + if (_item != null) { + try { + joiner.add(String.format("%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, i, containerSuffix), + URLEncoder.encode(String.valueOf(_item), "UTF-8").replaceAll("\\+", "%20"))); + } catch (UnsupportedEncodingException e) { + // Should never happen, UTF-8 is always supported + throw new RuntimeException(e); + } + } + i++; + } + } + {{/uniqueItems}} + {{^uniqueItems}} + if ({{getter}}() != null) { + for (int i = 0; i < {{getter}}().size(); i++) { + if ({{getter}}().get(i) != null) { + try { + joiner.add(String.format("%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, i, containerSuffix), + URLEncoder.encode(String.valueOf({{getter}}().get(i)), "UTF-8").replaceAll("\\+", "%20"))); + } catch (UnsupportedEncodingException e) { + // Should never happen, UTF-8 is always supported + throw new RuntimeException(e); + } + } + } + } + {{/uniqueItems}} + {{/items.isModel}} + {{/items.isPrimitiveType}} + {{/isArray}} + {{^isArray}} + {{#isMap}} + {{^items.isModel}} + if ({{getter}}() != null) { + for (String _key : {{getter}}().keySet()) { + try { + joiner.add(String.format("%s{{baseName}}%s%s=%s", prefix, suffix, + "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, _key, containerSuffix), + {{getter}}().get(_key), URLEncoder.encode(String.valueOf({{getter}}().get(_key)), "UTF-8").replaceAll("\\+", "%20"))); + } catch (UnsupportedEncodingException e) { + // Should never happen, UTF-8 is always supported + throw new RuntimeException(e); + } + } + } + {{/items.isModel}} + {{#items.isModel}} + if ({{getter}}() != null) { + for (String _key : {{getter}}().keySet()) { + if ({{getter}}().get(_key) != null) { + joiner.add({{getter}}().get(_key).toUrlQueryString(String.format("%s{{baseName}}%s%s", prefix, suffix, + "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, _key, containerSuffix)))); + } + } + } + {{/items.isModel}} + {{/isMap}} + {{^isMap}} + {{#isPrimitiveType}} + if ({{getter}}() != null) { + try { + joiner.add(String.format("%s{{{baseName}}}%s=%s", prefix, suffix, URLEncoder.encode(String.valueOf({{{getter}}}()), "UTF-8").replaceAll("\\+", "%20"))); + } catch (UnsupportedEncodingException e) { + // Should never happen, UTF-8 is always supported + throw new RuntimeException(e); + } + } + {{/isPrimitiveType}} + {{^isPrimitiveType}} + {{#isModel}} + if ({{getter}}() != null) { + joiner.add({{getter}}().toUrlQueryString(prefix + "{{{baseName}}}" + suffix)); + } + {{/isModel}} + {{^isModel}} + if ({{getter}}() != null) { + try { + joiner.add(String.format("%s{{{baseName}}}%s=%s", prefix, suffix, URLEncoder.encode(String.valueOf({{{getter}}}()), "UTF-8").replaceAll("\\+", "%20"))); + } catch (UnsupportedEncodingException e) { + // Should never happen, UTF-8 is always supported + throw new RuntimeException(e); + } + } + {{/isModel}} + {{/isPrimitiveType}} + {{/isMap}} + {{/isArray}} + + {{/allVars}} + return joiner.toString(); + } +{{/supportUrlQuery}} +{{#parcelableModel}} + + public void writeToParcel(Parcel out, int flags) { +{{#model}} +{{#isArray}} + out.writeList(this); +{{/isArray}} +{{^isArray}} +{{#parent}} + super.writeToParcel(out, flags); +{{/parent}} +{{#vars}} + out.writeValue({{name}}); +{{/vars}} +{{/isArray}} +{{/model}} + } + + {{classname}}(Parcel in) { +{{#isArray}} + in.readTypedList(this, {{arrayModelType}}.CREATOR); +{{/isArray}} +{{^isArray}} +{{#parent}} + super(in); +{{/parent}} +{{#vars}} +{{#isPrimitiveType}} + {{name}} = ({{{datatypeWithEnum}}})in.readValue(null); +{{/isPrimitiveType}} +{{^isPrimitiveType}} + {{name}} = ({{{datatypeWithEnum}}})in.readValue({{complexType}}.class.getClassLoader()); +{{/isPrimitiveType}} +{{/vars}} +{{/isArray}} + } + + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator<{{classname}}> CREATOR = new Parcelable.Creator<{{classname}}>() { + public {{classname}} createFromParcel(Parcel in) { +{{#model}} +{{#isArray}} + {{classname}} result = new {{classname}}(); + result.addAll(in.readArrayList({{arrayModelType}}.class.getClassLoader())); + return result; +{{/isArray}} +{{^isArray}} + return new {{classname}}(in); +{{/isArray}} +{{/model}} + } + public {{classname}}[] newArray(int size) { + return new {{classname}}[size]; + } + }; +{{/parcelableModel}} + +} diff --git a/config/clients/java/template/Java/pojo_doc.mustache b/config/clients/java/template/Java/pojo_doc.mustache new file mode 100644 index 00000000..bae0bc48 --- /dev/null +++ b/config/clients/java/template/Java/pojo_doc.mustache @@ -0,0 +1,37 @@ +# {{#vendorExtensions.x-is-one-of-interface}}Interface {{/vendorExtensions.x-is-one-of-interface}}{{classname}} + +{{#description}}{{&description}} +{{/description}} +{{^vendorExtensions.x-is-one-of-interface}} + +## Properties + +| Name | Type | Description | Notes | +|------------ | ------------- | ------------- | -------------| +{{#vars}}|**{{name}}** | {{#isEnum}}[**{{datatypeWithEnum}}**](#{{datatypeWithEnum}}){{/isEnum}}{{^isEnum}}{{#isContainer}}{{#isArray}}{{#items}}{{#isModel}}[{{/isModel}}{{/items}}**{{baseType}}{{#items}}<{{dataType}}>**{{#isModel}}]({{^baseType}}{{dataType}}{{/baseType}}{{baseType}}.md){{/isModel}}{{/items}}{{/isArray}}{{#isMap}}{{#items}}{{#isModel}}[{{/isModel}}**Map<String, {{dataType}}>**{{#isModel}}]({{^baseType}}{{dataType}}{{/baseType}}{{baseType}}.md){{/isModel}}{{/items}}{{/isMap}}{{/isContainer}}{{^isContainer}}{{#isModel}}[{{/isModel}}**{{dataType}}**{{#isModel}}]({{^baseType}}{{dataType}}{{/baseType}}{{baseType}}.md){{/isModel}}{{/isContainer}}{{/isEnum}} | {{description}} | {{^required}} [optional]{{/required}}{{#isReadOnly}} [readonly]{{/isReadOnly}} | +{{/vars}} +{{#vars}}{{#isEnum}} + + +## Enum: {{datatypeWithEnum}} + +| Name | Value | +|---- | -----|{{#allowableValues}}{{#enumVars}} +| {{name}} | {{value}} |{{/enumVars}}{{/allowableValues}} +{{/isEnum}}{{/vars}} +{{#vendorExtensions.x-implements.0}} + +## Implemented Interfaces + +{{#vendorExtensions.x-implements}} +* {{{.}}} +{{/vendorExtensions.x-implements}} +{{/vendorExtensions.x-implements.0}} +{{/vendorExtensions.x-is-one-of-interface}} +{{#vendorExtensions.x-is-one-of-interface}} +## Implementing Classes + +{{#oneOf}} +* {{{.}}} +{{/oneOf}} +{{/vendorExtensions.x-is-one-of-interface}} diff --git a/config/clients/java/template/Java/pom.mustache b/config/clients/java/template/Java/pom.mustache new file mode 100644 index 00000000..0a1bb6a5 --- /dev/null +++ b/config/clients/java/template/Java/pom.mustache @@ -0,0 +1,359 @@ + + 4.0.0 + {{groupId}} + {{artifactId}} + jar + {{artifactId}} + {{artifactVersion}} + {{artifactUrl}} + {{artifactDescription}} + + {{scmConnection}} + {{scmDeveloperConnection}} + {{scmUrl}} + +{{#parentOverridden}} + + {{{parentGroupId}}} + {{{parentArtifactId}}} + {{{parentVersion}}} + +{{/parentOverridden}} + + + + {{licenseName}} + {{licenseUrl}} + repo + + + + + + {{developerName}} + {{developerEmail}} + {{developerOrganization}} + {{developerOrganizationUrl}} + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 1.8 + 1.8 + true + 128m + 512m + + -Xlint:all + -J-Xss4m + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + 3.0.0-M1 + + + enforce-maven + + enforce + + + + + 2.2.0 + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.12 + + + + loggerPath + conf/log4j.properties + + + -Xms512m -Xmx1500m + methods + pertest + + + + maven-dependency-plugin + + + package + + copy-dependencies + + + ${project.build.directory}/lib + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.2 + + + + jar + test-jar + + + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 1.10 + + + add_sources + generate-sources + + add-source + + + + src/main/java + + + + + add_test_sources + generate-test-sources + + add-test-source + + + + src/test/java + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.3.2 + + none + 1.8 + + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-source-plugin + 2.2.1 + + + attach-sources + + jar-no-fork + + + + + + + + + + sign-artifacts + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.5 + + + sign-artifacts + verify + + sign + + + + + + + + + + + {{#swagger1AnnotationLibrary}} + + io.swagger + swagger-annotations + ${swagger-annotations-version} + + {{/swagger1AnnotationLibrary}} + {{#swagger2AnnotationLibrary}} + + io.swagger.core.v3 + swagger-annotations + ${swagger-annotations-version} + + {{/swagger2AnnotationLibrary}} + + + + com.google.code.findbugs + jsr305 + 3.0.2 + + + + + + com.sun.jersey + jersey-client + ${jersey-version} + + + com.sun.jersey.contribs + jersey-multipart + ${jersey-version} + + + + + com.fasterxml.jackson.core + jackson-core + ${jackson-version} + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson-version} + + + com.fasterxml.jackson.core + jackson-databind + ${jackson-databind-version} + + + com.fasterxml.jackson.jaxrs + jackson-jaxrs-json-provider + ${jackson-version} + + {{#withXml}} + + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + ${jackson-version} + + + {{/withXml}} + {{#joda}} + + com.fasterxml.jackson.datatype + jackson-datatype-joda + ${jackson-version} + + {{/joda}} + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + ${jackson-version} + + {{#useBeanValidation}} + + + jakarta.validation + jakarta.validation-api + ${beanvalidation-version} + provided + + {{/useBeanValidation}} + {{#performBeanValidation}} + + + org.hibernate + hibernate-validator + 5.4.1.Final + + {{/performBeanValidation}} + {{#parcelableModel}} + + + com.google.android + android + 4.1.1.4 + provided + + {{/parcelableModel}} + + jakarta.annotation + jakarta.annotation-api + ${jakarta-annotation-version} + provided + + + + junit + junit + ${junit-version} + test + + + + UTF-8 + {{#swagger1AnnotationLibrary}} + 1.6.6 + {{/swagger1AnnotationLibrary}} + {{#swagger2AnnotationLibrary}} + 2.2.9 + {{/swagger2AnnotationLibrary}} + 1.19.4 + 2.12.6 + 2.12.6.1 + {{#useJakartaEe}} + 2.1.1 + {{/useJakartaEe}} + {{^useJakartaEe}} + 1.3.5 + {{/useJakartaEe}} + +{{#useBeanValidation}} + 2.0.2 +{{/useBeanValidation}} + 1.0.0 + 4.13.2 + + diff --git a/config/clients/java/template/Java/settings.gradle.mustache b/config/clients/java/template/Java/settings.gradle.mustache new file mode 100644 index 00000000..b8fd6c4c --- /dev/null +++ b/config/clients/java/template/Java/settings.gradle.mustache @@ -0,0 +1 @@ +rootProject.name = "{{artifactId}}" \ No newline at end of file diff --git a/config/clients/java/template/Java/travis.mustache b/config/clients/java/template/Java/travis.mustache new file mode 100644 index 00000000..1b6741c0 --- /dev/null +++ b/config/clients/java/template/Java/travis.mustache @@ -0,0 +1,22 @@ +# +# Generated by OpenAPI Generator: https://openapi-generator.tech +# +# Ref: https://docs.travis-ci.com/user/languages/java/ +# +language: java +jdk: + - openjdk12 + - openjdk11 + - openjdk10 + - openjdk9 + - openjdk8 +before_install: + # ensure gradlew has proper permission + - chmod a+x ./gradlew +script: + # test using maven + #- mvn test + # test using gradle + - gradle test + # test using sbt + # - sbt test diff --git a/config/clients/java/template/Java/typeInfoAnnotation.mustache b/config/clients/java/template/Java/typeInfoAnnotation.mustache new file mode 100644 index 00000000..c833321e --- /dev/null +++ b/config/clients/java/template/Java/typeInfoAnnotation.mustache @@ -0,0 +1,17 @@ +{{#jackson}} + +@JsonIgnoreProperties( + value = "{{{discriminator.propertyBaseName}}}", // ignore manually set {{{discriminator.propertyBaseName}}}, it will be automatically generated by Jackson during serialization + allowSetters = true // allows the {{{discriminator.propertyBaseName}}} to be set during deserialization +) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "{{{discriminator.propertyBaseName}}}", visible = true) +{{#discriminator.mappedModels}} +{{#-first}} +@JsonSubTypes({ +{{/-first}} + @JsonSubTypes.Type(value = {{modelName}}.class, name = "{{^vendorExtensions.x-discriminator-value}}{{mappingName}}{{/vendorExtensions.x-discriminator-value}}{{#vendorExtensions.x-discriminator-value}}{{{vendorExtensions.x-discriminator-value}}}{{/vendorExtensions.x-discriminator-value}}"), +{{#-last}} +}) +{{/-last}} +{{/discriminator.mappedModels}} +{{/jackson}} diff --git a/config/clients/java/template/Java/xmlAnnotation.mustache b/config/clients/java/template/Java/xmlAnnotation.mustache new file mode 100644 index 00000000..4f3b448c --- /dev/null +++ b/config/clients/java/template/Java/xmlAnnotation.mustache @@ -0,0 +1,6 @@ +{{#withXml}} + +@XmlRootElement({{#xmlNamespace}}namespace="{{.}}", {{/xmlNamespace}}name = "{{xmlName}}{{^xmlName}}{{classname}}{{/xmlName}}") +@XmlAccessorType(XmlAccessType.FIELD) +{{#jackson}} +@JacksonXmlRootElement({{#xmlNamespace}}namespace="{{.}}", {{/xmlNamespace}}localName = "{{xmlName}}{{^xmlName}}{{classname}}{{/xmlName}}"){{/jackson}}{{/withXml}} \ No newline at end of file diff --git a/config/clients/java/template/README_api_endpoints.mustache b/config/clients/java/template/README_api_endpoints.mustache new file mode 100644 index 00000000..e69de29b diff --git a/config/clients/java/template/README_calling_api.mustache b/config/clients/java/template/README_calling_api.mustache new file mode 100644 index 00000000..e69de29b diff --git a/config/clients/java/template/README_custom_badges.mustache b/config/clients/java/template/README_custom_badges.mustache new file mode 100644 index 00000000..e69de29b diff --git a/config/clients/java/template/README_initializing.mustache b/config/clients/java/template/README_initializing.mustache new file mode 100644 index 00000000..e69de29b diff --git a/config/clients/java/template/README_installation.mustache b/config/clients/java/template/README_installation.mustache new file mode 100644 index 00000000..e69de29b diff --git a/config/clients/java/template/README_license_disclaimer.mustache b/config/clients/java/template/README_license_disclaimer.mustache new file mode 100644 index 00000000..a297302a --- /dev/null +++ b/config/clients/java/template/README_license_disclaimer.mustache @@ -0,0 +1 @@ +The code in this repo was auto generated by [OpenAPI Generator](https://github.com/OpenAPITools/openapi-generator) from a template based on the [Java template](https://github.com/OpenAPITools/openapi-generator/tree/master/modules/openapi-generator/src/main/resources/Java), licensed under the [Apache License 2.0](https://github.com/OpenAPITools/openapi-generator/blob/master/LICENSE). diff --git a/config/clients/java/template/README_models.mustache b/config/clients/java/template/README_models.mustache new file mode 100644 index 00000000..cfea61fa --- /dev/null +++ b/config/clients/java/template/README_models.mustache @@ -0,0 +1 @@ +[Models](https://{{gitHost}}/{{gitUserId}}/{{gitRepoId}}/blob/main/apiModel.ts) diff --git a/config/clients/java/template/gitignore_custom.mustache b/config/clients/java/template/gitignore_custom.mustache new file mode 100644 index 00000000..e69de29b From 9bd577573e04cd5ab8933297a96a96b79354f7fa Mon Sep 17 00:00:00 2001 From: "Justin \"J.R.\" Hill" Date: Fri, 28 Jul 2023 09:08:30 -0700 Subject: [PATCH 2/9] fix(java): Fix incorrect license url --- config/clients/java/config.overrides.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/clients/java/config.overrides.json b/config/clients/java/config.overrides.json index 4ed4f6fb..8093463a 100644 --- a/config/clients/java/config.overrides.json +++ b/config/clients/java/config.overrides.json @@ -16,7 +16,7 @@ "developerOrganization": "OpenFGA", "developerOrganizationUrl": "https://openfga.dev", "licenseName": "Apache-2.0", - "licenseUrl": "https://github.com/openfga/dotnet-sdk/blob/main/LICENSE", + "licenseUrl": "https://github.com/openfga/java-sdk/blob/main/LICENSE", "scmConnection": "scm:git:git@github.com:openfga/java-sdk.git", "scmDeveloperConnection": "scm:git:git@github.com:openfga/java-sdk.git", "scmUrl": "https://github.com/openfga/java-sdk", From e2b44172fbab7f5dd8b6d629fc5fa232f47eb1d9 Mon Sep 17 00:00:00 2001 From: "Justin \"J.R.\" Hill" Date: Tue, 1 Aug 2023 10:10:48 -0700 Subject: [PATCH 3/9] fix(java): Fix mustache generation by moving files out of a 'Java' directory --- config/clients/java/config.overrides.json | 2 +- .../java/template/{Java => }/ApiClient.mustache | 0 .../{Java => }/BeanValidationException.mustache | 0 .../java/template/{Java => }/Configuration.mustache | 0 .../{Java => }/CustomInstantDeserializer.mustache | 0 .../clients/java/template/{Java => }/JSON.mustache | 0 .../java/template/Java/settings.gradle.mustache | 1 - .../template/{Java => }/JavaTimeFormatter.mustache | 0 .../clients/java/template/{Java => }/Pair.mustache | 0 .../java/template/{Java => }/README.mustache | 0 .../template/{Java => }/RFC3339DateFormat.mustache | 0 .../{Java => }/ServerConfiguration.mustache | 0 .../template/{Java => }/ServerVariable.mustache | 0 .../java/template/{Java => }/StringUtil.mustache | 0 .../additionalEnumTypeAnnotations.mustache | 0 .../additionalModelTypeAnnotations.mustache | 0 .../additionalOneOfTypeAnnotations.mustache | 0 .../clients/java/template/{Java => }/api.mustache | 0 .../java/template/{Java => }/apiException.mustache | 0 .../java/template/{Java => }/apiOperation.mustache | 0 .../java/template/{Java => }/api_doc.mustache | 0 .../java/template/{Java => }/api_test.mustache | 0 .../template/{Java => }/auth/ApiKeyAuth.mustache | 0 .../{Java => }/auth/Authentication.mustache | 0 .../template/{Java => }/auth/HttpBasicAuth.mustache | 0 .../{Java => }/auth/HttpBearerAuth.mustache | 0 .../java/template/{Java => }/auth/OAuth.mustache | 0 .../template/{Java => }/auth/OAuthFlow.mustache | 0 .../template/{Java => }/beanValidation.mustache | 0 .../template/{Java => }/beanValidationCore.mustache | 0 .../{Java => }/beanValidationQueryParams.mustache | 0 .../java/template/{Java => }/build.gradle.mustache | 0 .../template/{Java => }/enum_outer_doc.mustache | 0 .../{Java => }/generatedAnnotation.mustache | 0 .../java/template/{Java => }/git_push.sh.mustache | 0 .../java/template/{Java => }/gitignore.mustache | 0 .../java/template/{Java => }/gradle-wrapper.jar | Bin .../{Java => }/gradle-wrapper.properties.mustache | 0 .../template/{Java => }/gradle.properties.mustache | 0 .../java/template/{Java => }/gradlew.bat.mustache | 0 .../java/template/{Java => }/gradlew.mustache | 0 .../{Java => }/jackson_annotations.mustache | 0 .../libraries/native/AbstractOpenApiSchema.mustache | 0 .../{Java => }/libraries/native/ApiClient.mustache | 0 .../libraries/native/ApiResponse.mustache | 0 .../{Java => }/libraries/native/JSON.mustache | 0 .../{Java => }/libraries/native/README.mustache | 0 .../libraries/native/additional_properties.mustache | 0 .../libraries/native/anyof_model.mustache | 0 .../{Java => }/libraries/native/api.mustache | 0 .../libraries/native/apiException.mustache | 0 .../{Java => }/libraries/native/api_doc.mustache | 0 .../{Java => }/libraries/native/api_test.mustache | 0 .../libraries/native/generatedAnnotation.mustache | 0 .../libraries/native/gradle.properties.mustache | 0 .../{Java => }/libraries/native/model.mustache | 0 .../{Java => }/libraries/native/modelEnum.mustache | 0 .../libraries/native/model_anyof_doc.mustache | 0 .../{Java => }/libraries/native/model_doc.mustache | 0 .../libraries/native/model_oneof_doc.mustache | 0 .../libraries/native/oneof_model.mustache | 0 .../{Java => }/libraries/native/pojo.mustache | 0 .../{Java => }/libraries/native/pom.mustache | 0 .../{Java => }/libraries/native/travis.mustache | 0 .../java/template/{Java => }/licenseInfo.mustache | 0 .../java/template/{Java => }/manifest.mustache | 0 .../java/template/{Java => }/maven.yml.mustache | 0 .../clients/java/template/{Java => }/model.mustache | 0 .../java/template/{Java => }/modelEnum.mustache | 0 .../template/{Java => }/modelInnerEnum.mustache | 0 .../java/template/{Java => }/model_doc.mustache | 0 .../java/template/{Java => }/model_test.mustache | 0 .../template/{Java => }/oneof_interface.mustache | 0 .../java/template/{Java => }/openapi.mustache | 0 .../clients/java/template/{Java => }/pojo.mustache | 0 .../java/template/{Java => }/pojo_doc.mustache | 0 .../clients/java/template/{Java => }/pom.mustache | 0 .../clients/java/template/settings.gradle.mustache | 1 + .../java/template/{Java => }/travis.mustache | 0 .../template/{Java => }/typeInfoAnnotation.mustache | 0 .../java/template/{Java => }/xmlAnnotation.mustache | 0 81 files changed, 2 insertions(+), 2 deletions(-) rename config/clients/java/template/{Java => }/ApiClient.mustache (100%) rename config/clients/java/template/{Java => }/BeanValidationException.mustache (100%) rename config/clients/java/template/{Java => }/Configuration.mustache (100%) rename config/clients/java/template/{Java => }/CustomInstantDeserializer.mustache (100%) rename config/clients/java/template/{Java => }/JSON.mustache (100%) delete mode 100644 config/clients/java/template/Java/settings.gradle.mustache rename config/clients/java/template/{Java => }/JavaTimeFormatter.mustache (100%) rename config/clients/java/template/{Java => }/Pair.mustache (100%) rename config/clients/java/template/{Java => }/README.mustache (100%) rename config/clients/java/template/{Java => }/RFC3339DateFormat.mustache (100%) rename config/clients/java/template/{Java => }/ServerConfiguration.mustache (100%) rename config/clients/java/template/{Java => }/ServerVariable.mustache (100%) rename config/clients/java/template/{Java => }/StringUtil.mustache (100%) rename config/clients/java/template/{Java => }/additionalEnumTypeAnnotations.mustache (100%) rename config/clients/java/template/{Java => }/additionalModelTypeAnnotations.mustache (100%) rename config/clients/java/template/{Java => }/additionalOneOfTypeAnnotations.mustache (100%) rename config/clients/java/template/{Java => }/api.mustache (100%) rename config/clients/java/template/{Java => }/apiException.mustache (100%) rename config/clients/java/template/{Java => }/apiOperation.mustache (100%) rename config/clients/java/template/{Java => }/api_doc.mustache (100%) rename config/clients/java/template/{Java => }/api_test.mustache (100%) rename config/clients/java/template/{Java => }/auth/ApiKeyAuth.mustache (100%) rename config/clients/java/template/{Java => }/auth/Authentication.mustache (100%) rename config/clients/java/template/{Java => }/auth/HttpBasicAuth.mustache (100%) rename config/clients/java/template/{Java => }/auth/HttpBearerAuth.mustache (100%) rename config/clients/java/template/{Java => }/auth/OAuth.mustache (100%) rename config/clients/java/template/{Java => }/auth/OAuthFlow.mustache (100%) rename config/clients/java/template/{Java => }/beanValidation.mustache (100%) rename config/clients/java/template/{Java => }/beanValidationCore.mustache (100%) rename config/clients/java/template/{Java => }/beanValidationQueryParams.mustache (100%) rename config/clients/java/template/{Java => }/build.gradle.mustache (100%) rename config/clients/java/template/{Java => }/enum_outer_doc.mustache (100%) rename config/clients/java/template/{Java => }/generatedAnnotation.mustache (100%) rename config/clients/java/template/{Java => }/git_push.sh.mustache (100%) rename config/clients/java/template/{Java => }/gitignore.mustache (100%) rename config/clients/java/template/{Java => }/gradle-wrapper.jar (100%) rename config/clients/java/template/{Java => }/gradle-wrapper.properties.mustache (100%) rename config/clients/java/template/{Java => }/gradle.properties.mustache (100%) rename config/clients/java/template/{Java => }/gradlew.bat.mustache (100%) rename config/clients/java/template/{Java => }/gradlew.mustache (100%) rename config/clients/java/template/{Java => }/jackson_annotations.mustache (100%) rename config/clients/java/template/{Java => }/libraries/native/AbstractOpenApiSchema.mustache (100%) rename config/clients/java/template/{Java => }/libraries/native/ApiClient.mustache (100%) rename config/clients/java/template/{Java => }/libraries/native/ApiResponse.mustache (100%) rename config/clients/java/template/{Java => }/libraries/native/JSON.mustache (100%) rename config/clients/java/template/{Java => }/libraries/native/README.mustache (100%) rename config/clients/java/template/{Java => }/libraries/native/additional_properties.mustache (100%) rename config/clients/java/template/{Java => }/libraries/native/anyof_model.mustache (100%) rename config/clients/java/template/{Java => }/libraries/native/api.mustache (100%) rename config/clients/java/template/{Java => }/libraries/native/apiException.mustache (100%) rename config/clients/java/template/{Java => }/libraries/native/api_doc.mustache (100%) rename config/clients/java/template/{Java => }/libraries/native/api_test.mustache (100%) rename config/clients/java/template/{Java => }/libraries/native/generatedAnnotation.mustache (100%) rename config/clients/java/template/{Java => }/libraries/native/gradle.properties.mustache (100%) rename config/clients/java/template/{Java => }/libraries/native/model.mustache (100%) rename config/clients/java/template/{Java => }/libraries/native/modelEnum.mustache (100%) rename config/clients/java/template/{Java => }/libraries/native/model_anyof_doc.mustache (100%) rename config/clients/java/template/{Java => }/libraries/native/model_doc.mustache (100%) rename config/clients/java/template/{Java => }/libraries/native/model_oneof_doc.mustache (100%) rename config/clients/java/template/{Java => }/libraries/native/oneof_model.mustache (100%) rename config/clients/java/template/{Java => }/libraries/native/pojo.mustache (100%) rename config/clients/java/template/{Java => }/libraries/native/pom.mustache (100%) rename config/clients/java/template/{Java => }/libraries/native/travis.mustache (100%) rename config/clients/java/template/{Java => }/licenseInfo.mustache (100%) rename config/clients/java/template/{Java => }/manifest.mustache (100%) rename config/clients/java/template/{Java => }/maven.yml.mustache (100%) rename config/clients/java/template/{Java => }/model.mustache (100%) rename config/clients/java/template/{Java => }/modelEnum.mustache (100%) rename config/clients/java/template/{Java => }/modelInnerEnum.mustache (100%) rename config/clients/java/template/{Java => }/model_doc.mustache (100%) rename config/clients/java/template/{Java => }/model_test.mustache (100%) rename config/clients/java/template/{Java => }/oneof_interface.mustache (100%) rename config/clients/java/template/{Java => }/openapi.mustache (100%) rename config/clients/java/template/{Java => }/pojo.mustache (100%) rename config/clients/java/template/{Java => }/pojo_doc.mustache (100%) rename config/clients/java/template/{Java => }/pom.mustache (100%) create mode 100644 config/clients/java/template/settings.gradle.mustache rename config/clients/java/template/{Java => }/travis.mustache (100%) rename config/clients/java/template/{Java => }/typeInfoAnnotation.mustache (100%) rename config/clients/java/template/{Java => }/xmlAnnotation.mustache (100%) diff --git a/config/clients/java/config.overrides.json b/config/clients/java/config.overrides.json index 8093463a..ba37467b 100644 --- a/config/clients/java/config.overrides.json +++ b/config/clients/java/config.overrides.json @@ -26,7 +26,7 @@ "allowUnicodeIdentifiers": true, "caseInsensitiveResponseHeaders": true, "files": { - "Java/build.gradle.mustache" : { + "build.gradle.mustache" : { "destinationFilename": "build.gradle", "templateType": "SupportingFiles" } diff --git a/config/clients/java/template/Java/ApiClient.mustache b/config/clients/java/template/ApiClient.mustache similarity index 100% rename from config/clients/java/template/Java/ApiClient.mustache rename to config/clients/java/template/ApiClient.mustache diff --git a/config/clients/java/template/Java/BeanValidationException.mustache b/config/clients/java/template/BeanValidationException.mustache similarity index 100% rename from config/clients/java/template/Java/BeanValidationException.mustache rename to config/clients/java/template/BeanValidationException.mustache diff --git a/config/clients/java/template/Java/Configuration.mustache b/config/clients/java/template/Configuration.mustache similarity index 100% rename from config/clients/java/template/Java/Configuration.mustache rename to config/clients/java/template/Configuration.mustache diff --git a/config/clients/java/template/Java/CustomInstantDeserializer.mustache b/config/clients/java/template/CustomInstantDeserializer.mustache similarity index 100% rename from config/clients/java/template/Java/CustomInstantDeserializer.mustache rename to config/clients/java/template/CustomInstantDeserializer.mustache diff --git a/config/clients/java/template/Java/JSON.mustache b/config/clients/java/template/JSON.mustache similarity index 100% rename from config/clients/java/template/Java/JSON.mustache rename to config/clients/java/template/JSON.mustache diff --git a/config/clients/java/template/Java/settings.gradle.mustache b/config/clients/java/template/Java/settings.gradle.mustache deleted file mode 100644 index b8fd6c4c..00000000 --- a/config/clients/java/template/Java/settings.gradle.mustache +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = "{{artifactId}}" \ No newline at end of file diff --git a/config/clients/java/template/Java/JavaTimeFormatter.mustache b/config/clients/java/template/JavaTimeFormatter.mustache similarity index 100% rename from config/clients/java/template/Java/JavaTimeFormatter.mustache rename to config/clients/java/template/JavaTimeFormatter.mustache diff --git a/config/clients/java/template/Java/Pair.mustache b/config/clients/java/template/Pair.mustache similarity index 100% rename from config/clients/java/template/Java/Pair.mustache rename to config/clients/java/template/Pair.mustache diff --git a/config/clients/java/template/Java/README.mustache b/config/clients/java/template/README.mustache similarity index 100% rename from config/clients/java/template/Java/README.mustache rename to config/clients/java/template/README.mustache diff --git a/config/clients/java/template/Java/RFC3339DateFormat.mustache b/config/clients/java/template/RFC3339DateFormat.mustache similarity index 100% rename from config/clients/java/template/Java/RFC3339DateFormat.mustache rename to config/clients/java/template/RFC3339DateFormat.mustache diff --git a/config/clients/java/template/Java/ServerConfiguration.mustache b/config/clients/java/template/ServerConfiguration.mustache similarity index 100% rename from config/clients/java/template/Java/ServerConfiguration.mustache rename to config/clients/java/template/ServerConfiguration.mustache diff --git a/config/clients/java/template/Java/ServerVariable.mustache b/config/clients/java/template/ServerVariable.mustache similarity index 100% rename from config/clients/java/template/Java/ServerVariable.mustache rename to config/clients/java/template/ServerVariable.mustache diff --git a/config/clients/java/template/Java/StringUtil.mustache b/config/clients/java/template/StringUtil.mustache similarity index 100% rename from config/clients/java/template/Java/StringUtil.mustache rename to config/clients/java/template/StringUtil.mustache diff --git a/config/clients/java/template/Java/additionalEnumTypeAnnotations.mustache b/config/clients/java/template/additionalEnumTypeAnnotations.mustache similarity index 100% rename from config/clients/java/template/Java/additionalEnumTypeAnnotations.mustache rename to config/clients/java/template/additionalEnumTypeAnnotations.mustache diff --git a/config/clients/java/template/Java/additionalModelTypeAnnotations.mustache b/config/clients/java/template/additionalModelTypeAnnotations.mustache similarity index 100% rename from config/clients/java/template/Java/additionalModelTypeAnnotations.mustache rename to config/clients/java/template/additionalModelTypeAnnotations.mustache diff --git a/config/clients/java/template/Java/additionalOneOfTypeAnnotations.mustache b/config/clients/java/template/additionalOneOfTypeAnnotations.mustache similarity index 100% rename from config/clients/java/template/Java/additionalOneOfTypeAnnotations.mustache rename to config/clients/java/template/additionalOneOfTypeAnnotations.mustache diff --git a/config/clients/java/template/Java/api.mustache b/config/clients/java/template/api.mustache similarity index 100% rename from config/clients/java/template/Java/api.mustache rename to config/clients/java/template/api.mustache diff --git a/config/clients/java/template/Java/apiException.mustache b/config/clients/java/template/apiException.mustache similarity index 100% rename from config/clients/java/template/Java/apiException.mustache rename to config/clients/java/template/apiException.mustache diff --git a/config/clients/java/template/Java/apiOperation.mustache b/config/clients/java/template/apiOperation.mustache similarity index 100% rename from config/clients/java/template/Java/apiOperation.mustache rename to config/clients/java/template/apiOperation.mustache diff --git a/config/clients/java/template/Java/api_doc.mustache b/config/clients/java/template/api_doc.mustache similarity index 100% rename from config/clients/java/template/Java/api_doc.mustache rename to config/clients/java/template/api_doc.mustache diff --git a/config/clients/java/template/Java/api_test.mustache b/config/clients/java/template/api_test.mustache similarity index 100% rename from config/clients/java/template/Java/api_test.mustache rename to config/clients/java/template/api_test.mustache diff --git a/config/clients/java/template/Java/auth/ApiKeyAuth.mustache b/config/clients/java/template/auth/ApiKeyAuth.mustache similarity index 100% rename from config/clients/java/template/Java/auth/ApiKeyAuth.mustache rename to config/clients/java/template/auth/ApiKeyAuth.mustache diff --git a/config/clients/java/template/Java/auth/Authentication.mustache b/config/clients/java/template/auth/Authentication.mustache similarity index 100% rename from config/clients/java/template/Java/auth/Authentication.mustache rename to config/clients/java/template/auth/Authentication.mustache diff --git a/config/clients/java/template/Java/auth/HttpBasicAuth.mustache b/config/clients/java/template/auth/HttpBasicAuth.mustache similarity index 100% rename from config/clients/java/template/Java/auth/HttpBasicAuth.mustache rename to config/clients/java/template/auth/HttpBasicAuth.mustache diff --git a/config/clients/java/template/Java/auth/HttpBearerAuth.mustache b/config/clients/java/template/auth/HttpBearerAuth.mustache similarity index 100% rename from config/clients/java/template/Java/auth/HttpBearerAuth.mustache rename to config/clients/java/template/auth/HttpBearerAuth.mustache diff --git a/config/clients/java/template/Java/auth/OAuth.mustache b/config/clients/java/template/auth/OAuth.mustache similarity index 100% rename from config/clients/java/template/Java/auth/OAuth.mustache rename to config/clients/java/template/auth/OAuth.mustache diff --git a/config/clients/java/template/Java/auth/OAuthFlow.mustache b/config/clients/java/template/auth/OAuthFlow.mustache similarity index 100% rename from config/clients/java/template/Java/auth/OAuthFlow.mustache rename to config/clients/java/template/auth/OAuthFlow.mustache diff --git a/config/clients/java/template/Java/beanValidation.mustache b/config/clients/java/template/beanValidation.mustache similarity index 100% rename from config/clients/java/template/Java/beanValidation.mustache rename to config/clients/java/template/beanValidation.mustache diff --git a/config/clients/java/template/Java/beanValidationCore.mustache b/config/clients/java/template/beanValidationCore.mustache similarity index 100% rename from config/clients/java/template/Java/beanValidationCore.mustache rename to config/clients/java/template/beanValidationCore.mustache diff --git a/config/clients/java/template/Java/beanValidationQueryParams.mustache b/config/clients/java/template/beanValidationQueryParams.mustache similarity index 100% rename from config/clients/java/template/Java/beanValidationQueryParams.mustache rename to config/clients/java/template/beanValidationQueryParams.mustache diff --git a/config/clients/java/template/Java/build.gradle.mustache b/config/clients/java/template/build.gradle.mustache similarity index 100% rename from config/clients/java/template/Java/build.gradle.mustache rename to config/clients/java/template/build.gradle.mustache diff --git a/config/clients/java/template/Java/enum_outer_doc.mustache b/config/clients/java/template/enum_outer_doc.mustache similarity index 100% rename from config/clients/java/template/Java/enum_outer_doc.mustache rename to config/clients/java/template/enum_outer_doc.mustache diff --git a/config/clients/java/template/Java/generatedAnnotation.mustache b/config/clients/java/template/generatedAnnotation.mustache similarity index 100% rename from config/clients/java/template/Java/generatedAnnotation.mustache rename to config/clients/java/template/generatedAnnotation.mustache diff --git a/config/clients/java/template/Java/git_push.sh.mustache b/config/clients/java/template/git_push.sh.mustache similarity index 100% rename from config/clients/java/template/Java/git_push.sh.mustache rename to config/clients/java/template/git_push.sh.mustache diff --git a/config/clients/java/template/Java/gitignore.mustache b/config/clients/java/template/gitignore.mustache similarity index 100% rename from config/clients/java/template/Java/gitignore.mustache rename to config/clients/java/template/gitignore.mustache diff --git a/config/clients/java/template/Java/gradle-wrapper.jar b/config/clients/java/template/gradle-wrapper.jar similarity index 100% rename from config/clients/java/template/Java/gradle-wrapper.jar rename to config/clients/java/template/gradle-wrapper.jar diff --git a/config/clients/java/template/Java/gradle-wrapper.properties.mustache b/config/clients/java/template/gradle-wrapper.properties.mustache similarity index 100% rename from config/clients/java/template/Java/gradle-wrapper.properties.mustache rename to config/clients/java/template/gradle-wrapper.properties.mustache diff --git a/config/clients/java/template/Java/gradle.properties.mustache b/config/clients/java/template/gradle.properties.mustache similarity index 100% rename from config/clients/java/template/Java/gradle.properties.mustache rename to config/clients/java/template/gradle.properties.mustache diff --git a/config/clients/java/template/Java/gradlew.bat.mustache b/config/clients/java/template/gradlew.bat.mustache similarity index 100% rename from config/clients/java/template/Java/gradlew.bat.mustache rename to config/clients/java/template/gradlew.bat.mustache diff --git a/config/clients/java/template/Java/gradlew.mustache b/config/clients/java/template/gradlew.mustache similarity index 100% rename from config/clients/java/template/Java/gradlew.mustache rename to config/clients/java/template/gradlew.mustache diff --git a/config/clients/java/template/Java/jackson_annotations.mustache b/config/clients/java/template/jackson_annotations.mustache similarity index 100% rename from config/clients/java/template/Java/jackson_annotations.mustache rename to config/clients/java/template/jackson_annotations.mustache diff --git a/config/clients/java/template/Java/libraries/native/AbstractOpenApiSchema.mustache b/config/clients/java/template/libraries/native/AbstractOpenApiSchema.mustache similarity index 100% rename from config/clients/java/template/Java/libraries/native/AbstractOpenApiSchema.mustache rename to config/clients/java/template/libraries/native/AbstractOpenApiSchema.mustache diff --git a/config/clients/java/template/Java/libraries/native/ApiClient.mustache b/config/clients/java/template/libraries/native/ApiClient.mustache similarity index 100% rename from config/clients/java/template/Java/libraries/native/ApiClient.mustache rename to config/clients/java/template/libraries/native/ApiClient.mustache diff --git a/config/clients/java/template/Java/libraries/native/ApiResponse.mustache b/config/clients/java/template/libraries/native/ApiResponse.mustache similarity index 100% rename from config/clients/java/template/Java/libraries/native/ApiResponse.mustache rename to config/clients/java/template/libraries/native/ApiResponse.mustache diff --git a/config/clients/java/template/Java/libraries/native/JSON.mustache b/config/clients/java/template/libraries/native/JSON.mustache similarity index 100% rename from config/clients/java/template/Java/libraries/native/JSON.mustache rename to config/clients/java/template/libraries/native/JSON.mustache diff --git a/config/clients/java/template/Java/libraries/native/README.mustache b/config/clients/java/template/libraries/native/README.mustache similarity index 100% rename from config/clients/java/template/Java/libraries/native/README.mustache rename to config/clients/java/template/libraries/native/README.mustache diff --git a/config/clients/java/template/Java/libraries/native/additional_properties.mustache b/config/clients/java/template/libraries/native/additional_properties.mustache similarity index 100% rename from config/clients/java/template/Java/libraries/native/additional_properties.mustache rename to config/clients/java/template/libraries/native/additional_properties.mustache diff --git a/config/clients/java/template/Java/libraries/native/anyof_model.mustache b/config/clients/java/template/libraries/native/anyof_model.mustache similarity index 100% rename from config/clients/java/template/Java/libraries/native/anyof_model.mustache rename to config/clients/java/template/libraries/native/anyof_model.mustache diff --git a/config/clients/java/template/Java/libraries/native/api.mustache b/config/clients/java/template/libraries/native/api.mustache similarity index 100% rename from config/clients/java/template/Java/libraries/native/api.mustache rename to config/clients/java/template/libraries/native/api.mustache diff --git a/config/clients/java/template/Java/libraries/native/apiException.mustache b/config/clients/java/template/libraries/native/apiException.mustache similarity index 100% rename from config/clients/java/template/Java/libraries/native/apiException.mustache rename to config/clients/java/template/libraries/native/apiException.mustache diff --git a/config/clients/java/template/Java/libraries/native/api_doc.mustache b/config/clients/java/template/libraries/native/api_doc.mustache similarity index 100% rename from config/clients/java/template/Java/libraries/native/api_doc.mustache rename to config/clients/java/template/libraries/native/api_doc.mustache diff --git a/config/clients/java/template/Java/libraries/native/api_test.mustache b/config/clients/java/template/libraries/native/api_test.mustache similarity index 100% rename from config/clients/java/template/Java/libraries/native/api_test.mustache rename to config/clients/java/template/libraries/native/api_test.mustache diff --git a/config/clients/java/template/Java/libraries/native/generatedAnnotation.mustache b/config/clients/java/template/libraries/native/generatedAnnotation.mustache similarity index 100% rename from config/clients/java/template/Java/libraries/native/generatedAnnotation.mustache rename to config/clients/java/template/libraries/native/generatedAnnotation.mustache diff --git a/config/clients/java/template/Java/libraries/native/gradle.properties.mustache b/config/clients/java/template/libraries/native/gradle.properties.mustache similarity index 100% rename from config/clients/java/template/Java/libraries/native/gradle.properties.mustache rename to config/clients/java/template/libraries/native/gradle.properties.mustache diff --git a/config/clients/java/template/Java/libraries/native/model.mustache b/config/clients/java/template/libraries/native/model.mustache similarity index 100% rename from config/clients/java/template/Java/libraries/native/model.mustache rename to config/clients/java/template/libraries/native/model.mustache diff --git a/config/clients/java/template/Java/libraries/native/modelEnum.mustache b/config/clients/java/template/libraries/native/modelEnum.mustache similarity index 100% rename from config/clients/java/template/Java/libraries/native/modelEnum.mustache rename to config/clients/java/template/libraries/native/modelEnum.mustache diff --git a/config/clients/java/template/Java/libraries/native/model_anyof_doc.mustache b/config/clients/java/template/libraries/native/model_anyof_doc.mustache similarity index 100% rename from config/clients/java/template/Java/libraries/native/model_anyof_doc.mustache rename to config/clients/java/template/libraries/native/model_anyof_doc.mustache diff --git a/config/clients/java/template/Java/libraries/native/model_doc.mustache b/config/clients/java/template/libraries/native/model_doc.mustache similarity index 100% rename from config/clients/java/template/Java/libraries/native/model_doc.mustache rename to config/clients/java/template/libraries/native/model_doc.mustache diff --git a/config/clients/java/template/Java/libraries/native/model_oneof_doc.mustache b/config/clients/java/template/libraries/native/model_oneof_doc.mustache similarity index 100% rename from config/clients/java/template/Java/libraries/native/model_oneof_doc.mustache rename to config/clients/java/template/libraries/native/model_oneof_doc.mustache diff --git a/config/clients/java/template/Java/libraries/native/oneof_model.mustache b/config/clients/java/template/libraries/native/oneof_model.mustache similarity index 100% rename from config/clients/java/template/Java/libraries/native/oneof_model.mustache rename to config/clients/java/template/libraries/native/oneof_model.mustache diff --git a/config/clients/java/template/Java/libraries/native/pojo.mustache b/config/clients/java/template/libraries/native/pojo.mustache similarity index 100% rename from config/clients/java/template/Java/libraries/native/pojo.mustache rename to config/clients/java/template/libraries/native/pojo.mustache diff --git a/config/clients/java/template/Java/libraries/native/pom.mustache b/config/clients/java/template/libraries/native/pom.mustache similarity index 100% rename from config/clients/java/template/Java/libraries/native/pom.mustache rename to config/clients/java/template/libraries/native/pom.mustache diff --git a/config/clients/java/template/Java/libraries/native/travis.mustache b/config/clients/java/template/libraries/native/travis.mustache similarity index 100% rename from config/clients/java/template/Java/libraries/native/travis.mustache rename to config/clients/java/template/libraries/native/travis.mustache diff --git a/config/clients/java/template/Java/licenseInfo.mustache b/config/clients/java/template/licenseInfo.mustache similarity index 100% rename from config/clients/java/template/Java/licenseInfo.mustache rename to config/clients/java/template/licenseInfo.mustache diff --git a/config/clients/java/template/Java/manifest.mustache b/config/clients/java/template/manifest.mustache similarity index 100% rename from config/clients/java/template/Java/manifest.mustache rename to config/clients/java/template/manifest.mustache diff --git a/config/clients/java/template/Java/maven.yml.mustache b/config/clients/java/template/maven.yml.mustache similarity index 100% rename from config/clients/java/template/Java/maven.yml.mustache rename to config/clients/java/template/maven.yml.mustache diff --git a/config/clients/java/template/Java/model.mustache b/config/clients/java/template/model.mustache similarity index 100% rename from config/clients/java/template/Java/model.mustache rename to config/clients/java/template/model.mustache diff --git a/config/clients/java/template/Java/modelEnum.mustache b/config/clients/java/template/modelEnum.mustache similarity index 100% rename from config/clients/java/template/Java/modelEnum.mustache rename to config/clients/java/template/modelEnum.mustache diff --git a/config/clients/java/template/Java/modelInnerEnum.mustache b/config/clients/java/template/modelInnerEnum.mustache similarity index 100% rename from config/clients/java/template/Java/modelInnerEnum.mustache rename to config/clients/java/template/modelInnerEnum.mustache diff --git a/config/clients/java/template/Java/model_doc.mustache b/config/clients/java/template/model_doc.mustache similarity index 100% rename from config/clients/java/template/Java/model_doc.mustache rename to config/clients/java/template/model_doc.mustache diff --git a/config/clients/java/template/Java/model_test.mustache b/config/clients/java/template/model_test.mustache similarity index 100% rename from config/clients/java/template/Java/model_test.mustache rename to config/clients/java/template/model_test.mustache diff --git a/config/clients/java/template/Java/oneof_interface.mustache b/config/clients/java/template/oneof_interface.mustache similarity index 100% rename from config/clients/java/template/Java/oneof_interface.mustache rename to config/clients/java/template/oneof_interface.mustache diff --git a/config/clients/java/template/Java/openapi.mustache b/config/clients/java/template/openapi.mustache similarity index 100% rename from config/clients/java/template/Java/openapi.mustache rename to config/clients/java/template/openapi.mustache diff --git a/config/clients/java/template/Java/pojo.mustache b/config/clients/java/template/pojo.mustache similarity index 100% rename from config/clients/java/template/Java/pojo.mustache rename to config/clients/java/template/pojo.mustache diff --git a/config/clients/java/template/Java/pojo_doc.mustache b/config/clients/java/template/pojo_doc.mustache similarity index 100% rename from config/clients/java/template/Java/pojo_doc.mustache rename to config/clients/java/template/pojo_doc.mustache diff --git a/config/clients/java/template/Java/pom.mustache b/config/clients/java/template/pom.mustache similarity index 100% rename from config/clients/java/template/Java/pom.mustache rename to config/clients/java/template/pom.mustache diff --git a/config/clients/java/template/settings.gradle.mustache b/config/clients/java/template/settings.gradle.mustache new file mode 100644 index 00000000..448dc076 --- /dev/null +++ b/config/clients/java/template/settings.gradle.mustache @@ -0,0 +1 @@ +rootProject.name = '{{artifactId}}' \ No newline at end of file diff --git a/config/clients/java/template/Java/travis.mustache b/config/clients/java/template/travis.mustache similarity index 100% rename from config/clients/java/template/Java/travis.mustache rename to config/clients/java/template/travis.mustache diff --git a/config/clients/java/template/Java/typeInfoAnnotation.mustache b/config/clients/java/template/typeInfoAnnotation.mustache similarity index 100% rename from config/clients/java/template/Java/typeInfoAnnotation.mustache rename to config/clients/java/template/typeInfoAnnotation.mustache diff --git a/config/clients/java/template/Java/xmlAnnotation.mustache b/config/clients/java/template/xmlAnnotation.mustache similarity index 100% rename from config/clients/java/template/Java/xmlAnnotation.mustache rename to config/clients/java/template/xmlAnnotation.mustache From 0ab6f22336e43d0d376dab66c3761cedbf0227e7 Mon Sep 17 00:00:00 2001 From: "Justin \"J.R.\" Hill" Date: Tue, 1 Aug 2023 10:17:19 -0700 Subject: [PATCH 4/9] clean(java): Remove base template files also present in libraries/native --- .../clients/java/template/ApiClient.mustache | 887 ------------------ config/clients/java/template/JSON.mustache | 539 ----------- config/clients/java/template/README.mustache | 246 ----- config/clients/java/template/api.mustache | 120 --- .../java/template/apiException.mustache | 89 -- config/clients/java/template/api_doc.mustache | 108 --- .../clients/java/template/api_test.mustache | 51 - .../template/generatedAnnotation.mustache | 1 - .../java/template/gradle.properties.mustache | 9 - .../template/libraries/native/pom.mustache | 299 ------ config/clients/java/template/model.mustache | 68 -- .../clients/java/template/modelEnum.mustache | 117 --- .../clients/java/template/model_doc.mustache | 4 - config/clients/java/template/pojo.mustache | 617 ------------ config/clients/java/template/pom.mustache | 359 ------- config/clients/java/template/travis.mustache | 22 - 16 files changed, 3536 deletions(-) delete mode 100644 config/clients/java/template/ApiClient.mustache delete mode 100644 config/clients/java/template/JSON.mustache delete mode 100644 config/clients/java/template/README.mustache delete mode 100644 config/clients/java/template/api.mustache delete mode 100644 config/clients/java/template/apiException.mustache delete mode 100644 config/clients/java/template/api_doc.mustache delete mode 100644 config/clients/java/template/api_test.mustache delete mode 100644 config/clients/java/template/generatedAnnotation.mustache delete mode 100644 config/clients/java/template/gradle.properties.mustache delete mode 100644 config/clients/java/template/libraries/native/pom.mustache delete mode 100644 config/clients/java/template/model.mustache delete mode 100644 config/clients/java/template/modelEnum.mustache delete mode 100644 config/clients/java/template/model_doc.mustache delete mode 100644 config/clients/java/template/pojo.mustache delete mode 100644 config/clients/java/template/pom.mustache delete mode 100644 config/clients/java/template/travis.mustache diff --git a/config/clients/java/template/ApiClient.mustache b/config/clients/java/template/ApiClient.mustache deleted file mode 100644 index ed5012fc..00000000 --- a/config/clients/java/template/ApiClient.mustache +++ /dev/null @@ -1,887 +0,0 @@ -{{>licenseInfo}} -package {{invokerPackage}}; - -import com.fasterxml.jackson.annotation.*; -import com.fasterxml.jackson.databind.*; -{{#joda}} -import com.fasterxml.jackson.datatype.joda.JodaModule; -{{/joda}} -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import java.time.OffsetDateTime; -import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider; - -import com.sun.jersey.api.client.Client; -import com.sun.jersey.api.client.ClientResponse; -import com.sun.jersey.api.client.GenericType; -import com.sun.jersey.api.client.config.DefaultClientConfig; -import com.sun.jersey.api.client.filter.GZIPContentEncodingFilter; -import com.sun.jersey.api.client.filter.LoggingFilter; -import com.sun.jersey.api.client.WebResource.Builder; - -import com.sun.jersey.multipart.FormDataMultiPart; -import com.sun.jersey.multipart.file.FileDataBodyPart; - -import {{javaxPackage}}.ws.rs.core.Cookie; -import {{javaxPackage}}.ws.rs.core.Response.Status.Family; -import {{javaxPackage}}.ws.rs.core.MediaType; - -import java.util.Collection; -import java.util.Collections; -import java.util.Map; -import java.util.Map.Entry; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Arrays; -import java.util.ArrayList; -import java.util.Date; -import java.util.TimeZone; - -import java.net.URLEncoder; - -import java.io.File; -import java.io.UnsupportedEncodingException; - -import java.text.DateFormat; - -import {{invokerPackage}}.auth.Authentication; -{{#hasHttpBasicMethods}} -import {{invokerPackage}}.auth.HttpBasicAuth; -{{/hasHttpBasicMethods}} -{{#hasHttpBearerMethods}} -import {{invokerPackage}}.auth.HttpBearerAuth; -{{/hasHttpBearerMethods}} -{{#hasApiKeyMethods}} -import {{invokerPackage}}.auth.ApiKeyAuth; -{{/hasApiKeyMethods}} -{{#hasOAuthMethods}} -import {{invokerPackage}}.auth.OAuth; -{{/hasOAuthMethods}} - -{{>generatedAnnotation}} -public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} { - private Map defaultHeaderMap = new HashMap(); - private Map defaultCookieMap = new HashMap(); - private String basePath = "{{{basePath}}}"; - protected List servers = new ArrayList({{#servers}}{{#-first}}Arrays.asList( -{{/-first}} new ServerConfiguration( - "{{{url}}}", - "{{{description}}}{{^description}}No description provided{{/description}}", - new HashMap(){{#variables}}{{#-first}} {{ -{{/-first}} put("{{{name}}}", new ServerVariable( - "{{{description}}}{{^description}}No description provided{{/description}}", - "{{{defaultValue}}}", - new HashSet( - {{#enumValues}} - {{#-first}} - Arrays.asList( - {{/-first}} - "{{{.}}}"{{^-last}},{{/-last}} - {{#-last}} - ) - {{/-last}} - {{/enumValues}} - ) - )); - {{#-last}} - }}{{/-last}}{{/variables}} - ){{^-last}},{{/-last}} - {{#-last}} - ){{/-last}}{{/servers}}); - protected Integer serverIndex = 0; - protected Map serverVariables = null; - private boolean debugging = false; - private int connectionTimeout = 0; - - private Client httpClient; - private ObjectMapper objectMapper; - - private Map authentications; - - private int statusCode; - private Map> responseHeaders; - - private DateFormat dateFormat; - - public ApiClient() { - objectMapper = new ObjectMapper(); - objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); - objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - objectMapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false); - objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); - objectMapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING); - objectMapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING); - {{#joda}} - objectMapper.registerModule(new JodaModule()); - {{/joda}} - objectMapper.registerModule(new JavaTimeModule()); - objectMapper.setDateFormat(ApiClient.buildDefaultDateFormat()); - - dateFormat = ApiClient.buildDefaultDateFormat(); - - // Set default User-Agent. - setUserAgent("{{{httpUserAgent}}}{{^httpUserAgent}}OpenAPI-Generator/{{{artifactVersion}}}/java{{/httpUserAgent}}"); - - // Setup authentications (key: authentication name, value: authentication). - authentications = new HashMap();{{#authMethods}}{{#isBasic}}{{#isBasicBasic}} - authentications.put("{{name}}", new HttpBasicAuth());{{/isBasicBasic}}{{#isBasicBearer}} - authentications.put("{{name}}", new HttpBearerAuth("{{scheme}}"));{{/isBasicBearer}}{{/isBasic}}{{#isApiKey}} - authentications.put("{{name}}", new ApiKeyAuth({{#isKeyInHeader}}"header"{{/isKeyInHeader}}{{^isKeyInHeader}}"query"{{/isKeyInHeader}}, "{{keyParamName}}"));{{/isApiKey}}{{#isOAuth}} - authentications.put("{{name}}", new OAuth());{{/isOAuth}}{{/authMethods}} - // Prevent the authentications from being modified. - authentications = Collections.unmodifiableMap(authentications); - - rebuildHttpClient(); - } - - public static DateFormat buildDefaultDateFormat() { - return new RFC3339DateFormat(); - } - - /** - * Build the Client used to make HTTP requests with the latest settings, - * i.e. objectMapper and debugging. - * TODO: better to use the Builder Pattern? - * @return API client - */ - public ApiClient rebuildHttpClient() { - // Add the JSON serialization support to Jersey - JacksonJsonProvider jsonProvider = new JacksonJsonProvider(objectMapper); - DefaultClientConfig conf = new DefaultClientConfig(); - conf.getSingletons().add(jsonProvider); - Client client = Client.create(conf); - client.addFilter(new GZIPContentEncodingFilter({{#useGzipFeature}}true{{/useGzipFeature}}{{^useGzipFeature}}false{{/useGzipFeature}})); - if (debugging) { - client.addFilter(new LoggingFilter()); - } - this.httpClient = client; - return this; - } - - /** - * Returns the current object mapper used for JSON serialization/deserialization. - *

- * Note: If you make changes to the object mapper, remember to set it back via - * setObjectMapper in order to trigger HTTP client rebuilding. - *

- * @return Object mapper - */ - public ObjectMapper getObjectMapper() { - return objectMapper; - } - - public ApiClient setObjectMapper(ObjectMapper objectMapper) { - this.objectMapper = objectMapper; - // Need to rebuild the Client as it depends on object mapper. - rebuildHttpClient(); - return this; - } - - public Client getHttpClient() { - return httpClient; - } - - public ApiClient setHttpClient(Client httpClient) { - this.httpClient = httpClient; - return this; - } - - public String getBasePath() { - return basePath; - } - - public ApiClient setBasePath(String basePath) { - this.basePath = basePath; - this.serverIndex = null; - return this; - } - - public List getServers() { - return servers; - } - - public ApiClient setServers(List servers) { - this.servers = servers; - return this; - } - - public Integer getServerIndex() { - return serverIndex; - } - - public ApiClient setServerIndex(Integer serverIndex) { - this.serverIndex = serverIndex; - return this; - } - - public Map getServerVariables() { - return serverVariables; - } - - public ApiClient setServerVariables(Map serverVariables) { - this.serverVariables = serverVariables; - return this; - } - - /** - * Gets the status code of the previous request - * @return Status code - */ - public int getStatusCode() { - return statusCode; - } - - /** - * Gets the response headers of the previous request - * @return Response headers - */ - public Map> getResponseHeaders() { - return responseHeaders; - } - - /** - * Get authentications (key: authentication name, value: authentication). - * @return Map of authentication - */ - public Map getAuthentications() { - return authentications; - } - - /** - * Get authentication for the given name. - * - * @param authName The authentication name - * @return The authentication, null if not found - */ - public Authentication getAuthentication(String authName) { - return authentications.get(authName); - } - - {{#hasHttpBearerMethods}} - /** - * Helper method to set access token for the first Bearer authentication. - * @param bearerToken Bearer token - * @return API client - */ - public void setBearerToken(String bearerToken) { - for (Authentication auth : authentications.values()) { - if (auth instanceof HttpBearerAuth) { - ((HttpBearerAuth) auth).setBearerToken(bearerToken); - return; - } - } - throw new RuntimeException("No Bearer authentication configured!"); - } - - {{/hasHttpBearerMethods}} - - {{#hasHttpBasicMethods}} - /** - * Helper method to set username for the first HTTP basic authentication. - * @param username Username - */ - public void setUsername(String username) { - for (Authentication auth : authentications.values()) { - if (auth instanceof HttpBasicAuth) { - ((HttpBasicAuth) auth).setUsername(username); - return; - } - } - throw new RuntimeException("No HTTP basic authentication configured!"); - } - - /** - * Helper method to set password for the first HTTP basic authentication. - * @param password Password - */ - public void setPassword(String password) { - for (Authentication auth : authentications.values()) { - if (auth instanceof HttpBasicAuth) { - ((HttpBasicAuth) auth).setPassword(password); - return; - } - } - throw new RuntimeException("No HTTP basic authentication configured!"); - } - - {{/hasHttpBasicMethods}} - - {{#hasApiKeyMethods}} - /** - * Helper method to set API key value for the first API key authentication. - * @param apiKey the API key - */ - public void setApiKey(String apiKey) { - for (Authentication auth : authentications.values()) { - if (auth instanceof ApiKeyAuth) { - ((ApiKeyAuth) auth).setApiKey(apiKey); - return; - } - } - throw new RuntimeException("No API key authentication configured!"); - } - - /** - * Helper method to set API key prefix for the first API key authentication. - * @param apiKeyPrefix API key prefix - */ - public void setApiKeyPrefix(String apiKeyPrefix) { - for (Authentication auth : authentications.values()) { - if (auth instanceof ApiKeyAuth) { - ((ApiKeyAuth) auth).setApiKeyPrefix(apiKeyPrefix); - return; - } - } - throw new RuntimeException("No API key authentication configured!"); - } - - {{/hasApiKeyMethods}} - - {{#hasOAuthMethods}} - /** - * Helper method to set access token for the first OAuth2 authentication. - * @param accessToken Access token - */ - public void setAccessToken(String accessToken) { - for (Authentication auth : authentications.values()) { - if (auth instanceof OAuth) { - ((OAuth) auth).setAccessToken(accessToken); - return; - } - } - throw new RuntimeException("No OAuth2 authentication configured!"); - } - - {{/hasOAuthMethods}} - - /** - * Set the User-Agent header's value (by adding to the default header map). - * @param userAgent User agent - * @return API client - */ - public ApiClient setUserAgent(String userAgent) { - addDefaultHeader("User-Agent", userAgent); - return this; - } - - /** - * Add a default header. - * - * @param key The header's key - * @param value The header's value - * @return API client - */ - public ApiClient addDefaultHeader(String key, String value) { - defaultHeaderMap.put(key, value); - return this; - } - - /** - * Add a default cookie. - * - * @param key The cookie's key - * @param value The cookie's value - * @return API client - */ - public ApiClient addDefaultCookie(String key, String value) { - defaultCookieMap.put(key, value); - return this; - } - - /** - * Check that whether debugging is enabled for this API client. - * @return True if debugging is on - */ - public boolean isDebugging() { - return debugging; - } - - /** - * Enable/disable debugging for this API client. - * - * @param debugging To enable (true) or disable (false) debugging - * @return API client - */ - public ApiClient setDebugging(boolean debugging) { - this.debugging = debugging; - // Need to rebuild the Client as it depends on the value of debugging. - rebuildHttpClient(); - return this; - } - - /** - * Connect timeout (in milliseconds). - * @return Connection timeout - */ - public int getConnectTimeout() { - return connectionTimeout; - } - - /** - * Set the connect timeout (in milliseconds). - * A value of 0 means no timeout, otherwise values must be between 1 and - * {@link Integer#MAX_VALUE}. - * @param connectionTimeout Connection timeout in milliseconds - * @return API client - */ - public ApiClient setConnectTimeout(int connectionTimeout) { - this.connectionTimeout = connectionTimeout; - httpClient.setConnectTimeout(connectionTimeout); - return this; - } - - /** - * Get the date format used to parse/format date parameters. - * @return Date format - */ - public DateFormat getDateFormat() { - return dateFormat; - } - - /** - * Set the date format used to parse/format date parameters. - * @param dateFormat Date format - * @return API client - */ - public ApiClient setDateFormat(DateFormat dateFormat) { - this.dateFormat = dateFormat; - // Also set the date format for model (de)serialization with Date properties. - this.objectMapper.setDateFormat((DateFormat) dateFormat.clone()); - // Need to rebuild the Client as objectMapper changes. - rebuildHttpClient(); - return this; - } - - /** - * Parse the given string into Date object. - * @param str String - * @return Date - */ - public Date parseDate(String str) { - try { - return dateFormat.parse(str); - } catch (java.text.ParseException e) { - throw new RuntimeException(e); - } - } - - /** - * Format the given Date object into string. - * @param date Date - * @return Date in string format - */ - public String formatDate(Date date) { - return dateFormat.format(date); - } - - /** - * Format the given parameter object into string. - * @param param Object - * @return Object in string format - */ - public String parameterToString(Object param) { - if (param == null) { - return ""; - } else if (param instanceof Date) { - return formatDate((Date) param); - } {{#jsr310}}else if (param instanceof OffsetDateTime) { - return formatOffsetDateTime((OffsetDateTime) param); - } {{/jsr310}}else if (param instanceof Collection) { - StringBuilder b = new StringBuilder(); - for(Object o : (Collection)param) { - if(b.length() > 0) { - b.append(','); - } - b.append(String.valueOf(o)); - } - return b.toString(); - } else { - return String.valueOf(param); - } - } - - /** - * Formats the specified query parameter to a list containing a single {@code Pair} object. - * - * Note that {@code value} must not be a collection. - * - * @param name The name of the parameter. - * @param value The value of the parameter. - * @return A list containing a single {@code Pair} object. - */ - public List parameterToPair(String name, Object value) { - List params = new ArrayList(); - - // preconditions - if (name == null || name.isEmpty() || value == null || value instanceof Collection) return params; - - params.add(new Pair(name, parameterToString(value))); - return params; - } - - /** - * Formats the specified collection query parameters to a list of {@code Pair} objects. - * - * Note that the values of each of the returned Pair objects are percent-encoded. - * - * @param collectionFormat The collection format of the parameter. - * @param name The name of the parameter. - * @param value The value of the parameter. - * @return A list of {@code Pair} objects. - */ - public List parameterToPairs(String collectionFormat, String name, Collection value) { - List params = new ArrayList(); - - // preconditions - if (name == null || name.isEmpty() || value == null) { - return params; - } - - // create the params based on the collection format - if ("multi".equals(collectionFormat)) { - for (Object item : value) { - params.add(new Pair(name, escapeString(parameterToString(item)))); - } - return params; - } - - // collectionFormat is assumed to be "csv" by default - String delimiter = ","; - - // escape all delimiters except commas, which are URI reserved - // characters - if ("ssv".equals(collectionFormat)) { - delimiter = escapeString(" "); - } else if ("tsv".equals(collectionFormat)) { - delimiter = escapeString("\t"); - } else if ("pipes".equals(collectionFormat)) { - delimiter = escapeString("|"); - } - - StringBuilder sb = new StringBuilder() ; - for (Object item : value) { - sb.append(delimiter); - sb.append(escapeString(parameterToString(item))); - } - - params.add(new Pair(name, sb.substring(delimiter.length()))); - - return params; - } - - /** - * Check if the given MIME is a JSON MIME. - * JSON MIME examples: - * application/json - * application/json; charset=UTF8 - * APPLICATION/JSON - * application/vnd.company+json - * @param mime MIME - * @return True if MIME type is boolean - */ - public boolean isJsonMime(String mime) { - String jsonMime = "(?i)^(application/json|[^;/ \t]+/[^;/ \t]+[+]json)[ \t]*(;.*)?$"; - return mime != null && (mime.matches(jsonMime) || mime.equals("*/*")); - } - - /** - * Select the Accept header's value from the given accepts array: - * if JSON exists in the given array, use it; - * otherwise use all of them (joining into a string) - * - * @param accepts The accepts array to select from - * @return The Accept header to use. If the given array is empty, - * null will be returned (not to set the Accept header explicitly). - */ - public String selectHeaderAccept(String[] accepts) { - if (accepts.length == 0) { - return null; - } - for (String accept : accepts) { - if (isJsonMime(accept)) { - return accept; - } - } - return StringUtil.join(accepts, ","); - } - - /** - * Select the Content-Type header's value from the given array: - * if JSON exists in the given array, use it; - * otherwise use the first one of the array. - * - * @param contentTypes The Content-Type array to select from - * @return The Content-Type header to use. If the given array is empty, - * or matches "any", JSON will be used. - */ - public String selectHeaderContentType(String[] contentTypes) { - if (contentTypes.length == 0 || contentTypes[0].equals("*/*")) { - return "application/json"; - } - for (String contentType : contentTypes) { - if (isJsonMime(contentType)) { - return contentType; - } - } - return contentTypes[0]; - } - - /** - * Escape the given string to be used as URL query value. - * @param str String - * @return Escaped string - */ - public String escapeString(String str) { - try { - return URLEncoder.encode(str, "utf8").replaceAll("\\+", "%20"); - } catch (UnsupportedEncodingException e) { - return str; - } - } - - /** - * Serialize the given Java object into string according the given - * Content-Type (only JSON is supported for now). - * @param obj Object - * @param contentType Content type - * @param formParams Form parameters - * @return Object - * @throws ApiException API exception - */ - public Object serialize(Object obj, String contentType, Map formParams) throws ApiException { - if (contentType.startsWith("multipart/form-data")) { - FormDataMultiPart mp = new FormDataMultiPart(); - for (Entry param: formParams.entrySet()) { - if( param.getValue() instanceof List && !( ( List ) param.getValue() ).isEmpty() - && ( ( List ) param.getValue() ).get( 0 ) instanceof File ) { - @SuppressWarnings( "unchecked" ) - List files = ( List ) param.getValue(); - for( File file : files ) { - mp.bodyPart( new FileDataBodyPart( param.getKey(), file, MediaType.APPLICATION_OCTET_STREAM_TYPE ) ); - } - } else if (param.getValue() instanceof File) { - File file = (File) param.getValue(); - mp.bodyPart(new FileDataBodyPart(param.getKey(), file, MediaType.APPLICATION_OCTET_STREAM_TYPE)); - } else { - mp.field(param.getKey(), parameterToString(param.getValue()), MediaType.MULTIPART_FORM_DATA_TYPE); - } - } - return mp; - } else if (contentType.startsWith("application/x-www-form-urlencoded")) { - return this.getXWWWFormUrlencodedParams(formParams); - } else { - // We let Jersey attempt to serialize the body - return obj; - } - } - - /** - * Build full URL by concatenating base path, the given sub path and query parameters. - * - * @param path The sub path - * @param queryParams The query parameters - * @param collectionQueryParams The collection query parameters - * @return The full URL - */ - private String buildUrl(String path, List queryParams, List collectionQueryParams) { - String baseURL; - if (serverIndex != null) { - if (serverIndex < 0 || serverIndex >= servers.size()) { - throw new ArrayIndexOutOfBoundsException(String.format( - "Invalid index %d when selecting the host settings. Must be less than %d", serverIndex, servers.size() - )); - } - baseURL = servers.get(serverIndex).URL(serverVariables); - } else { - baseURL = basePath; - } - - final StringBuilder url = new StringBuilder(); - url.append(baseURL).append(path); - - if (queryParams != null && !queryParams.isEmpty()) { - // support (constant) query string in `path`, e.g. "/posts?draft=1" - String prefix = path.contains("?") ? "&" : "?"; - for (Pair param : queryParams) { - if (param.getValue() != null) { - if (prefix != null) { - url.append(prefix); - prefix = null; - } else { - url.append("&"); - } - String value = parameterToString(param.getValue()); - url.append(escapeString(param.getName())).append("=").append(escapeString(value)); - } - } - } - - if (collectionQueryParams != null && !collectionQueryParams.isEmpty()) { - String prefix = url.toString().contains("?") ? "&" : "?"; - for (Pair param : collectionQueryParams) { - if (param.getValue() != null) { - if (prefix != null) { - url.append(prefix); - prefix = null; - } else { - url.append("&"); - } - String value = parameterToString(param.getValue()); - // collection query parameter value already escaped as part of parameterToPairs - url.append(escapeString(param.getName())).append("=").append(value); - } - } - } - - return url.toString(); - } - - private ClientResponse getAPIResponse(String path, String method, List queryParams, List collectionQueryParams, Object body, Map headerParams, Map cookieParams, Map formParams, String accept, String contentType, String[] authNames) throws ApiException { - if (body != null && !formParams.isEmpty()) { - throw new ApiException(500, "Cannot have body and form params"); - } - - updateParamsForAuth(authNames, queryParams, headerParams, cookieParams); - - final String url = buildUrl(path, queryParams, collectionQueryParams); - Builder builder; - if (accept == null) { - builder = httpClient.resource(url).getRequestBuilder(); - } else { - builder = httpClient.resource(url).accept(accept); - } - - for (Entry keyValue : headerParams.entrySet()) { - builder = builder.header(keyValue.getKey(), keyValue.getValue()); - } - for (Map.Entry keyValue : defaultHeaderMap.entrySet()) { - if (!headerParams.containsKey(keyValue.getKey())) { - builder = builder.header(keyValue.getKey(), keyValue.getValue()); - } - } - - for (Entry keyValue : cookieParams.entrySet()) { - builder = builder.cookie(new Cookie(keyValue.getKey(), keyValue.getValue())); - } - for (Map.Entry keyValue : defaultCookieMap.entrySet()) { - if (!cookieParams.containsKey(keyValue.getKey())) { - builder = builder.cookie(new Cookie(keyValue.getKey(), keyValue.getValue())); - } - } - - ClientResponse response = null; - - if ("GET".equals(method)) { - response = (ClientResponse) builder.get(ClientResponse.class); - } else if ("POST".equals(method)) { - response = builder.type(contentType).post(ClientResponse.class, serialize(body, contentType, formParams)); - } else if ("PUT".equals(method)) { - response = builder.type(contentType).put(ClientResponse.class, serialize(body, contentType, formParams)); - } else if ("DELETE".equals(method)) { - response = builder.type(contentType).delete(ClientResponse.class, serialize(body, contentType, formParams)); - } else if ("PATCH".equals(method)) { - response = builder.type(contentType).header("X-HTTP-Method-Override", "PATCH").post(ClientResponse.class, serialize(body, contentType, formParams)); - } else if ("HEAD".equals(method)) { - response = builder.head(); - } else { - throw new ApiException(500, "unknown method type " + method); - } - return response; - } - - /** - * Invoke API by sending HTTP request with the given options. - * - * @param Type - * @param path The sub-path of the HTTP URL - * @param method The request method, one of "GET", "POST", "PUT", and "DELETE" - * @param queryParams The query parameters - * @param collectionQueryParams The collection query parameters - * @param body The request body object - if it is not binary, otherwise null - * @param headerParams The header parameters - * @param cookieParams The cookie parameters - * @param formParams The form parameters - * @param accept The request's Accept header - * @param contentType The request's Content-Type header - * @param authNames The authentications to apply - * @param returnType Return type - * @return The response body in type of string - * @throws ApiException API exception - */ - public T invokeAPI(String path, String method, List queryParams, List collectionQueryParams, Object body, Map headerParams, Map cookieParams, Map formParams, String accept, String contentType, String[] authNames, GenericType returnType) throws ApiException { - - ClientResponse response = getAPIResponse(path, method, queryParams, collectionQueryParams, body, headerParams, cookieParams, formParams, accept, contentType, authNames); - - statusCode = response.getStatusInfo().getStatusCode(); - responseHeaders = response.getHeaders(); - - if(response.getStatusInfo().getStatusCode() == ClientResponse.Status.NO_CONTENT.getStatusCode()) { - return null; - } else if (response.getStatusInfo().getFamily() == Family.SUCCESSFUL) { - if (returnType == null) - return null; - else - return response.getEntity(returnType); - } else { - String message = "error"; - String respBody = null; - if (response.hasEntity()) { - try { - respBody = response.getEntity(String.class); - message = respBody; - } catch (RuntimeException e) { - // e.printStackTrace(); - } - } - throw new ApiException( - response.getStatusInfo().getStatusCode(), - message, - response.getHeaders(), - respBody); - } - } - - /** - * Update query and header parameters based on authentication settings. - * - * @param authNames The authentications to apply - * @param queryParams Query parameters - * @param headerParams Header parameters - * @param cookieParams Cookie parameters - */ - private void updateParamsForAuth(String[] authNames, List queryParams, Map headerParams, Map cookieParams) { - for (String authName : authNames) { - Authentication auth = authentications.get(authName); - if (auth == null) throw new RuntimeException("Authentication undefined: " + authName); - auth.applyToParams(queryParams, headerParams, cookieParams); - } - } - - /** - * Encode the given form parameters as request body. - * @param formParams Form parameters - * @return HTTP form encoded parameters - */ - private String getXWWWFormUrlencodedParams(Map formParams) { - StringBuilder formParamBuilder = new StringBuilder(); - - for (Entry param : formParams.entrySet()) { - String valueStr = parameterToString(param.getValue()); - try { - formParamBuilder.append(URLEncoder.encode(param.getKey(), "utf8")) - .append("=") - .append(URLEncoder.encode(valueStr, "utf8")); - formParamBuilder.append("&"); - } catch (UnsupportedEncodingException e) { - // move on to next - } - } - - String encodedFormParams = formParamBuilder.toString(); - if (encodedFormParams.endsWith("&")) { - encodedFormParams = encodedFormParams.substring(0, encodedFormParams.length() - 1); - } - - return encodedFormParams; - } -} diff --git a/config/clients/java/template/JSON.mustache b/config/clients/java/template/JSON.mustache deleted file mode 100644 index 1d0a8138..00000000 --- a/config/clients/java/template/JSON.mustache +++ /dev/null @@ -1,539 +0,0 @@ -{{>licenseInfo}} - -package {{invokerPackage}}; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonParseException; -import com.google.gson.TypeAdapter; -import com.google.gson.internal.bind.util.ISO8601Utils; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonWriter; -import com.google.gson.JsonElement; -import io.gsonfire.GsonFireBuilder; -import io.gsonfire.TypeSelector; -{{#joda}} -import org.joda.time.DateTime; -import org.joda.time.LocalDate; -import org.joda.time.format.DateTimeFormatter; -import org.joda.time.format.DateTimeFormatterBuilder; -import org.joda.time.format.ISODateTimeFormat; -{{/joda}} - -{{#models.0}} -import {{modelPackage}}.*; -{{/models.0}} -import okio.ByteString; - -import java.io.IOException; -import java.io.StringReader; -import java.lang.reflect.Type; -import java.text.DateFormat; -import java.text.ParseException; -import java.text.ParsePosition; -import java.time.LocalDate; -import java.time.OffsetDateTime; -import java.time.format.DateTimeFormatter; -import java.util.Date; -import java.util.Locale; -import java.util.Map; -import java.util.HashMap; - -public class JSON { - private Gson gson; - private boolean isLenientOnJson = false; - private DateTypeAdapter dateTypeAdapter = new DateTypeAdapter(); - private SqlDateTypeAdapter sqlDateTypeAdapter = new SqlDateTypeAdapter(); - {{#joda}} - private DateTimeTypeAdapter dateTimeTypeAdapter = new DateTimeTypeAdapter(); - private LocalDateTypeAdapter localDateTypeAdapter = new LocalDateTypeAdapter(); - {{/joda}} - {{#jsr310}} - private OffsetDateTimeTypeAdapter offsetDateTimeTypeAdapter = new OffsetDateTimeTypeAdapter(); - private LocalDateTypeAdapter localDateTypeAdapter = new LocalDateTypeAdapter(); - {{/jsr310}} - private ByteArrayAdapter byteArrayAdapter = new ByteArrayAdapter(); - - @SuppressWarnings("unchecked") - public static GsonBuilder createGson() { - GsonFireBuilder fireBuilder = new GsonFireBuilder() - {{#models}} - {{#model}} - {{#discriminator}} - .registerTypeSelector({{classname}}.class, new TypeSelector<{{classname}}>() { - @Override - public Class getClassForElement(JsonElement readElement) { - Map classByDiscriminatorValue = new HashMap(); - {{#mappedModels}} - classByDiscriminatorValue.put("{{mappingName}}"{{^discriminatorCaseSensitive}}.toUpperCase(Locale.ROOT){{/discriminatorCaseSensitive}}, {{modelName}}.class); - {{/mappedModels}} - classByDiscriminatorValue.put("{{name}}"{{^discriminatorCaseSensitive}}.toUpperCase(Locale.ROOT){{/discriminatorCaseSensitive}}, {{classname}}.class); - return getClassByDiscriminator(classByDiscriminatorValue, - getDiscriminatorValue(readElement, "{{{propertyBaseName}}}")); - } - }) - {{/discriminator}} - {{/model}} - {{/models}} - ; - GsonBuilder builder = fireBuilder.createGsonBuilder(); - {{#disableHtmlEscaping}} - builder.disableHtmlEscaping(); - {{/disableHtmlEscaping}} - return builder; - } - - private static String getDiscriminatorValue(JsonElement readElement, String discriminatorField) { - JsonElement element = readElement.getAsJsonObject().get(discriminatorField); - if (null == element) { - throw new IllegalArgumentException("missing discriminator field: <" + discriminatorField + ">"); - } - return element.getAsString(); - } - - /** - * Returns the Java class that implements the OpenAPI schema for the specified discriminator value. - * - * @param classByDiscriminatorValue The map of discriminator values to Java classes. - * @param discriminatorValue The value of the OpenAPI discriminator in the input data. - * @return The Java class that implements the OpenAPI schema - */ - private static Class getClassByDiscriminator(Map classByDiscriminatorValue, String discriminatorValue) { - Class clazz = (Class) classByDiscriminatorValue.get(discriminatorValue{{^discriminatorCaseSensitive}}.toUpperCase(Locale.ROOT){{/discriminatorCaseSensitive}}); - if (null == clazz) { - throw new IllegalArgumentException("cannot determine model class of name: <" + discriminatorValue + ">"); - } - return clazz; - } - - public JSON() { - gson = createGson() - .registerTypeAdapter(Date.class, dateTypeAdapter) - .registerTypeAdapter(java.sql.Date.class, sqlDateTypeAdapter) - {{#joda}} - .registerTypeAdapter(DateTime.class, dateTimeTypeAdapter) - .registerTypeAdapter(LocalDate.class, localDateTypeAdapter) - {{/joda}} - {{#jsr310}} - .registerTypeAdapter(OffsetDateTime.class, offsetDateTimeTypeAdapter) - .registerTypeAdapter(LocalDate.class, localDateTypeAdapter) - {{/jsr310}} - .registerTypeAdapter(byte[].class, byteArrayAdapter) - .create(); - } - - /** - * Get Gson. - * - * @return Gson - */ - public Gson getGson() { - return gson; - } - - /** - * Set Gson. - * - * @param gson Gson - * @return JSON - */ - public JSON setGson(Gson gson) { - this.gson = gson; - return this; - } - - /** - * Configure the parser to be liberal in what it accepts. - * - * @param lenientOnJson Set it to true to ignore some syntax errors - * @return JSON - * @see https://www.javadoc.io/doc/com.google.code.gson/gson/2.8.5/com/google/gson/stream/JsonReader.html - */ - public JSON setLenientOnJson(boolean lenientOnJson) { - isLenientOnJson = lenientOnJson; - return this; - } - - /** - * Serialize the given Java object into JSON string. - * - * @param obj Object - * @return String representation of the JSON - */ - public String serialize(Object obj) { - return gson.toJson(obj); - } - - /** - * Deserialize the given JSON string to Java object. - * - * @param Type - * @param body The JSON string - * @param returnType The type to deserialize into - * @return The deserialized Java object - */ - @SuppressWarnings("unchecked") - public T deserialize(String body, Type returnType) { - try { - if (isLenientOnJson) { - JsonReader jsonReader = new JsonReader(new StringReader(body)); - // see https://www.javadoc.io/doc/com.google.code.gson/gson/2.8.5/com/google/gson/stream/JsonReader.html - jsonReader.setLenient(true); - return gson.fromJson(jsonReader, returnType); - } else { - return gson.fromJson(body, returnType); - } - } catch (JsonParseException e) { - // Fallback processing when failed to parse JSON form response body: - // return the response body string directly for the String return type; - if (returnType.equals(String.class)) { - return (T) body; - } else { - throw (e); - } - } - } - - /** - * Gson TypeAdapter for Byte Array type - */ - public class ByteArrayAdapter extends TypeAdapter { - - @Override - public void write(JsonWriter out, byte[] value) throws IOException { - if (value == null) { - out.nullValue(); - } else { - out.value(ByteString.of(value).base64()); - } - } - - @Override - public byte[] read(JsonReader in) throws IOException { - switch (in.peek()) { - case NULL: - in.nextNull(); - return null; - default: - String bytesAsBase64 = in.nextString(); - ByteString byteString = ByteString.decodeBase64(bytesAsBase64); - return byteString.toByteArray(); - } - } - } - - {{#joda}} - /** - * Gson TypeAdapter for Joda DateTime type - */ - public static class DateTimeTypeAdapter extends TypeAdapter { - - private DateTimeFormatter formatter; - - public DateTimeTypeAdapter() { - this(new DateTimeFormatterBuilder() - .append(ISODateTimeFormat.dateTime().getPrinter(), ISODateTimeFormat.dateOptionalTimeParser().getParser()) - .toFormatter()); - } - - public DateTimeTypeAdapter(DateTimeFormatter formatter) { - this.formatter = formatter; - } - - public void setFormat(DateTimeFormatter dateFormat) { - this.formatter = dateFormat; - } - - @Override - public void write(JsonWriter out, DateTime date) throws IOException { - if (date == null) { - out.nullValue(); - } else { - out.value(formatter.print(date)); - } - } - - @Override - public DateTime read(JsonReader in) throws IOException { - switch (in.peek()) { - case NULL: - in.nextNull(); - return null; - default: - String date = in.nextString(); - return formatter.parseDateTime(date); - } - } - } - - /** - * Gson TypeAdapter for Joda LocalDate type - */ - public class LocalDateTypeAdapter extends TypeAdapter { - - private DateTimeFormatter formatter; - - public LocalDateTypeAdapter() { - this(ISODateTimeFormat.date()); - } - - public LocalDateTypeAdapter(DateTimeFormatter formatter) { - this.formatter = formatter; - } - - public void setFormat(DateTimeFormatter dateFormat) { - this.formatter = dateFormat; - } - - @Override - public void write(JsonWriter out, LocalDate date) throws IOException { - if (date == null) { - out.nullValue(); - } else { - out.value(formatter.print(date)); - } - } - - @Override - public LocalDate read(JsonReader in) throws IOException { - switch (in.peek()) { - case NULL: - in.nextNull(); - return null; - default: - String date = in.nextString(); - return formatter.parseLocalDate(date); - } - } - } - - public JSON setDateTimeFormat(DateTimeFormatter dateFormat) { - dateTimeTypeAdapter.setFormat(dateFormat); - return this; - } - - public JSON setLocalDateFormat(DateTimeFormatter dateFormat) { - localDateTypeAdapter.setFormat(dateFormat); - return this; - } - - {{/joda}} - {{#jsr310}} - /** - * Gson TypeAdapter for JSR310 OffsetDateTime type - */ - public static class OffsetDateTimeTypeAdapter extends TypeAdapter { - - private DateTimeFormatter formatter; - - public OffsetDateTimeTypeAdapter() { - this(DateTimeFormatter.ISO_OFFSET_DATE_TIME); - } - - public OffsetDateTimeTypeAdapter(DateTimeFormatter formatter) { - this.formatter = formatter; - } - - public void setFormat(DateTimeFormatter dateFormat) { - this.formatter = dateFormat; - } - - @Override - public void write(JsonWriter out, OffsetDateTime date) throws IOException { - if (date == null) { - out.nullValue(); - } else { - out.value(formatter.format(date)); - } - } - - @Override - public OffsetDateTime read(JsonReader in) throws IOException { - switch (in.peek()) { - case NULL: - in.nextNull(); - return null; - default: - String date = in.nextString(); - if (date.endsWith("+0000")) { - date = date.substring(0, date.length()-5) + "Z"; - } - return OffsetDateTime.parse(date, formatter); - } - } - } - - /** - * Gson TypeAdapter for JSR310 LocalDate type - */ - public class LocalDateTypeAdapter extends TypeAdapter { - - private DateTimeFormatter formatter; - - public LocalDateTypeAdapter() { - this(DateTimeFormatter.ISO_LOCAL_DATE); - } - - public LocalDateTypeAdapter(DateTimeFormatter formatter) { - this.formatter = formatter; - } - - public void setFormat(DateTimeFormatter dateFormat) { - this.formatter = dateFormat; - } - - @Override - public void write(JsonWriter out, LocalDate date) throws IOException { - if (date == null) { - out.nullValue(); - } else { - out.value(formatter.format(date)); - } - } - - @Override - public LocalDate read(JsonReader in) throws IOException { - switch (in.peek()) { - case NULL: - in.nextNull(); - return null; - default: - String date = in.nextString(); - return LocalDate.parse(date, formatter); - } - } - } - - public JSON setOffsetDateTimeFormat(DateTimeFormatter dateFormat) { - offsetDateTimeTypeAdapter.setFormat(dateFormat); - return this; - } - - public JSON setLocalDateFormat(DateTimeFormatter dateFormat) { - localDateTypeAdapter.setFormat(dateFormat); - return this; - } - - {{/jsr310}} - /** - * Gson TypeAdapter for java.sql.Date type - * If the dateFormat is null, a simple "yyyy-MM-dd" format will be used - * (more efficient than SimpleDateFormat). - */ - public static class SqlDateTypeAdapter extends TypeAdapter { - - private DateFormat dateFormat; - - public SqlDateTypeAdapter() {} - - public SqlDateTypeAdapter(DateFormat dateFormat) { - this.dateFormat = dateFormat; - } - - public void setFormat(DateFormat dateFormat) { - this.dateFormat = dateFormat; - } - - @Override - public void write(JsonWriter out, java.sql.Date date) throws IOException { - if (date == null) { - out.nullValue(); - } else { - String value; - if (dateFormat != null) { - value = dateFormat.format(date); - } else { - value = date.toString(); - } - out.value(value); - } - } - - @Override - public java.sql.Date read(JsonReader in) throws IOException { - switch (in.peek()) { - case NULL: - in.nextNull(); - return null; - default: - String date = in.nextString(); - try { - if (dateFormat != null) { - return new java.sql.Date(dateFormat.parse(date).getTime()); - } - return new java.sql.Date(ISO8601Utils.parse(date, new ParsePosition(0)).getTime()); - } catch (ParseException e) { - throw new JsonParseException(e); - } - } - } - } - - /** - * Gson TypeAdapter for java.util.Date type - * If the dateFormat is null, ISO8601Utils will be used. - */ - public static class DateTypeAdapter extends TypeAdapter { - - private DateFormat dateFormat; - - public DateTypeAdapter() {} - - public DateTypeAdapter(DateFormat dateFormat) { - this.dateFormat = dateFormat; - } - - public void setFormat(DateFormat dateFormat) { - this.dateFormat = dateFormat; - } - - @Override - public void write(JsonWriter out, Date date) throws IOException { - if (date == null) { - out.nullValue(); - } else { - String value; - if (dateFormat != null) { - value = dateFormat.format(date); - } else { - value = ISO8601Utils.format(date, true); - } - out.value(value); - } - } - - @Override - public Date read(JsonReader in) throws IOException { - try { - switch (in.peek()) { - case NULL: - in.nextNull(); - return null; - default: - String date = in.nextString(); - try { - if (dateFormat != null) { - return dateFormat.parse(date); - } - return ISO8601Utils.parse(date, new ParsePosition(0)); - } catch (ParseException e) { - throw new JsonParseException(e); - } - } - } catch (IllegalArgumentException e) { - throw new JsonParseException(e); - } - } - } - - public JSON setDateFormat(DateFormat dateFormat) { - dateTypeAdapter.setFormat(dateFormat); - return this; - } - - public JSON setSqlDateFormat(DateFormat dateFormat) { - sqlDateTypeAdapter.setFormat(dateFormat); - return this; - } - -} diff --git a/config/clients/java/template/README.mustache b/config/clients/java/template/README.mustache deleted file mode 100644 index 1b2d1299..00000000 --- a/config/clients/java/template/README.mustache +++ /dev/null @@ -1,246 +0,0 @@ -# {{artifactId}} - -{{appName}} - -- API version: {{appVersion}} -{{^hideGenerationTimestamp}} - -- Build date: {{generatedDate}} -{{/hideGenerationTimestamp}} - -{{{appDescriptionWithNewLines}}} - -{{#infoUrl}} - For more information, please visit [{{{infoUrl}}}]({{{infoUrl}}}) -{{/infoUrl}} - -*Automatically generated by the [OpenAPI Generator](https://openapi-generator.tech)* - -## Requirements - -Building the API client library requires: - -1. Java 1.8+ -{{#jersey2}} -2. Maven (3.8.3+)/Gradle (7.2+) -{{/jersey2}} -{{^jersey2}} -2. Maven/Gradle -{{/jersey2}} - -## Installation - -To install the API client library to your local Maven repository, simply execute: - -```shell -mvn clean install -``` - -To deploy it to a remote Maven repository instead, configure the settings of the repository and execute: - -```shell -mvn clean deploy -``` - -Refer to the [OSSRH Guide](http://central.sonatype.org/pages/ossrh-guide.html) for more information. - -### Maven users - -Add this dependency to your project's POM: - -```xml - - {{{groupId}}} - {{{artifactId}}} - {{{artifactVersion}}} - compile - -``` - -### Gradle users - -Add this dependency to your project's build file: - -```groovy - repositories { - mavenCentral() // Needed if the '{{{artifactId}}}' jar has been published to maven central. - mavenLocal() // Needed if the '{{{artifactId}}}' jar has been published to the local maven repo. - } - - dependencies { - implementation "{{{groupId}}}:{{{artifactId}}}:{{{artifactVersion}}}" - } -``` - -### Others - -At first generate the JAR by executing: - -```shell -mvn clean package -``` - -Then manually install the following JARs: - -- `target/{{{artifactId}}}-{{{artifactVersion}}}.jar` -- `target/lib/*.jar` - -{{#jersey2}} -## Usage - -To add a HTTP proxy for the API client, use `ClientConfig`: -```java -{{#apiInfo}}{{#apis}}{{#-first}}{{#operations}}{{#operation}}{{#-first}} -import org.glassfish.jersey.apache.connector.ApacheConnectorProvider; -import org.glassfish.jersey.client.ClientConfig; -import org.glassfish.jersey.client.ClientProperties; -import {{{invokerPackage}}}.*; -import {{{package}}}.{{{classname}}}; - -... - -ApiClient defaultClient = Configuration.getDefaultApiClient(); -ClientConfig clientConfig = defaultClient.getClientConfig(); -clientConfig.connectorProvider(new ApacheConnectorProvider()); -clientConfig.property(ClientProperties.PROXY_URI, "http://proxy_url_here"); -clientConfig.property(ClientProperties.PROXY_USERNAME, "proxy_username"); -clientConfig.property(ClientProperties.PROXY_PASSWORD, "proxy_password"); -defaultClient.setClientConfig(clientConfig); - -{{{classname}}} apiInstance = new {{{classname}}}(defaultClient); -{{/-first}}{{/operation}}{{/operations}}{{/-first}}{{/apis}}{{/apiInfo}} -``` - -{{/jersey2}} -## Getting Started - -Please follow the [installation](#installation) instruction and execute the following Java code: - -```java -{{#apiInfo}}{{#apis}}{{#-first}}{{#operations}}{{#operation}}{{#-first}} -import {{{invokerPackage}}}.*; -import {{{invokerPackage}}}.auth.*; -import {{{modelPackage}}}.*; -import {{{package}}}.{{{classname}}}; - -public class {{{classname}}}Example { - - public static void main(String[] args) { - ApiClient defaultClient = Configuration.getDefaultApiClient(); - defaultClient.setBasePath("{{{basePath}}}"); - {{#hasAuthMethods}}{{#authMethods}}{{#isBasic}}{{#isBasicBasic}} - // Configure HTTP basic authorization: {{{name}}} - HttpBasicAuth {{{name}}} = (HttpBasicAuth) defaultClient.getAuthentication("{{{name}}}"); - {{{name}}}.setUsername("YOUR USERNAME"); - {{{name}}}.setPassword("YOUR PASSWORD");{{/isBasicBasic}}{{#isBasicBearer}} - // Configure HTTP bearer authorization: {{{name}}} - HttpBearerAuth {{{name}}} = (HttpBearerAuth) defaultClient.getAuthentication("{{{name}}}"); - {{{name}}}.setBearerToken("BEARER TOKEN");{{/isBasicBearer}}{{/isBasic}}{{#isApiKey}} - // Configure API key authorization: {{{name}}} - ApiKeyAuth {{{name}}} = (ApiKeyAuth) defaultClient.getAuthentication("{{{name}}}"); - {{{name}}}.setApiKey("YOUR API KEY"); - // Uncomment the following line to set a prefix for the API key, e.g. "Token" (defaults to null) - //{{{name}}}.setApiKeyPrefix("Token");{{/isApiKey}}{{#isOAuth}} - // Configure OAuth2 access token for authorization: {{{name}}} - OAuth {{{name}}} = (OAuth) defaultClient.getAuthentication("{{{name}}}"); - {{{name}}}.setAccessToken("YOUR ACCESS TOKEN");{{/isOAuth}}{{#isHttpSignature}} - // Configure HTTP signature authorization: {{{name}}} - HttpSignatureAuth {{{name}}} = (HttpSignatureAuth) defaultClient.getAuthentication("{{{name}}}"); - // All the HTTP signature parameters below should be customized to your environment. - // Configure the keyId - {{{name}}}.setKeyId("YOUR KEY ID"); - // Configure the signature algorithm - {{{name}}}.setSigningAlgorithm(SigningAlgorithm.HS2019); - // Configure the specific cryptographic algorithm - {{{name}}}.setAlgorithm(Algorithm.ECDSA_SHA256); - // Configure the cryptographic algorithm parameters, if applicable - {{{name}}}.setAlgorithmParameterSpec(null); - // Set the cryptographic digest algorithm. - {{{name}}}.setDigestAlgorithm("SHA-256"); - // Set the HTTP headers that should be included in the HTTP signature. - {{{name}}}.setHeaders(Arrays.asList("date", "host")); - // Set the private key used to sign the HTTP messages - {{{name}}}.setPrivateKey();{{/isHttpSignature}} - {{/authMethods}} - {{/hasAuthMethods}} - - {{{classname}}} apiInstance = new {{{classname}}}(defaultClient); - {{#allParams}} - {{{dataType}}} {{{paramName}}} = {{{example}}}; // {{{dataType}}} | {{{description}}} - {{/allParams}} - try { - {{#returnType}}{{{.}}} result = {{/returnType}}apiInstance.{{{operationId}}}({{#allParams}}{{{paramName}}}{{^-last}}, {{/-last}}{{/allParams}});{{#returnType}} - System.out.println(result);{{/returnType}} - } catch (ApiException e) { - System.err.println("Exception when calling {{{classname}}}#{{{operationId}}}"); - System.err.println("Status code: " + e.getCode()); - System.err.println("Reason: " + e.getResponseBody()); - System.err.println("Response headers: " + e.getResponseHeaders()); - e.printStackTrace(); - } - } -} -{{/-first}}{{/operation}}{{/operations}}{{/-first}}{{/apis}}{{/apiInfo}} -``` - -## Documentation for API Endpoints - -All URIs are relative to *{{basePath}}* - -Class | Method | HTTP request | Description ------------- | ------------- | ------------- | ------------- -{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}*{{classname}}* | [**{{operationId}}**]({{apiDocPath}}{{classname}}.md#{{operationId}}) | **{{httpMethod}}** {{commonPath}}{{path}} | {{summary}} -{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}} - -## Documentation for Models - -{{#models}}{{#model}} - [{{classname}}]({{modelDocPath}}{{classname}}.md) -{{/model}}{{/models}} - - -## Documentation for Authorization - -{{^authMethods}}Endpoints do not require authorization.{{/authMethods}} -{{#hasAuthMethods}}Authentication schemes defined for the API:{{/hasAuthMethods}} -{{#authMethods}} - -### {{name}} - -{{#isApiKey}} - -- **Type**: API key -- **API key parameter name**: {{keyParamName}} -- **Location**: {{#isKeyInQuery}}URL query string{{/isKeyInQuery}}{{#isKeyInHeader}}HTTP header{{/isKeyInHeader}} -{{/isApiKey}} -{{#isBasicBasic}} - -- **Type**: HTTP basic authentication -{{/isBasicBasic}} -{{#isBasicBearer}} - -- **Type**: HTTP Bearer Token authentication{{#bearerFormat}} ({{{.}}}){{/bearerFormat}} -{{/isBasicBearer}} -{{#isHttpSignature}} - -- **Type**: HTTP signature authentication -{{/isHttpSignature}} -{{#isOAuth}} - -- **Type**: OAuth -- **Flow**: {{flow}} -- **Authorization URL**: {{authorizationUrl}} -- **Scopes**: {{^scopes}}N/A{{/scopes}} -{{#scopes}} - {{scope}}: {{description}} -{{/scopes}} -{{/isOAuth}} - -{{/authMethods}} - -## Recommendation - -It's recommended to create an instance of `ApiClient` per thread in a multithreaded environment to avoid any potential issues. - -## Author - -{{#apiInfo}}{{#apis}}{{#-last}}{{infoEmail}} -{{/-last}}{{/apis}}{{/apiInfo}} diff --git a/config/clients/java/template/api.mustache b/config/clients/java/template/api.mustache deleted file mode 100644 index 98f6d0cb..00000000 --- a/config/clients/java/template/api.mustache +++ /dev/null @@ -1,120 +0,0 @@ -{{>licenseInfo}} -package {{package}}; - -import com.sun.jersey.api.client.GenericType; - -import {{invokerPackage}}.ApiException; -import {{invokerPackage}}.ApiClient; -import {{invokerPackage}}.Configuration; -import {{modelPackage}}.*; -import {{invokerPackage}}.Pair; - -{{#imports}}import {{import}}; -{{/imports}} - - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -{{>generatedAnnotation}} -{{#operations}} -public class {{classname}} { - private ApiClient apiClient; - - public {{classname}}() { - this(Configuration.getDefaultApiClient()); - } - - public {{classname}}(ApiClient apiClient) { - this.apiClient = apiClient; - } - - public ApiClient getApiClient() { - return apiClient; - } - - public void setApiClient(ApiClient apiClient) { - this.apiClient = apiClient; - } - - {{#operation}} - /** - * {{summary}} - * {{notes}} - {{#allParams}} - * @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{^isContainer}}{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/isContainer}}{{/required}} - {{/allParams}} - {{#returnType}} - * @return {{.}} - {{/returnType}} - * @throws ApiException if fails to make API call - {{#isDeprecated}} - * @deprecated - {{/isDeprecated}} - {{#externalDocs}} - * {{description}} - * @see {{summary}} Documentation - {{/externalDocs}} - */ - {{#isDeprecated}} - @Deprecated - {{/isDeprecated}} - public {{#returnType}}{{{.}}} {{/returnType}}{{^returnType}}void {{/returnType}}{{operationId}}({{#allParams}}{{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws ApiException { - Object localVarPostBody = {{#bodyParam}}{{paramName}}{{/bodyParam}}{{^bodyParam}}null{{/bodyParam}}; - {{#allParams}}{{#required}} - // verify the required parameter '{{paramName}}' is set - if ({{paramName}} == null) { - throw new ApiException(400, "Missing the required parameter '{{paramName}}' when calling {{operationId}}"); - } - {{/required}}{{/allParams}} - // create path and map variables - String localVarPath = "{{{path}}}"{{#pathParams}} - .replaceAll("\\{" + "{{baseName}}" + "\\}", apiClient.escapeString({{{paramName}}}.toString())){{/pathParams}}; - - // query params - List localVarQueryParams = new ArrayList(); - List localVarCollectionQueryParams = new ArrayList(); - Map localVarHeaderParams = new HashMap(); - Map localVarCookieParams = new HashMap(); - Map localVarFormParams = new HashMap(); - - {{#queryParams}} - {{#collectionFormat}}localVarCollectionQueryParams.addAll(apiClient.parameterToPairs("{{{.}}}", {{/collectionFormat}}{{^collectionFormat}}localVarQueryParams.addAll(apiClient.parameterToPair({{/collectionFormat}}"{{baseName}}", {{paramName}})); - {{/queryParams}} - - {{#headerParams}}if ({{paramName}} != null) - localVarHeaderParams.put("{{baseName}}", apiClient.parameterToString({{paramName}})); - {{/headerParams}} - - {{#cookieParams}}if ({{paramName}} != null) - localVarCookieParams.put("{{baseName}}", apiClient.parameterToString({{paramName}})); - {{/cookieParams}} - - {{#formParams}}if ({{paramName}} != null) - localVarFormParams.put("{{baseName}}", {{paramName}}); - {{/formParams}} - - final String[] localVarAccepts = { - {{#produces}}"{{{mediaType}}}"{{^-last}}, {{/-last}}{{/produces}} - }; - final String localVarAccept = apiClient.selectHeaderAccept(localVarAccepts); - - final String[] localVarContentTypes = { - {{#consumes}}"{{{mediaType}}}"{{^-last}}, {{/-last}}{{/consumes}} - }; - final String localVarContentType = apiClient.selectHeaderContentType(localVarContentTypes); - - String[] localVarAuthNames = new String[] { {{#authMethods}}"{{name}}"{{^-last}}, {{/-last}}{{/authMethods}} }; - - {{#returnType}} - GenericType<{{{returnType}}}> localVarReturnType = new GenericType<{{{returnType}}}>() {}; - return apiClient.invokeAPI(localVarPath, "{{httpMethod}}", localVarQueryParams, localVarCollectionQueryParams, localVarPostBody, localVarHeaderParams, localVarCookieParams, localVarFormParams, localVarAccept, localVarContentType, localVarAuthNames, localVarReturnType); - {{/returnType}}{{^returnType}} - apiClient.invokeAPI(localVarPath, "{{httpMethod}}", localVarQueryParams, localVarCollectionQueryParams, localVarPostBody, localVarHeaderParams, localVarCookieParams, localVarFormParams, localVarAccept, localVarContentType, localVarAuthNames, null); - {{/returnType}} - } - {{/operation}} -} -{{/operations}} diff --git a/config/clients/java/template/apiException.mustache b/config/clients/java/template/apiException.mustache deleted file mode 100644 index 31d2a807..00000000 --- a/config/clients/java/template/apiException.mustache +++ /dev/null @@ -1,89 +0,0 @@ -{{>licenseInfo}} - -package {{invokerPackage}}; - -import java.util.Map; -import java.util.List; - -{{>generatedAnnotation}} -public class ApiException extends{{#useRuntimeException}} RuntimeException {{/useRuntimeException}}{{^useRuntimeException}} Exception {{/useRuntimeException}}{ - private int code = 0; - private Map> responseHeaders = null; - private String responseBody = null; - - public ApiException() {} - - public ApiException(Throwable throwable) { - super(throwable); - } - - public ApiException(String message) { - super(message); - } - - public ApiException(String message, Throwable throwable, int code, Map> responseHeaders, String responseBody) { - super(message, throwable); - this.code = code; - this.responseHeaders = responseHeaders; - this.responseBody = responseBody; - } - - public ApiException(String message, int code, Map> responseHeaders, String responseBody) { - this(message, (Throwable) null, code, responseHeaders, responseBody); - } - - public ApiException(String message, Throwable throwable, int code, Map> responseHeaders) { - this(message, throwable, code, responseHeaders, null); - } - - public ApiException(int code, Map> responseHeaders, String responseBody) { - this("Response Code: " + code + " Response Body: " + responseBody, (Throwable) null, code, responseHeaders, responseBody); - } - - public ApiException(int code, String message) { - super(message); - this.code = code; - } - - public ApiException(int code, String message, Map> responseHeaders, String responseBody) { - this(code, message); - this.responseHeaders = responseHeaders; - this.responseBody = responseBody; - } - - /** - * Get the HTTP status code. - * - * @return HTTP status code - */ - public int getCode() { - return code; - } - - /** - * Get the HTTP response headers. - * - * @return A map of list of string - */ - public Map> getResponseHeaders() { - return responseHeaders; - } - - /** - * Get the HTTP response body. - * - * @return Response body in the form of string - */ - public String getResponseBody() { - return responseBody; - } - - @Override - public String toString() { - return "ApiException{" + - "code=" + code + - ", responseHeaders=" + responseHeaders + - ", responseBody='" + responseBody + '\'' + - '}'; - } -} diff --git a/config/clients/java/template/api_doc.mustache b/config/clients/java/template/api_doc.mustache deleted file mode 100644 index 3d320395..00000000 --- a/config/clients/java/template/api_doc.mustache +++ /dev/null @@ -1,108 +0,0 @@ -# {{classname}}{{#description}} - -{{.}}{{/description}} - -All URIs are relative to *{{basePath}}* - -| Method | HTTP request | Description | -|------------- | ------------- | -------------| -{{#operations}}{{#operation}}| [**{{operationId}}**]({{classname}}.md#{{operationId}}) | **{{httpMethod}}** {{commonPath}}{{path}} | {{summary}} | -{{/operation}}{{/operations}} - -{{#operations}} -{{#operation}} - -## {{operationId}} - -> {{#returnType}}{{.}} {{/returnType}}{{operationId}}({{#allParams}}{{{paramName}}}{{^-last}}, {{/-last}}{{/allParams}}) - -{{summary}}{{#notes}} - -{{.}}{{/notes}} - -### Example - -```java -// Import classes: -import {{{invokerPackage}}}.ApiClient; -import {{{invokerPackage}}}.ApiException; -import {{{invokerPackage}}}.Configuration;{{#hasAuthMethods}} -import {{{invokerPackage}}}.auth.*;{{/hasAuthMethods}} -import {{{invokerPackage}}}.models.*; -import {{{package}}}.{{{classname}}}; - -public class Example { - public static void main(String[] args) { - ApiClient defaultClient = Configuration.getDefaultApiClient(); - defaultClient.setBasePath("{{{basePath}}}"); - {{#hasAuthMethods}} - {{#authMethods}}{{#isBasic}}{{#isBasicBasic}} - // Configure HTTP basic authorization: {{{name}}} - HttpBasicAuth {{{name}}} = (HttpBasicAuth) defaultClient.getAuthentication("{{{name}}}"); - {{{name}}}.setUsername("YOUR USERNAME"); - {{{name}}}.setPassword("YOUR PASSWORD");{{/isBasicBasic}}{{#isBasicBearer}} - // Configure HTTP bearer authorization: {{{name}}} - HttpBearerAuth {{{name}}} = (HttpBearerAuth) defaultClient.getAuthentication("{{{name}}}"); - {{{name}}}.setBearerToken("BEARER TOKEN");{{/isBasicBearer}}{{/isBasic}}{{#isApiKey}} - // Configure API key authorization: {{{name}}} - ApiKeyAuth {{{name}}} = (ApiKeyAuth) defaultClient.getAuthentication("{{{name}}}"); - {{{name}}}.setApiKey("YOUR API KEY"); - // Uncomment the following line to set a prefix for the API key, e.g. "Token" (defaults to null) - //{{{name}}}.setApiKeyPrefix("Token");{{/isApiKey}}{{#isOAuth}} - // Configure OAuth2 access token for authorization: {{{name}}} - OAuth {{{name}}} = (OAuth) defaultClient.getAuthentication("{{{name}}}"); - {{{name}}}.setAccessToken("YOUR ACCESS TOKEN");{{/isOAuth}} - {{/authMethods}} - {{/hasAuthMethods}} - - {{{classname}}} apiInstance = new {{{classname}}}(defaultClient); - {{#allParams}} - {{{dataType}}} {{{paramName}}} = {{{example}}}; // {{{dataType}}} | {{{description}}} - {{/allParams}} - try { - {{#returnType}}{{{.}}} result = {{/returnType}}apiInstance.{{{operationId}}}({{#allParams}}{{{paramName}}}{{^-last}}, {{/-last}}{{/allParams}});{{#returnType}} - System.out.println(result);{{/returnType}} - } catch (ApiException e) { - System.err.println("Exception when calling {{{classname}}}#{{{operationId}}}"); - System.err.println("Status code: " + e.getCode()); - System.err.println("Reason: " + e.getResponseBody()); - System.err.println("Response headers: " + e.getResponseHeaders()); - e.printStackTrace(); - } - } -} -``` - -### Parameters - -{{^allParams}}This endpoint does not need any parameter.{{/allParams}}{{#allParams}}{{#-last}} -| Name | Type | Description | Notes | -|------------- | ------------- | ------------- | -------------|{{/-last}}{{/allParams}} -{{#allParams}}| **{{paramName}}** | {{#isPrimitiveType}}**{{dataType}}**{{/isPrimitiveType}}{{^isPrimitiveType}}{{#isFile}}**{{dataType}}**{{/isFile}}{{^isFile}}[**{{dataType}}**]({{baseType}}.md){{/isFile}}{{/isPrimitiveType}}| {{description}} |{{^required}} [optional]{{/required}}{{^isContainer}}{{#defaultValue}} [default to {{.}}]{{/defaultValue}}{{/isContainer}}{{#allowableValues}} [enum: {{#values}}{{{.}}}{{^-last}}, {{/-last}}{{/values}}]{{/allowableValues}} | -{{/allParams}} - -### Return type - -{{#returnType}}{{#returnTypeIsPrimitive}}**{{returnType}}**{{/returnTypeIsPrimitive}}{{^returnTypeIsPrimitive}}[**{{returnType}}**]({{returnBaseType}}.md){{/returnTypeIsPrimitive}}{{/returnType}}{{^returnType}}null (empty response body){{/returnType}} - -### Authorization - -{{^authMethods}}No authorization required{{/authMethods}}{{#authMethods}}[{{name}}](../README.md#{{name}}){{^-last}}, {{/-last}}{{/authMethods}} - -### HTTP request headers - -- **Content-Type**: {{#consumes}}{{{mediaType}}}{{^-last}}, {{/-last}}{{/consumes}}{{^consumes}}Not defined{{/consumes}} -- **Accept**: {{#produces}}{{{mediaType}}}{{^-last}}, {{/-last}}{{/produces}}{{^produces}}Not defined{{/produces}} - -{{#responses.0}} - -### HTTP response details -| Status code | Description | Response headers | -|-------------|-------------|------------------| -{{#responses}} -| **{{code}}** | {{message}} | {{#headers}} * {{baseName}} - {{description}}
{{/headers}}{{^headers.0}} - {{/headers.0}} | -{{/responses}} -{{/responses.0}} - -{{/operation}} -{{/operations}} diff --git a/config/clients/java/template/api_test.mustache b/config/clients/java/template/api_test.mustache deleted file mode 100644 index a579d741..00000000 --- a/config/clients/java/template/api_test.mustache +++ /dev/null @@ -1,51 +0,0 @@ -{{>licenseInfo}} - -package {{package}}; - -import {{invokerPackage}}.ApiException; -{{#imports}}import {{import}}; -{{/imports}} -import org.junit.Test; -import org.junit.Ignore; -import org.junit.Assert; - -import java.time.LocalDate; -import java.time.OffsetDateTime; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * API tests for {{classname}} - */ -public class {{classname}}Test { - - private final {{classname}} api = new {{classname}}(); - - {{#operations}} - {{#operation}} - /** - {{#summary}} - * {{.}} - * - {{/summary}} - {{#notes}} - * {{.}} - * - {{/notes}} - * @throws ApiException - * if the Api call fails - */ - @Test - public void {{operationId}}Test() throws ApiException { - //{{#allParams}} - //{{{dataType}}} {{paramName}} = null; - //{{/allParams}} - //{{#returnType}}{{{.}}} response = {{/returnType}}api.{{operationId}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}); - - // TODO: test validations - } - {{/operation}} - {{/operations}} -} diff --git a/config/clients/java/template/generatedAnnotation.mustache b/config/clients/java/template/generatedAnnotation.mustache deleted file mode 100644 index f408f319..00000000 --- a/config/clients/java/template/generatedAnnotation.mustache +++ /dev/null @@ -1 +0,0 @@ -@{{javaxPackage}}.annotation.Generated(value = "{{generatorClass}}"{{^hideGenerationTimestamp}}, date = "{{generatedDate}}"{{/hideGenerationTimestamp}}) \ No newline at end of file diff --git a/config/clients/java/template/gradle.properties.mustache b/config/clients/java/template/gradle.properties.mustache deleted file mode 100644 index 095ab31d..00000000 --- a/config/clients/java/template/gradle.properties.mustache +++ /dev/null @@ -1,9 +0,0 @@ -# This file is automatically generated by OpenAPI Generator (https://github.com/openAPITools/openapi-generator). -# To include other gradle properties as part of the code generation process, please use the `gradleProperties` option. -# -# Gradle properties reference: https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties -# For example, uncomment below to build for Android -#target = android -{{#gradleProperties}} -{{{.}}} -{{/gradleProperties}} diff --git a/config/clients/java/template/libraries/native/pom.mustache b/config/clients/java/template/libraries/native/pom.mustache deleted file mode 100644 index fb3ca69c..00000000 --- a/config/clients/java/template/libraries/native/pom.mustache +++ /dev/null @@ -1,299 +0,0 @@ - - 4.0.0 - {{groupId}} - {{artifactId}} - jar - {{artifactId}} - {{artifactVersion}} - {{artifactUrl}} - {{artifactDescription}} - - {{scmConnection}} - {{scmDeveloperConnection}} - {{scmUrl}} - -{{#parentOverridden}} - - {{{parentGroupId}}} - {{{parentArtifactId}}} - {{{parentVersion}}} - -{{/parentOverridden}} - - - - {{licenseName}} - {{licenseUrl}} - repo - - - - - - {{developerName}} - {{developerEmail}} - {{developerOrganization}} - {{developerOrganizationUrl}} - - - - - - - maven-enforcer-plugin - 3.1.0 - - - enforce-maven - - enforce - - - - - 3 - - - 11 - - - - - - - - maven-surefire-plugin - 3.0.0-M7 - - - conf/log4j.properties - - -Xms512m -Xmx1500m - methods - 10 - - - - maven-dependency-plugin - 3.3.0 - - - package - - copy-dependencies - - - ${project.build.directory}/lib - - - - - - - - org.apache.maven.plugins - maven-jar-plugin - 3.3.0 - - - - test-jar - - - - - - - - maven-compiler-plugin - 3.10.1 - - - org.apache.maven.plugins - maven-javadoc-plugin - 3.4.1 - - - attach-javadocs - - jar - - - - - - maven-source-plugin - 3.2.1 - - - attach-sources - - jar-no-fork - - - - - - - com.diffplug.spotless - spotless-maven-plugin - ${spotless.version} - - - - - - - .gitignore - - - - - - true - 4 - - - - - - - - - - 1.8 - - true - - - - - - - - - - - - sign-artifacts - - - - maven-gpg-plugin - 3.0.1 - - - sign-artifacts - verify - - sign - - - - - - - - - - - {{#swagger1AnnotationLibrary}} - - io.swagger - swagger-annotations - ${swagger-annotations-version} - - {{/swagger1AnnotationLibrary}} - {{#swagger2AnnotationLibrary}} - - io.swagger.core.v3 - swagger-annotations - ${swagger-annotations-version} - - {{/swagger2AnnotationLibrary}} - - - - com.fasterxml.jackson.core - jackson-core - ${jackson-version} - - - com.fasterxml.jackson.core - jackson-annotations - ${jackson-version} - - - com.fasterxml.jackson.core - jackson-databind - ${jackson-version} - - - com.fasterxml.jackson.datatype - jackson-datatype-jsr310 - ${jackson-version} - - - org.openapitools - jackson-databind-nullable - ${jackson-databind-nullable-version} - - - - - com.google.code.findbugs - jsr305 - 3.0.2 - - - jakarta.annotation - jakarta.annotation-api - ${jakarta-annotation-version} - provided - - {{#hasFormParamsInSpec}} - - org.apache.httpcomponents - httpmime - ${httpmime-version} - - {{/hasFormParamsInSpec}} - - - - junit - junit - ${junit-version} - test - - - - - UTF-8 - {{#swagger1AnnotationLibrary}} - 1.6.9 - {{/swagger1AnnotationLibrary}} - {{#swagger2AnnotationLibrary}} - 2.2.9 - {{/swagger2AnnotationLibrary}} - 11 - 11 - 2.14.1 - 0.2.6 - {{#useJakartaEe}} - 2.1.1 - {{/useJakartaEe}} - {{^useJakartaEe}} - 1.3.5 - {{/useJakartaEe}} - {{#hasFormParamsInSpec}} - 4.5.13 - {{/hasFormParamsInSpec}} - 4.13.2 - 2.27.2 - - diff --git a/config/clients/java/template/model.mustache b/config/clients/java/template/model.mustache deleted file mode 100644 index 55e6678d..00000000 --- a/config/clients/java/template/model.mustache +++ /dev/null @@ -1,68 +0,0 @@ -{{>licenseInfo}} - -package {{package}}; - -{{#useReflectionEqualsHashCode}} -import org.apache.commons.lang3.builder.EqualsBuilder; -import org.apache.commons.lang3.builder.HashCodeBuilder; -{{/useReflectionEqualsHashCode}} -import java.util.Objects; -import java.util.Arrays; -{{#imports}} -import {{import}}; -{{/imports}} -{{#serializableModel}} -import java.io.Serializable; -{{/serializableModel}} -{{#jackson}} -import com.fasterxml.jackson.annotation.JsonPropertyOrder; -import com.fasterxml.jackson.annotation.JsonTypeName; -{{#withXml}} -import com.fasterxml.jackson.dataformat.xml.annotation.*; -{{/withXml}} -{{#vendorExtensions.x-has-readonly-properties}} -import com.fasterxml.jackson.annotation.JsonCreator; -{{/vendorExtensions.x-has-readonly-properties}} -{{/jackson}} -{{#withXml}} -import {{javaxPackage}}.xml.bind.annotation.*; -import {{javaxPackage}}.xml.bind.annotation.adapters.*; -import io.github.threetenjaxb.core.*; -{{/withXml}} -{{#jsonb}} -import java.lang.reflect.Type; -import {{javaxPackage}}.json.bind.annotation.JsonbTypeDeserializer; -import {{javaxPackage}}.json.bind.annotation.JsonbTypeSerializer; -import {{javaxPackage}}.json.bind.serializer.DeserializationContext; -import {{javaxPackage}}.json.bind.serializer.JsonbDeserializer; -import {{javaxPackage}}.json.bind.serializer.JsonbSerializer; -import {{javaxPackage}}.json.bind.serializer.SerializationContext; -import {{javaxPackage}}.json.stream.JsonGenerator; -import {{javaxPackage}}.json.stream.JsonParser; -import {{javaxPackage}}.json.bind.annotation.JsonbProperty; -{{#vendorExtensions.x-has-readonly-properties}} -import {{javaxPackage}}.json.bind.annotation.JsonbCreator; -{{/vendorExtensions.x-has-readonly-properties}} -{{/jsonb}} -{{#parcelableModel}} -import android.os.Parcelable; -import android.os.Parcel; -{{/parcelableModel}} -{{#useBeanValidation}} -import {{javaxPackage}}.validation.constraints.*; -import {{javaxPackage}}.validation.Valid; -{{/useBeanValidation}} -{{#performBeanValidation}} -import org.hibernate.validator.constraints.*; -{{/performBeanValidation}} -{{#supportUrlQuery}} -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.util.StringJoiner; -{{/supportUrlQuery}} - -{{#models}} -{{#model}} -{{#isEnum}}{{>modelEnum}}{{/isEnum}}{{^isEnum}}{{#vendorExtensions.x-is-one-of-interface}}{{>oneof_interface}}{{/vendorExtensions.x-is-one-of-interface}}{{^vendorExtensions.x-is-one-of-interface}}{{>pojo}}{{/vendorExtensions.x-is-one-of-interface}}{{/isEnum}} -{{/model}} -{{/models}} diff --git a/config/clients/java/template/modelEnum.mustache b/config/clients/java/template/modelEnum.mustache deleted file mode 100644 index 54d90dae..00000000 --- a/config/clients/java/template/modelEnum.mustache +++ /dev/null @@ -1,117 +0,0 @@ -{{#jackson}} -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonValue; -{{/jackson}} -{{#gson}} -import java.io.IOException; -import com.google.gson.TypeAdapter; -import com.google.gson.annotations.JsonAdapter; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonWriter; -{{/gson}} - -/** - * {{description}}{{^description}}Gets or Sets {{{name}}}{{/description}} - */ -{{#gson}} -@JsonAdapter({{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}.Adapter.class) -{{/gson}} -{{#jsonb}} -@JsonbTypeSerializer({{datatypeWithEnum}}.Serializer.class) -@JsonbTypeDeserializer({{datatypeWithEnum}}.Deserializer.class) -{{/jsonb}} -{{>additionalEnumTypeAnnotations}}public enum {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} { - {{#allowableValues}}{{#enumVars}} - {{#enumDescription}} - /** - * {{.}} - */ - {{/enumDescription}} - {{#withXml}} - @XmlEnumValue({{#isInteger}}"{{/isInteger}}{{#isDouble}}"{{/isDouble}}{{#isLong}}"{{/isLong}}{{#isFloat}}"{{/isFloat}}{{{value}}}{{#isInteger}}"{{/isInteger}}{{#isDouble}}"{{/isDouble}}{{#isLong}}"{{/isLong}}{{#isFloat}}"{{/isFloat}}) - {{/withXml}} - {{{name}}}({{{value}}}){{^-last}}, - {{/-last}}{{#-last}};{{/-last}}{{/enumVars}}{{/allowableValues}} - - private {{{dataType}}} value; - - {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}({{{dataType}}} value) { - this.value = value; - } - -{{#jackson}} - @JsonValue -{{/jackson}} - public {{{dataType}}} getValue() { - return value; - } - - @Override - public String toString() { - return String.valueOf(value); - } - -{{#jackson}} - @JsonCreator -{{/jackson}} - public static {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} fromValue({{{dataType}}} value) { - for ({{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} b : {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}.values()) { - if (b.value.{{^isString}}equals{{/isString}}{{#isString}}{{#useEnumCaseInsensitive}}equalsIgnoreCase{{/useEnumCaseInsensitive}}{{^useEnumCaseInsensitive}}equals{{/useEnumCaseInsensitive}}{{/isString}}(value)) { - return b; - } - } - {{#isNullable}}return null;{{/isNullable}}{{^isNullable}}{{#enumUnknownDefaultCase}}{{#allowableValues}}{{#enumVars}}{{#-last}}return {{{name}}};{{/-last}}{{/enumVars}}{{/allowableValues}}{{/enumUnknownDefaultCase}}{{^enumUnknownDefaultCase}}throw new IllegalArgumentException("Unexpected value '" + value + "'");{{/enumUnknownDefaultCase}}{{/isNullable}} - } -{{#gson}} - - public static class Adapter extends TypeAdapter<{{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}> { - @Override - public void write(final JsonWriter jsonWriter, final {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} enumeration) throws IOException { - jsonWriter.value(enumeration.getValue()); - } - - @Override - public {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} read(final JsonReader jsonReader) throws IOException { - {{^isNumber}}{{{dataType}}}{{/isNumber}}{{#isNumber}}String{{/isNumber}} value = jsonReader.{{#isNumber}}nextString(){{/isNumber}}{{#isInteger}}nextInt(){{/isInteger}}{{^isNumber}}{{^isInteger}}next{{{dataType}}}(){{/isInteger}}{{/isNumber}}; - return {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}.fromValue({{#isNumber}}new BigDecimal({{/isNumber}}value{{#isNumber}}){{/isNumber}}); - } - } -{{/gson}} -{{#jsonb}} - - public static final class Deserializer implements JsonbDeserializer<{{datatypeWithEnum}}> { - @Override - public {{datatypeWithEnum}} deserialize(JsonParser parser, DeserializationContext ctx, Type rtType) { - for ({{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} b : {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}.values()) { - if (String.valueOf(b.value).equals(parser.getString())) { - return b; - } - } - {{#useNullForUnknownEnumValue}}return null;{{/useNullForUnknownEnumValue}}{{^useNullForUnknownEnumValue}}throw new IllegalArgumentException("Unexpected value '" + parser.getString() + "'");{{/useNullForUnknownEnumValue}} - } - } - - public static final class Serializer implements JsonbSerializer<{{datatypeWithEnum}}> { - @Override - public void serialize({{datatypeWithEnum}} obj, JsonGenerator generator, SerializationContext ctx) { - generator.write(obj.value); - } - } -{{/jsonb}} -{{#supportUrlQuery}} - - /** - * Convert the instance into URL query string. - * - * @param prefix prefix of the query string - * @return URL query string - */ - public String toUrlQueryString(String prefix) { - if (prefix == null) { - prefix = ""; - } - - return String.format("%s=%s", prefix, this.toString()); - } -{{/supportUrlQuery}} -} diff --git a/config/clients/java/template/model_doc.mustache b/config/clients/java/template/model_doc.mustache deleted file mode 100644 index 9a7fe146..00000000 --- a/config/clients/java/template/model_doc.mustache +++ /dev/null @@ -1,4 +0,0 @@ -{{#models}}{{#model}} - -{{#isEnum}}{{>enum_outer_doc}}{{/isEnum}}{{^isEnum}}{{>pojo_doc}}{{/isEnum}} -{{/model}}{{/models}} diff --git a/config/clients/java/template/pojo.mustache b/config/clients/java/template/pojo.mustache deleted file mode 100644 index 20f18dab..00000000 --- a/config/clients/java/template/pojo.mustache +++ /dev/null @@ -1,617 +0,0 @@ -/** - * {{description}}{{^description}}{{classname}}{{/description}}{{#isDeprecated}} - * @deprecated{{/isDeprecated}} - */{{#isDeprecated}} -@Deprecated{{/isDeprecated}} -{{#swagger1AnnotationLibrary}} -{{#description}} -@ApiModel(description = "{{{.}}}") -{{/description}} -{{/swagger1AnnotationLibrary}} -{{#swagger2AnnotationLibrary}} -{{#description}} -@Schema(description = "{{{.}}}") -{{/description}} -{{/swagger2AnnotationLibrary}} -{{#jackson}} -@JsonPropertyOrder({ -{{#vars}} - {{classname}}.JSON_PROPERTY_{{nameInSnakeCase}}{{^-last}},{{/-last}} -{{/vars}} -}) -{{#isClassnameSanitized}} -{{^hasDiscriminatorWithNonEmptyMapping}} -@JsonTypeName("{{name}}") -{{/hasDiscriminatorWithNonEmptyMapping}} -{{/isClassnameSanitized}} -{{/jackson}} -{{>additionalModelTypeAnnotations}}{{>generatedAnnotation}}{{#discriminator}}{{>typeInfoAnnotation}}{{/discriminator}}{{>xmlAnnotation}} -{{#vendorExtensions.x-class-extra-annotation}} -{{{vendorExtensions.x-class-extra-annotation}}} -{{/vendorExtensions.x-class-extra-annotation}} -public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtensions.x-implements}}{{#-first}}implements {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{#-last}} {{/-last}}{{/vendorExtensions.x-implements}}{ -{{#serializableModel}} - private static final long serialVersionUID = 1L; - -{{/serializableModel}} - {{#vars}} - {{#isEnum}} - {{^isContainer}} -{{>modelInnerEnum}} - {{/isContainer}} - {{#isContainer}} - {{#mostInnerItems}} -{{>modelInnerEnum}} - {{/mostInnerItems}} - {{/isContainer}} - {{/isEnum}} - {{#gson}} - public static final String SERIALIZED_NAME_{{nameInSnakeCase}} = "{{baseName}}"; - {{/gson}} - {{#jackson}} - public static final String JSON_PROPERTY_{{nameInSnakeCase}} = "{{baseName}}"; - {{/jackson}} - {{#withXml}} - {{#isXmlAttribute}} - @XmlAttribute(name = "{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}") - {{/isXmlAttribute}} - {{^isXmlAttribute}} - {{^isContainer}} - @XmlElement({{#xmlNamespace}}namespace="{{.}}", {{/xmlNamespace}}name = "{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}") - {{/isContainer}} - {{#isContainer}} - // Is a container wrapped={{isXmlWrapped}} - {{#items}} - // items.name={{name}} items.baseName={{baseName}} items.xmlName={{xmlName}} items.xmlNamespace={{xmlNamespace}} - // items.example={{example}} items.type={{dataType}} - @XmlElement({{#xmlNamespace}}namespace="{{.}}", {{/xmlNamespace}}name = "{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}") - {{/items}} - {{#isXmlWrapped}} - @XmlElementWrapper({{#xmlNamespace}}namespace="{{.}}", {{/xmlNamespace}}name = "{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}") - {{/isXmlWrapped}} - {{/isContainer}} - {{#isDateTime}} - @XmlJavaTypeAdapter(OffsetDateTimeXmlAdapter.class) - {{/isDateTime}} - {{/isXmlAttribute}} - {{/withXml}} - {{#gson}} - @SerializedName(SERIALIZED_NAME_{{nameInSnakeCase}}) - {{/gson}} - {{#vendorExtensions.x-field-extra-annotation}} - {{{vendorExtensions.x-field-extra-annotation}}} - {{/vendorExtensions.x-field-extra-annotation}} - {{#vendorExtensions.x-is-jackson-optional-nullable}} - {{#isContainer}} - private JsonNullable<{{{datatypeWithEnum}}}> {{name}} = JsonNullable.<{{{datatypeWithEnum}}}>undefined(); - {{/isContainer}} - {{^isContainer}} - private JsonNullable<{{{datatypeWithEnum}}}> {{name}} = JsonNullable.<{{{datatypeWithEnum}}}>{{#defaultValue}}of({{{.}}}){{/defaultValue}}{{^defaultValue}}undefined(){{/defaultValue}}; - {{/isContainer}} - {{/vendorExtensions.x-is-jackson-optional-nullable}} - {{^vendorExtensions.x-is-jackson-optional-nullable}} - {{#isContainer}} - private {{{datatypeWithEnum}}} {{name}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}; - {{/isContainer}} - {{^isContainer}} - {{#isDiscriminator}}protected{{/isDiscriminator}}{{^isDiscriminator}}private{{/isDiscriminator}} {{{datatypeWithEnum}}} {{name}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}; - {{/isContainer}} - {{/vendorExtensions.x-is-jackson-optional-nullable}} - - {{/vars}} - public {{classname}}() { - {{#parent}} - {{#parcelableModel}} - super();{{/parcelableModel}} - {{/parent}} - {{#gson}} - {{#discriminator}} - {{#discriminator.isEnum}} - this.{{{discriminatorName}}} = this.getClass().getSimpleName(); - {{/discriminator.isEnum}} - {{/discriminator}} - {{/gson}} - } - {{#vendorExtensions.x-has-readonly-properties}} - {{^withXml}} - - {{#jsonb}}@JsonbCreator{{/jsonb}}{{#jackson}}@JsonCreator{{/jackson}} - public {{classname}}( - {{#readOnlyVars}} - {{#jsonb}}@JsonbProperty(value = "{{baseName}}"{{^required}}, nullable = true{{/required}}){{/jsonb}}{{#jackson}}@JsonProperty(JSON_PROPERTY_{{nameInSnakeCase}}){{/jackson}} {{{datatypeWithEnum}}} {{name}}{{^-last}}, {{/-last}} - {{/readOnlyVars}} - ) { - this(); - {{#readOnlyVars}} - this.{{name}} = {{#vendorExtensions.x-is-jackson-optional-nullable}}{{name}} == null ? JsonNullable.<{{{datatypeWithEnum}}}>undefined() : JsonNullable.of({{name}}){{/vendorExtensions.x-is-jackson-optional-nullable}}{{^vendorExtensions.x-is-jackson-optional-nullable}}{{name}}{{/vendorExtensions.x-is-jackson-optional-nullable}}; - {{/readOnlyVars}} - } - {{/withXml}} - {{/vendorExtensions.x-has-readonly-properties}} - {{#vars}} - - {{^isReadOnly}} - public {{classname}} {{name}}({{{datatypeWithEnum}}} {{name}}) { - {{#vendorExtensions.x-is-jackson-optional-nullable}}this.{{name}} = JsonNullable.<{{{datatypeWithEnum}}}>of({{name}});{{/vendorExtensions.x-is-jackson-optional-nullable}} - {{^vendorExtensions.x-is-jackson-optional-nullable}}this.{{name}} = {{name}};{{/vendorExtensions.x-is-jackson-optional-nullable}} - return this; - } - {{#isArray}} - - public {{classname}} add{{nameInCamelCase}}Item({{{items.datatypeWithEnum}}} {{name}}Item) { - {{#vendorExtensions.x-is-jackson-optional-nullable}} - if (this.{{name}} == null || !this.{{name}}.isPresent()) { - this.{{name}} = JsonNullable.<{{{datatypeWithEnum}}}>of({{{defaultValue}}}{{^defaultValue}}new {{#uniqueItems}}LinkedHashSet{{/uniqueItems}}{{^uniqueItems}}ArrayList{{/uniqueItems}}<>(){{/defaultValue}}); - } - try { - this.{{name}}.get().add({{name}}Item); - } catch (java.util.NoSuchElementException e) { - // this can never happen, as we make sure above that the value is present - } - return this; - {{/vendorExtensions.x-is-jackson-optional-nullable}} - {{^vendorExtensions.x-is-jackson-optional-nullable}} - if (this.{{name}} == null) { - this.{{name}} = {{{defaultValue}}}{{^defaultValue}}new {{#uniqueItems}}LinkedHashSet{{/uniqueItems}}{{^uniqueItems}}ArrayList{{/uniqueItems}}<>(){{/defaultValue}}; - } - this.{{name}}.add({{name}}Item); - return this; - {{/vendorExtensions.x-is-jackson-optional-nullable}} - } - {{/isArray}} - {{#isMap}} - - public {{classname}} put{{nameInCamelCase}}Item(String key, {{{items.datatypeWithEnum}}} {{name}}Item) { - {{#vendorExtensions.x-is-jackson-optional-nullable}} - if (this.{{name}} == null || !this.{{name}}.isPresent()) { - this.{{name}} = JsonNullable.<{{{datatypeWithEnum}}}>of({{{defaultValue}}}{{^defaultValue}}new HashMap<>(){{/defaultValue}}); - } - try { - this.{{name}}.get().put(key, {{name}}Item); - } catch (java.util.NoSuchElementException e) { - // this can never happen, as we make sure above that the value is present - } - return this; - {{/vendorExtensions.x-is-jackson-optional-nullable}} - {{^vendorExtensions.x-is-jackson-optional-nullable}} - {{^required}} - if (this.{{name}} == null) { - this.{{name}} = {{{defaultValue}}}{{^defaultValue}}new HashMap<>(){{/defaultValue}}; - } - {{/required}} - this.{{name}}.put(key, {{name}}Item); - return this; - {{/vendorExtensions.x-is-jackson-optional-nullable}} - } - {{/isMap}} - - {{/isReadOnly}} - /** - {{#description}} - * {{.}} - {{/description}} - {{^description}} - * Get {{name}} - {{/description}} - {{#minimum}} - * minimum: {{.}} - {{/minimum}} - {{#maximum}} - * maximum: {{.}} - {{/maximum}} - * @return {{name}} - {{#deprecated}} - * @deprecated - {{/deprecated}} - **/ -{{#deprecated}} - @Deprecated -{{/deprecated}} -{{#required}} -{{#isNullable}} - @{{javaxPackage}}.annotation.Nullable -{{/isNullable}} -{{^isNullable}} - @{{javaxPackage}}.annotation.Nonnull -{{/isNullable}} -{{/required}} -{{^required}} - @{{javaxPackage}}.annotation.Nullable -{{/required}} -{{#jsonb}} - @JsonbProperty("{{baseName}}") -{{/jsonb}} -{{#useBeanValidation}} -{{>beanValidation}} -{{/useBeanValidation}} -{{#swagger1AnnotationLibrary}} - @ApiModelProperty({{#example}}example = "{{{.}}}", {{/example}}{{#required}}required = {{required}}, {{/required}}value = "{{{description}}}") -{{/swagger1AnnotationLibrary}} -{{#swagger2AnnotationLibrary}} - @Schema({{#example}}example = "{{{.}}}", {{/example}}requiredMode = {{#required}}Schema.RequiredMode.REQUIRED{{/required}}{{^required}}Schema.RequiredMode.NOT_REQUIRED{{/required}}, description = "{{{description}}}") -{{/swagger2AnnotationLibrary}} -{{#vendorExtensions.x-extra-annotation}} - {{{vendorExtensions.x-extra-annotation}}} -{{/vendorExtensions.x-extra-annotation}} -{{#vendorExtensions.x-is-jackson-optional-nullable}} - {{!unannotated, Jackson would pick this up automatically and add it *in addition* to the _JsonNullable getter field}} - @JsonIgnore -{{/vendorExtensions.x-is-jackson-optional-nullable}} -{{^vendorExtensions.x-is-jackson-optional-nullable}}{{#jackson}}{{> jackson_annotations}}{{/jackson}}{{/vendorExtensions.x-is-jackson-optional-nullable}} - public {{{datatypeWithEnum}}} {{getter}}() { - {{#vendorExtensions.x-is-jackson-optional-nullable}} - {{#isReadOnly}}{{! A readonly attribute doesn't have setter => jackson will set null directly if explicitly returned by API, so make sure we have an empty JsonNullable}} - if ({{name}} == null) { - {{name}} = JsonNullable.<{{{datatypeWithEnum}}}>{{#defaultValue}}of({{{.}}}){{/defaultValue}}{{^defaultValue}}undefined(){{/defaultValue}}; - } - {{/isReadOnly}} - return {{name}}.orElse(null); - {{/vendorExtensions.x-is-jackson-optional-nullable}} - {{^vendorExtensions.x-is-jackson-optional-nullable}} - return {{name}}; - {{/vendorExtensions.x-is-jackson-optional-nullable}} - } - - {{#vendorExtensions.x-is-jackson-optional-nullable}} -{{> jackson_annotations}} - public JsonNullable<{{{datatypeWithEnum}}}> {{getter}}_JsonNullable() { - return {{name}}; - } - {{/vendorExtensions.x-is-jackson-optional-nullable}}{{#vendorExtensions.x-is-jackson-optional-nullable}} - @JsonProperty(JSON_PROPERTY_{{nameInSnakeCase}}) - {{#isReadOnly}}private{{/isReadOnly}}{{^isReadOnly}}public{{/isReadOnly}} void {{setter}}_JsonNullable(JsonNullable<{{{datatypeWithEnum}}}> {{name}}) { - {{! For getters/setters that have name differing from attribute name, we must include setter (albeit private) for jackson to be able to set the attribute}} - this.{{name}} = {{name}}; - } - {{/vendorExtensions.x-is-jackson-optional-nullable}} - - {{^isReadOnly}} -{{#vendorExtensions.x-setter-extra-annotation}} {{{vendorExtensions.x-setter-extra-annotation}}} -{{/vendorExtensions.x-setter-extra-annotation}}{{#jackson}}{{^vendorExtensions.x-is-jackson-optional-nullable}}{{> jackson_annotations}}{{/vendorExtensions.x-is-jackson-optional-nullable}}{{/jackson}} public void {{setter}}({{{datatypeWithEnum}}} {{name}}) { - {{#vendorExtensions.x-is-jackson-optional-nullable}} - this.{{name}} = JsonNullable.<{{{datatypeWithEnum}}}>of({{name}}); - {{/vendorExtensions.x-is-jackson-optional-nullable}} - {{^vendorExtensions.x-is-jackson-optional-nullable}} - this.{{name}} = {{name}}; - {{/vendorExtensions.x-is-jackson-optional-nullable}} - } - {{/isReadOnly}} - - {{/vars}} - {{#parent}} - {{#allVars}} - {{#isOverridden}} - @Override - public {{classname}} {{name}}({{{datatypeWithEnum}}} {{name}}) { - {{#vendorExtensions.x-is-jackson-optional-nullable}} - this.{{setter}}(JsonNullable.<{{{datatypeWithEnum}}}>of({{name}})); - {{/vendorExtensions.x-is-jackson-optional-nullable}} - {{^vendorExtensions.x-is-jackson-optional-nullable}} - this.{{setter}}({{name}}); - {{/vendorExtensions.x-is-jackson-optional-nullable}} - return this; - } - - {{/isOverridden}} - {{/allVars}} - {{/parent}} - @Override - public boolean equals(Object o) { - {{#useReflectionEqualsHashCode}} - return EqualsBuilder.reflectionEquals(this, o, false, null, true); - {{/useReflectionEqualsHashCode}} - {{^useReflectionEqualsHashCode}} - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - }{{#hasVars}} - {{classname}} {{classVarName}} = ({{classname}}) o; - return {{#vars}}{{#vendorExtensions.x-is-jackson-optional-nullable}}equalsNullable(this.{{name}}, {{classVarName}}.{{name}}){{/vendorExtensions.x-is-jackson-optional-nullable}}{{^vendorExtensions.x-is-jackson-optional-nullable}}{{#isByteArray}}Arrays{{/isByteArray}}{{^isByteArray}}Objects{{/isByteArray}}.equals(this.{{name}}, {{classVarName}}.{{name}}){{/vendorExtensions.x-is-jackson-optional-nullable}}{{^-last}} && - {{/-last}}{{/vars}}{{#parent}} && - super.equals(o){{/parent}};{{/hasVars}}{{^hasVars}} - return {{#parent}}super.equals(o){{/parent}}{{^parent}}true{{/parent}};{{/hasVars}} - {{/useReflectionEqualsHashCode}} - }{{#vendorExtensions.x-jackson-optional-nullable-helpers}} - - private static boolean equalsNullable(JsonNullable a, JsonNullable b) { - return a == b || (a != null && b != null && a.isPresent() && b.isPresent() && Objects.deepEquals(a.get(), b.get())); - }{{/vendorExtensions.x-jackson-optional-nullable-helpers}} - - @Override - public int hashCode() { - {{#useReflectionEqualsHashCode}} - return HashCodeBuilder.reflectionHashCode(this); - {{/useReflectionEqualsHashCode}} - {{^useReflectionEqualsHashCode}} - return Objects.hash({{#vars}}{{#vendorExtensions.x-is-jackson-optional-nullable}}hashCodeNullable({{name}}){{/vendorExtensions.x-is-jackson-optional-nullable}}{{^vendorExtensions.x-is-jackson-optional-nullable}}{{^isByteArray}}{{name}}{{/isByteArray}}{{#isByteArray}}Arrays.hashCode({{name}}){{/isByteArray}}{{/vendorExtensions.x-is-jackson-optional-nullable}}{{^-last}}, {{/-last}}{{/vars}}{{#parent}}{{#hasVars}}, {{/hasVars}}super.hashCode(){{/parent}}); - {{/useReflectionEqualsHashCode}} - }{{#vendorExtensions.x-jackson-optional-nullable-helpers}} - - private static int hashCodeNullable(JsonNullable a) { - if (a == null) { - return 1; - } - return a.isPresent() ? Arrays.deepHashCode(new Object[]{a.get()}) : 31; - }{{/vendorExtensions.x-jackson-optional-nullable-helpers}} - - @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{{#jsonb}} static{{/jsonb}} String toIndentedString(Object o) { - if (o == null) { - return "null"; - } - return o.toString().replace("\n", "\n "); - } -{{#supportUrlQuery}} - - /** - * Convert the instance into URL query string. - * - * @return URL query string - */ - public String toUrlQueryString() { - return toUrlQueryString(null); - } - - /** - * Convert the instance into URL query string. - * - * @param prefix prefix of the query string - * @return URL query string - */ - public String toUrlQueryString(String prefix) { - String suffix = ""; - String containerSuffix = ""; - String containerPrefix = ""; - if (prefix == null) { - // style=form, explode=true, e.g. /pet?name=cat&type=manx - prefix = ""; - } else { - // deepObject style e.g. /pet?id[name]=cat&id[type]=manx - prefix = prefix + "["; - suffix = "]"; - containerSuffix = "]"; - containerPrefix = "["; - } - - StringJoiner joiner = new StringJoiner("&"); - - {{#allVars}} - // add `{{baseName}}` to the URL query string - {{#isArray}} - {{#items.isPrimitiveType}} - {{#uniqueItems}} - if ({{getter}}() != null) { - int i = 0; - for ({{{items.dataType}}} _item : {{getter}}()) { - try { - joiner.add(String.format("%s{{baseName}}%s%s=%s", prefix, suffix, - "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, i, containerSuffix), - URLEncoder.encode(String.valueOf(_item), "UTF-8").replaceAll("\\+", "%20"))); - } catch (UnsupportedEncodingException e) { - // Should never happen, UTF-8 is always supported - throw new RuntimeException(e); - } - } - i++; - } - {{/uniqueItems}} - {{^uniqueItems}} - if ({{getter}}() != null) { - for (int i = 0; i < {{getter}}().size(); i++) { - try { - joiner.add(String.format("%s{{baseName}}%s%s=%s", prefix, suffix, - "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, i, containerSuffix), - URLEncoder.encode(String.valueOf({{getter}}().get(i)), "UTF-8").replaceAll("\\+", "%20"))); - } catch (UnsupportedEncodingException e) { - // Should never happen, UTF-8 is always supported - throw new RuntimeException(e); - } - } - } - {{/uniqueItems}} - {{/items.isPrimitiveType}} - {{^items.isPrimitiveType}} - {{#items.isModel}} - {{#uniqueItems}} - if ({{getter}}() != null) { - int i = 0; - for ({{{items.dataType}}} _item : {{getter}}()) { - if (_item != null) { - joiner.add(_item.toUrlQueryString(String.format("%s{{baseName}}%s%s", prefix, suffix, - "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, i, containerSuffix)))); - } - } - i++; - } - {{/uniqueItems}} - {{^uniqueItems}} - if ({{getter}}() != null) { - for (int i = 0; i < {{getter}}().size(); i++) { - if ({{getter}}().get(i) != null) { - joiner.add({{getter}}().get(i).toUrlQueryString(String.format("%s{{baseName}}%s%s", prefix, suffix, - "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, i, containerSuffix)))); - } - } - } - {{/uniqueItems}} - {{/items.isModel}} - {{^items.isModel}} - {{#uniqueItems}} - if ({{getter}}() != null) { - int i = 0; - for ({{{items.dataType}}} _item : {{getter}}()) { - if (_item != null) { - try { - joiner.add(String.format("%s{{baseName}}%s%s=%s", prefix, suffix, - "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, i, containerSuffix), - URLEncoder.encode(String.valueOf(_item), "UTF-8").replaceAll("\\+", "%20"))); - } catch (UnsupportedEncodingException e) { - // Should never happen, UTF-8 is always supported - throw new RuntimeException(e); - } - } - i++; - } - } - {{/uniqueItems}} - {{^uniqueItems}} - if ({{getter}}() != null) { - for (int i = 0; i < {{getter}}().size(); i++) { - if ({{getter}}().get(i) != null) { - try { - joiner.add(String.format("%s{{baseName}}%s%s=%s", prefix, suffix, - "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, i, containerSuffix), - URLEncoder.encode(String.valueOf({{getter}}().get(i)), "UTF-8").replaceAll("\\+", "%20"))); - } catch (UnsupportedEncodingException e) { - // Should never happen, UTF-8 is always supported - throw new RuntimeException(e); - } - } - } - } - {{/uniqueItems}} - {{/items.isModel}} - {{/items.isPrimitiveType}} - {{/isArray}} - {{^isArray}} - {{#isMap}} - {{^items.isModel}} - if ({{getter}}() != null) { - for (String _key : {{getter}}().keySet()) { - try { - joiner.add(String.format("%s{{baseName}}%s%s=%s", prefix, suffix, - "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, _key, containerSuffix), - {{getter}}().get(_key), URLEncoder.encode(String.valueOf({{getter}}().get(_key)), "UTF-8").replaceAll("\\+", "%20"))); - } catch (UnsupportedEncodingException e) { - // Should never happen, UTF-8 is always supported - throw new RuntimeException(e); - } - } - } - {{/items.isModel}} - {{#items.isModel}} - if ({{getter}}() != null) { - for (String _key : {{getter}}().keySet()) { - if ({{getter}}().get(_key) != null) { - joiner.add({{getter}}().get(_key).toUrlQueryString(String.format("%s{{baseName}}%s%s", prefix, suffix, - "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, _key, containerSuffix)))); - } - } - } - {{/items.isModel}} - {{/isMap}} - {{^isMap}} - {{#isPrimitiveType}} - if ({{getter}}() != null) { - try { - joiner.add(String.format("%s{{{baseName}}}%s=%s", prefix, suffix, URLEncoder.encode(String.valueOf({{{getter}}}()), "UTF-8").replaceAll("\\+", "%20"))); - } catch (UnsupportedEncodingException e) { - // Should never happen, UTF-8 is always supported - throw new RuntimeException(e); - } - } - {{/isPrimitiveType}} - {{^isPrimitiveType}} - {{#isModel}} - if ({{getter}}() != null) { - joiner.add({{getter}}().toUrlQueryString(prefix + "{{{baseName}}}" + suffix)); - } - {{/isModel}} - {{^isModel}} - if ({{getter}}() != null) { - try { - joiner.add(String.format("%s{{{baseName}}}%s=%s", prefix, suffix, URLEncoder.encode(String.valueOf({{{getter}}}()), "UTF-8").replaceAll("\\+", "%20"))); - } catch (UnsupportedEncodingException e) { - // Should never happen, UTF-8 is always supported - throw new RuntimeException(e); - } - } - {{/isModel}} - {{/isPrimitiveType}} - {{/isMap}} - {{/isArray}} - - {{/allVars}} - return joiner.toString(); - } -{{/supportUrlQuery}} -{{#parcelableModel}} - - public void writeToParcel(Parcel out, int flags) { -{{#model}} -{{#isArray}} - out.writeList(this); -{{/isArray}} -{{^isArray}} -{{#parent}} - super.writeToParcel(out, flags); -{{/parent}} -{{#vars}} - out.writeValue({{name}}); -{{/vars}} -{{/isArray}} -{{/model}} - } - - {{classname}}(Parcel in) { -{{#isArray}} - in.readTypedList(this, {{arrayModelType}}.CREATOR); -{{/isArray}} -{{^isArray}} -{{#parent}} - super(in); -{{/parent}} -{{#vars}} -{{#isPrimitiveType}} - {{name}} = ({{{datatypeWithEnum}}})in.readValue(null); -{{/isPrimitiveType}} -{{^isPrimitiveType}} - {{name}} = ({{{datatypeWithEnum}}})in.readValue({{complexType}}.class.getClassLoader()); -{{/isPrimitiveType}} -{{/vars}} -{{/isArray}} - } - - public int describeContents() { - return 0; - } - - public static final Parcelable.Creator<{{classname}}> CREATOR = new Parcelable.Creator<{{classname}}>() { - public {{classname}} createFromParcel(Parcel in) { -{{#model}} -{{#isArray}} - {{classname}} result = new {{classname}}(); - result.addAll(in.readArrayList({{arrayModelType}}.class.getClassLoader())); - return result; -{{/isArray}} -{{^isArray}} - return new {{classname}}(in); -{{/isArray}} -{{/model}} - } - public {{classname}}[] newArray(int size) { - return new {{classname}}[size]; - } - }; -{{/parcelableModel}} - -} diff --git a/config/clients/java/template/pom.mustache b/config/clients/java/template/pom.mustache deleted file mode 100644 index 0a1bb6a5..00000000 --- a/config/clients/java/template/pom.mustache +++ /dev/null @@ -1,359 +0,0 @@ - - 4.0.0 - {{groupId}} - {{artifactId}} - jar - {{artifactId}} - {{artifactVersion}} - {{artifactUrl}} - {{artifactDescription}} - - {{scmConnection}} - {{scmDeveloperConnection}} - {{scmUrl}} - -{{#parentOverridden}} - - {{{parentGroupId}}} - {{{parentArtifactId}}} - {{{parentVersion}}} - -{{/parentOverridden}} - - - - {{licenseName}} - {{licenseUrl}} - repo - - - - - - {{developerName}} - {{developerEmail}} - {{developerOrganization}} - {{developerOrganizationUrl}} - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.8.1 - - 1.8 - 1.8 - true - 128m - 512m - - -Xlint:all - -J-Xss4m - - - - - - org.apache.maven.plugins - maven-enforcer-plugin - 3.0.0-M1 - - - enforce-maven - - enforce - - - - - 2.2.0 - - - - - - - - org.apache.maven.plugins - maven-surefire-plugin - 2.12 - - - - loggerPath - conf/log4j.properties - - - -Xms512m -Xmx1500m - methods - pertest - - - - maven-dependency-plugin - - - package - - copy-dependencies - - - ${project.build.directory}/lib - - - - - - - - org.apache.maven.plugins - maven-jar-plugin - 2.2 - - - - jar - test-jar - - - - - - - - - org.codehaus.mojo - build-helper-maven-plugin - 1.10 - - - add_sources - generate-sources - - add-source - - - - src/main/java - - - - - add_test_sources - generate-test-sources - - add-test-source - - - - src/test/java - - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 3.3.2 - - none - 1.8 - - - - attach-javadocs - - jar - - - - - - org.apache.maven.plugins - maven-source-plugin - 2.2.1 - - - attach-sources - - jar-no-fork - - - - - - - - - - sign-artifacts - - - - org.apache.maven.plugins - maven-gpg-plugin - 1.5 - - - sign-artifacts - verify - - sign - - - - - - - - - - - {{#swagger1AnnotationLibrary}} - - io.swagger - swagger-annotations - ${swagger-annotations-version} - - {{/swagger1AnnotationLibrary}} - {{#swagger2AnnotationLibrary}} - - io.swagger.core.v3 - swagger-annotations - ${swagger-annotations-version} - - {{/swagger2AnnotationLibrary}} - - - - com.google.code.findbugs - jsr305 - 3.0.2 - - - - - - com.sun.jersey - jersey-client - ${jersey-version} - - - com.sun.jersey.contribs - jersey-multipart - ${jersey-version} - - - - - com.fasterxml.jackson.core - jackson-core - ${jackson-version} - - - com.fasterxml.jackson.core - jackson-annotations - ${jackson-version} - - - com.fasterxml.jackson.core - jackson-databind - ${jackson-databind-version} - - - com.fasterxml.jackson.jaxrs - jackson-jaxrs-json-provider - ${jackson-version} - - {{#withXml}} - - - - com.fasterxml.jackson.dataformat - jackson-dataformat-xml - ${jackson-version} - - - {{/withXml}} - {{#joda}} - - com.fasterxml.jackson.datatype - jackson-datatype-joda - ${jackson-version} - - {{/joda}} - - com.fasterxml.jackson.datatype - jackson-datatype-jsr310 - ${jackson-version} - - {{#useBeanValidation}} - - - jakarta.validation - jakarta.validation-api - ${beanvalidation-version} - provided - - {{/useBeanValidation}} - {{#performBeanValidation}} - - - org.hibernate - hibernate-validator - 5.4.1.Final - - {{/performBeanValidation}} - {{#parcelableModel}} - - - com.google.android - android - 4.1.1.4 - provided - - {{/parcelableModel}} - - jakarta.annotation - jakarta.annotation-api - ${jakarta-annotation-version} - provided - - - - junit - junit - ${junit-version} - test - - - - UTF-8 - {{#swagger1AnnotationLibrary}} - 1.6.6 - {{/swagger1AnnotationLibrary}} - {{#swagger2AnnotationLibrary}} - 2.2.9 - {{/swagger2AnnotationLibrary}} - 1.19.4 - 2.12.6 - 2.12.6.1 - {{#useJakartaEe}} - 2.1.1 - {{/useJakartaEe}} - {{^useJakartaEe}} - 1.3.5 - {{/useJakartaEe}} - -{{#useBeanValidation}} - 2.0.2 -{{/useBeanValidation}} - 1.0.0 - 4.13.2 - - diff --git a/config/clients/java/template/travis.mustache b/config/clients/java/template/travis.mustache deleted file mode 100644 index 1b6741c0..00000000 --- a/config/clients/java/template/travis.mustache +++ /dev/null @@ -1,22 +0,0 @@ -# -# Generated by OpenAPI Generator: https://openapi-generator.tech -# -# Ref: https://docs.travis-ci.com/user/languages/java/ -# -language: java -jdk: - - openjdk12 - - openjdk11 - - openjdk10 - - openjdk9 - - openjdk8 -before_install: - # ensure gradlew has proper permission - - chmod a+x ./gradlew -script: - # test using maven - #- mvn test - # test using gradle - - gradle test - # test using sbt - # - sbt test From 73563af198fd19592b0ea3c6ee3f246cb00d98a1 Mon Sep 17 00:00:00 2001 From: "Justin \"J.R.\" Hill" Date: Tue, 1 Aug 2023 11:30:00 -0700 Subject: [PATCH 5/9] clean(java): Remove more unneeded or unused templates --- config/clients/java/.openapi-generator-ignore | 4 + .../template/BeanValidationException.mustache | 27 --- .../template/ServerConfiguration.mustache | 58 ------ .../java/template/ServerVariable.mustache | 23 --- .../java/template/auth/HttpBasicAuth.mustache | 42 ---- .../template/auth/HttpBearerAuth.mustache | 49 ----- .../java/template/beanValidation.mustache | 18 -- .../java/template/beanValidationCore.mustache | 20 -- .../beanValidationQueryParams.mustache | 1 - .../java/template/git_push.sh.mustache | 57 ------ .../template/libraries/native/README.mustache | 189 ------------------ 11 files changed, 4 insertions(+), 484 deletions(-) delete mode 100644 config/clients/java/template/BeanValidationException.mustache delete mode 100644 config/clients/java/template/ServerConfiguration.mustache delete mode 100644 config/clients/java/template/ServerVariable.mustache delete mode 100644 config/clients/java/template/auth/HttpBasicAuth.mustache delete mode 100644 config/clients/java/template/auth/HttpBearerAuth.mustache delete mode 100644 config/clients/java/template/beanValidation.mustache delete mode 100644 config/clients/java/template/beanValidationCore.mustache delete mode 100644 config/clients/java/template/beanValidationQueryParams.mustache delete mode 100755 config/clients/java/template/git_push.sh.mustache delete mode 100644 config/clients/java/template/libraries/native/README.mustache diff --git a/config/clients/java/.openapi-generator-ignore b/config/clients/java/.openapi-generator-ignore index 0b58f4f8..0c58fed4 100644 --- a/config/clients/java/.openapi-generator-ignore +++ b/config/clients/java/.openapi-generator-ignore @@ -1,3 +1,7 @@ +**/ServerConfiguration.java +**/ServerVariable.java src/main/AndroidManifest.xml build.sbt +pom.xml +git_push.sh .travis.yml diff --git a/config/clients/java/template/BeanValidationException.mustache b/config/clients/java/template/BeanValidationException.mustache deleted file mode 100644 index acdaef19..00000000 --- a/config/clients/java/template/BeanValidationException.mustache +++ /dev/null @@ -1,27 +0,0 @@ -package {{invokerPackage}}; - -import java.util.Set; - -import {{javaxPackage}}.validation.ConstraintViolation; -import {{javaxPackage}}.validation.ValidationException; - -public class BeanValidationException extends ValidationException { - /** - * - */ - private static final long serialVersionUID = -5294733947409491364L; - Set> violations; - - public BeanValidationException(Set> violations) { - this.violations = violations; - } - - public Set> getViolations() { - return violations; - } - - public void setViolations(Set> violations) { - this.violations = violations; - } - -} diff --git a/config/clients/java/template/ServerConfiguration.mustache b/config/clients/java/template/ServerConfiguration.mustache deleted file mode 100644 index 8b4cf59f..00000000 --- a/config/clients/java/template/ServerConfiguration.mustache +++ /dev/null @@ -1,58 +0,0 @@ -package {{invokerPackage}}; - -import java.util.Map; - -/** - * Representing a Server configuration. - */ -public class ServerConfiguration { - public String URL; - public String description; - public Map variables; - - /** - * @param URL A URL to the target host. - * @param description A description of the host designated by the URL. - * @param variables A map between a variable name and its value. The value is used for substitution in the server's URL template. - */ - public ServerConfiguration(String URL, String description, Map variables) { - this.URL = URL; - this.description = description; - this.variables = variables; - } - - /** - * Format URL template using given variables. - * - * @param variables A map between a variable name and its value. - * @return Formatted URL. - */ - public String URL(Map variables) { - String url = this.URL; - - // go through variables and replace placeholders - for (Map.Entry variable: this.variables.entrySet()) { - String name = variable.getKey(); - ServerVariable serverVariable = variable.getValue(); - String value = serverVariable.defaultValue; - - if (variables != null && variables.containsKey(name)) { - value = variables.get(name); - if (serverVariable.enumValues.size() > 0 && !serverVariable.enumValues.contains(value)) { - throw new IllegalArgumentException("The variable " + name + " in the server URL has invalid value " + value + "."); - } - } - url = url.replace("{" + name + "}", value); - } - return url; - } - - /** - * Format URL template using default server variables. - * - * @return Formatted URL. - */ - public String URL() { - return URL(null); - } -} diff --git a/config/clients/java/template/ServerVariable.mustache b/config/clients/java/template/ServerVariable.mustache deleted file mode 100644 index 1978b1eb..00000000 --- a/config/clients/java/template/ServerVariable.mustache +++ /dev/null @@ -1,23 +0,0 @@ -package {{invokerPackage}}; - -import java.util.HashSet; - -/** - * Representing a Server Variable for server URL template substitution. - */ -public class ServerVariable { - public String description; - public String defaultValue; - public HashSet enumValues = null; - - /** - * @param description A description for the server variable. - * @param defaultValue The default value to use for substitution. - * @param enumValues An enumeration of string values to be used if the substitution options are from a limited set. - */ - public ServerVariable(String description, String defaultValue, HashSet enumValues) { - this.description = description; - this.defaultValue = defaultValue; - this.enumValues = enumValues; - } -} diff --git a/config/clients/java/template/auth/HttpBasicAuth.mustache b/config/clients/java/template/auth/HttpBasicAuth.mustache deleted file mode 100644 index b5c72de6..00000000 --- a/config/clients/java/template/auth/HttpBasicAuth.mustache +++ /dev/null @@ -1,42 +0,0 @@ -{{>licenseInfo}} - -package {{invokerPackage}}.auth; - -import {{invokerPackage}}.Pair; - -import java.util.Base64; -import java.nio.charset.StandardCharsets; - -import java.util.Map; -import java.util.List; - -{{>generatedAnnotation}} -public class HttpBasicAuth implements Authentication { - private String username; - private String password; - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } - - @Override - public void applyToParams(List queryParams, Map headerParams, Map cookieParams) { - if (username == null && password == null) { - return; - } - String str = (username == null ? "" : username) + ":" + (password == null ? "" : password); - headerParams.put("Authorization", "Basic " + Base64.getEncoder().encodeToString(str.getBytes(StandardCharsets.UTF_8))); - } -} diff --git a/config/clients/java/template/auth/HttpBearerAuth.mustache b/config/clients/java/template/auth/HttpBearerAuth.mustache deleted file mode 100644 index 322281f8..00000000 --- a/config/clients/java/template/auth/HttpBearerAuth.mustache +++ /dev/null @@ -1,49 +0,0 @@ -{{>licenseInfo}} - -package {{invokerPackage}}.auth; - -import {{invokerPackage}}.Pair; - -import java.util.Map; -import java.util.List; - -{{>generatedAnnotation}} -public class HttpBearerAuth implements Authentication { - private final String scheme; - private String bearerToken; - - public HttpBearerAuth(String scheme) { - this.scheme = scheme; - } - - /** - * Gets the token, which together with the scheme, will be sent as the value of the Authorization header. - * - * @return The bearer token - */ - public String getBearerToken() { - return bearerToken; - } - - /** - * Sets the token, which together with the scheme, will be sent as the value of the Authorization header. - * - * @param bearerToken The bearer token to send in the Authorization header - */ - public void setBearerToken(String bearerToken) { - this.bearerToken = bearerToken; - } - - @Override - public void applyToParams(List queryParams, Map headerParams, Map cookieParams) { - if(bearerToken == null) { - return; - } - - headerParams.put("Authorization", (scheme != null ? upperCaseBearer(scheme) + " " : "") + bearerToken); - } - - private static String upperCaseBearer(String scheme) { - return ("bearer".equalsIgnoreCase(scheme)) ? "Bearer" : scheme; - } -} diff --git a/config/clients/java/template/beanValidation.mustache b/config/clients/java/template/beanValidation.mustache deleted file mode 100644 index 47f7109e..00000000 --- a/config/clients/java/template/beanValidation.mustache +++ /dev/null @@ -1,18 +0,0 @@ -{{#required}} -{{^isReadOnly}} - @NotNull -{{/isReadOnly}} -{{/required}} -{{#isContainer}} -{{^isPrimitiveType}} -{{^isEnum}} - @Valid -{{/isEnum}} -{{/isPrimitiveType}} -{{/isContainer}} -{{^isContainer}} -{{^isPrimitiveType}} - @Valid -{{/isPrimitiveType}} -{{/isContainer}} -{{>beanValidationCore}} \ No newline at end of file diff --git a/config/clients/java/template/beanValidationCore.mustache b/config/clients/java/template/beanValidationCore.mustache deleted file mode 100644 index ff4ffe26..00000000 --- a/config/clients/java/template/beanValidationCore.mustache +++ /dev/null @@ -1,20 +0,0 @@ -{{#pattern}}{{^isByteArray}} @Pattern(regexp="{{{pattern}}}"){{/isByteArray}}{{/pattern}}{{! -minLength && maxLength set -}}{{#minLength}}{{#maxLength}} @Size(min={{minLength}},max={{maxLength}}){{/maxLength}}{{/minLength}}{{! -minLength set, maxLength not -}}{{#minLength}}{{^maxLength}} @Size(min={{minLength}}){{/maxLength}}{{/minLength}}{{! -minLength not set, maxLength set -}}{{^minLength}}{{#maxLength}} @Size(max={{.}}){{/maxLength}}{{/minLength}}{{! -@Size: minItems && maxItems set -}}{{#minItems}}{{#maxItems}} @Size(min={{minItems}},max={{maxItems}}){{/maxItems}}{{/minItems}}{{! -@Size: minItems set, maxItems not -}}{{#minItems}}{{^maxItems}} @Size(min={{minItems}}){{/maxItems}}{{/minItems}}{{! -@Size: minItems not set && maxItems set -}}{{^minItems}}{{#maxItems}} @Size(max={{.}}){{/maxItems}}{{/minItems}}{{! -check for integer or long / all others=decimal type with @Decimal* -isInteger set -}}{{#isInteger}}{{#minimum}} @Min({{.}}){{/minimum}}{{#maximum}} @Max({{.}}){{/maximum}}{{/isInteger}}{{! -isLong set -}}{{#isLong}}{{#minimum}} @Min({{.}}L){{/minimum}}{{#maximum}} @Max({{.}}L){{/maximum}}{{/isLong}}{{! -Not Integer, not Long => we have a decimal value! -}}{{^isInteger}}{{^isLong}}{{#minimum}} @DecimalMin({{#exclusiveMinimum}}value={{/exclusiveMinimum}}"{{minimum}}"{{#exclusiveMinimum}},inclusive=false{{/exclusiveMinimum}}){{/minimum}}{{#maximum}} @DecimalMax({{#exclusiveMaximum}}value={{/exclusiveMaximum}}"{{maximum}}"{{#exclusiveMaximum}},inclusive=false{{/exclusiveMaximum}}){{/maximum}}{{/isLong}}{{/isInteger}} \ No newline at end of file diff --git a/config/clients/java/template/beanValidationQueryParams.mustache b/config/clients/java/template/beanValidationQueryParams.mustache deleted file mode 100644 index 0f99bffd..00000000 --- a/config/clients/java/template/beanValidationQueryParams.mustache +++ /dev/null @@ -1 +0,0 @@ -{{#required}} @NotNull {{/required}}{{>beanValidationCore}} \ No newline at end of file diff --git a/config/clients/java/template/git_push.sh.mustache b/config/clients/java/template/git_push.sh.mustache deleted file mode 100755 index 0e3776ae..00000000 --- a/config/clients/java/template/git_push.sh.mustache +++ /dev/null @@ -1,57 +0,0 @@ -#!/bin/sh -# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/ -# -# Usage example: /bin/sh ./git_push.sh wing328 openapi-petstore-perl "minor update" "gitlab.com" - -git_user_id=$1 -git_repo_id=$2 -release_note=$3 -git_host=$4 - -if [ "$git_host" = "" ]; then - git_host="{{{gitHost}}}" - echo "[INFO] No command line input provided. Set \$git_host to $git_host" -fi - -if [ "$git_user_id" = "" ]; then - git_user_id="{{{gitUserId}}}" - echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id" -fi - -if [ "$git_repo_id" = "" ]; then - git_repo_id="{{{gitRepoId}}}" - echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id" -fi - -if [ "$release_note" = "" ]; then - release_note="{{{releaseNote}}}" - echo "[INFO] No command line input provided. Set \$release_note to $release_note" -fi - -# Initialize the local directory as a Git repository -git init - -# Adds the files in the local repository and stages them for commit. -git add . - -# Commits the tracked changes and prepares them to be pushed to a remote repository. -git commit -m "$release_note" - -# Sets the new remote -git_remote=$(git remote) -if [ "$git_remote" = "" ]; then # git remote not defined - - if [ "$GIT_TOKEN" = "" ]; then - echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment." - git remote add origin https://${git_host}/${git_user_id}/${git_repo_id}.git - else - git remote add origin https://${git_user_id}:"${GIT_TOKEN}"@${git_host}/${git_user_id}/${git_repo_id}.git - fi - -fi - -git pull origin master - -# Pushes (Forces) the changes in the local repository up to the remote repository -echo "Git pushing to https://${git_host}/${git_user_id}/${git_repo_id}.git" -git push origin master 2>&1 | grep -v 'To https' diff --git a/config/clients/java/template/libraries/native/README.mustache b/config/clients/java/template/libraries/native/README.mustache deleted file mode 100644 index 0d183007..00000000 --- a/config/clients/java/template/libraries/native/README.mustache +++ /dev/null @@ -1,189 +0,0 @@ -# {{artifactId}} - -{{appName}} - -- API version: {{appVersion}} -{{^hideGenerationTimestamp}} - -- Build date: {{generatedDate}} -{{/hideGenerationTimestamp}} - -{{{appDescriptionWithNewLines}}} - -{{#infoUrl}} - For more information, please visit [{{{infoUrl}}}]({{{infoUrl}}}) -{{/infoUrl}} - -*Automatically generated by the [OpenAPI Generator](https://openapi-generator.tech)* - -## Requirements - -Building the API client library requires: - -1. Java 11+ -2. Maven/Gradle - -## Installation - -To install the API client library to your local Maven repository, simply execute: - -```shell -mvn clean install -``` - -To deploy it to a remote Maven repository instead, configure the settings of the repository and execute: - -```shell -mvn clean deploy -``` - -Refer to the [OSSRH Guide](http://central.sonatype.org/pages/ossrh-guide.html) for more information. - -### Maven users - -Add this dependency to your project's POM: - -```xml - - {{{groupId}}} - {{{artifactId}}} - {{{artifactVersion}}} - compile - -``` - -### Gradle users - -Add this dependency to your project's build file: - -```groovy -compile "{{{groupId}}}:{{{artifactId}}}:{{{artifactVersion}}}" -``` - -### Others - -At first generate the JAR by executing: - -```shell -mvn clean package -``` - -Then manually install the following JARs: - -- `target/{{{artifactId}}}-{{{artifactVersion}}}.jar` -- `target/lib/*.jar` - -## Getting Started - -Please follow the [installation](#installation) instruction and execute the following Java code: - -```java -{{#apiInfo}}{{#apis}}{{#-first}}{{#operations}}{{#operation}}{{#-first}} -import {{{invokerPackage}}}.*; -import {{{modelPackage}}}.*; -import {{{package}}}.{{{classname}}};{{#vendorExtensions.x-group-parameters}} -import {{{package}}}.{{{classname}}}.*;{{/vendorExtensions.x-group-parameters}} -{{#asyncNative}} -import java.util.concurrent.CompletableFuture; -{{/asyncNative}} - -public class {{{classname}}}Example { - - public static void main(String[] args) { - ApiClient defaultClient = Configuration.getDefaultApiClient(); - // Configure clients using the `defaultClient` object, such as - // overriding the host and port, timeout, etc. - {{{classname}}} apiInstance = new {{{classname}}}(defaultClient); - {{#allParams}} - {{{dataType}}} {{{paramName}}} = {{{example}}}; // {{{dataType}}} | {{{description}}} - {{/allParams}} - try { - {{^vendorExtensions.x-group-parameters}} - {{#returnType}}{{#asyncNative}}CompletableFuture<{{{returnType}}}>{{/asyncNative}}{{^asyncNative}}{{{returnType}}}{{/asyncNative}} result = {{/returnType}}{{^returnType}}{{#asyncNative}}CompletableFuture result = {{/asyncNative}}{{/returnType}}apiInstance.{{{operationId}}}({{#allParams}}{{{paramName}}}{{^-last}}, {{/-last}}{{/allParams}}); - {{/vendorExtensions.x-group-parameters}} - {{#vendorExtensions.x-group-parameters}} - {{#hasParams}} - API{{operationId}}Request request = API{{operationId}}Request.newBuilder(){{#allParams}} - .{{paramName}}({{paramName}}){{/allParams}} - .build(); - {{/hasParams}} - {{#returnType}}{{#asyncNative}}CompletableFuture<{{{returnType}}}>{{/asyncNative}}{{^asyncNative}}{{{returnType}}}{{/asyncNative}} result = {{/returnType}}{{^returnType}}{{#asyncNative}}CompletableFuture result = {{/asyncNative}}{{/returnType}}apiInstance.{{operationId}}({{#hasParams}}request{{/hasParams}}); - {{/vendorExtensions.x-group-parameters}} - {{#returnType}} - System.out.println(result{{#asyncNative}}.get(){{/asyncNative}}); - {{/returnType}} - } catch (ApiException e) { - System.err.println("Exception when calling {{{classname}}}#{{{operationId}}}"); - System.err.println("Status code: " + e.getCode()); - System.err.println("Reason: " + e.getResponseBody()); - System.err.println("Response headers: " + e.getResponseHeaders()); - e.printStackTrace(); - } - } -} -{{/-first}}{{/operation}}{{/operations}}{{/-first}}{{/apis}}{{/apiInfo}} -``` - -## Documentation for API Endpoints - -All URIs are relative to *{{basePath}}* - -Class | Method | HTTP request | Description ------------- | ------------- | ------------- | ------------- -{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}*{{classname}}* | [**{{operationId}}**]({{apiDocPath}}{{classname}}.md#{{operationId}}) | **{{httpMethod}}** {{path}} | {{summary}} -*{{classname}}* | [**{{operationId}}WithHttpInfo**]({{apiDocPath}}{{classname}}.md#{{operationId}}WithHttpInfo) | **{{httpMethod}}** {{path}} | {{summary}} -{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}} - -## Documentation for Models - -{{#models}}{{#model}} - [{{classname}}]({{modelDocPath}}{{classname}}.md) -{{/model}}{{/models}} - - -## Documentation for Authorization - -{{^authMethods}}Endpoints do not require authorization.{{/authMethods}} -{{#hasAuthMethods}}Authentication schemes defined for the API:{{/hasAuthMethods}} -{{#authMethods}} - -### {{name}} - -{{#isApiKey}} - -- **Type**: API key -- **API key parameter name**: {{keyParamName}} -- **Location**: {{#isKeyInQuery}}URL query string{{/isKeyInQuery}}{{#isKeyInHeader}}HTTP header{{/isKeyInHeader}} -{{/isApiKey}} -{{#isBasicBasic}} - -- **Type**: HTTP basic authentication -{{/isBasicBasic}} -{{#isBasicBearer}} - -- **Type**: HTTP Bearer Token authentication{{#bearerFormat}} ({{{.}}}){{/bearerFormat}} -{{/isBasicBearer}} -{{#isHttpSignature}} - -- **Type**: HTTP signature authentication -{{/isHttpSignature}} -{{#isOAuth}} - -- **Type**: OAuth -- **Flow**: {{flow}} -- **Authorization URL**: {{authorizationUrl}} -- **Scopes**: {{^scopes}}N/A{{/scopes}} -{{#scopes}} - {{scope}}: {{description}} -{{/scopes}} -{{/isOAuth}} - -{{/authMethods}} - -## Recommendation - -It's recommended to create an instance of `ApiClient` per thread in a multithreaded environment to avoid any potential issues. -However, the instances of the api clients created from the `ApiClient` are thread-safe and can be re-used. - -## Author - -{{#apiInfo}}{{#apis}}{{#-last}}{{infoEmail}} -{{/-last}}{{/apis}}{{/apiInfo}} From 849f2a2d24247b1ec299c73ebe4485305712c5ea Mon Sep 17 00:00:00 2001 From: "Justin \"J.R.\" Hill" Date: Wed, 2 Aug 2023 09:22:48 -0700 Subject: [PATCH 6/9] test(java): Add Java integration tests (#160) Adds integration tests for the Java SDK For intermediate commit details, see https://github.com/openfga/sdk-generator/pull/160 --------- Co-authored-by: Raghd Hamzeh --- .github/workflows/main.yaml | 53 +++ Makefile | 15 +- config/clients/java/config.overrides.json | 9 + .../java/template/.github/workflows/main.yml | 0 .../.github/workflows/main.yml.mustache | 27 ++ .../template/OpenFgaApiIntegrationTest.java | 318 ++++++++++++++++++ .../java/template/build.gradle.mustache | 38 ++- .../clients/java/template/gradle-wrapper.jar | Bin 59536 -> 63375 bytes .../libraries/native/api_test.mustache | 58 ---- .../clients/java/template/maven.yml.mustache | 31 -- 10 files changed, 455 insertions(+), 94 deletions(-) delete mode 100644 config/clients/java/template/.github/workflows/main.yml create mode 100644 config/clients/java/template/.github/workflows/main.yml.mustache create mode 100644 config/clients/java/template/OpenFgaApiIntegrationTest.java delete mode 100644 config/clients/java/template/libraries/native/api_test.mustache delete mode 100644 config/clients/java/template/maven.yml.mustache diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 261b2d93..6186d52d 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -168,3 +168,56 @@ jobs: GITHUB_ORG_ID: ${{ secrets.PYTHON_SDK_GITHUB_ORG_ID }} GITHUB_REPO_ID: ${{ secrets.PYTHON_SDK_GITHUB_REPO_ID }} SSH_KEY: ${{ secrets.PYTHON_SDK_SSH_KEY }} + + build-and-test-java-sdk: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up JDK + uses: actions/setup-java@v2 + with: + java-version: 11 + distribution: 'temurin' + cache: 'gradle' + + - name: Setup git + run: ./scripts/setup_git.sh + env: + GIT_USER_NAME: ${{ secrets.GIT_USER_NAME }} + GIT_USER_EMAIL: ${{ secrets.GIT_USER_EMAIL }} + + # - name: Clone the existing SDK + # run: ./scripts/clone_sdk.sh + # env: + # GITHUB_ORG_ID: ${{ secrets.JAVA_SDK_GITHUB_ORG_ID }} + # GITHUB_REPO_ID: ${{ secrets.JAVA_SDK_GITHUB_REPO_ID }} + # SSH_KEY: ${{ secrets.JAVA_SDK_SSH_KEY }} + # SDK_PATH: clients/fga-java-sdk + # KNOWN_HOSTS: ${{secrets.KNOWN_HOSTS}} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + # TODO: Don't run this twice. For some reason, the initial run of + # `make build-client-java` doesn't work, but subsequent runs do. + - name: Build Java SDK + continue-on-error: true + run: |- + mkdir -p clients/fga-java-sdk + make build-client-java + + - name: Run All Tests + run: |- + make test-integration-client-java + + # - name: Check for SDK changes + # run: ./scripts/commit_push_changes.sh + # env: + # SDK_PATH: clients/fga-java-sdk + # DRY_RUN: 1 + # TAGGING_DISABLE: 1 + # GITHUB_ORG_ID: ${{ secrets.JAVA_SDK_GITHUB_ORG_ID }} + # GITHUB_REPO_ID: ${{ secrets.JAVA_SDK_GITHUB_REPO_ID }} + # SSH_KEY: ${{ secrets.JAVA_SDK_SSH_KEY }} diff --git a/Makefile b/Makefile index 25ec41c0..08a77211 100644 --- a/Makefile +++ b/Makefile @@ -29,6 +29,7 @@ pull-docker-images: docker pull golangci/golangci-lint:${GOLINT_DOCKER_TAG} docker pull mcr.microsoft.com/dotnet/sdk:${DOTNET_DOCKER_TAG} docker pull busybox:${BUSYBOX_DOCKER_TAG} + docker pull gradle:${GRADLE_DOCKER_TAG} ## Publishing publish-client-dotnet: build-client-dotnet @@ -43,10 +44,10 @@ test: test-all-clients build: build-all-clients .PHONY: test-all-clients -test-all-clients: test-client-js test-client-go test-client-dotnet test-client-python +test-all-clients: test-client-js test-client-go test-client-dotnet test-client-python test-client-java .PHONY: build-all-clients -build-all-clients: build-client-js build-client-go build-client-dotnet build-client-python +build-all-clients: build-client-js build-client-go build-client-dotnet build-client-python build-client-java ### JavaScript .PHONY: tag-client-js @@ -130,6 +131,7 @@ run-in-docker: -v "${CLIENTS_OUTPUT_DIR}/fga-${sdk_language}-sdk":/module \ -v ${CONFIG_DIR}:/config \ -w /module \ + --net="host" \ ${image} \ ${command} @@ -207,7 +209,14 @@ setup-new-sdk: .PHONY: build-client-java build-client-java: make build-client sdk_language=java tmpdir=${TMP_DIR} - make run-in-docker sdk_language=java image=gradle:${GRADLE_DOCKER_TAG} command="/bin/sh -c 'gradle fmt'" + make run-in-docker sdk_language=java image=gradle:${GRADLE_DOCKER_TAG} command="/bin/sh -c 'chmod +x ./gradlew && gradle fmt build'" + +.PHONY: test-integration-client-java +test-integration-client-java: build-client-java + docker container rm --force openfga-for-java-client || true + docker run --detach --name openfga-for-java-client -p 8080:8080 openfga/openfga run + make run-in-docker sdk_language=java image=gradle:${GRADLE_DOCKER_TAG} command="/bin/sh -c './gradlew test-integration'" + docker container rm --force openfga-for-java-client .PHONY: test-client-java test-client-java: build-client-java diff --git a/config/clients/java/config.overrides.json b/config/clients/java/config.overrides.json index ba37467b..7cf47284 100644 --- a/config/clients/java/config.overrides.json +++ b/config/clients/java/config.overrides.json @@ -5,6 +5,7 @@ "artifactId": "openfga-sdk", "groupId": "dev.openfga", "apiPackage": "dev.openfga.api", + "invokerPackage": "dev.openfga.api.invoker", "modelPackage": "dev.openfga.api.model", "packageVersion": "0.0.1", "snapshotVersion": false, @@ -29,6 +30,14 @@ "build.gradle.mustache" : { "destinationFilename": "build.gradle", "templateType": "SupportingFiles" + }, + "OpenFgaApiIntegrationTest.java" : { + "destinationFilename": "src/test-integration/java/dev/openfga/api/OpenFgaApiIntegrationTest.java", + "templateType": "SupportingFiles" + }, + "gradle-wrapper.properties.mustache" : { + "destinationFilename": "gradle/wrapper/gradle-wrapper.properties", + "templateType": "SupportingFiles" } } } diff --git a/config/clients/java/template/.github/workflows/main.yml b/config/clients/java/template/.github/workflows/main.yml deleted file mode 100644 index e69de29b..00000000 diff --git a/config/clients/java/template/.github/workflows/main.yml.mustache b/config/clients/java/template/.github/workflows/main.yml.mustache new file mode 100644 index 00000000..770a141b --- /dev/null +++ b/config/clients/java/template/.github/workflows/main.yml.mustache @@ -0,0 +1,27 @@ +name: Java CI with Gradle + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + name: Build {{{appName}}} + runs-on: ubuntu-latest + strategy: + matrix: + java: [ '11', '17' ] + steps: + - uses: actions/checkout@v2 + - name: Set up JDK + uses: actions/setup-java@v2 + with: + {{=< >=}} + java-version: ${{ matrix.java }} + distribution: 'temurin' + cache: gradle + - name: Build with Gradle + run: | + ./gradlew build diff --git a/config/clients/java/template/OpenFgaApiIntegrationTest.java b/config/clients/java/template/OpenFgaApiIntegrationTest.java new file mode 100644 index 00000000..3bd925f1 --- /dev/null +++ b/config/clients/java/template/OpenFgaApiIntegrationTest.java @@ -0,0 +1,318 @@ +/* + * OpenFGA + * A high performance and flexible authorization/permission engine built for developers and inspired by Google Zanzibar. + * + * The version of the OpenAPI document: 0.1 + * Contact: community@openfga.dev + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +package dev.openfga.api; + +import static org.junit.jupiter.api.Assertions.*; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import dev.openfga.api.invoker.*; +import dev.openfga.api.model.*; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class OpenFgaApiIntegrationTest { + private static final ObjectMapper mapper = new ObjectMapper(); + private static final String DEFAULT_AUTH_MODEL = + "{\"schema_version\":\"1.1\",\"type_definitions\":[{\"type\":\"user\"},{\"type\":\"document\",\"relations\":{\"reader\":{\"this\":{}},\"writer\":{\"this\":{}},\"owner\":{\"this\":{}}},\"metadata\":{\"relations\":{\"reader\":{\"directly_related_user_types\":[{\"type\":\"user\"}]},\"writer\":{\"directly_related_user_types\":[{\"type\":\"user\"}]},\"owner\":{\"directly_related_user_types\":[{\"type\":\"user\"}]}}}}]}"; + private static final String DEFAULT_USER = "user:81684243-9356-4421-8fbf-a4f8d36aa31b"; + private static final String DEFAULT_DOC = "document:2021-budget"; + public static final TupleKey DEFAULT_TUPLE_KEY = + new TupleKey().user(DEFAULT_USER).relation("reader")._object(DEFAULT_DOC); + public static final List DEFAULT_TUPLE_KEYS = List.of(DEFAULT_TUPLE_KEY); + + private OpenFgaApi api; + + @BeforeEach + public void initializeApi() { + ApiClient apiClient = new ApiClient().setHost("localhost").setPort(8080); + api = new OpenFgaApi(apiClient); + } + + @Test + public void createStore() throws ApiException { + // Given + String storeName = thisTestName(); + CreateStoreRequest createStoreRequest = new CreateStoreRequest().name(storeName); + + // When + CreateStoreResponse response = api.createStore(createStoreRequest); + + // Then + assertEquals("dev.openfga.api.OpenFgaApiIntegrationTest.createStore", response.getName()); + } + + @Test + public void deleteStore() throws ApiException { + // Given + String storeName = thisTestName(); + String storeId = createStore(storeName); + + // When + api.deleteStore(storeId); + + // Then + ListStoresResponse response = api.listStores(100, null); + boolean itWasDeleted = response.getStores().stream().map(Store::getName).noneMatch(storeName::equals); + assertTrue(itWasDeleted, String.format("No stores should remain with the name %s.", storeName)); + } + + @Test + public void getStore() throws ApiException { + // Given + String storeName = thisTestName(); + String storeId = createStore(storeName); + + // When + GetStoreResponse response = api.getStore(storeId); + + // Then + assertEquals(storeName, response.getName()); + } + + @Test + public void listStores() throws ApiException { + // Given + String testName = thisTestName(); + String store1 = testName + "-store1"; + String store2 = testName + "-store2"; + String store3 = testName + "-store3"; + List stores = List.of(store1, store2, store3); + for (String store : stores) { + createStore(store); + } + + // When + ListStoresResponse response = api.listStores(100, null); + + // Then + for (String store : stores) { + boolean exists = response.getStores().stream().map(Store::getName).anyMatch(store::equals); + assertTrue(exists, String.format("Store %s should be in listStores response", store)); + } + } + + @Test + public void readAuthModel() throws Exception { + // Given + String storeName = thisTestName(); + String storeId = createStore(storeName); + String authModelId = writeAuthModel(storeId); + + // When + ReadAuthorizationModelResponse response = api.readAuthorizationModel(storeId, authModelId); + + // Then + AuthorizationModel authModel = response.getAuthorizationModel(); + assertEquals(authModelId, authModel.getId()); + String typeDefsJson = mapper.writeValueAsString(authModel.getTypeDefinitions()); + assertEquals( + "[{\"type\":\"user\",\"relations\":{},\"metadata\":null},{\"type\":\"document\",\"relations\":{\"owner\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"reader\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"writer\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null}},\"metadata\":{\"relations\":{\"owner\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null}]},\"reader\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null}]},\"writer\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null}]}}}}]", + typeDefsJson); + } + + @Test + public void readAuthModels() throws Exception { + // Given + String storeName = thisTestName(); + String storeId = createStore(storeName); + String authModelId = writeAuthModel(storeId); + + // When + ReadAuthorizationModelsResponse response = api.readAuthorizationModels(storeId, 100, null); + + // Then + response.getAuthorizationModels().stream() + .filter(authModel -> authModel.getId().equals(authModelId)) + .forEach(authModel -> { + assertEquals(authModelId, authModel.getId()); + try { + String typeDefsJson = mapper.writeValueAsString(authModel.getTypeDefinitions()); + + assertEquals( + "[{\"type\":\"user\",\"relations\":{},\"metadata\":null},{\"type\":\"document\",\"relations\":{\"owner\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"reader\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null},\"writer\":{\"this\":{},\"computedUserset\":null,\"tupleToUserset\":null,\"union\":null,\"intersection\":null,\"difference\":null}},\"metadata\":{\"relations\":{\"owner\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null}]},\"reader\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null}]},\"writer\":{\"directly_related_user_types\":[{\"type\":\"user\",\"relation\":null,\"wildcard\":null}]}}}}]", + typeDefsJson); + } catch (JsonProcessingException ex) { + assertNull(ex); + } + }); + } + + @Test + public void writeAuthModel() throws ApiException, JsonProcessingException { + // Given + String storeName = thisTestName(); + String storeId = createStore(storeName); + WriteAuthorizationModelRequest request = + mapper.readValue(DEFAULT_AUTH_MODEL, WriteAuthorizationModelRequest.class); + + // When + WriteAuthorizationModelResponse response = api.writeAuthorizationModel(storeId, request); + + // Then + assertNotNull(response); + assertNotNull(response.getAuthorizationModelId()); + assertNotEquals("", response.getAuthorizationModelId()); + } + + @Test + public void write_and_read() throws Exception { + // Given + String storeName = thisTestName(); + String storeId = createStore(storeName); + String _authModelId = writeAuthModel(storeId); + WriteRequest writeRequest = new WriteRequest().writes(new TupleKeys().tupleKeys(List.of(DEFAULT_TUPLE_KEY))); + ReadRequest readRequest = + new ReadRequest().tupleKey(new TupleKey().user(DEFAULT_USER)._object(DEFAULT_DOC)); + + // When + api.write(storeId, writeRequest); + ReadResponse response = api.read(storeId, readRequest); + + // Then + TupleKey key = response.getTuples().get(0).getKey(); + assertEquals(DEFAULT_USER, key.getUser()); + assertEquals("reader", key.getRelation()); + assertEquals(DEFAULT_DOC, key.getObject()); + } + + @Test + public void write_and_check() throws Exception { + // Given + String storeName = thisTestName(); + String storeId = createStore(storeName); + String _authModelId = writeAuthModel(storeId); + WriteRequest writeRequest = new WriteRequest().writes(new TupleKeys().tupleKeys(DEFAULT_TUPLE_KEYS)); + CheckRequest checkRequest = new CheckRequest() + .tupleKey(new TupleKey().user(DEFAULT_USER).relation("reader")._object(DEFAULT_DOC)); + + // When + api.write(storeId, writeRequest); + CheckResponse response = api.check(storeId, checkRequest); + + // Then + assertTrue(response.getAllowed()); + } + + @Test + public void write_and_expand() throws Exception { + // Given + String storeName = thisTestName(); + String storeId = createStore(storeName); + String _authModelId = writeAuthModel(storeId); + WriteRequest writeRequest = new WriteRequest().writes(new TupleKeys().tupleKeys(DEFAULT_TUPLE_KEYS)); + ExpandRequest expandRequest = + new ExpandRequest().tupleKey(new TupleKey()._object(DEFAULT_DOC).relation("reader")); + + // When + api.write(storeId, writeRequest); + ExpandResponse response = api.expand(storeId, expandRequest); + + // Then + assertNotNull(response.getTree()); + String responseJson = mapper.writeValueAsString(response); + assertEquals( + "{\"tree\":{\"root\":{\"name\":\"document:2021-budget#reader\",\"leaf\":{\"users\":{\"users\":[\"user:81684243-9356-4421-8fbf-a4f8d36aa31b\"]},\"computed\":null,\"tupleToUserset\":null},\"difference\":null,\"union\":null,\"intersection\":null}}}", + responseJson); + } + + @Test + public void write_and_listObjects() throws Exception { + // Given + String storeName = thisTestName(); + String storeId = createStore(storeName); + String _authModelId = writeAuthModel(storeId); + WriteRequest writeRequest = new WriteRequest().writes(new TupleKeys().tupleKeys(DEFAULT_TUPLE_KEYS)); + ListObjectsRequest listObjectsRequest = + new ListObjectsRequest().user(DEFAULT_USER).relation("reader").type("document"); + + // When + api.write(storeId, writeRequest); + ListObjectsResponse response = api.listObjects(storeId, listObjectsRequest); + + // Then + assertEquals(1, response.getObjects().size()); + assertEquals(DEFAULT_DOC, response.getObjects().get(0)); + } + + @Test + public void write_and_readChanges() throws Exception { + // Given + String storeName = thisTestName(); + String storeId = createStore(storeName); + String _authModelId = writeAuthModel(storeId); + WriteRequest writeRequest = new WriteRequest().writes(new TupleKeys().tupleKeys(DEFAULT_TUPLE_KEYS)); + + // When + api.write(storeId, writeRequest); + ReadChangesResponse response = api.readChanges(storeId, null, null, null); + + // Then + assertEquals(1, response.getChanges().size()); + String firstTupleKeyJson = mapper.writeValueAsString(response.getChanges().get(0).getTupleKey()); + assertEquals("{\"object\":\"document:2021-budget\",\"relation\":\"reader\",\"user\":\"user:81684243-9356-4421-8fbf-a4f8d36aa31b\"}", firstTupleKeyJson); + } + + @Test + public void write_readAssertions() throws Exception { + // Given + String storeName = thisTestName(); + String storeId = createStore(storeName); + String authModelId = writeAuthModel(storeId); + WriteAssertionsRequest writeRequest = new WriteAssertionsRequest() + .assertions(List.of(new Assertion().tupleKey(DEFAULT_TUPLE_KEY).expectation(true))); + + // When + api.writeAssertions(storeId, authModelId, writeRequest); + ReadAssertionsResponse response = api.readAssertions(storeId, authModelId); + + // Then + String responseJson = mapper.writeValueAsString(response.getAssertions()); + assertEquals( + "[{\"tuple_key\":{\"object\":\"document:2021-budget\",\"relation\":\"reader\",\"user\":\"user:81684243-9356-4421-8fbf-a4f8d36aa31b\"},\"expectation\":true}]", + responseJson); + } + + /** + * Create a store for a given name. If tests fail here, troubleshoot with the no-arguments + * test method createStore(). + * @return The created Store ID + */ + private String createStore(String storeName) throws ApiException { + CreateStoreResponse response = api.createStore(new CreateStoreRequest().name(storeName)); + return response.getId(); + } + + /** + * Add a default authorization model to a store. If tests fail here, troubleshoot with the + * no-arguments @Test writeAuthModel() method. + * @return The created Authorization Model ID + */ + private String writeAuthModel(String storeId) throws ApiException, JsonProcessingException { + WriteAuthorizationModelRequest request = + mapper.readValue(DEFAULT_AUTH_MODEL, WriteAuthorizationModelRequest.class); + WriteAuthorizationModelResponse response = api.writeAuthorizationModel(storeId, request); + return response.getAuthorizationModelId(); + } + + /** Get the name of the test that invokes this function. Returned in the form: "$class.$fn" */ + private String thisTestName() { + // Tracing the stack gives an array of: + // 0: getStackTrace(), 1: getThisFunctionName(), 2: , 3: ... + StackTraceElement callingFn = Thread.currentThread().getStackTrace()[2]; + String callingClass = callingFn.getClassName().replace("def.openfga.api.", ""); + + return String.format("%s.%s", callingClass, callingFn.getMethodName()); + } +} diff --git a/config/clients/java/template/build.gradle.mustache b/config/clients/java/template/build.gradle.mustache index 43d789d0..9b3c5cee 100644 --- a/config/clients/java/template/build.gradle.mustache +++ b/config/clients/java/template/build.gradle.mustache @@ -1,9 +1,10 @@ plugins { id 'java' + id 'jvm-test-suite' id 'maven-publish' id 'idea' id 'eclipse' - id 'com.diffplug.spotless' version '6.+' + id 'com.diffplug.spotless' version '6.20.0' } group = '{{groupId}}' @@ -30,6 +31,11 @@ java { withSourcesJar() } +javadoc { + // Ignore warnings. + options.addStringOption('Xdoclint:none', '-quiet') +} + ext { {{#swagger1AnnotationLibrary}} swagger_annotations_version = "1.6.9" @@ -39,7 +45,7 @@ ext { {{/swagger2AnnotationLibrary}} jackson_version = "2.14.1" jakarta_annotation_version = "1.3.5" - junit_version = "4.13.2" + junit_version = "4.13.2" // TODO: JUnit 5 (Jupiter) {{#hasFormParamsInSpec}} httpmime_version = "4.5.13" {{/hasFormParamsInSpec}} @@ -62,7 +68,31 @@ dependencies { {{#hasFormParamsInSpec}} implementation "org.apache.httpcomponents:httpmime:$httpmime_version" {{/hasFormParamsInSpec}} + + // Test-only dependencies testImplementation "junit:junit:$junit_version" + +} + +testing { + suites { + integration(JvmTestSuite) { + testType = TestSuiteType.INTEGRATION_TEST + useJUnitJupiter() + + dependencies { + implementation "com.fasterxml.jackson.core:jackson-core:$jackson_version" + implementation "com.fasterxml.jackson.core:jackson-databind:$jackson_version" + implementation project() + } + + sources { + java { + srcDirs = ['src/test-integration/java'] + } + } + } + } } // Use spotless plugin to automatically format code, remove unused import, etc @@ -92,3 +122,7 @@ spotless { tasks.register('fmt') { dependsOn 'spotlessApply' } + +tasks.register('test-integration') { + dependsOn testing.suites.integration +} diff --git a/config/clients/java/template/gradle-wrapper.jar b/config/clients/java/template/gradle-wrapper.jar index 7454180f2ae8848c63b8b4dea2cb829da983f2fa..033e24c4cdf41af1ab109bc7f253b2b887023340 100644 GIT binary patch delta 41451 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 delta 37613 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�aus#JUD;nS;yy5u_yvon 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 F@Bb2_FB|{> diff --git a/config/clients/java/template/libraries/native/api_test.mustache b/config/clients/java/template/libraries/native/api_test.mustache deleted file mode 100644 index dc56ce78..00000000 --- a/config/clients/java/template/libraries/native/api_test.mustache +++ /dev/null @@ -1,58 +0,0 @@ -{{>licenseInfo}} - -package {{package}}; - -import {{invokerPackage}}.ApiException; -{{#imports}}import {{import}}; -{{/imports}} -import org.junit.Test; -import org.junit.Ignore; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -{{#asyncNative}} -import java.util.concurrent.CompletableFuture; -{{/asyncNative}} - -/** - * API tests for {{classname}} - */ -@Ignore -public class {{classname}}Test { - - private final {{classname}} api = new {{classname}}(); - - {{#operations}}{{#operation}} - /** - * {{summary}} - * - * {{notes}} - * - * @throws ApiException - * if the Api call fails - */ - @Test - public void {{operationId}}Test() throws ApiException { - {{#allParams}} - {{{dataType}}} {{paramName}} = null; - {{/allParams}} - {{^vendorExtensions.x-group-parameters}} - {{#returnType}}{{#asyncNative}}CompletableFuture<{{{returnType}}}>{{/asyncNative}}{{^asyncNative}}{{{returnType}}}{{/asyncNative}} response = {{/returnType}} - {{^returnType}}{{#asyncNative}}CompletableFuture response = {{/asyncNative}}{{/returnType}}api.{{operationId}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}); - {{/vendorExtensions.x-group-parameters}} - {{#vendorExtensions.x-group-parameters}}{{#hasParams}} - {{classname}}.API{{operationId}}Request request = {{classname}}.API{{operationId}}Request.newBuilder(){{#allParams}} - .{{paramName}}({{paramName}}){{/allParams}} - .build();{{/hasParams}} - {{#returnType}}{{#asyncNative}}CompletableFuture<{{{returnType}}}>{{/asyncNative}}{{^asyncNative}}{{{returnType}}}{{/asyncNative}} response = {{/returnType}} - {{^returnType}}{{#asyncNative}}CompletableFuture response = {{/asyncNative}}{{/returnType}}api.{{operationId}}({{#hasParams}}request{{/hasParams}}); - {{/vendorExtensions.x-group-parameters}} - - // TODO: test validations - } - {{/operation}}{{/operations}} -} diff --git a/config/clients/java/template/maven.yml.mustache b/config/clients/java/template/maven.yml.mustache deleted file mode 100644 index f3c4733c..00000000 --- a/config/clients/java/template/maven.yml.mustache +++ /dev/null @@ -1,31 +0,0 @@ -# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time -# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven -# -# This file is auto-generated by OpenAPI Generator (https://openapi-generator.tech) - -name: Java CI with Maven - -on: - push: - branches: [ main, master ] - pull_request: - branches: [ main, master ] - -jobs: - build: - name: Build {{{appName}}} - runs-on: ubuntu-latest - strategy: - matrix: - java: [ '8' ] - steps: - - uses: actions/checkout@v2 - - name: Set up JDK - uses: actions/setup-java@v2 - with: - {{=< >=}} - java-version: ${{ matrix.java }} - distribution: 'temurin' - cache: maven - - name: Build with Maven - run: mvn -B package --no-transfer-progress --file pom.xml From c2f7ed8f151396944e13250d8e34d3cff71310ba Mon Sep 17 00:00:00 2001 From: "Justin \"J.R.\" Hill" Date: Tue, 1 Aug 2023 13:58:32 -0700 Subject: [PATCH 7/9] chore(java): Update to JUnit 5 --- .../java/template/build.gradle.mustache | 9 ++- .../libraries/native/api_test.mustache | 58 +++++++++++++++++++ .../clients/java/template/model_test.mustache | 6 +- 3 files changed, 68 insertions(+), 5 deletions(-) create mode 100644 config/clients/java/template/libraries/native/api_test.mustache diff --git a/config/clients/java/template/build.gradle.mustache b/config/clients/java/template/build.gradle.mustache index 9b3c5cee..dc6d7c06 100644 --- a/config/clients/java/template/build.gradle.mustache +++ b/config/clients/java/template/build.gradle.mustache @@ -45,7 +45,7 @@ ext { {{/swagger2AnnotationLibrary}} jackson_version = "2.14.1" jakarta_annotation_version = "1.3.5" - junit_version = "4.13.2" // TODO: JUnit 5 (Jupiter) + junit_version = "5.7.1" {{#hasFormParamsInSpec}} httpmime_version = "4.5.13" {{/hasFormParamsInSpec}} @@ -70,7 +70,8 @@ dependencies { {{/hasFormParamsInSpec}} // Test-only dependencies - testImplementation "junit:junit:$junit_version" + testImplementation "org.junit.jupiter:junit-jupiter:$junit_version" + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } @@ -95,6 +96,10 @@ testing { } } +tasks.named('test', Test) { + useJUnitPlatform() +} + // Use spotless plugin to automatically format code, remove unused import, etc // To apply changes directly to the file, run `gradlew spotlessApply` // Ref: https://github.com/diffplug/spotless/tree/main/plugin-gradle diff --git a/config/clients/java/template/libraries/native/api_test.mustache b/config/clients/java/template/libraries/native/api_test.mustache new file mode 100644 index 00000000..a31e204e --- /dev/null +++ b/config/clients/java/template/libraries/native/api_test.mustache @@ -0,0 +1,58 @@ +{{>licenseInfo}} + +package {{package}}; + +import {{invokerPackage}}.ApiException; +{{#imports}}import {{import}}; +{{/imports}} +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Disabled; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +{{#asyncNative}} +import java.util.concurrent.CompletableFuture; +{{/asyncNative}} + +/** + * API tests for {{classname}} + */ +@Disabled +public class {{classname}}Test { + + private final {{classname}} api = new {{classname}}(); + + {{#operations}}{{#operation}} + /** + * {{summary}} + * + * {{notes}} + * + * @throws ApiException + * if the Api call fails + */ + @Test + public void {{operationId}}Test() throws ApiException { + {{#allParams}} + {{{dataType}}} {{paramName}} = null; + {{/allParams}} + {{^vendorExtensions.x-group-parameters}} + {{#returnType}}{{#asyncNative}}CompletableFuture<{{{returnType}}}>{{/asyncNative}}{{^asyncNative}}{{{returnType}}}{{/asyncNative}} response = {{/returnType}} + {{^returnType}}{{#asyncNative}}CompletableFuture response = {{/asyncNative}}{{/returnType}}api.{{operationId}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}); + {{/vendorExtensions.x-group-parameters}} + {{#vendorExtensions.x-group-parameters}}{{#hasParams}} + {{classname}}.API{{operationId}}Request request = {{classname}}.API{{operationId}}Request.newBuilder(){{#allParams}} + .{{paramName}}({{paramName}}){{/allParams}} + .build();{{/hasParams}} + {{#returnType}}{{#asyncNative}}CompletableFuture<{{{returnType}}}>{{/asyncNative}}{{^asyncNative}}{{{returnType}}}{{/asyncNative}} response = {{/returnType}} + {{^returnType}}{{#asyncNative}}CompletableFuture response = {{/asyncNative}}{{/returnType}}api.{{operationId}}({{#hasParams}}request{{/hasParams}}); + {{/vendorExtensions.x-group-parameters}} + + // TODO: test validations + } + {{/operation}}{{/operations}} +} diff --git a/config/clients/java/template/model_test.mustache b/config/clients/java/template/model_test.mustache index d5c03d1e..99c56f61 100644 --- a/config/clients/java/template/model_test.mustache +++ b/config/clients/java/template/model_test.mustache @@ -4,9 +4,9 @@ package {{package}}; {{#imports}}import {{import}}; {{/imports}} -import org.junit.Assert; -import org.junit.Ignore; -import org.junit.Test; +import org.junit.jupiter.api.Assert; +import org.junit.jupiter.api.Ignore; +import org.junit.jupiter.api.Test; /** * Model tests for {{classname}} From 23f834da0d4553a8d3e7b49ed8f263e39ca2f5eb Mon Sep 17 00:00:00 2001 From: "Justin \"J.R.\" Hill" Date: Thu, 3 Aug 2023 11:31:05 -0700 Subject: [PATCH 8/9] test(java): Move generated OpenFgaApiTest to a template --- config/clients/java/config.overrides.json | 6 +- ...> OpenFgaApiIntegrationTest.java.mustache} | 0 .../template/OpenFgaApiTest.java.mustache | 289 ++++++++++++++++++ 3 files changed, 294 insertions(+), 1 deletion(-) rename config/clients/java/template/{OpenFgaApiIntegrationTest.java => OpenFgaApiIntegrationTest.java.mustache} (100%) create mode 100644 config/clients/java/template/OpenFgaApiTest.java.mustache diff --git a/config/clients/java/config.overrides.json b/config/clients/java/config.overrides.json index 7cf47284..eeaa6092 100644 --- a/config/clients/java/config.overrides.json +++ b/config/clients/java/config.overrides.json @@ -31,7 +31,11 @@ "destinationFilename": "build.gradle", "templateType": "SupportingFiles" }, - "OpenFgaApiIntegrationTest.java" : { + "OpenFgaApiTest.java.mustache" : { + "destinationFilename": "src/test/java/dev/openfga/api/OpenFgaApiTest.java", + "templateType": "SupportingFiles" + }, + "OpenFgaApiIntegrationTest.java.mustache" : { "destinationFilename": "src/test-integration/java/dev/openfga/api/OpenFgaApiIntegrationTest.java", "templateType": "SupportingFiles" }, diff --git a/config/clients/java/template/OpenFgaApiIntegrationTest.java b/config/clients/java/template/OpenFgaApiIntegrationTest.java.mustache similarity index 100% rename from config/clients/java/template/OpenFgaApiIntegrationTest.java rename to config/clients/java/template/OpenFgaApiIntegrationTest.java.mustache diff --git a/config/clients/java/template/OpenFgaApiTest.java.mustache b/config/clients/java/template/OpenFgaApiTest.java.mustache new file mode 100644 index 00000000..9baaef51 --- /dev/null +++ b/config/clients/java/template/OpenFgaApiTest.java.mustache @@ -0,0 +1,289 @@ +{{>licenseInfo}} + +package dev.openfga.api; + +import dev.openfga.api.invoker.ApiException; +import dev.openfga.api.model.*; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +/** + * API tests for OpenFgaApi + */ +public class OpenFgaApiTest { + + private final OpenFgaApi api = new OpenFgaApi(); + + /** + * Check whether a user is authorized to access an object + * + * The Check API queries to check if the user has a certain relationship with an object in a certain store. A `contextual_tuples` object may also be included in the body of the request. This object contains one field `tuple_keys`, which is an array of tuple keys. You may also provide an `authorization_model_id` in the body. This will be used to assert that the input `tuple_key` is valid for the model specified. If not specified, the assertion will be made against the latest authorization model ID. It is strongly recommended to specify authorization model id for better performance. The response will return whether the relationship exists in the field `allowed`. ## Example In order to check if user `user:anne` of type `user` has a `reader` relationship with object `document:2021-budget` given the following contextual tuple ```json { \"user\": \"user:anne\", \"relation\": \"member\", \"object\": \"time_slot:office_hours\" } ``` the Check API can be used with the following request body: ```json { \"tuple_key\": { \"user\": \"user:anne\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"contextual_tuples\": { \"tuple_keys\": [ { \"user\": \"user:anne\", \"relation\": \"member\", \"object\": \"time_slot:office_hours\" } ] }, \"authorization_model_id\": \"01G50QVV17PECNVAHX1GG4Y5NC\" } ``` OpenFGA's response will include `{ \"allowed\": true }` if there is a relationship and `{ \"allowed\": false }` if there isn't. + * + * @throws ApiException + * if the Api call fails + */ + @Test + @Disabled + public void checkTest() throws ApiException { + String storeId = null; + CheckRequest body = null; + CheckResponse response = api.check(storeId, body); + + // TODO: test validations + } + + /** + * Create a store + * + * Create a unique OpenFGA store which will be used to store authorization models and relationship tuples. + * + * @throws ApiException + * if the Api call fails + */ + @Test + @Disabled + public void createStoreTest() throws ApiException { + CreateStoreRequest body = null; + CreateStoreResponse response = api.createStore(body); + + // TODO: test validations + } + + /** + * Delete a store + * + * Delete an OpenFGA store. This does not delete the data associated with the store, like tuples or authorization models. + * + * @throws ApiException + * if the Api call fails + */ + @Test + @Disabled + public void deleteStoreTest() throws ApiException { + String storeId = null; + + api.deleteStore(storeId); + + // TODO: test validations + } + + /** + * Expand all relationships in userset tree format, and following userset rewrite rules. Useful to reason about and debug a certain relationship + * + * The Expand API will return all users and usersets that have certain relationship with an object in a certain store. This is different from the `/stores/{store_id}/read` API in that both users and computed usersets are returned. Body parameters `tuple_key.object` and `tuple_key.relation` are all required. The response will return a tree whose leaves are the specific users and usersets. Union, intersection and difference operator are located in the intermediate nodes. ## Example To expand all users that have the `reader` relationship with object `document:2021-budget`, use the Expand API with the following request body ```json { \"tuple_key\": { \"object\": \"document:2021-budget\", \"relation\": \"reader\" }, \"authorization_model_id\": \"01G50QVV17PECNVAHX1GG4Y5NC\" } ``` OpenFGA's response will be a userset tree of the users and usersets that have read access to the document. ```json { \"tree\":{ \"root\":{ \"type\":\"document:2021-budget#reader\", \"union\":{ \"nodes\":[ { \"type\":\"document:2021-budget#reader\", \"leaf\":{ \"users\":{ \"users\":[ \"user:bob\" ] } } }, { \"type\":\"document:2021-budget#reader\", \"leaf\":{ \"computed\":{ \"userset\":\"document:2021-budget#writer\" } } } ] } } } } ``` The caller can then call expand API for the `writer` relationship for the `document:2021-budget`. + * + * @throws ApiException + * if the Api call fails + */ + @Test + @Disabled + public void expandTest() throws ApiException { + String storeId = null; + ExpandRequest body = null; + ExpandResponse response = api.expand(storeId, body); + + // TODO: test validations + } + + /** + * Get a store + * + * Returns an OpenFGA store by its identifier + * + * @throws ApiException + * if the Api call fails + */ + @Test + @Disabled + public void getStoreTest() throws ApiException { + String storeId = null; + GetStoreResponse response = api.getStore(storeId); + + // TODO: test validations + } + + /** + * List all objects of the given type that the user has a relation with + * + * The ListObjects API returns a list of all the objects of the given type that the user has a relation with. To achieve this, both the store tuples and the authorization model are used. An `authorization_model_id` may be specified in the body. If it is not specified, the latest authorization model ID will be used. It is strongly recommended to specify authorization model id for better performance. You may also specify `contextual_tuples` that will be treated as regular tuples. The response will contain the related objects in an array in the \"objects\" field of the response and they will be strings in the object format `<type>:<id>` (e.g. \"document:roadmap\"). The number of objects in the response array will be limited by the execution timeout specified in the flag OPENFGA_LIST_OBJECTS_DEADLINE and by the upper bound specified in the flag OPENFGA_LIST_OBJECTS_MAX_RESULTS, whichever is hit first. + * + * @throws ApiException + * if the Api call fails + */ + @Test + @Disabled + public void listObjectsTest() throws ApiException { + String storeId = null; + ListObjectsRequest body = null; + ListObjectsResponse response = api.listObjects(storeId, body); + + // TODO: test validations + } + + /** + * List all stores + * + * Returns a paginated list of OpenFGA stores and a continuation token to get additional stores. The continuation token will be empty if there are no more stores. + * + * @throws ApiException + * if the Api call fails + */ + @Test + @Disabled + public void listStoresTest() throws ApiException { + Integer pageSize = null; + String continuationToken = null; + ListStoresResponse response = api.listStores(pageSize, continuationToken); + + // TODO: test validations + } + + /** + * Get tuples from the store that matches a query, without following userset rewrite rules + * + * The Read API will return the tuples for a certain store that match a query filter specified in the body of the request. It is different from the `/stores/{store_id}/expand` API in that it only returns relationship tuples that are stored in the system and satisfy the query. In the body: 1. `tuple_key` is optional. If not specified, it will return all tuples in the store. 2. `tuple_key.object` is mandatory if `tuple_key` is specified. It can be a full object (e.g., `type:object_id`) or type only (e.g., `type:`). 3. `tuple_key.user` is mandatory if tuple_key is specified in the case the `tuple_key.object` is a type only. ## Examples ### Query for all objects in a type definition To query for all objects that `user:bob` has `reader` relationship in the `document` type definition, call read API with body of ```json { \"tuple_key\": { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:\" } } ``` The API will return tuples and a continuation token, something like ```json { \"tuples\": [ { \"key\": { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"timestamp\": \"2021-10-06T15:32:11.128Z\" } ], \"continuation_token\": \"eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==\" } ``` This means that `user:bob` has a `reader` relationship with 1 document `document:2021-budget`. Note that this API, unlike the List Objects API, does not evaluate the tuples in the store. The continuation token will be empty if there are no more tuples to query.### Query for all stored relationship tuples that have a particular relation and object To query for all users that have `reader` relationship with `document:2021-budget`, call read API with body of ```json { \"tuple_key\": { \"object\": \"document:2021-budget\", \"relation\": \"reader\" } } ``` The API will return something like ```json { \"tuples\": [ { \"key\": { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"timestamp\": \"2021-10-06T15:32:11.128Z\" } ], \"continuation_token\": \"eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==\" } ``` This means that `document:2021-budget` has 1 `reader` (`user:bob`). Note that, even if the model said that all `writers` are also `readers`, the API will not return writers such as `user:anne` because it only returns tuples and does not evaluate them. ### Query for all users with all relationships for a particular document To query for all users that have any relationship with `document:2021-budget`, call read API with body of ```json { \"tuple_key\": { \"object\": \"document:2021-budget\" } } ``` The API will return something like ```json { \"tuples\": [ { \"key\": { \"user\": \"user:anne\", \"relation\": \"writer\", \"object\": \"document:2021-budget\" }, \"timestamp\": \"2021-10-05T13:42:12.356Z\" }, { \"key\": { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"timestamp\": \"2021-10-06T15:32:11.128Z\" } ], \"continuation_token\": \"eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==\" } ``` This means that `document:2021-budget` has 1 `reader` (`user:bob`) and 1 `writer` (`user:anne`). + * + * @throws ApiException + * if the Api call fails + */ + @Test + @Disabled + public void readTest() throws ApiException { + String storeId = null; + ReadRequest body = null; + ReadResponse response = api.read(storeId, body); + + // TODO: test validations + } + + /** + * Read assertions for an authorization model ID + * + * The ReadAssertions API will return, for a given authorization model id, all the assertions stored for it. An assertion is an object that contains a tuple key, and the expectation of whether a call to the Check API of that tuple key will return true or false. + * + * @throws ApiException + * if the Api call fails + */ + @Test + @Disabled + public void readAssertionsTest() throws ApiException { + String storeId = null; + String authorizationModelId = null; + ReadAssertionsResponse response = api.readAssertions(storeId, authorizationModelId); + + // TODO: test validations + } + + /** + * Return a particular version of an authorization model + * + * The ReadAuthorizationModel API returns an authorization model by its identifier. The response will return the authorization model for the particular version. ## Example To retrieve the authorization model with ID `01G5JAVJ41T49E9TT3SKVS7X1J` for the store, call the GET authorization-models by ID API with `01G5JAVJ41T49E9TT3SKVS7X1J` as the `id` path parameter. The API will return: ```json { \"authorization_model\":{ \"id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\", \"type_definitions\":[ { \"type\":\"user\" }, { \"type\":\"document\", \"relations\":{ \"reader\":{ \"union\":{ \"child\":[ { \"this\":{} }, { \"computedUserset\":{ \"object\":\"\", \"relation\":\"writer\" } } ] } }, \"writer\":{ \"this\":{} } } } ] } } ``` In the above example, there are 2 types (`user` and `document`). The `document` type has 2 relations (`writer` and `reader`). + * + * @throws ApiException + * if the Api call fails + */ + @Test + @Disabled + public void readAuthorizationModelTest() throws ApiException { + String storeId = null; + String id = null; + ReadAuthorizationModelResponse response = api.readAuthorizationModel(storeId, id); + + // TODO: test validations + } + + /** + * Return all the authorization models for a particular store + * + * The ReadAuthorizationModels API will return all the authorization models for a certain store. OpenFGA's response will contain an array of all authorization models, sorted in descending order of creation. ## Example Assume that a store's authorization model has been configured twice. To get all the authorization models that have been created in this store, call GET authorization-models. The API will return a response that looks like: ```json { \"authorization_models\": [ { \"id\": \"01G50QVV17PECNVAHX1GG4Y5NC\", \"type_definitions\": [...] }, { \"id\": \"01G4ZW8F4A07AKQ8RHSVG9RW04\", \"type_definitions\": [...] }, ], \"continuation_token\": \"eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==\" } ``` If there are no more authorization models available, the `continuation_token` field will be empty ```json { \"authorization_models\": [ { \"id\": \"01G50QVV17PECNVAHX1GG4Y5NC\", \"type_definitions\": [...] }, { \"id\": \"01G4ZW8F4A07AKQ8RHSVG9RW04\", \"type_definitions\": [...] }, ], \"continuation_token\": \"\" } ``` + * + * @throws ApiException + * if the Api call fails + */ + @Test + @Disabled + public void readAuthorizationModelsTest() throws ApiException { + String storeId = null; + Integer pageSize = null; + String continuationToken = null; + ReadAuthorizationModelsResponse response = api.readAuthorizationModels(storeId, pageSize, continuationToken); + + // TODO: test validations + } + + /** + * Return a list of all the tuple changes + * + * The ReadChanges API will return a paginated list of tuple changes (additions and deletions) that occurred in a given store, sorted by ascending time. The response will include a continuation token that is used to get the next set of changes. If there are no changes after the provided continuation token, the same token will be returned in order for it to be used when new changes are recorded. If the store never had any tuples added or removed, this token will be empty. You can use the `type` parameter to only get the list of tuple changes that affect objects of that type. + * + * @throws ApiException + * if the Api call fails + */ + @Test + @Disabled + public void readChangesTest() throws ApiException { + String storeId = null; + String type = null; + Integer pageSize = null; + String continuationToken = null; + ReadChangesResponse response = api.readChanges(storeId, type, pageSize, continuationToken); + + // TODO: test validations + } + + /** + * Add or delete tuples from the store + * + * The Write API will update the tuples for a certain store. Tuples and type definitions allow OpenFGA to determine whether a relationship exists between an object and an user. In the body, `writes` adds new tuples while `deletes` removes existing tuples. The API is not idempotent: if, later on, you try to add the same tuple, or if you try to delete a non-existing tuple, it will throw an error. An `authorization_model_id` may be specified in the body. If it is, it will be used to assert that each written tuple (not deleted) is valid for the model specified. If it is not specified, the latest authorization model ID will be used. ## Example ### Adding relationships To add `user:anne` as a `writer` for `document:2021-budget`, call write API with the following ```json { \"writes\": { \"tuple_keys\": [ { \"user\": \"user:anne\", \"relation\": \"writer\", \"object\": \"document:2021-budget\" } ] }, \"authorization_model_id\": \"01G50QVV17PECNVAHX1GG4Y5NC\" } ``` ### Removing relationships To remove `user:bob` as a `reader` for `document:2021-budget`, call write API with the following ```json { \"deletes\": { \"tuple_keys\": [ { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" } ] } } ``` + * + * @throws ApiException + * if the Api call fails + */ + @Test + @Disabled + public void writeTest() throws ApiException { + String storeId = null; + WriteRequest body = null; + Object response = api.write(storeId, body); + + // TODO: test validations + } + + /** + * Upsert assertions for an authorization model ID + * + * The WriteAssertions API will upsert new assertions for an authorization model id, or overwrite the existing ones. An assertion is an object that contains a tuple key, and the expectation of whether a call to the Check API of that tuple key will return true or false. + * + * @throws ApiException + * if the Api call fails + */ + @Test + @Disabled + public void writeAssertionsTest() throws ApiException { + String storeId = null; + String authorizationModelId = null; + WriteAssertionsRequest body = null; + + api.writeAssertions(storeId, authorizationModelId, body); + + // TODO: test validations + } + + /** + * Create a new authorization model + * + * The WriteAuthorizationModel API will add a new authorization model to a store. Each item in the `type_definitions` array is a type definition as specified in the field `type_definition`. The response will return the authorization model's ID in the `id` field. ## Example To add an authorization model with `user` and `document` type definitions, call POST authorization-models API with the body: ```json { \"type_definitions\":[ { \"type\":\"user\" }, { \"type\":\"document\", \"relations\":{ \"reader\":{ \"union\":{ \"child\":[ { \"this\":{} }, { \"computedUserset\":{ \"object\":\"\", \"relation\":\"writer\" } } ] } }, \"writer\":{ \"this\":{} } } } ] } ``` OpenFGA's response will include the version id for this authorization model, which will look like ``` {\"authorization_model_id\": \"01G50QVV17PECNVAHX1GG4Y5NC\"} ``` + * + * @throws ApiException + * if the Api call fails + */ + @Test + @Disabled + public void writeAuthorizationModelTest() throws ApiException { + String storeId = null; + WriteAuthorizationModelRequest body = null; + WriteAuthorizationModelResponse response = api.writeAuthorizationModel(storeId, body); + + // TODO: test validations + } +} From 8b97767edeb0d76aa83efd18b40afdbb67e376c7 Mon Sep 17 00:00:00 2001 From: "Justin \"J.R.\" Hill" Date: Thu, 3 Aug 2023 13:54:58 -0700 Subject: [PATCH 9/9] test(java): Unit test the Check API, introduce Mockito, update javadocs --- .../OpenFgaApiIntegrationTest.java.mustache | 12 +- .../template/OpenFgaApiTest.java.mustache | 162 ++++++++++++------ .../java/template/build.gradle.mustache | 4 +- 3 files changed, 113 insertions(+), 65 deletions(-) diff --git a/config/clients/java/template/OpenFgaApiIntegrationTest.java.mustache b/config/clients/java/template/OpenFgaApiIntegrationTest.java.mustache index 3bd925f1..1401e021 100644 --- a/config/clients/java/template/OpenFgaApiIntegrationTest.java.mustache +++ b/config/clients/java/template/OpenFgaApiIntegrationTest.java.mustache @@ -1,14 +1,4 @@ -/* - * OpenFGA - * A high performance and flexible authorization/permission engine built for developers and inspired by Google Zanzibar. - * - * The version of the OpenAPI document: 0.1 - * Contact: community@openfga.dev - * - * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - * https://openapi-generator.tech - * Do not edit the class manually. - */ +{{>licenseInfo}} package dev.openfga.api; diff --git a/config/clients/java/template/OpenFgaApiTest.java.mustache b/config/clients/java/template/OpenFgaApiTest.java.mustache index 9baaef51..17e3091b 100644 --- a/config/clients/java/template/OpenFgaApiTest.java.mustache +++ b/config/clients/java/template/OpenFgaApiTest.java.mustache @@ -2,8 +2,22 @@ package dev.openfga.api; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; + +import com.fasterxml.jackson.databind.ObjectMapper; +import dev.openfga.api.invoker.ApiClient; import dev.openfga.api.invoker.ApiException; import dev.openfga.api.model.*; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.net.http.HttpClient; +import java.net.http.HttpHeaders; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.util.HashMap; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -11,30 +25,65 @@ import org.junit.jupiter.api.Test; * API tests for OpenFgaApi */ public class OpenFgaApiTest { - - private final OpenFgaApi api = new OpenFgaApi(); + private final ObjectMapper mapper = new ObjectMapper(); + private OpenFgaApi fga; + private ApiClient mockApiClient; + private HttpClient mockHttpClient; + + @BeforeEach + public void beforeEachTest() { + mockHttpClient = mock(HttpClient.class); + + mockApiClient = mock(ApiClient.class); + when(mockApiClient.getBaseUri()).thenReturn("https://localhost"); + when(mockApiClient.getObjectMapper()).thenReturn(mapper); + when(mockApiClient.getReadTimeout()).thenReturn(Duration.ofMillis(250)); + when(mockApiClient.getHttpClient()).thenReturn(mockHttpClient); + + fga = new OpenFgaApi(mockApiClient); + } /** - * Check whether a user is authorized to access an object - * + * Check whether a user is authorized to access an object. + *

* The Check API queries to check if the user has a certain relationship with an object in a certain store. A `contextual_tuples` object may also be included in the body of the request. This object contains one field `tuple_keys`, which is an array of tuple keys. You may also provide an `authorization_model_id` in the body. This will be used to assert that the input `tuple_key` is valid for the model specified. If not specified, the assertion will be made against the latest authorization model ID. It is strongly recommended to specify authorization model id for better performance. The response will return whether the relationship exists in the field `allowed`. ## Example In order to check if user `user:anne` of type `user` has a `reader` relationship with object `document:2021-budget` given the following contextual tuple ```json { \"user\": \"user:anne\", \"relation\": \"member\", \"object\": \"time_slot:office_hours\" } ``` the Check API can be used with the following request body: ```json { \"tuple_key\": { \"user\": \"user:anne\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"contextual_tuples\": { \"tuple_keys\": [ { \"user\": \"user:anne\", \"relation\": \"member\", \"object\": \"time_slot:office_hours\" } ] }, \"authorization_model_id\": \"01G50QVV17PECNVAHX1GG4Y5NC\" } ``` OpenFGA's response will include `{ \"allowed\": true }` if there is a relationship and `{ \"allowed\": false }` if there isn't. * * @throws ApiException * if the Api call fails */ @Test - @Disabled - public void checkTest() throws ApiException { - String storeId = null; - CheckRequest body = null; - CheckResponse response = api.check(storeId, body); + public void check() throws Exception { + // Given + String storeId = "whatever"; + HttpResponse rawResponse = basicHttpOkResponse(); + when(mockHttpClient.send(any(), any())).thenReturn(rawResponse); + CheckRequest request = new CheckRequest() + .tupleKey(new TupleKey()) + .contextualTuples(new ContextualTupleKeys()) + .authorizationModelId("anything"); + + // When + CheckResponse response = fga.check(storeId, new CheckRequest()); + + // Then + verify(mockApiClient).getBaseUri(); + verify(mockApiClient).getReadTimeout(); + verify(mockHttpClient).send(any(), any()); + assertEquals(response, new CheckResponse()); + } - // TODO: test validations + @Test + public void check_storeIdRequired() throws ApiException { + // When + ApiException exception = assertThrows(ApiException.class, () -> fga.check(null, new CheckRequest())); + + // Then + assertEquals("Missing the required parameter 'storeId' when calling check", exception.getMessage()); } /** - * Create a store - * + * Create a store. + *

* Create a unique OpenFGA store which will be used to store authorization models and relationship tuples. * * @throws ApiException @@ -44,14 +93,14 @@ public class OpenFgaApiTest { @Disabled public void createStoreTest() throws ApiException { CreateStoreRequest body = null; - CreateStoreResponse response = api.createStore(body); + CreateStoreResponse response = fga.createStore(body); // TODO: test validations } /** - * Delete a store - * + * Delete a store. + *

* Delete an OpenFGA store. This does not delete the data associated with the store, like tuples or authorization models. * * @throws ApiException @@ -62,14 +111,14 @@ public class OpenFgaApiTest { public void deleteStoreTest() throws ApiException { String storeId = null; - api.deleteStore(storeId); + fga.deleteStore(storeId); // TODO: test validations } /** - * Expand all relationships in userset tree format, and following userset rewrite rules. Useful to reason about and debug a certain relationship - * + * Expand all relationships in userset tree format, and following userset rewrite rules. Useful to reason about and debug a certain relationship. + *

* The Expand API will return all users and usersets that have certain relationship with an object in a certain store. This is different from the `/stores/{store_id}/read` API in that both users and computed usersets are returned. Body parameters `tuple_key.object` and `tuple_key.relation` are all required. The response will return a tree whose leaves are the specific users and usersets. Union, intersection and difference operator are located in the intermediate nodes. ## Example To expand all users that have the `reader` relationship with object `document:2021-budget`, use the Expand API with the following request body ```json { \"tuple_key\": { \"object\": \"document:2021-budget\", \"relation\": \"reader\" }, \"authorization_model_id\": \"01G50QVV17PECNVAHX1GG4Y5NC\" } ``` OpenFGA's response will be a userset tree of the users and usersets that have read access to the document. ```json { \"tree\":{ \"root\":{ \"type\":\"document:2021-budget#reader\", \"union\":{ \"nodes\":[ { \"type\":\"document:2021-budget#reader\", \"leaf\":{ \"users\":{ \"users\":[ \"user:bob\" ] } } }, { \"type\":\"document:2021-budget#reader\", \"leaf\":{ \"computed\":{ \"userset\":\"document:2021-budget#writer\" } } } ] } } } } ``` The caller can then call expand API for the `writer` relationship for the `document:2021-budget`. * * @throws ApiException @@ -80,14 +129,14 @@ public class OpenFgaApiTest { public void expandTest() throws ApiException { String storeId = null; ExpandRequest body = null; - ExpandResponse response = api.expand(storeId, body); + ExpandResponse response = fga.expand(storeId, body); // TODO: test validations } /** - * Get a store - * + * Get a store. + *

* Returns an OpenFGA store by its identifier * * @throws ApiException @@ -97,14 +146,14 @@ public class OpenFgaApiTest { @Disabled public void getStoreTest() throws ApiException { String storeId = null; - GetStoreResponse response = api.getStore(storeId); + GetStoreResponse response = fga.getStore(storeId); // TODO: test validations } /** - * List all objects of the given type that the user has a relation with - * + * List all objects of the given type that the user has a relation with. + *

* The ListObjects API returns a list of all the objects of the given type that the user has a relation with. To achieve this, both the store tuples and the authorization model are used. An `authorization_model_id` may be specified in the body. If it is not specified, the latest authorization model ID will be used. It is strongly recommended to specify authorization model id for better performance. You may also specify `contextual_tuples` that will be treated as regular tuples. The response will contain the related objects in an array in the \"objects\" field of the response and they will be strings in the object format `<type>:<id>` (e.g. \"document:roadmap\"). The number of objects in the response array will be limited by the execution timeout specified in the flag OPENFGA_LIST_OBJECTS_DEADLINE and by the upper bound specified in the flag OPENFGA_LIST_OBJECTS_MAX_RESULTS, whichever is hit first. * * @throws ApiException @@ -115,14 +164,14 @@ public class OpenFgaApiTest { public void listObjectsTest() throws ApiException { String storeId = null; ListObjectsRequest body = null; - ListObjectsResponse response = api.listObjects(storeId, body); + ListObjectsResponse response = fga.listObjects(storeId, body); // TODO: test validations } /** - * List all stores - * + * List all stores. + *

* Returns a paginated list of OpenFGA stores and a continuation token to get additional stores. The continuation token will be empty if there are no more stores. * * @throws ApiException @@ -133,14 +182,14 @@ public class OpenFgaApiTest { public void listStoresTest() throws ApiException { Integer pageSize = null; String continuationToken = null; - ListStoresResponse response = api.listStores(pageSize, continuationToken); + ListStoresResponse response = fga.listStores(pageSize, continuationToken); // TODO: test validations } /** - * Get tuples from the store that matches a query, without following userset rewrite rules - * + * Get tuples from the store that matches a query, without following userset rewrite rules. + *

* The Read API will return the tuples for a certain store that match a query filter specified in the body of the request. It is different from the `/stores/{store_id}/expand` API in that it only returns relationship tuples that are stored in the system and satisfy the query. In the body: 1. `tuple_key` is optional. If not specified, it will return all tuples in the store. 2. `tuple_key.object` is mandatory if `tuple_key` is specified. It can be a full object (e.g., `type:object_id`) or type only (e.g., `type:`). 3. `tuple_key.user` is mandatory if tuple_key is specified in the case the `tuple_key.object` is a type only. ## Examples ### Query for all objects in a type definition To query for all objects that `user:bob` has `reader` relationship in the `document` type definition, call read API with body of ```json { \"tuple_key\": { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:\" } } ``` The API will return tuples and a continuation token, something like ```json { \"tuples\": [ { \"key\": { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"timestamp\": \"2021-10-06T15:32:11.128Z\" } ], \"continuation_token\": \"eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==\" } ``` This means that `user:bob` has a `reader` relationship with 1 document `document:2021-budget`. Note that this API, unlike the List Objects API, does not evaluate the tuples in the store. The continuation token will be empty if there are no more tuples to query.### Query for all stored relationship tuples that have a particular relation and object To query for all users that have `reader` relationship with `document:2021-budget`, call read API with body of ```json { \"tuple_key\": { \"object\": \"document:2021-budget\", \"relation\": \"reader\" } } ``` The API will return something like ```json { \"tuples\": [ { \"key\": { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"timestamp\": \"2021-10-06T15:32:11.128Z\" } ], \"continuation_token\": \"eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==\" } ``` This means that `document:2021-budget` has 1 `reader` (`user:bob`). Note that, even if the model said that all `writers` are also `readers`, the API will not return writers such as `user:anne` because it only returns tuples and does not evaluate them. ### Query for all users with all relationships for a particular document To query for all users that have any relationship with `document:2021-budget`, call read API with body of ```json { \"tuple_key\": { \"object\": \"document:2021-budget\" } } ``` The API will return something like ```json { \"tuples\": [ { \"key\": { \"user\": \"user:anne\", \"relation\": \"writer\", \"object\": \"document:2021-budget\" }, \"timestamp\": \"2021-10-05T13:42:12.356Z\" }, { \"key\": { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" }, \"timestamp\": \"2021-10-06T15:32:11.128Z\" } ], \"continuation_token\": \"eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==\" } ``` This means that `document:2021-budget` has 1 `reader` (`user:bob`) and 1 `writer` (`user:anne`). * * @throws ApiException @@ -151,14 +200,14 @@ public class OpenFgaApiTest { public void readTest() throws ApiException { String storeId = null; ReadRequest body = null; - ReadResponse response = api.read(storeId, body); + ReadResponse response = fga.read(storeId, body); // TODO: test validations } /** - * Read assertions for an authorization model ID - * + * Read assertions for an authorization model ID. + *

* The ReadAssertions API will return, for a given authorization model id, all the assertions stored for it. An assertion is an object that contains a tuple key, and the expectation of whether a call to the Check API of that tuple key will return true or false. * * @throws ApiException @@ -169,14 +218,14 @@ public class OpenFgaApiTest { public void readAssertionsTest() throws ApiException { String storeId = null; String authorizationModelId = null; - ReadAssertionsResponse response = api.readAssertions(storeId, authorizationModelId); + ReadAssertionsResponse response = fga.readAssertions(storeId, authorizationModelId); // TODO: test validations } /** - * Return a particular version of an authorization model - * + * Return a particular version of an authorization model. + *

* The ReadAuthorizationModel API returns an authorization model by its identifier. The response will return the authorization model for the particular version. ## Example To retrieve the authorization model with ID `01G5JAVJ41T49E9TT3SKVS7X1J` for the store, call the GET authorization-models by ID API with `01G5JAVJ41T49E9TT3SKVS7X1J` as the `id` path parameter. The API will return: ```json { \"authorization_model\":{ \"id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\", \"type_definitions\":[ { \"type\":\"user\" }, { \"type\":\"document\", \"relations\":{ \"reader\":{ \"union\":{ \"child\":[ { \"this\":{} }, { \"computedUserset\":{ \"object\":\"\", \"relation\":\"writer\" } } ] } }, \"writer\":{ \"this\":{} } } } ] } } ``` In the above example, there are 2 types (`user` and `document`). The `document` type has 2 relations (`writer` and `reader`). * * @throws ApiException @@ -187,14 +236,14 @@ public class OpenFgaApiTest { public void readAuthorizationModelTest() throws ApiException { String storeId = null; String id = null; - ReadAuthorizationModelResponse response = api.readAuthorizationModel(storeId, id); + ReadAuthorizationModelResponse response = fga.readAuthorizationModel(storeId, id); // TODO: test validations } /** - * Return all the authorization models for a particular store - * + * Return all the authorization models for a particular store. + *

* The ReadAuthorizationModels API will return all the authorization models for a certain store. OpenFGA's response will contain an array of all authorization models, sorted in descending order of creation. ## Example Assume that a store's authorization model has been configured twice. To get all the authorization models that have been created in this store, call GET authorization-models. The API will return a response that looks like: ```json { \"authorization_models\": [ { \"id\": \"01G50QVV17PECNVAHX1GG4Y5NC\", \"type_definitions\": [...] }, { \"id\": \"01G4ZW8F4A07AKQ8RHSVG9RW04\", \"type_definitions\": [...] }, ], \"continuation_token\": \"eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ==\" } ``` If there are no more authorization models available, the `continuation_token` field will be empty ```json { \"authorization_models\": [ { \"id\": \"01G50QVV17PECNVAHX1GG4Y5NC\", \"type_definitions\": [...] }, { \"id\": \"01G4ZW8F4A07AKQ8RHSVG9RW04\", \"type_definitions\": [...] }, ], \"continuation_token\": \"\" } ``` * * @throws ApiException @@ -206,14 +255,14 @@ public class OpenFgaApiTest { String storeId = null; Integer pageSize = null; String continuationToken = null; - ReadAuthorizationModelsResponse response = api.readAuthorizationModels(storeId, pageSize, continuationToken); + ReadAuthorizationModelsResponse response = fga.readAuthorizationModels(storeId, pageSize, continuationToken); // TODO: test validations } /** - * Return a list of all the tuple changes - * + * Return a list of all the tuple changes. + *

* The ReadChanges API will return a paginated list of tuple changes (additions and deletions) that occurred in a given store, sorted by ascending time. The response will include a continuation token that is used to get the next set of changes. If there are no changes after the provided continuation token, the same token will be returned in order for it to be used when new changes are recorded. If the store never had any tuples added or removed, this token will be empty. You can use the `type` parameter to only get the list of tuple changes that affect objects of that type. * * @throws ApiException @@ -226,14 +275,14 @@ public class OpenFgaApiTest { String type = null; Integer pageSize = null; String continuationToken = null; - ReadChangesResponse response = api.readChanges(storeId, type, pageSize, continuationToken); + ReadChangesResponse response = fga.readChanges(storeId, type, pageSize, continuationToken); // TODO: test validations } /** - * Add or delete tuples from the store - * + * Add or delete tuples from the store. + *

* The Write API will update the tuples for a certain store. Tuples and type definitions allow OpenFGA to determine whether a relationship exists between an object and an user. In the body, `writes` adds new tuples while `deletes` removes existing tuples. The API is not idempotent: if, later on, you try to add the same tuple, or if you try to delete a non-existing tuple, it will throw an error. An `authorization_model_id` may be specified in the body. If it is, it will be used to assert that each written tuple (not deleted) is valid for the model specified. If it is not specified, the latest authorization model ID will be used. ## Example ### Adding relationships To add `user:anne` as a `writer` for `document:2021-budget`, call write API with the following ```json { \"writes\": { \"tuple_keys\": [ { \"user\": \"user:anne\", \"relation\": \"writer\", \"object\": \"document:2021-budget\" } ] }, \"authorization_model_id\": \"01G50QVV17PECNVAHX1GG4Y5NC\" } ``` ### Removing relationships To remove `user:bob` as a `reader` for `document:2021-budget`, call write API with the following ```json { \"deletes\": { \"tuple_keys\": [ { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" } ] } } ``` * * @throws ApiException @@ -244,14 +293,14 @@ public class OpenFgaApiTest { public void writeTest() throws ApiException { String storeId = null; WriteRequest body = null; - Object response = api.write(storeId, body); + Object response = fga.write(storeId, body); // TODO: test validations } /** - * Upsert assertions for an authorization model ID - * + * Upsert assertions for an authorization model ID. + *

* The WriteAssertions API will upsert new assertions for an authorization model id, or overwrite the existing ones. An assertion is an object that contains a tuple key, and the expectation of whether a call to the Check API of that tuple key will return true or false. * * @throws ApiException @@ -264,14 +313,14 @@ public class OpenFgaApiTest { String authorizationModelId = null; WriteAssertionsRequest body = null; - api.writeAssertions(storeId, authorizationModelId, body); + fga.writeAssertions(storeId, authorizationModelId, body); // TODO: test validations } /** - * Create a new authorization model - * + * Create a new authorization model. + *

* The WriteAuthorizationModel API will add a new authorization model to a store. Each item in the `type_definitions` array is a type definition as specified in the field `type_definition`. The response will return the authorization model's ID in the `id` field. ## Example To add an authorization model with `user` and `document` type definitions, call POST authorization-models API with the body: ```json { \"type_definitions\":[ { \"type\":\"user\" }, { \"type\":\"document\", \"relations\":{ \"reader\":{ \"union\":{ \"child\":[ { \"this\":{} }, { \"computedUserset\":{ \"object\":\"\", \"relation\":\"writer\" } } ] } }, \"writer\":{ \"this\":{} } } } ] } ``` OpenFGA's response will include the version id for this authorization model, which will look like ``` {\"authorization_model_id\": \"01G50QVV17PECNVAHX1GG4Y5NC\"} ``` * * @throws ApiException @@ -282,8 +331,17 @@ public class OpenFgaApiTest { public void writeAuthorizationModelTest() throws ApiException { String storeId = null; WriteAuthorizationModelRequest body = null; - WriteAuthorizationModelResponse response = api.writeAuthorizationModel(storeId, body); + WriteAuthorizationModelResponse response = fga.writeAuthorizationModel(storeId, body); // TODO: test validations } + + private HttpResponse basicHttpOkResponse() { + HttpResponse response = mock(HttpResponse.class); + when(response.headers()).thenReturn(HttpHeaders.of(new HashMap<>(), (_a, _b) -> true)); + InputStream b = new ByteArrayInputStream("{}".getBytes()); + when(response.body()).thenReturn(b); + when(response.statusCode()).thenReturn(200); + return response; + } } diff --git a/config/clients/java/template/build.gradle.mustache b/config/clients/java/template/build.gradle.mustache index dc6d7c06..3a638f83 100644 --- a/config/clients/java/template/build.gradle.mustache +++ b/config/clients/java/template/build.gradle.mustache @@ -71,8 +71,8 @@ dependencies { // Test-only dependencies testImplementation "org.junit.jupiter:junit-jupiter:$junit_version" - testRuntimeOnly 'org.junit.platform:junit-platform-launcher' - + testImplementation "org.mockito:mockito-core:3.+" + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } testing {